Поделиться через


Сравнение буферов OpenGL ES 2.0, униформ и атрибутов вершины с Direct3D

Важные API

Во время переноса в Direct3D 11 из OpenGL ES 2.0 необходимо изменить синтаксис и поведение API для передачи данных между приложением и программами шейдера.

В OpenGL ES 2.0 данные передаются в программы шейдера и из нее четырьмя способами: как единообразие для константных данных, в качестве атрибутов для данных вершин, в качестве буферных объектов для других данных ресурсов (например, текстур). В Direct3D 11 они примерно сопоставляются с буферами констант, буферами вершин и подресурсами. Несмотря на поверхностную общность, они обрабатываются совершенно иначе в использовании.

Ниже приведено базовое сопоставление.

OpenGL ES 2.0 Direct3D 11
униформа поле константного буфера (cbuffer).
атрибут Поле элемента буфера вершины, обозначенное входным макетом и помеченное семантикой HLSL.
объект buffer буфер; Сведения о определениях буферов общего использования см. в D3D11_SUBRESOURCE_DATA и D3D11_BUFFER_DESC.
Объект буфера кадра (FBO) целевые объекты отрисовки; См. раздел ID3D11RenderTargetView с id3D11Texture2D.
обратный буфер цепочка буферов с поверхностью back buffer; См. раздел IDXGISwapChain1 с присоединенным идентификатором IDXGISurface1.

 

Буферы портов

В OpenGL ES 2.0 процесс создания и привязки любого типа буфера обычно следует этому шаблону.

  • Вызовите glGenBuffers для создания одного или нескольких буферов и возврата дескрипторов.
  • Вызовите glBindBuffer, чтобы определить макет буфера, например GL_ELEMENT_ARRAY_BUFFER.
  • Вызовите glBufferData, чтобы заполнить буфер определенными данными (например, структурами вершин, данными индекса или цветными данными) в определенном макете.

Наиболее распространенным типом буфера является буфер вершин, который минимально содержит позиции вершин в некоторой системе координат. В типичном использовании вершина представлена структурой, которая содержит координаты позиции, обычный вектор к позиции вершины, тангентный вектор к позиции вершины и координаты подстановки текстуры (uv). Буфер содержит непрерывный список этих вершин, в каком-то порядке (например, список треугольников, полоса или вентилятор), и который вместе представляет видимые многоугольники в сцене. (В Direct3D 11, а также OpenGL ES 2.0 неэффективно иметь несколько буферов вершин на вызов рисования.)

Ниже приведен пример буфера вершин и буфера индекса, созданного с помощью OpenGL ES 2.0:

OpenGL ES 2.0: создание и заполнение буфера вершин и буфера индекса.

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

Другие буферы включают буферы пикселей и карты, такие как текстуры. Конвейер шейдера может отображаться в буферах текстур (pixmaps) или отображать объекты буфера и использовать эти буферы в будущих шейдерах. В самом простом случае поток вызовов:

  • Вызовите glGenFramebuffers для создания объекта буфера кадра.
  • Вызовите glBindFramebuffer, чтобы привязать объект буфера кадра для записи.
  • Вызовите glFramebufferTexture2D для рисования в указанной карте текстур.

В Direct3D 11 элементы данных буфера считаются "подресурсами" и могут варьироваться от отдельных элементов данных вершин до текстур карты MIP.

  • Заполните структуру D3D11_SUBRESOURCE_DATA конфигурацией для элемента буферных данных.
  • Заполните структуру D3D11_BUFFER_DESC размером отдельных элементов в буфере, а также типом буфера.
  • Вызов ID3D11Device1::CreateBuffer с этими двумя структурами.

Direct3D 11: создание и заполнение буфера вершин и буфера индекса.

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

Записываемые буферы пикселей или карты, например буфер кадра, можно создать как объекты ID3D11Texture2D. Они могут быть привязаны как ресурсы к идентификатору ID3D11RenderTargetView или ID3D11ShaderResourceView, который после рисования можно отобразить с связанной цепочкой буферов или передать в шейдер соответственно.

Direct3D 11: создание объекта буфера кадра.

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

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

Изменение униформ и универсальных буферных объектов на буферы констант Direct3D

В Open GL ES 2.0 единообразие — это механизм предоставления постоянных данных отдельным программам шейдера. Эти данные не могут быть изменены шейдерами.

Настройка единообразия обычно включает в себя предоставление одного из методов glUniform* с расположением отправки в GPU вместе с указателем на данные в памяти приложения. После выполнения метода glUniform* однородные данные отображаются в памяти GPU и доступны шейдерам, которые объявили это единообразие. Ожидается, что данные упакованы таким образом, чтобы шейдер интерпретировать его на основе универсального объявления в шейдере (с помощью совместимых типов).

OpenGL ES 2.0 Создание единообразной и отправки данных в него

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

// ...

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

В GLSL шейдера соответствующее универсальное объявление выглядит следующим образом:

Open GL ES 2.0: универсальное объявление GLSL

