Каков хороший подход для работы с униформой в современном OpenGL?

8

Я создаю рендерер с использованием современного OpenGL (версии 3.1 и выше), и теперь я пытаюсь создать эффективный, но гибкий способ обработки униформы. Я читал об объектах с единым буфером и о том, что такое «общий» подход к их использованию (последний, к сожалению, дал не так много результатов, как я надеялся).

Чтобы уменьшить количество вызовов API OpenGL и хранить данные в непрерывной памяти, я рассматриваю возможность создания нескольких больших буферов для каждой структуры данных, которая должна быть загружена в графический процессор. Максимальный размер каждого буфера составляет 16 КБ (насколько я понимаю, многое гарантировано будет доступно для UBO). Когда объект хочет иметь возможность загружать униформу в графический процессор, он выбирает первый буфер того типа, который должен быть загружен, который еще не заполнен, и получает следующий доступный индекс в этом буфере. Когда объект рисуется, он связывает UBO (если еще не привязан) и загружает индекс элемента UBO.

Это приводит к чему-то вроде этого:

layout(std140) uniform ModelData { 
    mat4 model_matrix[kNumInstancesPerModelUbo]; 
}
uniform int u_ModelDataIndex;

layout(std140) uniform SkeletonData { 
    mat4 bone_transforms[kNumInstancesPerSkeletonUbo][kMaxBones]; 
}
uniform int u_SkeletonDataIndex;

Однако я также рассматриваю следующее:

layout(std140) uniform MeshData {
    mat4 model_matrix[kNumInstancesPerMeshUbo];
    mat4 bone_transforms[kNumInstancesPerMeshUbo][kMaxBones];
}
uniform int u_MeshDataIndex;

В некотором смысле это выглядит намного чище, поскольку для доступа ко всем данным, связанным с загружаемой сеткой, требуется один индекс. С другой стороны, это может выйти из-под контроля (размер буфера больше 16 КБ, обрабатывает нерелевантные данные (например, меш без скелета) или даже проблемы с синхронизацией, поскольку у вас нет доступа к произнесению костей при загрузке матриц модели) и я не уверен, как это повлияет на расположение памяти на GPU.

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

Есть ли у вас какие-либо советы или ресурсы для меня, которые могли бы помочь мне здесь?

PhilipMR
источник

Ответы:

2

Субраспределение из большего буфера - абсолютно верный путь, с оговорками. Я больше прохожу с DirectX / Vulkan стороны, но это должно в равной степени относиться к OpenGL (у меня просто не будет прямых вызовов API здесь в этом ответе). Необходимо учитывать следующее:

  • Нужно ли индексировать в больший буфер или у вас все в порядке с привязкой ресурса к смещению каждый раз?
  • Вы позаботились о каких-либо / всех ограничениях выравнивания для ваших униформ, которые упакованы вместе (выравнивание 256 байтов является распространенным)?

Более новые графические API имеют «динамическое смещение», которое вы можете указать с помощью команды draw, что является довольно быстрым способом косвенного доступа к субрегиону буфера. Однако, если вы используете данные с тройной буферизацией, которые могут изменяться, в драйвере не должно быть практически никаких конфликтов для привязки данных (только некоторые фиксированные издержки).

Таким образом, да, выделение больших областей памяти / буферов и субаренды этих областей считается лучшей практикой. Это относится даже к объектам с разными шейдерами (если ваш распределитель может с этим справиться).

jeremyong
источник
0

Включите этап тестирования для обоих решений в своем приложении, а затем выберите выигрышное решение во время выполнения. Это просто, портативно и перспективно. Я имею в виду, у вас есть тест для этого, верно? ;-)

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

Andreas
источник