Compartilhar via


Comparar buffers, uniformes e atributos de vértice do OpenGL ES 2.0 com o Direct3D

APIs importantes

Durante o processo de portabilidade para o Direct3D 11 do OpenGL ES 2.0, você deve alterar a sintaxe e o comportamento da API para passar dados entre o aplicativo e os programas de sombreador.

No OpenGL ES 2.0, os dados são passados de e para programas de sombreamento de quatro maneiras: como uniformes para dados constantes, como atributos para dados de vértice, como objetos de buffer para outros dados de recursos (como texturas). No Direct3D 11, eles são mapeados aproximadamente para buffers constantes, buffers de vértice e sub-recursos. Apesar da semelhança superficial, eles são tratados de maneira bastante diferente no uso.

Aqui está o mapeamento básico.

OpenGL ES 2.0 Direct3D 11
uniforme campo de buffer constante (cbuffer).
atributo campo de elemento de buffer de vértice, designado por um layout de entrada e marcado com uma semântica HLSL específica.
objeto buffer buffer; Consulte D3D11_SUBRESOURCE_DATA e D3D11_BUFFER_DESC e para obter definições de buffer de uso geral.
FBO (objeto de buffer de quadro) renderizar destino(s); Consulte ID3D11RenderTargetView com ID3D11Texture2D.
buffer traseiro cadeia de troca com superfície de "buffer traseiro"; Consulte IDXGISwapChain1 com IDXGISurface1 anexado.

 

Buffers de porta

No OpenGL ES 2.0, o processo para criar e vincular qualquer tipo de buffer geralmente segue esse padrão

  • Chame glGenBuffers para gerar um ou mais buffers e retornar os identificadores para eles.
  • Chame glBindBuffer para definir o layout de um buffer, como GL_ELEMENT_ARRAY_BUFFER.
  • Chame glBufferData para preencher o buffer com dados específicos (como estruturas de vértice, dados de índice ou dados de cor) em um layout específico.

O tipo mais comum de buffer é o buffer de vértice, que contém minimamente as posições dos vértices em algum sistema de coordenadas. No uso típico, um vértice é representado por uma estrutura que contém as coordenadas de posição, um vetor normal para a posição do vértice, um vetor tangente para a posição do vértice e coordenadas de pesquisa de textura (uv). O buffer contém uma lista contígua desses vértices, em alguma ordem (como uma lista de triângulos, faixa ou leque), e que representam coletivamente os polígonos visíveis em sua cena. (No Direct3D 11, bem como no OpenGL ES 2.0, é ineficiente ter vários buffers de vértice por chamada de desenho.)

Aqui está um exemplo: um buffer de vértice e um buffer de índice criados com o OpenGL ES 2.0:

OpenGL ES 2.0: Criando e preenchendo um buffer de vértice e um buffer de índice.

glGenBuffers(1, &renderer->vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, renderer->vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex) * CUBE_VERTICES, renderer->vertices, GL_STATIC_DRAW);

glGenBuffers(1, &renderer->indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * CUBE_INDICES, renderer->vertexIndices, GL_STATIC_DRAW);

Outros buffers incluem buffers de pixel e mapas, como texturas. O pipeline do sombreador pode renderizar em buffers de textura (pixmaps) ou renderizar objetos de buffer e usar esses buffers em futuras passagens de sombreador. No caso mais simples, o fluxo de chamadas é:

  • Chame glGenFramebuffers para gerar um objeto de buffer de quadro.
  • Chame glBindFramebuffer para associar o objeto de buffer de quadro para gravação.
  • Chame glFramebufferTexture2D para desenhar em um mapa de textura especificado.

No Direct3D 11, os elementos de dados de buffer são considerados "sub-recursos" e podem variar de elementos de dados de vértice individuais a texturas de mapa MIP.

Direct3D 11: criando e preenchendo um buffer de vértice e um buffer de índice.

D3D11_SUBRESOURCE_DATA vertexBufferData = {0};
vertexBufferData.pSysMem = cubeVertices;
vertexBufferData.SysMemPitch = 0;
vertexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC vertexBufferDesc(sizeof(cubeVertices), D3D11_BIND_VERTEX_BUFFER);

m_d3dDevice->CreateBuffer(
  &vertexBufferDesc,
  &vertexBufferData,
  &m_vertexBuffer);

m_indexCount = ARRAYSIZE(cubeIndices);

D3D11_SUBRESOURCE_DATA indexBufferData = {0};
indexBufferData.pSysMem = cubeIndices;
indexBufferData.SysMemPitch = 0;
indexBufferData.SysMemSlicePitch = 0;
CD3D11_BUFFER_DESC indexBufferDesc(sizeof(cubeIndices), D3D11_BIND_INDEX_BUFFER);

