воскресенье, 20 февраля 2022 г.

Особенности рендер пассов

 Рендер пассы в Vulkan в основном нужны для оптимизации тайлового рендера в мобильных GPU (tile based deferred renderer), для этого в них используются сабпассы.

В начале рендер пасса данные аттачментов загружаются в кэш тайлового рендера, если loadOp = Load, либо очищаются (или помечаются как очищенные) если loadOp = Clear, либо содержимое кэша неопределено если loadOp = DontCare. В целях безопасности скорее всего DontCare работает аналогично Clear, чтобы никто не мог прочитать предыдущие данные из кэша.

При завершении всех сабпассов данные из кэша выгружаются в глобальную память если storeOp = Store, либо не выгружаются, если storeOp = DontCare. Каждый сабпасс определяет какие из аттачментов используются в данный момент. Если аттачмент никак не используется в сабпассе, то его данные к кэше могут быть затерты и переиспользованы, чтобы этого не произошло в описание сабпасса есть preserve attachment

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

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

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


Неявные зависимости

Если initialLayout отличается от layout, используемого в сабпассе, то создается неявная зависимость с (srcSubpass = External, dstSubpass = N), аналогично и для finalLayout - создается (srcSubpass = N, dstSubpass = External). Неявные зависимости внутри рендер пасса не создаются, поэтому их обязательно задавать вручную.


Явные зависимости

Как и для барьеров есть два типа зависимостей:

Execution dependency - когда оба access mask не определены (равны нулю), эта зависимость не дает сабпассам выполняться параллельно. Обычно достаточно поставить зависимость между записью в аттачменты:
srcStageMask = EarlyFragmentTests | ColorAttachmentOutput
dstStageMask = EarlyFragmentTests | ColorAttachmentOutput

Memory dependency - всегда требуется, когда у аттачмента меняется image layout, например color attachment переходит из ColorAttachmentOptimal в General (что очень неоптимально), либо когда меняется тип аттачмента, например из color attachment в input attachment, даже если layout не меняется: imageLayout = General.

Зависимость между color и input аттачментами выглядит так:
srcStageMask   = EarlyFragmentTests | ColorAttachmentOutput
srcAccessMask =  ColorAttachmentWrite
dstStageMask   = FragmentShader | EarlyFragmentTests | ColorAttachmentOutput
dstAccessMask = InputAttachmentRead

EarlyFragmentTests в dstStageMask нужен только чтобы явно разделить запись в аттачменты между сабпассами.
Сама растеризация содержит несколько неявных этапов:
  • Раyнее отсечение всего треугольника.
  • Растеризация треугольника.
  • EarlyFragmentTests где происходит тест глубины и трафарета (stencil) и запись в них.
  • Вызывается фрагментный шейдер.
В тайловой архитектуре (TBDR) часть вершинного шейдера, отвечающая за положение треугольника на экране, запускается раньше всех и для всех сабпассов сразу. Это нужно чтобы подготовить данные и нарезать их на тайлы. Поэтому зависимость между сабпассами не должна содержвать этапы до растеризации, в том числе AllCommands, AllGraphics, иначе это приведет к потере производительности.
Сам этап растеризации треугольника не имеет своей константы, поэтому если между сабпассами зависимости содержат только (EarlyFragmentTests | FragmentShader | LateFragmentTests | ColorAttachmentOutput), то растеризация для следующего сабпасса может начаться до завершения записи в color attachment.

Для preserve attachment достаточно только execution dependency, но для цепочки (color -> preseve -> input) уже потребуется memory dependency в одном из промежутков.

В описании заивисмости есть набор флагов dependencyFlags, где ByRegion - зависимость между тайлами, без нее нет преимущества от сабпассов на мобильных девайсах, ViewLocal - зависимость между отдельными view, это нужно для multiview rendering, например для VR.

В Vulkan поддерживается одновременное чтение из input attachment и запись в color attachment, для этого у аттачмента должен быть одинаковый imageLayout = General, так как только он поддерживает оба типа операций. Далее требуется subpass self dependency - когда srcSubpass == dstSubpass, это позволяет вызывать vkCmdPipelineBarrier() внутри рендер пасса. Теперь можно рисовать геометрию без перекрытия и обязательно ставить барьер между каждым рисованием. Между барьерами фрагментный шейдер для каждого пикселя должен выполниться только один раз, иначе будет неопределенное поведение. В Metal и новом расширении Vulkan есть поддержка rasterization order group что упрощает написание подобного алгоритма.

Команда vkCmdClearAttachments() аналогична рисованию квадрата на весь экран или тайл и не требует как либо синхронизаций.


Совместимость рендер пассов

Если совпадает описание сабпассов и формата аттачмента, то рендер пассы совместимы. Операции loadOp, storeOp, лейауты initialLayout, finalLayout и все VkAttachmentReference::layout могут отличаться.

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


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

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