Qual é uma boa abordagem para lidar com uniformes no OpenGL moderno?

8

Estou criando um renderizador usando o OpenGL moderno (3.1 e superior) e agora estou tentando criar uma maneira eficiente, mas flexível, de lidar com uniformes. Estive lendo sobre objetos de buffer uniformes e sobre o que é uma abordagem 'comum' para usá-los (infelizmente este último não me deu tantos resultados quanto eu esperava).

Para reduzir as chamadas à API do OpenGL e armazenar dados na memória contígua, estou pensando em criar vários buffers grandes para cada estrutura de dados que deve ser carregada na GPU. Cada buffer tem um tamanho máximo de 16kb (pelo que entendi, é garantido que isso esteja disponível para uma UBO). Quando um objeto deseja fazer o upload de uniformes para a GPU, ele busca o primeiro buffer do tipo a ser carregado que ainda não está cheio e obtém o próximo índice disponível nesse buffer. Quando o objeto é desenhado, ele vincula o UBO (se ainda não estiver vinculado) e carrega o índice do elemento do UBO.

Isso resulta em algo como isto:

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;

No entanto, também estou considerando o seguinte:

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

De certa forma, isso parece muito mais limpo, pois é necessário um único índice para acessar todos os dados relacionados à malha a ser carregada. Por outro lado, isso pode ficar fora de controle (tamanho do buffer maior que 16kb, manipula dados irrelevantes (por exemplo, uma malha sem esqueleto) ou até mesmo problemas de sincronização, pois você não tem acesso permitido para dizer o que está fazendo enquanto carrega as matrizes do modelo. e não tenho certeza de como isso afetaria o layout da memória na GPU.

Francamente, parece que estou preso aqui e não consigo encontrar um bom exemplo concreto de como você lidaria com a UBO de forma rápida e flexível.

Você tem algum conselho ou recurso para mim que possa me ajudar aqui?

PhilipMR
fonte

Respostas:

2

Subalocar de um buffer maior é absolutamente o caminho a percorrer, com ressalvas. Estou vindo mais do lado das coisas do DirectX / Vulkan, mas isso deve se aplicar igualmente ao OpenGL (simplesmente não terei chamadas diretas de API aqui nesta resposta). As coisas a considerar são as seguintes:

  • Você precisa indexar no buffer maior ou concorda em vincular o recurso ao deslocamento a cada vez?
  • Você já cuidou de alguma / todas as restrições de alinhamento de seus uniformes que são agrupadas (é comum o alinhamento de 256 bytes)?

As APIs gráficas mais recentes têm um "deslocamento dinâmico" que você pode especificar com o comando draw, que é uma maneira bastante rápida de acessar indiretamente uma sub-região de um buffer. No entanto, supondo que você tenha dados de buffer triplo mutável, deve haver pouca ou nenhuma contenção no driver para vincular os dados (apenas algumas despesas indiretas).

Em resumo, sim, alocar regiões maiores de memória / buffers e sublocar essas regiões é considerado uma prática recomendada. Isso se aplica mesmo a objetos com sombreadores diferentes (se o seu alocador puder lidar com isso).

jeremyong
fonte
0

Inclua uma fase de benchmarking para ambas as soluções em seu aplicativo e selecione a solução vencedora em tempo de execução. Isso é simples, portátil e à prova de futuro. Quero dizer, você tem teste para isso, certo? ;-)

Sei que esta é uma resposta bastante genérica às "melhores práticas" para obter alto desempenho, mas se você pensar nisso, existem milhares de configurações possíveis de destino e muitos fornecedores a considerar. Se você precisar desse pequeno extra, pague ao seu fornecedor um driver otimizado para o seu aplicativo.

Andreas
fonte