Урок DirectX 12. Рисуем первый треугольник

Урок DirectX 12. Рисуем первый треугольник

DirectX 12 — это последняя версия собственного API компьютерной графики от Microsoft, используемого для платформ Windows и Xbox. Он, как и Vulkan, нацелен на создание менее сложного драйвера и API, более близкого к архитектуре современных графических процессоров.

DirectX фокусируется на рендеринге в реальном времени, поэтому он предназначен для разработчиков игр и систем автоматизированного проектирования (CAD). Поскольку это отраслевой стандарт API компьютерной графики, можно ожидать, что почти все совместимые аппаратные средства будут иметь его надежную поддержку, и он станет стандартом для коммерческих проектов.

Этот графический API является самым популярным и вездесущим из всех: от программ для создания 3D/изображений наподобие Marmoset Toolbag 3, Adobe PhotoShop, Autodesk Maya, до коммерческих игр вроде OverWatch от Blizzard и Fortnite от Epic, подавляющего большинства игр на Steam от Valve и многого другого (несмотря на эксклюзивность платформы).

DirectX 12 сейчас поддерживается в:

  • Windows 10
  • Xbox One
  • Xbox One X

В Windows 7 имеется частичная поддержка через D3D12 On 7.

Его поддерживает множество языков:

  • C
  • C++
  • C#
  • Rust (через dx-sys)

Я подготовил хранилище Github со всем, что понадобится. На современном C++ мы собираемся пройтись по приложению Первый Треугольник. Это программа, которая создает треугольник и визуализирует его на экране.

Подготовка

Сначала установите:

Теперь напечатайте в терминале следующее.

# Клонируйте хранилище

git clone https://github.com/alaingalvan/directx12-seed --recurse-submodules

# Зайдите в папку

cd directx12-seed

# Если вы забыли про рекурсию подмодулей, всегда можно запустить:

git submodule update --init

# Создать папку для компиляции проекта

mkdir build

cd build

# Чтобы сделать ваше Visual Studio решение на Windows x64

cmake .. -A x64

# Чтобы сделать XCode проект на Mac OS

cmake .. -G Xcode

# Чтобы сделать .make файл на Linux

cmake ..

# Для любой платформы:

cmake --build .

Обзор

Документация DirectX 12 рекомендует использовать ComPtr <T> в качестве альтернативы к std :: shared_ptr <T>, потому что так лучше отлаживать и легче инициализировать структуры данных DirectX 12.

Независимо от того, выберете ли вы ComPtr <T> или нет, этапы рендеринга растровой графики с помощью DirectX 12 очень похожи на этапы других современных графических API:

  1. Инициализация API — создаются ваши Фабрика, Адаптер, Устройство, Очередь, Распределитель команд, Список команд, Цепочка обмена.
  2. Инициализация ресурсов — ваш Буфер вершин, Индексный буфер, Однородный буфер, Представления целей визуализации, Графический конвейер, Примитивы синхронизации.
  3. Визуализация — обновление ваших однородных данных, добавление команд в очередь и ожидание следующего фрейма.

Далее будут объяснены фрагменты, которые можно найти в хранилище Github, с определенными опущенными частями и переменными-членами (mMemberVariable), объявленными встроенными без префикса m, чтобы их тип был легче видеть. Примеры здесь могут работать сами по себе.

Создание окна

Мы используем CrossWindow для создания кроссплатформенного окна, поэтому создать окно и обновить его очень просто:

#include "CrossWindow/CrossWindow.h"
#include "Renderer.h"

#include 