uniform mat4 u_mvpMatrix;

Direct3D обозначает универсальные данные как "буферы констант", которые, например, униформы, содержат константные данные, предоставляемые отдельным шейдерам. Как и в случае с универсальными буферами, важно упаковать данные буфера констант в память так же, как шейдер ожидает его интерпретации. Использование типов DirectXMath (например, XMFLOAT4) вместо типов платформ (например, float* или float[4]) гарантирует правильное выравнивание элементов данных.

Буферы констант должны иметь связанный регистр GPU, используемый для ссылки на эти данные на GPU. Данные упаковываются в расположение регистра, как указано в макете буфера.

Direct3D 11: создание буфера констант и отправка данных в него

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

В HLSL шейдера соответствующее объявление буфера констант выглядит следующим образом:

Direct3D 11: объявление буфера констант HLSL

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

Обратите внимание, что регистр должен быть объявлен для каждого буфера констант. Разные уровни функций Direct3D имеют разные максимальные доступные регистры, поэтому не превышают максимальное число для минимального целевого уровня компонентов.

Атрибуты вершин порта для макетов входных данных Direct3D и семантики HLSL

Так как данные вершин могут быть изменены конвейером шейдера, OpenGL ES 2.0 требует, чтобы они были указаны как "атрибуты" вместо "униформ". (Это изменилось в более поздних версиях OpenGL и GLSL.) Данные, относящиеся к вершинам, обычные, тангенты и значения цвета, предоставляются шейдерам в качестве значений атрибутов. Эти значения атрибутов соответствуют определенным смещениям для каждого элемента в данных вершины; Например, первый атрибут может указывать на компонент позиции отдельной вершины, а второй — на обычный, и т. д.

Базовый процесс перемещения данных буфера вершин из основной памяти в GPU выглядит следующим образом:

  • Отправьте данные вершин с помощью glBindBuffer.
  • Получите расположение атрибутов на GPU с помощью glGetAttribLocation. Вызовите его для каждого атрибута в элементе данных вершины.
  • Вызовите glVertexAttribPointer, чтобы указать правильный размер атрибута и смещение внутри отдельного элемента данных вершины. Сделайте это для каждого атрибута.
  • Включите сведения о макете данных вершин с помощью glEnableVertexAttribArray.

OpenGL ES 2.0: отправка данных буфера вершин в атрибут шейдера

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

Теперь в шейдере вершин объявляются атрибуты с теми же именами, которые вы определили в вызове glGetAttribLocation.

OpenGL ES 2.0: объявление атрибута в GLSL

attribute vec4 a_position;
attribute vec4 a_color;                     

В некотором смысле тот же процесс сохраняется для Direct3D. Вместо атрибутов данные вершин предоставляются во входных буферах, которые включают буферы вершин и соответствующие буферы индекса. Однако, так как Direct3D не имеет объявления атрибутов, необходимо указать входной макет, который объявляет отдельный компонент элементов данных в буфере вершин и семантику HLSL, указывающую, где и как эти компоненты должны интерпретироваться шейдером вершин. Для семантики HLSL необходимо определить использование каждого компонента с определенной строкой, которая сообщает обработчику шейдеров о его назначении. Например, данные позиции вершин помечаются как POSITION, обычные данные помечаются как NORMAL, а данные цвета вершин помечаются как COLOR. (Другие этапы шейдера также требуют конкретной семантики, и эти семантики имеют различные интерпретации на основе этапа шейдера.) Дополнительные сведения о семантике HLSL см. в статье "Перенос конвейера шейдера" и "Семантика HLSL".

В совокупности процесс настройки буферов вершин и индекса и настройки входного макета называется этапом входной сборки (IA) графического конвейера Direct3D.

Direct3D 11: настройка этапа входной сборки

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

Входной макет объявлен и связан с шейдером вершин путем объявления формата элемента данных вершины и семантики, используемой для каждого компонента. Макет данных элемента вершины, описанный в создаваемом D3D11_INPUT_ELEMENT_DESC, должен соответствовать макету соответствующей структуры. Здесь вы создадите макет для данных вершин с двумя компонентами:

  • Координата положения вершины, представленная в основной памяти как XMFLOAT3, которая представляет собой выровненный массив из 32-разрядных значений с плавающей запятой для координат (x, y, z).
  • Значение цвета вершины, представленное как XMFLOAT4, которое представляет собой выровненный массив из 4 32-разрядных значений с плавающей запятой для цвета (RGBA).

Вы назначаете семантику для каждого из них, а также тип формата. Затем вы передаете описание в ID3D11Device1::CreateInputLayout. Входной макет используется при вызове ID3D11DeviceContext1::IASetInputLayout при настройке входной сборки во время нашего метода отрисовки.

Direct3D 11: описание входного макета с определенной семантикой

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

Наконец, убедитесь, что шейдер может понять входные данные, объявив входные данные. Семантика, назначенная в макете, используется для выбора правильных расположений в памяти GPU.

Direct3D 11: объявление входных данных шейдера с семантикой HLSL

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