m_d3dDevice->CreateBuffer(
  &indexBufferDesc,
  &indexBufferData,
  &m_indexBuffer);
    

Buffers de pixel graváveis ou mapas, como um buffer de quadros, podem ser criados como objetos ID3D11Texture2D. Eles podem ser associados como recursos a um ID3D11RenderTargetView ou ID3D11ShaderResourceView, que, uma vez desenhado, pode ser exibido com a cadeia de troca associada ou passado para um sombreador, respectivamente.

Direct3D 11: criando um objeto de buffer de quadro.

ComPtr<ID3D11RenderTargetView> m_d3dRenderTargetViewWin;
// ...
ComPtr<ID3D11Texture2D> frameBuffer;

m_swapChainCoreWindow->GetBuffer(0, IID_PPV_ARGS(&frameBuffer));
m_d3dDevice->CreateRenderTargetView(
  frameBuffer.Get(),
  nullptr,
  &m_d3dRenderTargetViewWin);

Alterar uniformes e objetos de buffer uniformes para buffers constantes do Direct3D

No Open GL ES 2.0, os uniformes são o mecanismo para fornecer dados constantes para programas de sombreamento individuais. Esses dados não podem ser alterados pelos sombreadores.

A configuração de um uniforme normalmente envolve fornecer a um dos métodos glUniform* o local de upload na GPU junto com um ponteiro para os dados na memória do aplicativo. Depois que o método glUniform* é executado, os dados uniformes estão na memória da GPU e podem ser acessados pelos sombreadores que declararam esse uniforme. Espera-se que você garanta que os dados sejam empacotados de forma que o sombreador possa interpretá-los com base na declaração uniforme no sombreador (usando tipos compatíveis).

OpenGL ES 2.0 Criando um uniforme e carregando dados para ele

renderer->mvpLoc = glGetUniformLocation(renderer->programObject, "u_mvpMatrix");

// ...

glUniformMatrix4fv(renderer->mvpLoc, 1, GL_FALSE, (GLfloat*) &renderer->mvpMatrix.m[0][0]);

No GLSL de um sombreador, a declaração uniforme correspondente tem esta aparência:

Open GL ES 2.0: Declaração uniforme GLSL

uniform mat4 u_mvpMatrix;

O Direct3D designa dados uniformes como "buffers constantes", que, como uniformes, contêm dados constantes fornecidos a sombreadores individuais. Assim como acontece com buffers uniformes, é importante empacotar os dados do buffer constante na memória de forma idêntica à maneira como o sombreador espera interpretá-los. O uso de tipos DirectXMath (como XMFLOAT4) em vez de tipos de plataforma (como float* ou float[4]) garante o alinhamento adequado do elemento de dados.

Os buffers constantes devem ter um registro de GPU associado usado para fazer referência a esses dados na GPU. Os dados são empacotados no local do registro, conforme indicado pelo layout do buffer.

Direct3D 11: Criando um buffer constante e carregando dados nele

struct ModelViewProjectionConstantBuffer
{
     DirectX::XMFLOAT4X4 mvp;
};

// ...

ModelViewProjectionConstantBuffer   m_constantBufferData;

// ...

XMStoreFloat4x4(&m_constantBufferData.mvp, mvpMatrix);

CD3D11_BUFFER_DESC constantBufferDesc(sizeof(ModelViewProjectionConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);
m_d3dDevice->CreateBuffer(
  &constantBufferDesc,
  nullptr,
  &m_constantBuffer);

No HLSL de um sombreador, a declaração de buffer constante correspondente tem esta aparência:

Direct3D 11: Declaração HLSL de buffer constante

cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
  matrix mvp;
};

Observe que um registro deve ser declarado para cada buffer constante. Diferentes níveis de recursos do Direct3D têm diferentes registros máximos disponíveis, portanto, não exceda o número máximo para o nível de recurso mais baixo que você está direcionando.

Atributos de vértice de porta para layouts de entrada do Direct3D e semântica HLSL

Como os dados de vértice podem ser modificados pelo pipeline do sombreador, o OpenGL ES 2.0 requer que você os especifique como "atributos" em vez de "uniformes". (Isso mudou em versões posteriores do OpenGL e GLSL.) Dados específicos do vértice, como posição do vértice, normais, tangentes e valores de cor, são fornecidos aos sombreadores como valores de atributo. Esses valores de atributo correspondem a deslocamentos específicos para cada elemento nos dados de vértice; Por exemplo, o primeiro atributo pode apontar para o componente de posição de um vértice individual e o segundo para o normal e assim por diante.

O processo básico para mover os dados do buffer de vértice da memória principal para a GPU é semelhante a este:

  • Carregue os dados de vértice com glBindBuffer.
  • Obtenha a localização dos atributos na GPU com glGetAttribLocation. Chame-o para cada atributo no elemento de dados de vértice.
  • Chame glVertexAttribPointer para definir o tamanho correto do atributo e o deslocamento dentro de um elemento de dados de vértice individual. Faça isso para cada atributo.
  • Habilite as informações de layout de dados de vértice com glEnableVertexAttribArray.