void xmain(int argc, const char** argv)
{
  // Создать окно
  xwin::WindowDesc wdesc;
  wdesc.title = "DirectX 12 Seed";
  wdesc.name = "MainWindow";
  wdesc.visible = true;
  wdesc.width = 640;
  wdesc.height = 640;
  wdesc.fullscreen = false;

  xwin::Window window;
  xwin::EventQueue eventQueue;

  if (!window.create(wdesc, eventQueue))
  { return; };

  // Создать рендерер
  Renderer renderer(window);

  // Цикл движка
  bool isRunning = true;
  while (isRunning)
  {
    bool shouldRender = true;

    // Обновить очередь событий
    eventQueue.update();

    // Итерация по этой очереди:
    while (!eventQueue.empty())
    {
      // Обновить события
      const xwin::Event& event = eventQueue.front();

      // При изменении размера:
      if (event.type == xwin::EventType::Resize)
      {
        const xwin::ResizeData data = event.data.resize;
        renderer.resize(data.width, data.height);
        shouldRender = false;
      }

      // При закрытии:
      if (event.type == xwin::EventType::Close)
      {
        window.close();
        shouldRender = false;
        isRunning = false;
      }

      eventQueue.pop();
    }

    // Обновление визуалов
    if (shouldRender)
    {
      renderer.render();
    }
  }
}

Инициализация API

Фабрика

Точка входа в DirectX 12 API

Фабрики — это точка входа в DirectX 12 API, и следовательно, первое, что вам нужно создать.

Вы также можете создавать отладочные структуры данных, такие как debugController, которые могут включать проверку использования API.

// Объявить дескрипторы DirectX 12
IDXGIFactory4* factory;
ID3D12Debug1* debugController;

// Создать фабрику
UINT dxgiFactoryFlags = 0;
#if defined(_DEBUG)
  ID3D12Debug* debugController;
  ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
  ThrowIfFailed(debugController->QueryInterface(IID_PPV_ARGS(&debugController)));
  debugController->EnableDebugLayer();
  debugController->SetEnableGPUBasedValidation(true);

  dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;

  debugController->Release();
  debugController = nullptr;
#endif

HRESULT result = CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory));

Адаптер

характеристики устройства DirectX

Адаптер предоставляет информацию о физических характеристиках данного устройства DirectX.

// Объявить дескрипторы
IDXGIAdapter1* adapter;

// Создать адаптер
for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != factory->EnumAdapters1(adapterIndex, &adapter); ++adapterIndex)
{
  DXGI_ADAPTER_DESC1 desc;
  adapter->GetDesc1(&desc);

  // Не выбирать адаптер для основного драйвера визуализации.
  if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
  { continue; }

  // Проверить, поддерживает ли адаптер Direct3D 12, и использовать его до конца приложения
  if (SUCCEEDED(D3D12CreateDevice(adapter, D3D_FEATURE_LEVEL_12_0, _uuidof(ID3D12Device), nullptr)))
  { break; }

  // Мы не будем использовать этот адаптер, поэтому освободим его
  adapter->Release();
}

Устройство

устройство и устройство отладки

Устройство — это ваша основная точка входа в DirectX 12 API, предоставляющая доступ к внутренним частям API. Это ключ к важным структурам данных, таким как конвейеры, шейдеры, состояние рендеринга и т.д.

Устройство отладки позволяет вам полагаться на режим отладки DirectX 12. Структуры данных, созданные с помощью DirectX, может быть трудно отслеживать. С Device вы сможете предотвратить утечку данных или проверить, правильно ли вы создаете или используете API.

// Объявить дескрипторы
ID3D12Device* device;

// Создать устройство
ID3D12Device *pDev = nullptr;
ThrowIfFailed(D3D12CreateDevice(
  adapter,
  D3D_FEATURE_LEVEL_12_0,
  IID_PPV_ARGS(&device)
));

device->SetName(L"Hello Triangle Device");

// Объявить дескрипторы
ID3D12DebugDevice* debugDevice;

#if defined(_DEBUG)
  // Получить устройство отладки
  ThrowIfFailed(device->QueryInterface(&debugDevice));
#endif

Очередь команд

очередь команд

Очередь команд позволяет вам отправлять группы вызовов отрисовки, известных как списки команд, для выполнения по порядку, что позволяет графическому процессору оставаться занятым и оптимизировать скорость его работы.

// Объявить дескрипторы
ID3D12CommandQueue* commandQueue;

