目次
- はじめに
- DirectX 12のリソースバインディングの課題
- Shader Model 6.6 Dynamic Resources (Bindless) の登場
- 簡易実行サンプルの発見
- 学習用環境をつくる
- 確認用コード
- メリットとデメリット
- 最後に
- 参考文献
はじめに
数年ぶりに記事を書きます。
筆者はDirectX12でDirectXRaytracingによる
ハードウェアレイトレーシングについて学んでいたのですが、
その過程でDirectX12からアプリケーションプログラマーに
作業負担を強いられることとなったリソースバインディングに疲弊していました。
この記事でできるようになること
Shader Model 6.6で導入されたDynamic Resources (Bindless Rendering) を活用することで、
リソースバインディングの問題がどのように解決されるのかを解説します。
具体的なサンプルコードを動かし、コードを読み解き、
さらに理解を深めるためのコード調整まで行います。
対象読者
- DirectX 12の基本的な知識がある方(シェーダー、パイプライン、コマンドリストなど)
- DirectX 12のリソースバインディングの煩雑さに悩んでいる方
- Dynamic Resources (Bindless Rendering) に興味がある方
- HLSL Shader Model 6.6 に対応したGPU環境をお持ちの方
前提知識と環境
ここで解説する処理を実行するPCには
シェーダモデル6.6に対応するGPUが必要となり、その前提のためご注意ください。
- C++ (C++20の機能を使用します)
- HLSL
- DirectX 12
- Visual Studio (または他の適切な開発環境)
DirectX 12のリソースバインディングの課題
リソースバインディングとは?
リソースバインディングとは、シェーダープログラム(HLSL)で使用する
テクスチャ、定数バッファ、サンプラーなどのリソースを
GPUに認識させるための仕組みです。
なぜDirectX 12で煩雑になったのか
DirectX11までは、比較的シンプルなAPIで
リソースバインディングを行うことができました。しかし、DirectX 12では
パフォーマンス向上のために、より低レベルな制御が可能になった反面、
リソースバインディングの記述が煩雑になりました。
具体的には、以下の要素が絡み合い、複雑さを増しています。
- ディスクリプタ (Descriptor): リソースの種類、フォーマット、サイズなどの情報を記述するもの。
- ディスクリプタヒープ (Descriptor Heap): ディスクリプタを格納するメモリ領域。
- ルートシグネチャ (Root Signature): シェーダーが使用するリソースの種類と、それらをどのようにバインドするかを定義するもの。
- コマンドリスト (Command List): GPUに実行させる一連のコマンドを記録するもの。リソースバインディングの指示もここで行う。
従来のバインディングの何が問題か
リソースバインディングについてはこちらの記事が詳しいです。
[DirectX12]Descriptor, DescriptorHeap, RootSignatureと
リソースバインディングの解説
筆者はDXRを学習していたのですが、何か検証用にテクスチャを増やしたり、
BLAS等を増やそうとすると、記述子設定の増減に伴って、
CPU、GPU、HLSLコードと複数の同時変更箇所が発生して、少しでも間違えると
クラッシュ等も起こりうるので、学習が億劫になる要因になっていました。
これはDirectX12を学習している方々が挫折しやすいポイントなんじゃないかと思います。
ShaderModel 6.6 DynamicResources (Bindless) の登場
Dynamic Resourcesとは?
Shader Model 6.6で導入されたDynamic Resourcesは、
リソースバインディングの扱いずらさを改善するものです。
従来は、ルートシグネチャで事前に定義したスロットにリソースを
バインドする必要がありましたが、
Dynamic Resourcesでは、シェーダー内でインデックスを使って動的に
リソースにアクセスできるようになります。
こちらで詳細が記載されています。
HLSL_SM_6_6_DynamicResources
また国内でも解説されている方がいらっしゃいます。
DynamicResource
Bindless Renderingとの関係
また、UnrealEngine5.5 においても Bindless Rendering という機能名で導入等が進んでおり、
内部的には近しいことをされているのではないかと推察します。
簡易実行サンプルの発見
しかし、上記までの情報だけですと、初学者には理解と実践のためのハードルが高いなあと
感じてました。そんな時に、このようなGitHubのリポジトリを見つけました
Hello-Bindless
これを実行してみたところ、所持しているPC上にて動作確認ができました。
動かなかった方、あるいは、このリポジトリの環境を動作させるのが面倒だった方は
リポジトリのRunフォルダ内のdllとdxcapi.h を改修して通常の学習等で
利用されているDX12用プロジェクトに入れておき、掲載している1ファイルの実行処理を
VisualStudio等でコンパイルしてみてください。
Hello-Bindless-D3D12のサンプルですが、DirectX12の実装経験がある方はご存じの通り、
コーディング量が従来のDirectXバージョンに比べて増大したことで、各々の実装者が
各々の持ちえた知見により巧みに、関数化、クラス化、構造体化されるため、
理解を目的にした時に、その数Stepが入ることで理解にさらに時間を要してしまいます。
今回はこれを元にさらに理解を促進する状態に調整してみることにします。
調整にあたり、以下のスライド内の構造体のメンバ変数名を指定した初期化
(Designated Initialization)の仕組みを利用しました。
そのために実装はC++20となります。
CEDEC2020 ゲーム開発者のための C++11~C++20, 将来の C++ の展望
学習用環境をつくる
以下の順序で作成をしていきます。
1.サンプルコードの動作確認
先ほどのGithubのデータを取得してきて実行します。
Hello Bindless D3D12
まずこれが動くようであれば、ShaderModel 6.6 に対応しているといえるでしょう。
2.コードリーディング
上記のソースを読んでみます。
残念ながら筆者の力量ではこの段階では、まだ理解には至れていないことがわかりました。
なので理解のための工夫を施します。
3.自動整形
理解のために多少整形します。
今の時代は ChatGPT,google Gemini等のAIの支援も受けられます。
理解のためにもう少し安全の範囲でコード量を減らしてもらいましょう。
プロンプト例
あなたは最強のプログラマーです。エンバグに細心の注意を払いながら、
Direct12がリソースバインディングの仕組み上、
些細な順番の入れ替わりでもクラッシュが引き起こされることを考慮しつつ、
責任のとれる安全な範囲内でバランスを見ながらコードを極限まで短くする
リファクタリングを実施してください。
4.さらにコードリーディング
昨今AIの進化が著しいですが、それでもAIに完全に甘えるのはまだ許されていません。
筆者は640行までAI整形されたものを元に0から思考しました。
掲載しているコードも最終的には自分で全て記述しなおしています。
そのため、最終結果コードはそれ以上に短く、理解のしやすさの両立を目指してます。
逆にいうと理解以上の目的を想定してないため、
読者の方が仕組み等をご理解されましたら、
インデントや改行、関数、クラス化等などをご調整頂く形を想定してます。
そこの範囲ならAIを頼ってもよいのかもしれません。
5.最小限コードの構築
先ほどのHello Bindless D3D12が動いている前提ですので、
そこのコードを以下に書き換えます。
確認用コード
結果のコードを掲載します。1ソースで動作します。
//------------------------------------------------------------
// DirectX12 Shader Model 6.6 Dynamic Resource 動作確認サンプル
//------------------------------------------------------------
//------------------------------------------------------------
// 実行ファイルのインクルード
//------------------------------------------------------------
#include <windows.h>
#include <d3d12.h>
#include "d3dx12.h"
#include <dxgi1_6.h>
#include <DirectXMath.h>
#include <dxcapi.h>
#include <wrl/client.h>
//------------------------------------------------------------
// 実行ライブラリ
//------------------------------------------------------------
#pragma comment(lib,"d3d12.lib")
#pragma comment(lib,"dxgi.lib")
#pragma comment(lib, "dxcompiler.lib")
//------------------------------------------------------------
// 定数設定
//------------------------------------------------------------
const int WINDOW_WIDTH = 1280;
const int WINDOW_HEIGHT = 720;
const int FRAME_COUNT = 3;
//------------------------------------------------------------
// マクロ宣言
//------------------------------------------------------------
#define ArrayCount(arr) (sizeof(arr) / sizeof((arr)[0]))
#define CHECK_HR(hr) assert(SUCCEEDED(hr))
#define STRINGIFY__(x) #x
#define STRINGIFY_(x) STRINGIFY__(x)
#define STRINGIFY(x) STRINGIFY_(x)
//------------------------------------------------------------
// Windowsのメインプログラム
//------------------------------------------------------------
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) {
//------------------------------------------------------------
// Windowsの簡略的な各種設定
//------------------------------------------------------------
static const char* Name = "DirectX12 Shader Model 6.6 DynamicResource MiniSample";
static int color_mode = 0;
static const auto WndProc = [](HWND hwnd, UINT mes, WPARAM wp, LPARAM lp) -> LRESULT {
if (mes == WM_DESTROY) { PostQuitMessage(0); return 0; }
if (mes == WM_KEYDOWN) { if (wp == 'K') color_mode ^= 1; }//Kキーで画面のクリアカラーが変わるようにしてあります。これでDirectX12がDevice Removeしてないことを簡易的に確認します"
return DefWindowProc(hwnd, mes, wp, lp);
};
MSG msg = {}; HWND hWnd;
WNDCLASSEX wcex = { sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInstance, 0, 0,(HBRUSH)COLOR_WINDOWFRAME, 0, Name, 0 };
if (!RegisterClassEx(&wcex) || !(hWnd = CreateWindow(Name, Name, WS_TILEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 0, 0, hInstance, 0)))return 0;
//------------------------------------------------------------
// DirectX12関連変数の宣言
//------------------------------------------------------------
using namespace DirectX;
using Microsoft::WRL::ComPtr;
ComPtr<IDXGISwapChain3> m_swapChain = nullptr;
ComPtr<ID3D12Device> m_device = nullptr;
ComPtr<ID3D12CommandAllocator> m_commandAllocator = nullptr;
ComPtr<ID3D12CommandQueue> m_commandQueue = nullptr;
ComPtr<ID3D12GraphicsCommandList> m_commandList = nullptr;
ComPtr<ID3D12DescriptorHeap> m_rtvHeap = nullptr;
ComPtr<ID3D12Fence> m_fence = nullptr; UINT64 m_fenceValue = 1;
ComPtr<IDxcCompiler3> m_dxc_compiler = nullptr;
ComPtr<IDxcBlob> m_vertexShader = nullptr;
ComPtr<IDxcBlob> m_pixelShader = nullptr;
ComPtr<ID3D12PipelineState> m_pipelineState = nullptr;
ComPtr<ID3D12RootSignature> m_rootSignature = nullptr;
ComPtr<ID3D12Resource> m_constantBuffer = nullptr;
ComPtr<ID3D12DescriptorHeap> m_descHeap = nullptr;
ComPtr<ID3D12Resource> m_vertexBuffer = nullptr;
ComPtr<ID3D12Resource> m_texture = nullptr;
ComPtr<ID3D12Resource> m_renderTargets[FRAME_COUNT];
D3D12_VERTEX_BUFFER_VIEW m_vertexBufferView;
//------------------------------------------------------------
//DirectX12 デバッグレイヤーの有効化
//------------------------------------------------------------
#ifdef _DEBUG
ComPtr<ID3D12Debug> pDebug = nullptr; //DirectX12は些細な違いでもクラッシュにつながることがあり、短い検証コードでも含めておいた方が良さそうです。
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(pDebug.GetAddressOf())))) { pDebug->EnableDebugLayer(); }
#endif
//------------------------------------------------------------
//DirectX12 各種の設定準備
//------------------------------------------------------------
HRESULT hr_compiler = DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&m_dxc_compiler));
D3D12CreateDevice(0, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(m_device.GetAddressOf()));
//------------------------------------------------------------
//DirectX12 ShaderModel6.6に対応しているか確認します
//------------------------------------------------------------
bool checkEnableDynamicResource = false;
D3D12_FEATURE_DATA_D3D12_OPTIONS featureOptions{};
D3D12_FEATURE_DATA_SHADER_MODEL shaderModel{};
shaderModel.HighestShaderModel = D3D_SHADER_MODEL_6_6;
if (SUCCEEDED(m_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &featureOptions, sizeof(featureOptions)))
&& SUCCEEDED(m_device->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel))))
{
bool isTier3 = featureOptions.ResourceBindingTier == D3D12_RESOURCE_BINDING_TIER_3;
checkEnableDynamicResource = isTier3;
}
if(!checkEnableDynamicResource)
{
MessageBox(NULL, "This Device Not Supported Dynamic Resource", "Check ShaderModel6.6 DynamicResource", MB_ICONERROR);
return -1;
}
//------------------------------------------------------------
//DirectX12 ShaderModel6.6に対応していたら、初期設定処理を続行
//------------------------------------------------------------
D3D12_COMMAND_QUEUE_DESC queueDesc = { .Type = D3D12_COMMAND_LIST_TYPE_DIRECT ,.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE };
m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(m_commandQueue.GetAddressOf()));
m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(m_commandAllocator.GetAddressOf()));
m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator.Get(), NULL, IID_PPV_ARGS(m_commandList.GetAddressOf()));
m_commandList->Close();
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {
.Width = WINDOW_WIDTH,
.Height = WINDOW_HEIGHT,
.Format = DXGI_FORMAT_R8G8B8A8_UNORM,
.SampleDesc = {.Count = 1},
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = FRAME_COUNT,
.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD
};
ComPtr<IDXGIFactory4> factory = nullptr;
CreateDXGIFactory1(IID_PPV_ARGS(factory.GetAddressOf()));
factory->CreateSwapChainForHwnd(m_commandQueue.Get(), hWnd, &swapChainDesc, NULL, NULL, (IDXGISwapChain1**)m_swapChain.GetAddressOf());
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV,.NumDescriptors = FRAME_COUNT,.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE};
m_device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(m_rtvHeap.GetAddressOf()));
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart());
for (UINT n = 0; n < FRAME_COUNT; n++)
{
m_swapChain->GetBuffer(n, IID_PPV_ARGS(m_renderTargets[n].GetAddressOf()));
m_device->CreateRenderTargetView(m_renderTargets[n].Get(), NULL, rtvHandle);
rtvHandle.Offset(1, m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV));
}
m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(m_fence.GetAddressOf()));
UINT CBSize = D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT;
m_device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), D3D12_HEAP_FLAG_NONE, &CD3DX12_RESOURCE_DESC::Buffer(CBSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(m_constantBuffer.GetAddressOf()));
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc = {
.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
.NumDescriptors = 2, //CBV + SRV
.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE
};
m_device->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(m_descHeap.GetAddressOf()));
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {.BufferLocation = m_constantBuffer->GetGPUVirtualAddress(),.SizeInBytes = CBSize };
D3D12_CPU_DESCRIPTOR_HANDLE cHandle = m_descHeap->GetCPUDescriptorHandleForHeapStart();
m_device->CreateConstantBufferView(&cbvDesc, cHandle);
//------------------------------------------------------------
//DirectX12 シェーダコード
//------------------------------------------------------------
const char VSCode[] = "#line " STRINGIFY(__LINE__) R"(
struct ConstantParam {
float4x4 worldViewProjection;
};
struct PS_INPUT {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
PS_INPUT VSMain(float4 pos : POSITION, float2 uv : TEXCOORD0)
{
PS_INPUT output;
ConstantBuffer<ConstantParam> param = ResourceDescriptorHeap[0];
output.pos = mul(pos, param.worldViewProjection);
output.uv = uv;
return output;
}
)";
const char PSCode[] = R"(
SamplerState g_sampler : register(s0);
struct PS_INPUT {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
float4 PSMain(PS_INPUT input) : SV_TARGET
{
Texture2D texture = ResourceDescriptorHeap[1];
//SM6.6 DynamicResouce の機能でg_samplerもSamplerDescriptorHeap[0]等と参照ができる仕組みがありますが、
//ここではResourceDescriptorHeapのみを扱い、Sampler側は読者の改良に委ねます
return texture.Sample(g_sampler, input.uv);
}
)";
//------------------------------------------------------------
//DirectX12 シェーダコードのコンパイル
//------------------------------------------------------------
auto DXC_CompileShader = [&](const char* source, uint32_t source_size, const wchar_t* entry_point, const wchar_t* target, IDxcBlob** result_blob) -> bool {
assert(m_dxc_compiler || !"Call DXC_Init before calling DXC_CompileShader");
const wchar_t* args[] = { L"-E", entry_point, L"-T", target, L"-WX", L"-Zi", };
DxcBuffer source_buffer = { .Ptr = source, .Size = source_size, .Encoding = 0, };
ComPtr<IDxcResult> compile_result = nullptr;
ComPtr<IDxcBlob> error_msg = nullptr;
HRESULT hr = m_dxc_compiler->Compile(&source_buffer, args, ArrayCount(args), nullptr, IID_PPV_ARGS(compile_result.GetAddressOf()));
if (SUCCEEDED(hr)) {
HRESULT compile_hr;
hr = compile_result->GetStatus(&compile_hr);
if (SUCCEEDED(compile_hr)) {
if (compile_result->HasOutput(DXC_OUT_OBJECT)) {
hr = compile_result->GetOutput(DXC_OUT_OBJECT, IID_PPV_ARGS(result_blob), nullptr);
CHECK_HR(hr);
return true;
}
}
if (compile_result->HasOutput(DXC_OUT_ERRORS)) {
hr = compile_result->GetOutput(DXC_OUT_ERRORS, IID_PPV_ARGS(error_msg.GetAddressOf()), nullptr);
CHECK_HR(hr);
}
}
const char* error_message = (char*)error_msg->GetBufferPointer();
OutputDebugStringA("Failed to compile shader:\n");
OutputDebugStringW(entry_point);
OutputDebugStringA("\n");
OutputDebugStringA(error_message);
assert(!"Failed to compile shader, see debugger output for details");
return false;
};
DXC_CompileShader(VSCode, strlen(VSCode), L"VSMain", L"vs_6_6", m_vertexShader.GetAddressOf());
DXC_CompileShader(PSCode, strlen(PSCode), L"PSMain", L"ps_6_6", m_pixelShader.GetAddressOf());
//------------------------------------------------------------
// スタティックサンプラーを作成してルートシグネチャに直接セット
//------------------------------------------------------------
D3D12_STATIC_SAMPLER_DESC sampler = {
.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT,
.AddressU = D3D12_TEXTURE_ADDRESS_MODE_WRAP,
.AddressV = D3D12_TEXTURE_ADDRESS_MODE_WRAP,
.AddressW = D3D12_TEXTURE_ADDRESS_MODE_WRAP,
.MipLODBias = 0,
.MaxAnisotropy = 0,
.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER,
.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK,
.MinLOD = 0.0f,
.MaxLOD = D3D12_FLOAT32_MAX,
.ShaderRegister = 0,
.RegisterSpace = 0,
.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL
};
//------------------------------------------------------------
// ルートパラメータ設定を行い、ルートシグネチャー作成
//------------------------------------------------------------
CD3DX12_DESCRIPTOR_RANGE1 ranges[2];
CD3DX12_ROOT_PARAMETER1 rootParameters[2];
//D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXEDのフラグを設定することで、
//ここでの定義順番がシェーダコード側のResourceDescriptorHeap[0],ResourceDescriptorHeap[1];として参照できるようになります。
ranges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);
ranges[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);
rootParameters[0].InitAsDescriptorTable(1, &ranges[0], D3D12_SHADER_VISIBILITY_ALL);
rootParameters[1].InitAsDescriptorTable(1, &ranges[1], D3D12_SHADER_VISIBILITY_ALL);
CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
rootSignatureDesc.Init_1_1(_countof(rootParameters), rootParameters, 1, &sampler , D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT|D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED);
ComPtr<ID3DBlob> signature = nullptr;
ComPtr<ID3DBlob> error = nullptr;
D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, signature.GetAddressOf(), error.GetAddressOf());
m_device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(m_rootSignature.GetAddressOf()));
//------------------------------------------------------------
// 入力レイアウトとパイプラインステートの作成
//------------------------------------------------------------
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, };
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {
.pRootSignature = m_rootSignature.Get(),
.VS = {.pShaderBytecode = m_vertexShader->GetBufferPointer(), .BytecodeLength = m_vertexShader->GetBufferSize()},
.PS = {.pShaderBytecode = m_pixelShader ->GetBufferPointer(), .BytecodeLength = m_pixelShader ->GetBufferSize()},
.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT),
.SampleMask = UINT_MAX,
.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT) ,
.DepthStencilState = {.DepthEnable = FALSE, .StencilEnable = FALSE},
.InputLayout = {.pInputElementDescs = inputElementDescs, .NumElements = _countof(inputElementDescs) },
.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
.NumRenderTargets = 1,
.RTVFormats = {DXGI_FORMAT_R8G8B8A8_UNORM},
.SampleDesc = {.Count = 1}
};
m_device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(m_pipelineState.GetAddressOf()));
//------------------------------------------------------------
//DirectX12 テクスチャーの作成
//------------------------------------------------------------
m_commandAllocator->Reset();
m_commandList->Reset(m_commandAllocator.Get(), 0);
//固定でメモリに色情報を書き込んでテクスチャと見なすようにする。
const int patternW = 4;
const int patternH = 4;
const int texW = patternW * 3;
const int texH = patternH * 3;
auto CreateCheckerBoardTex = [](int patternW, int patternH, int texW, int texH) -> std::vector<uint32_t> {
const auto col1 = 0xFFFFFFFF;
const auto col2 = 0xFF888888;
const uint32_t pattern[] = { col1, col2, col1, col2,col2, col1, col2, col1,col1, col2, col1, col2,col2, col1, col2, col1 };
std::vector<uint32_t> texture_pixels(texW * texH);
for (int y = 0; y < texH; ++y) {
for (int x = 0; x < texW; ++x) {
texture_pixels[y * texW + x] = pattern[(y % patternH) * patternW + (x % patternW)];
}
}
return texture_pixels;
};
auto texture_pixels = CreateCheckerBoardTex(patternW, patternH, texW, texH);
const D3D12_RESOURCE_DESC tdesc = { .Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D,.Alignment = 0,.Width = texW,.Height = texH,.DepthOrArraySize = 1,.MipLevels = 1,.Format = DXGI_FORMAT_R8G8B8A8_UNORM,.SampleDesc = {.Count = 1, .Quality = 0 },.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN,.Flags = D3D12_RESOURCE_FLAG_NONE };
m_device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_NONE, &tdesc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(m_texture.GetAddressOf()));
DWORD BufferSize = GetRequiredIntermediateSize(m_texture.Get(), 0, 1);
ComPtr<ID3D12Resource> stagingTexture = nullptr;
m_device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), D3D12_HEAP_FLAG_NONE, &CD3DX12_RESOURCE_DESC::Buffer(BufferSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(stagingTexture.GetAddressOf()));
D3D12_SUBRESOURCE_DATA Subres{ .pData = texture_pixels.data(),.RowPitch = texW * 4,.SlicePitch = texH * Subres.RowPitch };
UpdateSubresources(m_commandList.Get(), m_texture.Get(), stagingTexture.Get(), 0, 0, 1, &Subres);
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(m_texture.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
m_commandList->Close();
ID3D12CommandList* ppCommandLists[] = { m_commandList.Get()};
m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
auto waitGPU = [&](){// GPU同期処理を簡易的に実装したもの
m_commandQueue->Signal(m_fence.Get(), m_fenceValue);
do {} while (m_fence->GetCompletedValue() < m_fenceValue);
m_fenceValue++;
};
waitGPU();
D3D12_SHADER_RESOURCE_VIEW_DESC sdesc = {
.Format = tdesc.Format,
.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D,
.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
.Texture2D = {.MipLevels = 1}
};
D3D12_CPU_DESCRIPTOR_HANDLE handle = m_descHeap->GetCPUDescriptorHandleForHeapStart();
handle.ptr += m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
m_device->CreateShaderResourceView(m_texture.Get(), &sdesc, handle);
//------------------------------------------------------------
//DirectX12 テクスチャーの作成 ここまで
//------------------------------------------------------------
//------------------------------------------------------------
//DirectX12 ConstantBuffer,頂点バッファ等の設定
//------------------------------------------------------------
struct ConstantBufferData { XMMATRIX wvp = XMMatrixIdentity(); };
struct Vertex { XMFLOAT3 pos; XMFLOAT2 uv;};
Vertex triangleVertices[] = { { { 0.0f, 0.57733f , 0.0f },{0.5,0.0 }},{ { -0.5f, -0.288667f , 0.0f },{0.0,1.0 } },{ { 0.5f, -0.288667f , 0.0f },{1.0,1.0 } } };
const UINT vertexBufferSize = sizeof(triangleVertices);
m_device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), D3D12_HEAP_FLAG_NONE, &CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(m_vertexBuffer.GetAddressOf()));
UINT8* pVertexDataBegin;
CD3DX12_RANGE readRange(0, 0);
m_vertexBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pVertexDataBegin));
memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
m_vertexBuffer->Unmap(0, NULL);
m_vertexBufferView.BufferLocation = m_vertexBuffer->GetGPUVirtualAddress();
m_vertexBufferView.StrideInBytes = sizeof(Vertex);
m_vertexBufferView.SizeInBytes = vertexBufferSize;
ShowWindow(hWnd, nCmdShow);
do {
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); }
//------------------------------------------------------------
//DirectX12 描画実行の準備的処理を記述
//------------------------------------------------------------
UINT backBufferIndex = m_swapChain->GetCurrentBackBufferIndex();
auto barrier1 = CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[backBufferIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
auto barrier2 = CD3DX12_RESOURCE_BARRIER::Transition(m_renderTargets[backBufferIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
auto rtvHandle= CD3DX12_CPU_DESCRIPTOR_HANDLE(m_rtvHeap->GetCPUDescriptorHandleForHeapStart(), backBufferIndex, m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV));
auto viewport = CD3DX12_VIEWPORT(0.0f, 0.0f, (float)WINDOW_WIDTH, (float)WINDOW_HEIGHT);
auto scissorRect = CD3DX12_RECT(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
const float clearColor1[] = { 0.5f, 1.0f, 0.5f, 1.0f };
const float clearColor2[] = { 1.0f, 1.0f, 0.5f, 1.0f };
ID3D12CommandList* ppCommandLists[] = { m_commandList.Get()};
ID3D12DescriptorHeap* ppHeaps[] = { m_descHeap.Get()};
XMFLOAT3 eyeVec(0.0f, 0.0f, -3.0f);
XMFLOAT3 dirVec(0.0f, 0.0f, 1.0f);
XMFLOAT3 upVec(0.0f, 1.0f, 0.0f);
auto viewMat = XMMatrixLookToRH(XMLoadFloat3(&eyeVec), XMLoadFloat3(&dirVec), XMLoadFloat3(&upVec));
auto projMat = XMMatrixPerspectiveFovRH(3.14159 / 4, (FLOAT)WINDOW_WIDTH / (FLOAT)WINDOW_HEIGHT, 0.1f, 1000.0f);
auto readRange = CD3DX12_RANGE(0, 0);
UINT8* pCbvDataBegin;
m_constantBuffer->Map(0, &readRange, reinterpret_cast<void**>(&pCbvDataBegin));
XMMATRIX worldMat = XMMatrixTranslation(0, 0, 0);
ConstantBufferData constantBufferData = { .wvp = XMMatrixTranspose(worldMat * viewMat * projMat) };
memcpy(reinterpret_cast<char*>(pCbvDataBegin), &constantBufferData, sizeof(ConstantBufferData));
//------------------------------------------------------------
//DirectX12 描画コマンドを実行
//------------------------------------------------------------
m_commandAllocator->Reset();
m_commandList->Reset(m_commandAllocator.Get(), m_pipelineState.Get());
m_commandList->ResourceBarrier(1, &barrier1);
m_commandList->OMSetRenderTargets(1, &rtvHandle, false, NULL);
m_commandList->RSSetViewports(1, &viewport);
m_commandList->RSSetScissorRects(1, &scissorRect);
m_commandList->ClearRenderTargetView(rtvHandle, color_mode == 0 ? clearColor1 : clearColor2, 0, NULL);
m_commandList->SetGraphicsRootSignature(m_rootSignature.Get());
m_commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
//Dynamic Resource が有効ではない従来のリソースバインディングでは以下のコードの設定も必要でした。
//つまり記述子の増減に伴う変更箇所が軽減されているということです。
/*
m_commandList->SetGraphicsRootDescriptorTable(0, m_descHeap->GetGPUDescriptorHandleForHeapStart());
auto handle_gpu = m_descHeap->GetGPUDescriptorHandleForHeapStart();
handle_gpu.ptr += m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
m_commandList->SetGraphicsRootDescriptorTable(1, handle_gpu);
*/
m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
m_commandList->DrawInstanced(3, 1, 0, 0);
m_commandList->ResourceBarrier(1, &barrier2);
m_commandList->Close();
m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
m_swapChain->Present(1, 0);
waitGPU();
} while (msg.message != WM_QUIT);
return 0;
}
実行結果
実行に成功すると画像のような三角ポリゴンが表示されます。
Kキーを押すと背景色が変わります。
確認する部分
DynamicResourceで何が違うのか、を以下にまとめます。
- D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED で有効化
- 有効化されたことで、以下のようになります。
- シェーダ側はResourceDescriptorHeapで各リソースを参照できるようになる。
- SetGraphicsRootDescriptorTableのコメント部分のように設定部分が必須でなくなる。
注意点
- 理解の目的のために特化した記述をしています。
- WindowAPIの扱いも本来の記載形式とは異なる簡略的な記法です。
- インデント整形等のいわゆる行儀の良いコードという形ではないです。
- エラーハンドリングなども万全ではないため、カスタマイズをお願いします。
- 同様にコード省力化の理由でd3dx12.h系列のAPIに依存してます。
- 理解の後に外したい方はご調整お願いします。
- 変数にm_とつけているのは検索する時に変数と型を区別するためです。
- 理解の後クラスメンバに移されることを想定してます。
- m_は慣習としては古いものです。筆者の時代はこれで教わっていました。
- こちらも読者の方々で適宜ご調整お願いします。
- ここではComPtrを利用してます。理解のためのコード量を抑えつつ、
メモリ管理の手間も軽減する意図からです。 - 理解できた後は皆さんの目的に合わせて、再実装することを推奨します。
メリットとデメリット
メリット
個人が学習目的で扱う上では、リソースバインディングの
管理の複雑さが軽減されます。変更が楽です。
個人的にはこのメリットはデメリットを上回る恩恵を感じました。
デメリット
従来の方法ではコンスタントバッファはb0,
テクスチャ等はt0,t1 等の種別で分けて管理されてたので、
不具合があった時、それら毎で不具合を大まかに切り分けることができていました。
DynamicResourceでは、それらまとめて全て
ResourceDescriptorHeapからGPU側で参照するため、
不具合があった時、原因の特定がさらに困難になります。
特にチームや組織で開発している場合、思わぬところで、
管理から外れると収集がつかなくなる可能性があります。
これについて、以下の記事が参考になりそうです。
また、ShaderModel6.6以降の対応であることもデメリットともいえるかもしれません。
ハイエンドなGPUがないとこの機能は使えないということです。
最後に
- DirectX12の学習で多くの方が、リソースバインディングに
苦戦しているんじゃないかと思います。 - ShaderModel6.6 対応GPUが必要ですが、所持している方に
この記事の内容が学習の敷居を下げる選択肢となれば幸いです。