Зависимости: local_shadertoy2.
В данном задании нам предстоит научиться ускорять графические приложения потенциально в 2 раза, лучше понять что такое асинхронность и пайплайнинг, а также научиться профилировать ваши приложения.
- Обновите репозиторий курса.
- Скопируйте решение задания локальный Shadertoy 2 в эту папку.
- Установите Tracy profiler. Для Windows распространяется готовый бинарь, но на Linux вам придётся скомпилировать его руками. Имейте в виду, что если у вас Ubuntu старее чем 24.04, вам придётся компилировать Tracy со флажком LEGACY, заставив его использовать X11 вместо Wayland.
- Вспомните материал с занятий по асинхронности GPU и CPU.
Во-первых, чтобы сопоставить таймлайны CPU и GPU, нам придётся расставить немножко маркеров Tracy в вашем коде.
Сделайте это по аналогии с семплом shadowmap при помощи ETNA_PROFILE_GPU
для профилирования работы и GPU и CPU и ZoneScoped
для профилирования только CPU.
Не забудьте добавить вызовы ETNA_READ_BACK_GPU_PROFILING
и FrameMark
каждый кадр.
Соберите вашу программу в конфигурации RelWithDebInfo
.
В сборе Release
профилирование отключено, а в сборке Debug
выключены оптимизации и результаты замеров будут нерепрезентативны.
Запустите Tracy и вашу программу, подключите Tracy к ней, запишите некоторое количество кадров и посмотрите внимательно на то, как выглядит трейс.
Если Tracy не хочет подключаться, обновите репозиторий, почистите кэш сборки и попробуйте ещё раз.
Какие выводы можно сделать, сопоставив GPU и CPU маркеры?
Чтобы эмулировать более крупной приложение, добавьте и на CPU и на GPU дополнительной "бесполезной" работы, чтобы кадр занимал на CPU в районе 8 миллисекунд, а на GPU чуть меньше 16 миллисекунд. На CPU для этого можно использовать обычный sleep посреди записи команд, а на GPU повысьте качестве трассировки SDF. Также будьте аккуратны: в зависимости от угла под которым вы смотрите на сцену, время занимаемое на GPU на трассировку SDF может отличаться.
Ещё раз посмотрите в профайлер и сопоставьте маркеры на CPU и GPU.
Тратим ли мы время в ожидании чего-нибудь?
Меняется ли результат если отключить в коде vsync
?
На данный момент ваше приложение скорее всего передаёт данные с CPU на GPU только через буферы команд. Пуш-константы свои данные на самом деле хранят тоже прямо в буфере команд. К счастью или к сожалению, Etna написана так, чтобы буферы команд "просто работали" даже после выполнения следующего шага без какой либо дополнительной работы с вашей стороны.
Однако, в более крупных приложениях обойтись только лишь буферами команд не удастся в силу ограничения на количество памяти которое можно хранить в них при помощи пуш-констант и нам придётся использовать юниформные буферы (они же конст-буферы).
Добавьте в ваше приложение юниформ-буфер и передавайте координаты мышки и разрешение экрана при помощи него. За примером того как это сделать обратитесь к семплу shadowmap. Если вы до сих пор не передавали координаты мыши в шейдер, придётся это сделать сейчас.
В коде инициализации Etna поправьте параметр numFramesInFlight
на 2 или 3.
Ещё раз посмотрите в профайлер.
Что изменилось?
Если вы всё сделали правильно, общая частота кадров приложения должна вырасти. Однако, есть один нюанс. Попробуйте сделать шейдер ещё медленнее, чтобы в вашем приложении было <10 FPS. Подвигайте мышкой и узрите артефакты.
Конечно же, эти артефакты не заметны при высокой частоте кадров, однако если бы мы передавали более чувствительные к валидности данные чем координаты мышки, например индекс по которому нужно обращаться в массив, то мы бы быстро словили легендарный device lost (аналог segmentation fault но на GPU).
Вспомним, что uniform buffer — область памяти, общая для CPU и GPU. Посмотрите ещё раз в профайлер. В какой момент CPU пишет ваш юниформ буфер, а в какой момент его читает GPU?
Исправьте артефакты при помощи двойной буферизации.
Иначе говоря, создайте 2 разных юниформ буфера и чередуйте их при записи чётных и нечётных кадров.
Если вы выставили numFramesInFlight
в 3, то буферизацию придётся делать тройной.
Если вы любите аккуратный код, вас может заинтересовать шаблон etna::GpuSharedResource
.
Примеры того как его использовать вы можете найти в коде Этны.
В дальнейших домашках тоже рекомендуется пользоваться Tracy для понимания, какие места являются "узкими" в ваших приложениях, а также использовать frames-in-flight.