Как вы, возможно, знаете в мире Windows для рисования графики часто используется DirectX. В последних версиях (10, 11.x) библиотека серьёзно шагнула вперёд и именно на них построены движки многих современных игр. Кроме того, DirectX используется не только в играх — сам интерфейс ОС Windows тоже с непомню-какой версии (Vista?) рисуется через него, да и казалось-бы не сильно связанные с графикой программы, желая увеличить производительность и плавность зумаскрола переходят на последние версии DirectX. Так некоторое время назад на DirectX11 перешел рендер Google Chrome (вроде бы с версии 36).
Когда-то во времена Windows 95 и Pentium II была такая шутка, что чем медленнее компьютер — тем лучше можно понять работу операционной системы — невооруженным глазом видно в каком порядке прорисовываются элементы окон, обрабатываются события. Сегодня для подобных целей относительно DirectX есть отдельные инструменты — графические отладчики, позволяющие понять, как именно рисуется каждый пиксель каждого кадра, какие операции выполняет движок DirectX, какие ресурсы он использует, насколько быстро и правильно всё работает. Один из таких инструментов — RenderDoc от компании Crytek мы сегодня и рассмотрим. А в качестве примера разберём уже упомянутый выше новый рендер Google Chrome.
Прежде всего — почему я говорю о RenderDoc? Есть немало аналогичных инструментов:
- PIX
- Intel Graphics Performance Analyzers
- AMD GPU PerfStudio 2
- NVIDIA® Nsight™ Development Platform
- Visual Studio Graphics Diagnostics tools
Все они — очень хороши. Но:
- PIX — уже устарел, не развивается и не работает на последних ОС
- инструменты от IntelAMDNvidia — сделаны в духе «вот этими утилитами можно посмотреть, как офигительно классно и быстро работает DirectX на наших видеокартах», при этом некоторые прикладные, рутинные задачи отладки в них делать не очень удобно
- Visual Studio — заточена именно под разработку своего софта, а не анализ чужого
Что касается RenderDoc, то это:
- opensource, активно развивается
- заточен на последние версии Windows и DirectX11
- продукт от профессионалов 3D-разработки — компании, создавшей движок CryEngine со всей сопутствующей обвязкой
- пропитан практическим подходом к разработке и отладке — не знаю, как это лучше объяснить, но вот к примеру когда только задумываешься о том, что «хм, было бы неплохо в этом вызове увидеть вот эту информацию в вот такой-вот форме» внезапно оказывается, что RenderDoc показывает именно эту информацию, именно в этом вызове и именно в нужной форме. Видно, что инструмент живёт и полируется в руках Crytek.
- вообще единственный продукт, который понял от меня, что я хочу отлаживать отрисовку графики в дочернем процессе Chrome, запущенного из родительского, в котором никакой графики нет вообще. Казалось бы, лаунчер — простейшая вещь, используется во многих играх, но вот только RenderDoc дошел до необходимости его поддержки в отладчике.
Итак, качаем RenderDoc, устанавливаем. Запускаем, видим главное окно. Открываем в меню Tools->Options и указываем папку, в которую будут складываться временные файлы (дампы).
Теперь нам нужно запустить под отладчиком RenderDoc приложение, которое мы хотим отлаживать. Для этого на вкладке "Capture Executable" вводим путь к Chrome, его рабочую папку. Здесь есть несколько интересных моментов. Графика в Хроме рисуется в отдельном дочернем процессе, его можно определить запустив Process Hacker и найдя среди всех запущенных процессов chrome.exe тот, у которого среди параметров командной строки имеется флаг --type=gpu-process.
Мы не можем запустить этот процесс напрямую, поэтому мы должны запустить главный процесс Chrome, указав в RenderDoc, что хотим отслеживать вызовы DirectX-функций в том числе среди дочерних процессов (галочка Hook Into Children)
По-умолчанию дочерние процессы Chrome «живут» в песочнице — имеют низкий Integrity Level, который препятствует их взаимодействию с файловой системой, другими процессами и общими ресурсами ОС. Таким образом, если мы просто запустим Chrome, то RenderDoc не будет в состоянии взаимодействовать с процессом, в котором рисуется графика. Для этого есть хак — Chrome нужно запускать со специальным флагом --no-sandbox, который отключает «песочницу» Chrome.
Поскольку нам интересно вообще всё, что будет происходить по ходу рисования графики — включаем побольше разных полезных галочек. Также мы можем сразу указать, какой по счету фрейм мы хотим захватить (для этого есть галочка Queue Capture Of Frame #), а можем уже в приложении нажать кнопку PrintScreen, чтобы создать дамп для текущего кадра
В итоге вкладка Capture Executable у нас будет выглядеть вот так:
Жмём кнопку Capture, запускается Chrome.
В верхней части окна мы видим некую отладочную информацию, которая говорит нам как-минимум о нескольких вещах:
- Chrome действительно использует DirectX11 для рисования своего окна (причём целого окна: заголовка, тулбаров и контента страницы)
- RenderDoc успешно «прицепился» к Chrome
Теперь можно открыть в Chrome что-нибудь и нажать PrintScreen. В верхней части окна Chrome появится надпись о созданном скриншоте. Всё, Chrome можно закрывать, а в папке, которую вы указали в настройках должен появиться файл с расширением rdc. Это и есть наш дамп. Открываем его через "File->Open Log" и видим примерно вот такую картину:
В верхней части окна находится «таймлайн» — временная линия, на которой отмечены этапы рисования данного кадра. Их может быть до нескольких десятков (если вы их не видите — кликните по плюсику в левом верхнем углу окна «Timeline»). Те же этапы отмечены в панели "Event Browser" в левой части окна. Кликая по таймлайну или по событиям в «Event Browser» мы можем переходить к разным моментам по ходу рисования кадра.
Корневой узел дерева событий называется "Frame #N" и показывает, какой это кадр от момента инициализации DirectX в приложении. Далее следует мета-узел "Frame start" указывающий на момент начала рисования данного кадра (никаких реальных вызовов DirectX-методов к нему не привязано). Далее мы видим три узла: "Colour Pass #1 (1 Targets)", "Draw(4)" и "Present()". Из этого мы можем понять, что всё рисование контента окна хрома проходит в несколько этапов:
- Сначала всё рисуется в некую промежуточную текстуру (этап "Colour Pass #1 (1 Targets)")
- Затем вызов метода "Draw(4)" переносит содержание этой текстуры в Swap Chain Backbuffer
- И наконец метод Present вызывает отображение бэкбуфера на экран
Как видите, этап рисование в промежуточную текстуру в дереве можно раскрыть и мы увидим моменты рисования отдельных элементов окна Хрома.
Первым делом текстура очищается (вызов ClearRenderTargetView). Затем следует много этапов под названием "DrawIndexed(6)". 6 — это количество точек, ограничивающих область, рисуемую на данном этапе. То, что их 6, наталкивает на мысль, что это 2 треугольника, составляющих прямоугольник. Давайте выберем один их этапов "DrawIndexed(6)" (не первый, но и не последний) и рассмотрим его повнимательнее.
Начнём с вкладки "Pipeline State"
Как вы, возможно, знаете, в DirectX11 используется понятие «Pipelene» — это набор из нескольких последовательных операций, предназначенных для формирования окончательного кадра. Начинается пайплайн с этапа Input Assembler — здесь мы предоставляем все необходимые входные данные по вершинным, индексным и константным буферам, которые могут в дальнейшем понадобиться для рассчёта что и куда нужно будет рисовать. Далее следуют этапы обработки входных данных различными типами шейдеров и последняя фаза — Output Merger, в которой графика компонуется и выводится туда, куда должна быть выведена.
В окне Pipeline State мы можем кликнуть по любой стадии пайплайна и увидеть:
- Что пришло на вход стадии (буфер, шейдер, текстура), общую информацию об этом объекте
- Кликнув по зелёной стрелочке справа от входного параметра мы можем открыть окно с более полной информацией. Для буфера это его полное содержание, для текстуры — её картинка, для шейдера — его скомпилированный байткод.
- Для стадии Input Assembler мы также можем кликнуть по большой кнопке "Mesh" и посмотреть координаты точек, образующих рисуемую на данном этапе область. Здесь ещё раз можно убедиться, что рисуем мы именно прямоугольник.
Перейдя в окно текстур, мы можем увидеть используемые при рисовании данного кадра текстуры:
В тулбаре "PS Resources" мы видим текстуру(ы), которая будет рисоваться на данном этапе, а в тулбаре "OM Targets" — текстуру, куда будет происходить отрисовка. Оставаясь в окне текстур, можно покликать по этапам отрисовке сверху или слева — и мы увидим, что Хром рисует своё окно текстурами размером 256х256 пикселей. Начинает он с нижней части окна, потом рисует боковые края и затем — заголовок окна с тулбарами. После этого Хром приступает к рисованию контента вкладки (опять-таки кусочками по 256х256 пикселей). Потом рисуются лежащие «поверх» контента объекты — видео, флеш-баннеры, всплывающие подсказки. На последних этапах рисуются скроллбар и его ползунок. Теперь текстура готова к рисованию в бэкбуфер.
Что ещё интересного умеет RenderDoc
Показывать API вызываемых DirectX-методов для каждого этапа отрисовки
Невероятно полезная вещь. А учитывая, что видны не только названия методов, но и их параметры — это вообще красота.
Показывать колстек (откуда в коде был вызван тот или иной DirectX-метод)
Правда, для этого нужно подсунуть программе pdb-файл (который у вас есть только если вы сами — автор отлаживаемого кода). Весьма полезно для отладки своих программ, полностью бесполезно для анализа чужих.
Дебажить шейдеры
Для вершинных шейдеров отладка начинается в окне Mesh Output с клика правой кнопкой мыши по интересующей вершине.
Для пикселей — в окне текстур, где на интересующем пикселе нужно кликнуть правой кнопкой мыши и кликнуть в тулбаре "Pixel Content" кнопку "Debug this Pixel"
Вот такой полезный инструмент RenderDoc.
Удачной вам отладки графики.
Автор: tangro