// Создать очередь команд
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;

ThrowIfFailed(device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue)));

Распределитель команд

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

// Объявить дескрипторы
ID3D12CommandAllocator* commandAllocator;

// Создать распределитель команд
ThrowIfFailed(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator)));

Синхронизация

синхронизация

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

// Объявить дескрипторы
UINT frameIndex;
HANDLE fenceEvent;
ID3D12Fence* fence;
UINT64 fenceValue;

// Создать барьер
ThrowIfFailed(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)));

Swap chain

swapchain

Swap chain занимается обменом между фрейм-буферами, чтобы показать ваш рендер в заданном окне.

// Объявить данные
unsigned width = 640;
unsigned height = 640;

// Объявить дескрипторы
static const UINT backbufferCount = 2;
UINT currentBuffer;
ID3D12DescriptorHeap* renderTargetViewHeap;
ID3D12Resource* renderTargets[backbufferCount];

// Цепочка обмена
IDXGISwapChain3* swapchain;
D3D12_VIEWPORT viewport;
D3D12_RECT surfaceSize;

surfaceSize.left = 0;
surfaceSize.top = 0;
surfaceSize.right = static_cast(width);
surfaceSize.bottom = static_cast(height);

viewport.TopLeftX = 0.0f;
viewport.TopLeftY = 0.0f;
viewport.Width = static_cast(width);
viewport.Height = static_cast(height);
viewport.MinDepth = .1f;
viewport.MaxDepth = 1000.f;

  if (swapchain != nullptr)
  {
    // Создать соединения с целями визуализации из цепочки обмена
    swapchain->ResizeBuffers(backbufferCount, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, 0);
  }
  else
  {
    // Создать цепочку обмена
    DXGI_SWAP_CHAIN_DESC1 swapchainDesc = {};
    swapchainDesc.BufferCount = backbufferCount;
    swapchainDesc.Width = width;
    swapchainDesc.Height = height;
    swapchainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    swapchainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapchainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    swapchainDesc.SampleDesc.Count = 1;

    IDXGISwapChain1* newSwapchain = xgfx::createSwapchain(window, factory, commandQueue, &swapchainDesc);
    HRESULT swapchainSupport = swapchain->QueryInterface(__uuidof(IDXGISwapChain3), (void**)&newSwapchain);
    if (SUCCEEDED(swapchainSupport))
    {
      swapchain = (IDXGISwapChain3*)newSwapchain;
    }
  }

  frameIndex = swapchain->GetCurrentBackBufferIndex();

// Описать и создать кучу дескриптора для представления целей визуализации (RTV)
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = backbufferCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
ThrowIfFailed(device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&renderTargetViewHeap)));

mRtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);


// Создать ресурсы кадра

D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle(renderTargetViewHeap->GetCPUDescriptorHandleForHeapStart());

// Создать RTV для каждого кадра
for (UINT n = 0; n GetBuffer(n, IID_PPV_ARGS(&renderTargets[n])));
  device->CreateRenderTargetView(renderTargets[n], nullptr, rtvHandle);
  rtvHandle.ptr += (1 * mRtvDescriptorSize);
}

Инициализация ресурсов

Корневая подпись

Корневая подпись представляет собой объекты, которые определяют тип ресурсов, привязанных к графическому конвейеру:

// Создать корневую подпись
ID3D12RootSignature* rootSignature;
D3D12_FEATURE_DATA_ROOT_SIGNATURE featureData = {};

// Это самая последняя версия, которую поддерживает сэмпл. Если CheckFeatureSupport завершится успешно, возвращенное значение HighestVersion не будет превышать ее.
featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_1;

if (FAILED(mDevice->CheckFeatureSupport(D3D12_FEATURE_ROOT_SIGNATURE, &featureData, sizeof(featureData))))
{
  featureData.HighestVersion = D3D_ROOT_SIGNATURE_VERSION_1_0;
}

