Partager via


Comparer les tampons OpenGL ES 2.0, les uniformes et les attributs de vertex à Direct3D

API importantes

Pendant le processus de portage vers Direct3D 11 à partir d’OpenGL ES 2.0, vous devez modifier la syntaxe et le comportement de l’API pour transmettre des données entre l’application et les programmes de nuanceur.

Dans OpenGL ES 2.0, les données sont transmises à et à partir de programmes de nuanceur de quatre façons : en tant qu’uniformes pour les données constantes, en tant qu’attributs pour les données de vertex, en tant qu’objets de mémoire tampon pour d’autres données de ressources (telles que les textures). Dans Direct3D 11, ceux-ci correspondent approximativement aux mémoires tampons constantes, aux mémoires tampons de vertex et aux sous-ressources. Malgré la communalité superficielle, ils sont gérés tout à fait différemment dans l’utilisation.

Voici le mappage de base.

OpenGL ES 2.0 Direct3D 11
uniform champ de mémoire tampon constante (cbuffer).
attribute Champ d’élément de mémoire tampon de vertex, désigné par une disposition d’entrée et marqué avec une sémantique HLSL spécifique.
objet buffer tampon; Consultez D3D11_SUBRESOURCE_DATA et D3D11_BUFFER_DESC et pour obtenir des définitions de mémoire tampon à usage général.
objet de mémoire tampon frame (FBO) render target(s) ; Voir ID3D11RenderTargetView avec ID3D11Texture2D.
mémoire tampon d’arrière-mémoire tampon la chaîne d’échange avec la surface « mémoire tampon arrière » ; Consultez IDXGISwapChain1 avec IDXGISurface1 attaché.

 

Mémoires tampons de port

Dans OpenGL ES 2.0, le processus de création et de liaison d’un type de mémoire tampon suit généralement ce modèle

  • Appelez glGenBuffers pour générer une ou plusieurs mémoires tampons et renvoyer les handles vers eux.
  • Appelez glBindBuffer pour définir la disposition d’une mémoire tampon, par exemple GL_ELEMENT_ARRAY_BUFFER.
  • Appelez glBufferData pour remplir la mémoire tampon avec des données spécifiques (telles que des structures de vertex, des données d’index ou des données de couleur) dans une disposition spécifique.

Le type de mémoire tampon le plus courant est la mémoire tampon de vertex, qui contient minimalement les positions des sommets dans un système de coordonnées. En règle générale, un sommet est représenté par une structure qui contient les coordonnées de position, un vecteur normal vers la position de vertex, un vecteur tangent à la position de vertex et des coordonnées de recherche de texture (uv). La mémoire tampon contient une liste contiguë de ces sommets, dans un ordre quelconque (comme une liste de triangles, une bande ou un ventilateur) et qui représentent collectivement les polygones visibles dans votre scène. (Dans Direct3D 11 et OpenGL ES 2.0, il est inefficace d’avoir plusieurs mémoires tampons de vertex par appel de dessin.)

Voici un exemple de mémoire tampon de vertex et de mémoire tampon d’index créée avec OpenGL ES 2.0 :

OpenGL ES 2.0 : création et remplissage d’une mémoire tampon de vertex et d’une mémoire tampon d’index.

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);

D’autres mémoires tampons incluent des mémoires tampons de pixels et des mappages, tels que des textures. Le pipeline de nuanceur peut s’afficher en mémoires tampons de texture (pixmaps) ou afficher des objets de mémoire tampon et utiliser ces mémoires tampons dans les futures passes de nuanceur. Dans le cas le plus simple, le flux d’appels est :

  • Appelez glGenFramebuffers pour générer un objet de mémoire tampon de trame.
  • Appelez glBindFramebuffer pour lier l’objet de mémoire tampon de trame pour l’écriture.
  • Appelez glFramebufferTexture2D pour dessiner dans une carte de texture spécifiée.

Dans Direct3D 11, les éléments de données de mémoire tampon sont considérés comme des « sous-ressources » et peuvent aller des éléments de données de vertex individuels aux textures MIP-map.

