понедельник, 21 февраля 2022 г.

Как делается поддержка множества конфигураций железа

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

В Metal было несколько подходов, сначала был feature set, а потом его заменили на GPU family с Common/iOS/Mac семействами. Common задает общие фичи для всех конфигураций железа. Часть параметров также требуется запрашивать в рантайме. Сейчас свежее железо типа M1 и A14 имеет почти идентичные характеристики, что сильно упрощает разработку.

В Vulkan огромное количество фич и разнообразные лимиты, даже есть сайт, где можно посмотреть все конфиги: gpuinfo, также добавили слой device_simulation_layer, который частично эмулирует другие девайсы и упрощает тестирование.

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

К тому же AMD и NVidia имеют свои особенности, из-за чего для лучшей производительности приходится писать разные варианты кода. Например паттерн доступа к памяти для storage image у них противоположные, что приводит к кэшмиссам на одной из платформ. На NVidia отлично работает сжатие рендер таргетов, поэтому стоит использовать фрагментный шейдер вместо компьют когда это возможно, а на AMD RDNA появилось сжатие при записи в storage image, что позволяет чаще использовать компьют шейдер и максимально их распараллеливать (в том числе в async compute).

Даже доступ к константному буферу может сильно отличаться, судя по perftest на NVidia последовательное чтение может привести к большим потерям производительности, тогда как на AMD наоборот, это лучше случайного доступа.

Hardware occlussion culling давно добавлен на NVidia и мобилках, но отсутствует (или только недавно добавлен) в AMD, что также влияет на проходы рендера.

На мобилках все еще разнообразнее. До сих пор популярны старые GPU использующие векторные операции, тогда как новые уже все скалярные. Также на некоторых Mali work group на 128 потоков работает в 2 раза медленее из-за того что регистров выделяется в 2 раза меньше.

Также на Mali отсутствует workgroup shared memory, это та же системная память, поэтому использовать эту память для оптимизации не получится.

Кроме нюансов GPU еще есть отличие для встроенных/дискретных, есть разнообразные мониторы от 4к на 60Гц до VR или игровых мониторов на 360Гц. Например на 60Гц и макисмальном качеством графики лучше не использовать темпоральные техники, а вот на 360Гц это может ускорить рендер и не даст видимых артефактов. То же самое и для HDR и неHDR мониторов, к тому же стандартов HDR уже не мало.

Разный объем памяти на GPU также влияет, например G-Buffer для deferred renderer занимает много памяти в 4к разрешении, к томуже это создает нагрузку на шину памяти, получается что в некоторых случаях лучше использовать forward+, visibility buffer и другие техники.

В общем в сложном рендере может быть слишком много ветвлений, которые становится сложно читать и сложно тестировать.


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


Техника рендеринга содержит:
  • Список проходов рисования, компьюта, рейтреса. Для каждого прохода можно задавать базовый дескриптор сет. Для прохода рисования задается сабпасс в рендер пассе и базовые рендер стейты (часть можно переопределять, часть - нет).
  • Описание рендер пассов со всеми внутренними синхронизациями.
  • Список FeatureSet, которые должны поддерживаться.

Пайплайн лейаут задается вручную, чтобы минимизировать смену дескрипторов и упростить работу с пулом дескрипторов. Лейауты конвертируются в биндинги ресурсов в GLSL и добавляется к шейдеру. На этапе описания лейаута проверяется, что ресурсы совместимы с заданными FeatureSet - нет превышения лимитов, не используются расширения и тд.

Для пайплайна задается проход в технике рендеринга, после чего происходит валидация - все расширения в шейдерах должны быть заданы в FeatureSet, форматы для storage image должны поддерживаться, атомарные операции с ресурсами требуют отдельные фичи и тд.
Проверяется, что выходные значения из шейдера совпадают с типами аттачментов. Проверяются переопределения рендер стейтов.
То есть это гарантирует, что пайплайн будет работать, если поддерживается FeatureSet.

Для техники рендеринга описывается рендер пасс со всеми синхронизациями между сабпассов, с форматами аттачментов и тд. Рендер пассы также проходят валидацию.

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


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

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

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