Система асинхронных задач для графики.
RenderTask
, RenderTaskCoro
- используется для асинхронной записи командного буфера. Внутри метода Run()
создается командный буфер, заполняется и добавляется в CommandBatch
с помощью метода Execute(cmdbuf)
.
CommandBatch
- хранит массив командных буферов и семафоров для синхронизации с другими батчами и с ЦП. Аналогичен одному вызову vkQueueSubmit.
Исходник: RenderTask.h, CommandBatch.h
DrawTask
, DrawTaskCoro
- используется для асинхронной записи вторичного командного буфера, аналогично RenderTask.
DrawCommandBatch
- хранит массив вторичных командных буферов, которые затем выполняются в IGraphicsContext::ExecuteSecondary()
.
Исходник: DrawTask.h, DrawCommandBatch.h
Метода WaitNextFrame()
ожидает пока предыдущий кадр отправится на выполнение и кадр минус N завершит выполнение на ГП, при двойной буферизации N=1, при тройной N=2. Пока идет ожидание внутри выполняются другие задачи. При ошибке или слишком долгом ожидании метод вернет false
.
Метод BeginFrame()
начинает новый кадр, сбрасывает покадровый аллокатор, сбрасывает счетчики, создает задачу на отложенное удаление графических ресурсов и тд.
Для каждого кадра передаются параметры в BeginFrameConfig
, где можно установить лимиты на использование промежуточной памяти.
(TODO: ссылка на управление памятью)
Метод EndFrame()
создает задачу, которая ожидает завершения записи командных буферов и батчей команд, отправки результата на экран и тд.
Метод BeginCmdBatch()
создает батч, в который будут записываться командные буферы текущего кадра.
Зависимости между батчами указываются вручную через методы CommandBatch: AddInputDependency()
, AddInputSemaphore()
, AddOutputSemaphore()
, либо автоматически, через рендер граф (RG).
В зависимостях задачи передается указатель на батч CommandBatchPtr
. Когда начинается новый кадр, проверяется какие батчи уже выполнены и они помечаются как завершенные.
Дополнительно есть вариант подписаться на момент отправки батча на ГП (вызов vkQueueSubmit), для этого используется зависимость CmdBatchOnSubmit{batch}
.
Для работы с доступной для ЦП памятью используется зависимость OnFrameNextCycle
, в отличие от отдельного батча CommandBatchPtr
, эта зависимость выполняется после вызова vkInvalidateMappedMemoryRanges.
Исходник: RenderTaskScheduler.h
Запись команда в Vulkan И Metal имеют отличия - в Metal используются энкодеры, они нужны для лучшего распараллеливания команд и сортировки их встроенным рендер графом. Переключение между graphics и compute дорогое, также и graphics и transfer, поэтому подход из Metal заставляет писать более оптимизированный код за счет своей архитектуры.
На Vulkan каждый контекст имеет свой набор этапов (pipeline stages), для лучшей производительности внутри контекста нужно минимизировать синхронизации.
Вместо энкодеров в движке используются контексты: Draw, Graphics, Transfer, ASBuild, RayTracing и тд.
Все команды копирования.
На Vulkan часть команд может выполняться только в графических или вычислительных очередях (EQueueType::Graphics
, EQueueType::AsyncCompute
).
На Metal нет разделения по типам очередей, но они могут эмулироваться для совместимости с Vulkan.
Используется для запуска вычислительного шейдера (compute shader).
Используется только для начала и завершения рендер пасса.
Поддерживается синхронный рендер пасс:
BeginRenderPass() -> DrawCtx -> EndRenderPass()
Асинхронный рендер пасс BeginMtRenderPass()
создает DrawCommandBatch
, который создает задачи DrawTask
.
Текущая задача должна дождаться заполнения вторичных командных буферов, затем записать их в первичный командный буфер через ExecuteSecondary( drawBatch )
и завершить рендер пасс EndMtRenderPass()
.
Пример Test_RG_DrawAsync1.cpp
Все команды рисования.
Только трассировка лучей.
На Vulkan трассировка может запускаться в любой момент, даже внутри рендер пасса, выполняется на этапе VK_PIPELINE_STAGE_2_RAY_TRACING_SHADER_BIT
.
На Metal эмулируется через вычислительный энкодер.
Все команды построения ускоряющих структур для трассировки лучей, а также их обновление, копирование, сериализация в/из памяти, чтение свойств.
На Vulkan выполняется на этапах VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT
и VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_COPY_BIT
.
На Metal это отдельный энкодер.
Исходник: CommandBuffer.h
В Vulkan и Metal командный буфер может использоваться только в одном потоке. Если не ограничивать количество потоков рендера, то может создаться много командных буферов, и это особенно накладно, если записывается всего несколько команд. Оптимальнее записать команды в память в разных потоках, а потом в одном потоке записать их в нативный командный буфер.
При планировании архитектуры был выбор из двух вариантов:
- Привязка к кадрам. Все команды отправляются для конкретного кадра, даже async compute не может выполняться несколько кадров - при двойной буферизации 2й кадр будет ждать завершения всех команд. Но это дает и преимущества - кадр более предсказуемый, что дает и более стабильное время кадра.
- Без привязки к кадрам. Это позволяет делать долгие асинхронные вычисления или копирования, но делает поведение менее предсказуемым.
Был выбран вариант с привязкой к кадрам, таким образом:
- Ресурсы не удаляются сразу, а с задержкой в 2 кадра.
- Используется общий staging buffer, выделенная память гарантированно валидна в пределах кадра, это упростило работу с памятью. Например в при чтении из видеопамяти в
ITransferContext::ReadbackImage()
. - Ограничен максимальный размер staging buffer на кадр, так чтобы все данные успели передаться по шине PCI-E за время кадра. Таким образом ГП не простаивает в ожидании данных с ЦП и время кадра более стабильное.