среда, 11 мая 2022 г.

Нужен ли рендер граф? Часть 1

 Еще в 2018-ом я написал свой FrameGraph, который был моей первой попыткой упростить работу с Vulkan, за полгода активной разработки синхронизации и многопоточность были полностью переделаны 3 раза. В последней версии FG стал достаточно гибким, чтобы автоматически расставлять синхронизации внутри очереди, а с небольшими подсказками от пользователя мог расставлять синхронизации между очередями. Это отлично работает для прототипирования, но нужно ли для больших проектов?

Какие функции выполняет рендер граф

  • Расстановка синхронизаций (внутри очереди).
  • Синхронизации между очередями и передача владения ресурсами (queue ownership transfer - для большей производительности на AMD).
  • Позволяет выкидывать проходы, результат которых не используется.
  • Позволяет переиспользовать рендер таргеты для экономии памяти (виртуальные ресурсы).
  • Переставляет проходы для большей производительности.

Как рендер граф будет использоваться

Для прототипирования рендер граф это отличный алгоритм, но в конечном продукте чаще всего все проходы заранее определены и их порядок зафиксирован. Даже если есть какая-то вариативность, то она имеет небольшое количество частных случаев. Стоит ли ради этого тратить время CPU на построение графов?

Мобильные платформы

В идеале для тайловой архитектуры GPU весь кадр должен рисоваться за один рендер пасс, чтобы максимально использовать кэш тайла. В этом случае синхронизации заранее заданы в рендер пассе и используется фиксированный набор рендер таргетов. Чтобы выкинуть проходы из рендер пасса придется создавать новый и компилировать пайплайны для него, что очень медленно.

Получается, что для мобильных платформ нужно только расставить барьеры, и то не очень много, так как большинство синхронизаций уже зафиксированно в рендер пассе.

Async compute

Для правильного использования асинхронного компьюта нужно чтобы он занимал простаивающие вычислительные блоки, тогда как в другой очереди идет ожидание загрузки данных из памяти. Неправильное использование асинхронных очередей может только замедлить рендеринг из-за потери времени на синхронизации (семафор медленный) и слетевшей компрессии текстур на AMD.

Поэтому точки синхронизации обычно заданы заранее и нужно только указать какие ресурсы передать или скопировать для использования в другой очереди. Есть вариант для каждого прохода делать подсказку для рендер графа какая часть GPU в нем более нагружена (ALU, Mem, FFP), но это уже требует от пользователя либо предположения, либо предварительного использования профайлера, а значит это ни чем не отличается от вручную заданного распределения проходов по очередям.

пример из Horizon Zero Dawn: Wild West

Перестановка проходов в рендер графе

Один из хороших примеров оптимизирующего рендер графа - встроенный в Metal 2. Но так ли он нужен? С помощью профайлера можно найти простои GPU и устранить их, результат будет тотже что и при автоматической перестановке проходов в рендер графе или даже лучше. В этом случае рендер граф будет даже мешать, так как вместо полностью предсказуемого и заранее оптимизированного порядка выполнения все будет зависеть от алгоритма рендер графа, который может вносить нестабильность.

При перестановке проходов рендер граф старается минимизировать переключение контекстов, например:  graphics -> compute -> graphics, graphics -> transfer -> graphics. В мини-блоге я разбирал как обновление юниформ буфера между рендер пассами приводит к простою GPU. Достаточно один раз правильно расставить проходы руками и проверить профайлером, вместо того, чтобы рендер граф каждый кадр делал тоже самое.

Выкидывание проходов

Еще в первом докладе о фреймграфе от Dice описывалась эта фича. Идея в том, чтобы накидать в фреймграф все возможные проходы, а на этапе построения выкинуть те, результат которых нигде не используется. Идея хорошая, но так ли часто это используется?

Чаще всего комбинаций проходов не так много и можно все их описать заранее. При этом с фреймграфом начинаются сложности, когда результат используется в последующих кадрах. К тому же для тестирования нужно точно знать все возможные варианты рисования.

Оптимизация памяти рендер таргетов

Для некоторых GPU это актуально, например для дешевых с 4Гб памяти в комбинации с 4к мониторами. Для разных API есть разные варианты реализации переиспользования рендер таргетов, также у некоторых вендоров есть свои расширения под это. Всегда можно написать свой алгоритм для некоторой части рендера, чтобы оптимизировать расход памяти. Встраивать такой алгоритм внутрь движка возможно не лучшая идея. Также переиспользование памяти может понабодиться не только для рендеринга, но и для компьюта.

Изначально эта оптимизация была для GL/DX11, где выделение памяти спрятано в драйвере и может занимать много времени, в Vulkan память может быть переиспользована, а создание новых рендер таргетов достаточно быстрая операция, к тому же алгоритмы типа visibility buffer и forward+ rendering позволяют уменьшить расход памяти на G-Buffer.

Также на старых GPU было меньше регистров и меньше разница между производительностью ALU и доступом к памяти, поэтому использование множества рендер таргетов для промежуточных результатов было оправдано. Сейчас может быть эффективнее сделать большой компьют шейдер, где объединить все постпроцессы, чтобы обойтись без лишнего доступа к глобальной памяти.

Итог

  • Автоматическая расстановка барьеров - нужна.
  • Автоматическое использование асинхронных очередей - не нужно.
  • Выкидывание проходов, результат которых не используется - не нужно.
  • Переиспользование рендер таргетов - универсальный алгоритм не нужен, возможны специфичные реализации под задачу.
  • Перестановка проходов - не нужна.
Все это актуально для нового рендера, есть идет переход со старого API, например GL/DX11, то рендер граф заменяет функционал драйвера, который был урезан в Vulkan/DX12, поэтому он позволит портировать с меньшими затратами.

Комментариев нет:

Отправить комментарий