Direct3D 11 : création et remplissage d’une mémoire tampon de vertex et d’une mémoire tampon d’index.

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);
    

Les mémoires tampons de pixels accessibles en écriture ou les mappages, tels qu’une mémoire tampon d’images, peuvent être créées en tant qu’objets ID3D11Texture2D . Celles-ci peuvent être liées en tant que ressources à un ID3D11RenderTargetView ou ID3D11ShaderResourceView, qui, une fois dessinée, peuvent être affichées avec la chaîne d’échange associée ou passées à un nuanceur, respectivement.

Direct3D 11 : Création d’un objet de mémoire tampon de trame.

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

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

Modifier les uniformes et les objets de mémoire tampon uniforme en mémoires tampons constantes Direct3D

Dans Open GL ES 2.0, les uniformes sont le mécanisme permettant de fournir des données constantes à des programmes de nuanceur individuels. Ces données ne peuvent pas être modifiées par les nuanceurs.

La définition d’un uniforme implique généralement de fournir l’une des méthodes glUniform* avec l’emplacement de chargement dans le GPU, ainsi qu’un pointeur vers les données de la mémoire de l’application. Une fois la méthode ithe glUniform* exécutée, les données uniformes se situent dans la mémoire GPU et sont accessibles par les nuanceurs qui ont déclaré cet uniforme. Vous êtes censé vous assurer que les données sont empaquetées de telle sorte que le nuanceur puisse l’interpréter en fonction de la déclaration uniforme dans le nuanceur (à l’aide de types compatibles).

OpenGL ES 2.0 Création d’un fichier uniforme et chargement de données vers celui-ci

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

// ...

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

Dans le GLSL d’un nuanceur, la déclaration uniforme correspondante ressemble à ceci :

Open GL ES 2.0 : déclaration uniforme GLSL

uniform mat4 u_mvpMatrix;

Direct3D désigne les données uniformes en tant que « mémoires tampons constantes », qui, comme les uniformes, contiennent des données constantes fournies à des nuanceurs individuels. Comme avec les mémoires tampons uniformes, il est important de packer les données de mémoire tampon constantes en mémoire identique à la façon dont le nuanceur s’attend à l’interpréter. L’utilisation de types DirectXMath (tels que XMFLOAT4) au lieu de types de plateforme (tels que float* ou float[4]) garantit un alignement approprié des éléments de données.

Les mémoires tampons constantes doivent avoir un registre GPU associé utilisé pour référencer ces données sur le GPU. Les données sont empaquetées dans l’emplacement du registre, comme indiqué par la disposition de la mémoire tampon.

Direct3D 11 : création d’une mémoire tampon constante et chargement de données vers celui-ci

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);

Dans le HLSL d’un nuanceur, la déclaration de mémoire tampon constante correspondante ressemble à ceci :

Direct3D 11 : Déclaration HLSL de mémoire tampon constante

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

Notez qu’un registre doit être déclaré pour chaque mémoire tampon constante. Les différents niveaux de fonctionnalités Direct3D ont différents registres disponibles. Par conséquent, ne dépassez pas le nombre maximal pour le niveau de fonctionnalité le plus bas que vous ciblez.

Porter des attributs de vertex vers des dispositions d’entrée Direct3D et une sémantique HLSL

Étant donné que les données de vertex peuvent être modifiées par le pipeline de nuanceur, OpenGL ES 2.0 exige que vous les spécifiiez comme « attributs » au lieu de « uniformes ». (Cela a changé dans les versions ultérieures d’OpenGL et GLSL.) Les données spécifiques au vertex telles que la position de vertex, les normales, les tangentes et les valeurs de couleur sont fournies aux nuanceurs en tant que valeurs d’attribut. Ces valeurs d’attribut correspondent à des décalages spécifiques pour chaque élément dans les données de vertex ; Par exemple, le premier attribut peut pointer vers le composant de position d’un sommet individuel, et le second vers la normale, et ainsi de suite.