OpenGL ES 2.0: Carregando dados de buffer de vértice para o atributo shader

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, renderer->vertexBuffer);
loc = glGetAttribLocation(renderer->programObject, "a_position");

glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, 
  sizeof(Vertex), 0);
loc = glGetAttribLocation(renderer->programObject, "a_color");
glEnableVertexAttribArray(loc);

glVertexAttribPointer(loc, 4, GL_FLOAT, GL_FALSE, 
  sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));
glEnableVertexAttribArray(loc);

Agora, em seu sombreador de vértice, você declara atributos com os mesmos nomes definidos em sua chamada para glGetAttribLocation.

OpenGL ES 2.0: Declarando um atributo no GLSL

attribute vec4 a_position;
attribute vec4 a_color;                     

De certa forma, o mesmo processo vale para o Direct3D. Em vez de atributos, os dados de vértice são fornecidos em buffers de entrada, que incluem buffers de vértice e os buffers de índice correspondentes. No entanto, como o Direct3D não tem a declaração de "atributo", você deve especificar um layout de entrada que declare o componente individual dos elementos de dados no buffer de vértice e a semântica HLSL que indica onde e como esses componentes devem ser interpretados pelo sombreador de vértice. A semântica HLSL exige que você defina o uso de cada componente com uma cadeia de caracteres específica que informa o mecanismo de sombreador quanto à sua finalidade. Por exemplo, os dados de posição do vértice são marcados como POSITION, os dados normais são marcados como NORMAL e os dados de cor do vértice são marcados como COLOR. (Outros estágios do sombreador também exigem semântica específica, e essas semânticas têm interpretações diferentes com base no estágio do sombreador.) Para obter mais informações sobre a semântica HLSL, leia Portar o pipeline do sombreador e Semântica HLSL.

Coletivamente, o processo de definir os buffers de vértice e índice e definir o layout de entrada é chamado de estágio "Input Assembly" (IA) do pipeline de gráficos Direct3D.

Direct3D 11: Configurando o estágio de assembly de entrada

// Set up the IA stage corresponding to the current draw operation.
UINT stride = sizeof(VertexPositionColor);
UINT offset = 0;
m_d3dContext->IASetVertexBuffers(
        0,
        1,
        m_vertexBuffer.GetAddressOf(),
        &stride,
        &offset);

m_d3dContext->IASetIndexBuffer(
        m_indexBuffer.Get(),
        DXGI_FORMAT_R16_UINT,
        0);

m_d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_d3dContext->IASetInputLayout(m_inputLayout.Get());

Um layout de entrada é declarado e associado a um sombreador de vértice declarando o formato do elemento de dados de vértice e a semântica usada para cada componente. O layout de dados do elemento de vértice descrito no D3D11_INPUT_ELEMENT_DESC criado deve corresponder ao layout da estrutura correspondente. Aqui, você cria um layout para dados de vértice que tem dois componentes:

  • Uma coordenada de posição de vértice, representada na memória principal como um XMFLOAT3, que é uma matriz alinhada de 3 valores de ponto flutuante de 32 bits para as coordenadas (x, y, z).
  • Um valor de cor de vértice, representado como um XMFLOAT4, que é uma matriz alinhada de 4 valores de ponto flutuante de 32 bits para a cor (RGBA).

Você atribui uma semântica para cada um, bem como um tipo de formato. Em seguida, passe a descrição para ID3D11Device1::CreateInputLayout. O layout de entrada é usado quando chamamos ID3D11DeviceContext1::IASetInputLayout quando você configura o assembly de entrada durante nosso método de renderização.

Direct3D 11: Descrevendo um layout de entrada com semântica específica

ComPtr<ID3D11InputLayout> m_inputLayout;

// ...

const D3D11_INPUT_ELEMENT_DESC vertexDesc[] = 
{
  { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D11_INPUT_PER_VERTEX_DATA, 0 },
  { "COLOR",    0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};

m_d3dDevice->CreateInputLayout(
  vertexDesc,
  ARRAYSIZE(vertexDesc),
  fileData->Data,
  fileData->Length,
  &m_inputLayout);

// ...
// When we start the drawing process...

m_d3dContext->IASetInputLayout(m_inputLayout.Get());

Por fim, certifique-se de que o sombreador possa entender os dados de entrada declarando a entrada. A semântica atribuída no layout é usada para selecionar os locais corretos na memória da GPU.

Direct3D 11: Declarando dados de entrada do sombreador com semântica HLSL

struct VertexShaderInput
{
  float3 pos : POSITION;
  float3 color : COLOR;
};