D3D12_DESCRIPTOR_RANGE1 ranges[1];
ranges[0].BaseShaderRegister = 0;
ranges[0].RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
ranges[0].NumDescriptors = 1;
ranges[0].RegisterSpace = 0;
ranges[0].OffsetInDescriptorsFromTableStart = 0;
ranges[0].Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE;

D3D12_ROOT_PARAMETER1 rootParameters[1];
rootParameters[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
rootParameters[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_VERTEX;

rootParameters[0].DescriptorTable.NumDescriptorRanges = 1;
rootParameters[0].DescriptorTable.pDescriptorRanges = ranges;

D3D12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1;
rootSignatureDesc.Desc_1_1.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
rootSignatureDesc.Desc_1_1.NumParameters = 1;
rootSignatureDesc.Desc_1_1.pParameters = rootParameters;
rootSignatureDesc.Desc_1_1.NumStaticSamplers = 0;
rootSignatureDesc.Desc_1_1.pStaticSamplers = nullptr;

ID3DBlob* signature;
ID3DBlob* error;
try
{
  ThrowIfFailed(D3D12SerializeVersionedRootSignature(&rootSignatureDesc, &signature, &error));
  ThrowIfFailed(mDevice->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature)));
  rootSignature->SetName(L"Hello Triangle Root Signature");
}
catch (std::exception e)
{
  const char* errStr = (const char*)error->GetBufferPointer();
  std::cout <Release();
  error = nullptr;
}

if (signature)
{
  signature->Release();
  signature = nullptr;
}

Буфер вершин

вершинный буфер

Буфер вершин хранит информацию о каждой вершине, доступную в виде атрибутов в вашем вершинном шейдере. Все буферы являются объектами ID3D12Resource в DirectX 12, хоть вершинные, хоть индексные, однородные и т.д.

// Объявить данные
struct Vertex
{
  float position[3];
  float color[3];
};

Vertex vertexBufferData[3] =
{
  { { 1.0f,  -1.0f, 0.0f },{ 1.0f, 0.0f, 0.0f } },
  { { -1.0f,  -1.0f, 0.0f },{ 0.0f, 1.0f, 0.0f } },
  { { 0.0f, 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0f } }
};

// Объявить дескрипторы
ID3D12Resource* vertexBuffer;
D3D12_VERTEX_BUFFER_VIEW vertexBufferView;

const UINT vertexBufferSize = sizeof(vertexBufferData);

D3D12_HEAP_PROPERTIES heapProps;
heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProps.CreationNodeMask = 1;
heapProps.VisibleNodeMask = 1;

D3D12_RESOURCE_DESC vertexBufferResourceDesc;
vertexBufferResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
vertexBufferResourceDesc.Alignment = 0;
vertexBufferResourceDesc.Width = vertexBufferSize;
vertexBufferResourceDesc.Height = 1;
vertexBufferResourceDesc.DepthOrArraySize = 1;
vertexBufferResourceDesc.MipLevels = 1;
vertexBufferResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
vertexBufferResourceDesc.SampleDesc.Count = 1;
vertexBufferResourceDesc.SampleDesc.Quality = 0;
vertexBufferResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
vertexBufferResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;

ThrowIfFailed(device->CreateCommittedResource(
  &heapProps,
  D3D12_HEAP_FLAG_NONE,
  &vertexBufferResourceDesc,
  D3D12_RESOURCE_STATE_GENERIC_READ,
  nullptr,
  IID_PPV_ARGS(&vertexBuffer)));

// Копировать данные о треугольнике в буфер вершин.
UINT8* pVertexDataBegin;

// Мы не собираемся читать из этого ресурса в CPU.
D3D12_RANGE readRange;
readRange.Begin = 0;
readRange.End = 0;

ThrowIfFailed(vertexBuffer->Map(0, &readRange, reinterpret_cast(&pVertexDataBegin)));
memcpy(pVertexDataBegin, vertexBufferData, sizeof(vertexBufferData));
vertexBuffer->Unmap(0, nullptr);