Le processus de base pour déplacer les données de mémoire tampon de vertex de la mémoire principale vers le GPU ressemble à ceci :

  • Chargez les données de vertex avec glBindBuffer.
  • Obtenez l’emplacement des attributs sur le GPU avec glGetAttribLocation. Appelez-le pour chaque attribut dans l’élément de données de vertex.
  • Appelez glVertexAttribPointer pour fournir la taille et le décalage d’attribut corrects à l’intérieur d’un élément de données de vertex individuel. Effectuez cette opération pour chaque attribut.
  • Activez les informations de disposition des données de vertex avec glEnableVertexAttribArray.

OpenGL ES 2.0 : chargement des données de mémoire tampon de vertex dans l’attribut du nuanceur

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);

Maintenant, dans votre nuanceur de vertex, vous déclarez des attributs portant les mêmes noms que ceux que vous avez définis dans votre appel à glGetAttribLocation.

OpenGL ES 2.0 : Déclaration d’un attribut dans GLSL

attribute vec4 a_position;
attribute vec4 a_color;                     

De certaines façons, le même processus est réservé à Direct3D. Au lieu d’attributs, les données de vertex sont fournies dans les mémoires tampons d’entrée, notamment les mémoires tampons de vertex et les mémoires tampons d’index correspondantes. Toutefois, étant donné que Direct3D n’a pas la déclaration « attribute », vous devez spécifier une disposition d’entrée qui déclare le composant individuel des éléments de données dans la mémoire tampon de vertex et la sémantique HLSL qui indiquent où et comment ces composants doivent être interprétés par le nuanceur de vertex. La sémantique HLSL nécessite que vous définissiez l’utilisation de chaque composant avec une chaîne spécifique qui informe le moteur de nuanceur quant à son objectif. Par exemple, les données de position de vertex sont marquées comme POSITION, les données normales sont marquées comme NORMAL et les données de couleur de vertex sont marquées comme COLOR. (D’autres phases de nuanceur nécessitent également une sémantique spécifique, et ces sémantiques ont différentes interprétations en fonction de l’étape du nuanceur.) Pour plus d’informations sur la sémantique HLSL, lisez port votre pipeline de nuanceur et la sémantique HLSL.

Collectivement, le processus de définition des mémoires tampons de vertex et d’index, et la définition de la disposition d’entrée est appelée étape « Assembly d’entrée » (IA) du pipeline graphique Direct3D.

Direct3D 11 : configuration de la phase d’assembly d’entrée

// 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());

Une disposition d’entrée est déclarée et associée à un nuanceur de vertex en déclarant le format de l’élément de données de vertex et la sémantique utilisée pour chaque composant. La disposition des données d’élément de vertex décrite dans l’D3D11_INPUT_ELEMENT_DESC que vous créez doit correspondre à la disposition de la structure correspondante. Ici, vous créez une disposition pour les données de vertex qui ont deux composants :

  • Coordonnée de position de vertex, représentée en mémoire principale sous la forme d’un XMFLOAT3, qui est un tableau aligné de 3 valeurs à virgule flottante 32 bits pour les coordonnées (x, y, z).
  • Valeur de couleur de vertex, représentée sous la forme d’un XMFLOAT4, qui est un tableau aligné de 4 valeurs à virgule flottante 32 bits pour la couleur (RVBA).

Vous attribuez une sémantique pour chacun d’eux, ainsi qu’un type de format. Vous passez ensuite la description à ID3D11Device1 ::CreateInputLayout. La disposition d’entrée est utilisée lorsque nous appelons ID3D11DeviceContext1 ::IASetInputLayout lorsque vous configurez l’assembly d’entrée pendant notre méthode de rendu.

Direct3D 11 : description d’une disposition d’entrée avec une sémantique spécifique

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());

Enfin, vous assurez-vous que le nuanceur peut comprendre les données d’entrée en déclarant l’entrée. La sémantique que vous avez affectée dans la disposition est utilisée pour sélectionner les emplacements appropriés dans la mémoire GPU.

Direct3D 11 : Déclaration des données d’entrée du nuanceur avec la sémantique HLSL

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