Qual a melhor forma de lidar com ID3D11InputLayout no código de renderização?

7

Estou procurando uma maneira elegante de lidar com layouts de entrada no meu código directx11.

O problema que tenho é que tenho uma classe Effect e uma classe Element. A classe de efeito encapsula shaders e configurações semelhantes, e a classe Element contém algo que pode ser desenhado (modelo 3d, paisagem etc.)

Meu código de desenho define os shaders do dispositivo, etc., usando o efeito especificado e, em seguida, chama a função de desenho do elemento para desenhar a geometria real contida nele.

O problema é este - eu preciso criar um D3D11InputLayout em algum lugar. Isso realmente pertence à classe Element, pois não é da conta do resto do sistema como esse elemento escolhe representar seu layout de vértice. Mas, para criar o objeto, a API requer o bytecode do shader de vértice para o shader de vértice que será usado para desenhar o objeto. No directx9, era fácil, não havia dependência, portanto meu elemento poderia conter suas próprias estruturas de layout de entrada e defini-las sem o efeito estar envolvido.

Mas o elemento realmente não precisa saber nada sobre o efeito com o qual está sendo desenhado, isso é apenas as configurações de renderização e o elemento existe para fornecer geometria.

Então, eu realmente não sei onde armazenar e como selecionar o InputLayout para cada chamada de empate. Quero dizer, fiz algo funcionar, mas parece muito feio.

Isso me faz pensar que perdi algo óbvio, ou então o meu design de ter todas as configurações de renderização em um Efeito, a Geometria em um Elemento e um terceiro que desenha tudo é apenas falho.

Imaginando como mais alguém lida com seus layouts de entrada no directx11 de maneira elegante?

JohnB
fonte

Respostas:

3

A maneira como lida com isso é que tenho um arquivo VertexFormat.h no qual tenho vários layouts de entrada de diferentes tipos (VertexPositionNormal, VertexNormalUV ...) agrupados em estruturas para facilitar a criação / destruição. Funciona muito bem, embora seja IMO feio. É muito fácil adicionar um novo formato de vértice ao arquivo se você tiver um sombreador que use um layout que não é suportado no momento. Então, eu tenho um VertexFormatManager que cria / destrói esses layouts quando eu precisar deles. Portanto, quando eu preciso criar um, eu alimento o código de bytes do sombreador e um ID de formato de vertex ao gerente, e ele o cria para mim e o armazena em um pool para que você tenha apenas um de cada layout. Então você precisa criar sua geometria. Para fazer isso, eu tenho um método Create na minha classe de geometria que leva um ID para um formato de vértice. Na função create, ele consulta o gerenciador de formatos de vértices com o ID fornecido. O gerente retorna um número inteiro que serve como um campo de bits com um sinalizador definido para cada tipo de elemento que o layout de entrada contém. Então, eu poderia ter algo parecido com isto:

int bitfield = VertexFormatManager::QueryFormat( formatID );
if (bitfield & VertexFormat::NORMAL)
{
    // do somethings
}
if (bitfield & VertexFormat::UV)
{
    // do somethings
}
...

Cada elemento do layout de entrada é criado em um ID3D11Buffer separado. Isso me permite construir uma malha, dado qualquer tipo de formato de vértice. Digamos que você queira renderizar um cubo com uma textura, ele precisa de posições de vértice e cordas uv. Você alimenta o ID de formato VertexPositionUV (como exemplo) e a consulta retorna um campo de bits que contém bits definidos para os sinalizadores POSITION e UV. Isso cria dois ID3D11Buffers: um para as posições e outro para os cabos uv. Em seguida, você anexa esses dois ao pipeline antes de desenhar. A vantagem de fazer isso é que, se você quiser fazer algo como mapas de sombra, poderá renderizar uma malha apenas com seu conjunto de buffers de posição de vértice.

Não tenho muita certeza disso, estou no trabalho e tentarei escrever adequadamente hoje à noite. Nesse meio tempo, fiz essa pergunta há um tempo e recebi esta resposta . É bom, mas acho que do jeito que estou fazendo isso agora faz mais sentido, especialmente no caso do DX11.

dotminic
fonte
Isso faz muito sentido, obrigado pela resposta abrangente ... Então você usa vários buffers de vértice por comando draw?
31912 JohnB
Sim, eu uso no buffer para cada elemento (posição, normais, uv ...), é útil dependendo do tipo de renderização e pode ser bastante flexível. Certamente não é a única maneira de fazer isso, mas funciona bem para a maioria das rotinas de renderização IMHO. Adicionei um pouco mais de detalhes e pseudo-código aqui: pastebin.com/XfqS6N7g Espero que ajude.
dotminic
6

Obrigado pelas respostas, elas são muito úteis. Vou adicionar uma resposta à minha própria pergunta, porque segui em frente um pouco.