// Инициализировать представление буфера вершин.
vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();
vertexBufferView.StrideInBytes = sizeof(Vertex);
vertexBufferView.SizeInBytes = vertexBufferSize;

Индексный буфер

буфер индексов

Индексный буфер содержит индивидуальные индексы каждых треугольника/линии/точки, которые вы собираетесь нарисовать.

// Объявить данные
uint32_t indexBufferData[3] = { 0, 1, 2 };

// Объявить дескрипторы
ID3D12Resource* indexBuffer;
D3D12_INDEX_BUFFER_VIEW indexBufferView;

const UINT indexBufferSize = sizeof(indexBufferData);

D3D12_HEAP_PROPERTIES heapProps;
heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProps.CreationNodeMask = 1;
heapProps.VisibleNodeMask = 1;

D3D12_RESOURCE_DESC vertexBufferResourceDesc;
vertexBufferResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
vertexBufferResourceDesc.Alignment = 0;
vertexBufferResourceDesc.Width = indexBufferSize;
vertexBufferResourceDesc.Height = 1;
vertexBufferResourceDesc.DepthOrArraySize = 1;
vertexBufferResourceDesc.MipLevels = 1;
vertexBufferResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
vertexBufferResourceDesc.SampleDesc.Count = 1;
vertexBufferResourceDesc.SampleDesc.Quality = 0;
vertexBufferResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
vertexBufferResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;

ThrowIfFailed(device->CreateCommittedResource(
  &heapProps,
  D3D12_HEAP_FLAG_NONE,
  &vertexBufferResourceDesc,
  D3D12_RESOURCE_STATE_GENERIC_READ,
  nullptr,
  IID_PPV_ARGS(&indexBuffer)));

// Копировать данные в память драйвера DirectX 12.
UINT8* pVertexDataBegin;

D3D12_RANGE readRange;
readRange.Begin = 0;
readRange.End = 0;

ThrowIfFailed(indexBuffer->Map(0, &readRange, reinterpret_cast(&pVertexDataBegin)));
memcpy(pVertexDataBegin, indexBufferData, sizeof(indexBufferData));
indexBuffer->Unmap(0, nullptr);

// Инициализировать представление буфера вершин.
indexBufferView.BufferLocation = indexBuffer->GetGPUVirtualAddress();
indexBufferView.Format = DXGI_FORMAT_R32_UINT;
indexBufferView.SizeInBytes = indexBufferSize;

Константный uniform буфер

uniform buffer

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

// Объявить дескрипторы
ID3D12Resource* uniformBuffer;
ID3D12DescriptorHeap* uniformBufferHeap;
UINT8* mappedUniformBuffer;

// Создать UBO

D3D12_HEAP_PROPERTIES heapProps;
heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProps.CreationNodeMask = 1;
heapProps.VisibleNodeMask = 1;

D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};
heapDesc.NumDescriptors = 1;
heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
ThrowIfFailed(device->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&uniformBufferHeap)));

D3D12_RESOURCE_DESC uboResourceDesc;
uboResourceDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
uboResourceDesc.Alignment = 0;
uboResourceDesc.Width = (sizeof(uboVS) + 255) & ~255;
uboResourceDesc.Height = 1;
uboResourceDesc.DepthOrArraySize = 1;
uboResourceDesc.MipLevels = 1;
uboResourceDesc.Format = DXGI_FORMAT_UNKNOWN;
uboResourceDesc.SampleDesc.Count = 1;
uboResourceDesc.SampleDesc.Quality = 0;
uboResourceDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
uboResourceDesc.Flags = D3D12_RESOURCE_FLAG_NONE;


ThrowIfFailed(device->CreateCommittedResource(
  &heapProps,
  D3D12_HEAP_FLAG_NONE,
  &uboResourceDesc,
  D3D12_RESOURCE_STATE_GENERIC_READ,
  nullptr,
  IID_PPV_ARGS(&uniformBuffer)));
uniformBufferHeap->SetName(L"Constant Buffer Upload Resource Heap");

D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = uniformBuffer->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = (sizeof(uboVS) + 255) & ~255;    // Размер буфера должен быть кратен 256 байт

D3D12_CPU_DESCRIPTOR_HANDLE cbvHandle(uniformBufferHeap->GetCPUDescriptorHandleForHeapStart());
cbvHandle.ptr = cbvHandle.ptr + device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) * 0;

device->CreateConstantBufferView(&cbvDesc, cbvHandle);

// Мы не собираемся читать из этого ресурса в CPU. (Конец меньше или равен началу)
D3D12_RANGE readRange;
readRange.Begin = 0;
readRange.End = 0;

ThrowIfFailed(uniformBuffer->Map(0, &readRange, reinterpret_cast(&mappedUniformBuffer)));
memcpy(mappedUniformBuffer, &uboVS, sizeof(uboVS));
uniformBuffer->Unmap(0, &readRange);

Вершинный шейдер

vertex shader in DirectX 12

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

// Объявить дескрипторы
ID3DBlob* vertexShader = nullptr;
ID3DBlob* errors = nullptr;

#if defined(_DEBUG)
  // Включить лучшую отладку шейдеров с помощью инструментов отладки графики
  UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
  UINT compileFlags = 0;
#endif
std::string path = "";
char pBuf[1024];

_getcwd(pBuf, 1024);
path = pBuf;
path += "\";
std::wstring wpath = std::wstring(path.begin(), path.end());

std::wstring vertPath = wpath + L"assets/triangle.vert.hlsl";

try
{
  ThrowIfFailed(D3DCompileFromFile(vertPath.c_str(), nullptr, nullptr, "main", "vs_5_0", compileFlags, 0, &vertexShader, &errors));
}
catch (std::exception e)
{
  const char* errStr = (const char*)errors->GetBufferPointer();
  std::cout <Release();
  errors = nullptr;
}

Пиксельный шейдер

pixel shader

Пиксельные шейдеры выполняются для каждого пикселя вашего вывода, включая добавочные отрисовки, которые соответствуют этой пиксельной координате.

// Объявить дескрипторы
ID3DBlob* pixelShader = nullptr;
ID3DBlob* errors = nullptr;

#if defined(_DEBUG)
  // Включить лучшую отладку шейдеров с помощью инструментов отладки графики
  UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
  UINT compileFlags = 0;
#endif
std::string path = "";
char pBuf[1024];

_getcwd(pBuf, 1024);
path = pBuf;
path += "\";
std::wstring wpath = std::wstring(path.begin(), path.end());

std::wstring fragPath = wpath + L"assets/triangle.frag.hlsl";

try
{
  ThrowIfFailed(D3DCompileFromFile(fragPath.c_str(), nullptr, nullptr, "main", "ps_5_0", compileFlags, 0, &pixelShader, &errors));
}
catch (std::exception e)
{
  const char* errStr = (const char*)errors->GetBufferPointer();
  std::cout <Release();
  errors = nullptr;
}

Этап конвейера

DirectX 12 конвейер

Этап конвейера описывает все необходимое для выполнения основанного на растеризации данного вызова отрисовки.

// Объявить дескрипторы
ID3D12PipelineState* pipelineState;

// Определить схему ввода вершин
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
  { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
  { "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};


D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) };
psoDesc.pRootSignature = rootSignature;

D3D12_SHADER_BYTECODE vsBytecode;
vsBytecode.pShaderBytecode = vertexShader->GetBufferPointer();
vsBytecode.BytecodeLength = vertexShader->GetBufferSize();

psoDesc.VS = vsBytecode;

D3D12_SHADER_BYTECODE psBytecode;
psBytecode.pShaderBytecode = pixelShader->GetBufferPointer();
psBytecode.BytecodeLength = pixelShader->GetBufferSize();

psoDesc.PS = psBytecode;