No final, percebi que talvez querer separar completamente os shaders dos itens desenháveis ​​fosse um erro. Eles são fundamentalmente acoplados na "vida real", por isso talvez não seja um problema que eles estejam no design.

Por exemplo, um sombreador de água funcionará apenas com um elemento que deseja extrair água. Somente isso poderá fornecer os dados necessários. Um sombreador que desenha modelos animados funcionará apenas com um elemento de modelo animado. De repente, não consigo decidir usar um shader de água com um modelo animado. Simplesmente não pode funcionar.

Tentar criar uma classe abstrata de sombreador que possa carregar qualquer sombreador e trabalhar com qualquer elemento extraível é um erro. Não reflete como eles podem ser usados ​​na realidade.

Então, em vez disso, criei classes de sombreador AnimatedModelShader, WaterShader, LandscapeShader. Cada um deles pode ter opções que fazem com que ele carregue diferentes arquivos de sombreador físico, mas eles sempre terão a mesma interface básica por necessidade, porque um sombreador de paisagem sempre precisará do mesmo tipo de entrada de dados.

Portanto, o shader agora é responsável por criar seu próprio layout de entrada e informa ao elemento que o usa como fazer o layout de seus vértices, tendo um typedef público chamado vertexType que o elemento usa se desejar usar esse shader.

Nem um pouco do meu design original, mas, como se vê, o desejo de separar os dois conceitos não era muito útil.

JohnB
fonte
3

Faço algo semelhante ao dotminic - tenho uma rotina para a qual passo uma matriz de D3D11_INPUT_ELEMENT_DESC, que constrói um shader de vértice falso com uma assinatura de entrada correspondente, o compila e cria um layout a partir dele, finalmente liberando o shader falso.

Sim, é feio, e sim, significa precisar ser um pouco mais cuidadoso do que eu faria se fizesse da maneira correta, mas são trocas que estou preparada para aceitar em troca de uma separação mais limpa.

Maximus Minimus
fonte
Apenas para observar que agora me afastei dessa abordagem e em direção a um acoplamento mais estreito de layouts de entrada e vertex shaders, a ponto de sempre criá-los juntos. A abordagem que eu dou aqui provavelmente ainda está bem se você realmente deseja tê-los dissociados.
Maximus Minimus
2

Parece-me que você realmente não deveria se preocupar. Depois de olhar através da documentação do MSDN para CreateInputLayout, me deparei com isso:

Depois que um objeto de layout de entrada é criado a partir de uma assinatura de sombreador, o objeto de layout de entrada pode ser reutilizado com qualquer outro sombreador que tenha uma assinatura de entrada idêntica (semântica incluída). Isso pode simplificar a criação de objetos de layout de entrada quando você estiver trabalhando com muitos shaders com entradas idênticas. (Bingo) Se um tipo de dados na declaração de layout de entrada não corresponder ao tipo de dados em uma assinatura de entrada de sombreador, CreateInputLayout gerará um aviso durante a compilação. O aviso é simplesmente para chamar a atenção para o fato de que os dados podem ser reinterpretados quando lidos de um registro. Você pode desconsiderar esse aviso (se a reinterpretação for intencional) ou fazer com que os tipos de dados correspondam nas duas declarações para eliminar o aviso.

http://msdn.microsoft.com/en-gb/library/windows/desktop/ff476512(v=vs.85).aspx

Isso deixa bem claro que você deve poder tratar seus layouts de entrada quase exatamente da mesma maneira que suas antigas declarações de vértice do DX9. O fornecimento de bytecode do shader na primeira vez que você cria cada um deles permite que o DX11 os valide. E, aparentemente, o pior que acontecerá se você evitar ou falhar nesta etapa é um aviso de tempo de execução que parece seguro ignorar.

Portanto, o DX11 não está "criando uma dependência" entre seus layouts de entrada e seus shaders ... está oferecendo validação gratuita! : D

Richard Copperwaite
fonte
CreateInputLayouttambém pode ser usado para validar um sombreador em relação a um layout de entrada existente, para que você possa verificar todos os sombreadores que o usariam - o que eu recomendaria fazer, pelo menos em compilações de depuração, por segurança.
precisa saber é o seguinte
Hmm, quando tentei isso, descobri que fornecer um layout de entrada existente como parâmetro faria com que o ponteiro antigo fosse sobrescrito, resultando em um vazamento de memória. Pena que não há uma função separada 'Validar'.
Richard Copperwaite
Desculpe, por "um layout de entrada existente" eu realmente quis dizer a matriz de D3D11_INPUT_ELEMENT_DESCs usada para criá-lo. Isso pode ser validado no bytecode de outro shader sem criar um novo objeto de layout de entrada.
Nathan Reed