D3D12_RASTERIZER_DESC rasterDesc;
rasterDesc.FillMode = D3D12_FILL_MODE_SOLID;
rasterDesc.CullMode = D3D12_CULL_MODE_NONE;
rasterDesc.FrontCounterClockwise = FALSE;
rasterDesc.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
rasterDesc.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
rasterDesc.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
rasterDesc.DepthClipEnable = TRUE;
rasterDesc.MultisampleEnable = FALSE;
rasterDesc.AntialiasedLineEnable = FALSE;
rasterDesc.ForcedSampleCount = 0;
rasterDesc.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;

psoDesc.RasterizerState = rasterDesc;

D3D12_BLEND_DESC blendDesc;
blendDesc.AlphaToCoverageEnable = FALSE;
blendDesc.IndependentBlendEnable = FALSE;
const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc =
{
  FALSE,FALSE,
  D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD,
  D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD,
  D3D12_LOGIC_OP_NOOP,
  D3D12_COLOR_WRITE_ENABLE_ALL,
};
for (UINT i = 0; i CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineState)));
}
catch (std::exception e)
{
  std::cout << "Failed to create Graphics Pipeline!";
}

Рендеринг

рендеринг треугольника

Рендеринг в DirectX 12 — это простое дело обновления любых однородных объектов, которые вы намереваетесь обновить, отправка списков команд для выполнения, представление цепочки обмена, чтобы обновлялось окно вашей ОС, и оповещение вашего приложения о завершении рендеринга.

void Renderer::render()
{
  // Предел кадров устанавливается до 60 кадров в секунду
  tEnd = std::chrono::high_resolution_clock::now();
  float time = std::chrono::duration(tEnd - tStart).count();
  if (time Map(0, &readRange, reinterpret_cast(&mappedUniformBuffer)));
  memcpy(mappedUniformBuffer, &uboVS, sizeof(uboVS));
  uniformBuffer->Unmap(0, &readRange);
  
  setupCommands();

  ID3D12CommandList* ppCommandLists[] = { mCommandList };
  commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
  swapchain->Present(1, 0);


  const UINT64 fence = fenceValue;
  ThrowIfFailed(commandQueue->Signal(fence, fence));
  fenceValue++;


  if (fence->GetCompletedValue() SetEventOnCompletion(fence, fenceEvent));
    WaitForSingleObject(fenceEvent, INFINITE);
  }

  frameIndex = swapchain->GetCurrentBackBufferIndex();
}

Устранение дескрипторов

Если вы используете структуры данных ComPtr <T>, то как и в случае с общими указателями, вам не нужно беспокоиться об устранении любых созданных вами дескрипторов. Если же вы этого не сделаете, вы можете вызвать функцию Release (), встроенную в каждую структуру данных DirectX.

Заключение

DirectX 12 — это многофункциональный и надежный API компьютерной графики, идеально подходящий для коммерческих проектов. Он похож на другие современные графические API по своей архитектуре, а также является основным API, поддерживаемым как разработчиками драйверов оборудования, так и создателями коммерческих продуктов. В этом посте рассматривается отрисовка, основанная на растровых данных, однако есть и другие достойные рассмотрения аспекты DirectX, которые здесь не обсуждались, такие как:

  • DirectML — Аппаратно ускоренное выполнение модели машинного обучения.
  • DirectX Raytracing — Аппаратно ускоренные трассировка лучей и обход сцены.
  • Вычислительные шейдеры — Основанное на GPGPU выполнение произвольных задач, таких как обработка изображений, физика и т.д.

Весь исходный код, описанный в этом посте, вы найдете в хранилище Github.

Также обязательно ознакомьтесь со следующими проектами:

Источник: https://alain.xyz/blog/raw-directx12

Понравилась статья? Поделиться с друзьями:
Автор natalya
Переводит для Вас самые интересные статьи про разработку игр. По образованию физик-программист. Техническими переводами начала подрабатывать еще на старших курсах и постепенно это переросло в основное занятие. Интересуется гуманитарными технологиями, пробует себя в журналистике.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *