Direct3D12アプリを書いてみる(1)
まず、Direct3D12のアプリをビルドするには、Direct3D12のヘッダファイルをインクルードして、ライブラリファイルをリンクする必要があります。 以前であれば、DirectX SDK というものをダウンロードして、VisualStudioのインクルードとリンクの設定を書けばよかったのですが、 今は、DirectX SDKはなくなってしまい、Windows SDK に含まれているようです。
では、Windows SDKはどこにあるのかというと、Visual Studio Installer というアプリを実行して、VisualStudioのコンポーネントから選択して、インストールできます。
Windows10 SDK と Windows11 SDK があったり、複数のバージョンがあったり、どれを選択すればいいのか、わからないと思います。 自分でアプリを作るだけなら最新版のみインストールすればいいです。他人のプログラムをビルドする場合には古いバージョンも入れておいた方が安全です。
これでビルドできるようになったので、三角形を1つだけ描画するプログラムを書きました。ChatGPTに聞きながら書いたので、無駄なコードも含んでいるかもしれません。
#include <windows.h>
#include <d3d12.h>
#include <dxgi1_6.h>
#include <d3dcompiler.h>
#include <DirectXColors.h>
#include <wrl.h>
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3dcompiler.lib")
using namespace Microsoft::WRL;
using namespace DirectX;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_DESTROY) {
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
struct Vertex {
XMFLOAT3 position;
};
const char* vertexShaderSource = R"(
struct VSInput {
float3 position : POSITION;
};
struct PSInput {
float4 position : SV_POSITION;
};
PSInput VSMain(VSInput input) {
PSInput output;
output.position = float4(input.position, 1.0f);
return output;
}
)";
const char* pixelShaderSource = R"(
float4 PSMain() : SV_TARGET {
return float4(1.0f, 0.0f, 0.0f, 1.0f);
}
)";
// エントリーポイント
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// ウィンドウクラスの登録
WNDCLASSEX wcex = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, hInstance, nullptr, nullptr, nullptr, nullptr, "D3D12SampleWindowClass", nullptr };
RegisterClassEx(&wcex);
HWND hWnd = CreateWindow(wcex.lpszClassName, "Direct3D 12 Sample", WS_OVERLAPPEDWINDOW, 100, 100, 800, 600, nullptr, nullptr, wcex.hInstance, nullptr);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// Direct3D 12 デバイスとコマンドキューの作成
ComPtr<ID3D12Device> device;
ComPtr<IDXGISwapChain3> swapChain;
ComPtr<ID3D12CommandQueue> commandQueue;
ComPtr<ID3D12DescriptorHeap> rtvHeap;
ComPtr<ID3D12Resource> renderTargets[2];
ComPtr<ID3D12CommandAllocator> commandAllocator;
ComPtr<ID3D12GraphicsCommandList> commandList;
// スワップチェーン関連の設定
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = 2;
swapChainDesc.Width = 800;
swapChainDesc.Height = 600;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;
// DXGIファクトリの作成
ComPtr<IDXGIFactory4> factory;
CreateDXGIFactory1(IID_PPV_ARGS(&factory));
// デバイスの作成
D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_0, IID_PPV_ARGS(&device));
// コマンドキューの作成
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&commandQueue));
// スワップチェーンの作成
ComPtr<IDXGISwapChain1> tempSwapChain;
factory->CreateSwapChainForHwnd(commandQueue.Get(), hWnd, &swapChainDesc, nullptr, nullptr, &tempSwapChain);
tempSwapChain.As(&swapChain);
// 描画用のRTV(レンダーターゲットビュー)の作成
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = 2;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&rtvHeap));
// RTVのハンドルを取得
UINT rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = rtvHeap->GetCPUDescriptorHandleForHeapStart();
// レンダーターゲットをスワップチェーンから取得
for (UINT i = 0; i < 2; ++i) {
swapChain->GetBuffer(i, IID_PPV_ARGS(&renderTargets[i]));
device->CreateRenderTargetView(renderTargets[i].Get(), nullptr, rtvHandle);
rtvHandle.ptr += rtvDescriptorSize;
}
// コマンドアロケータとコマンドリストの作成
device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&commandAllocator));
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator.Get(), nullptr, IID_PPV_ARGS(&commandList));
// 頂点バッファデータ
Vertex triangleVertices[] = {
{ { 0.0f, 0.5f, 0.0f } },
{ { 0.5f, -0.5f, 0.0f } },
{ { -0.5f, -0.5f, 0.0f } }
};
// 頂点バッファの作成
D3D12_HEAP_PROPERTIES heapProps = {};
heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
D3D12_RESOURCE_DESC bufferDesc = {};
bufferDesc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
bufferDesc.Width = sizeof(triangleVertices);
bufferDesc.Height = 1;
bufferDesc.DepthOrArraySize = 1;
bufferDesc.MipLevels = 1;
bufferDesc.Format = DXGI_FORMAT_UNKNOWN;
bufferDesc.SampleDesc.Count = 1;
bufferDesc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
ComPtr<ID3D12Resource> vertexBuffer;
device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&bufferDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&vertexBuffer));
// 頂点バッファにデータをコピー
void* pVertexDataBegin;
D3D12_RANGE readRange = { 0, 0 };
vertexBuffer->Map(0, &readRange, &pVertexDataBegin);
memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
vertexBuffer->Unmap(0, nullptr);
// 頂点バッファビューの作成
D3D12_VERTEX_BUFFER_VIEW vertexBufferView = {};
vertexBufferView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();
vertexBufferView.SizeInBytes = sizeof(triangleVertices);
vertexBufferView.StrideInBytes = sizeof(Vertex);
// シェーダー
ComPtr<ID3DBlob> vertexShader;
ComPtr<ID3DBlob> pixelShader;
ComPtr<ID3DBlob> errorBlob;
D3DCompile(vertexShaderSource, strlen(vertexShaderSource), nullptr, nullptr, nullptr, "VSMain", "vs_5_0", 0, 0, &vertexShader, nullptr);
D3DCompile(pixelShaderSource, strlen(pixelShaderSource), nullptr, nullptr, nullptr, "PSMain", "ps_5_0", 0, 0, &pixelShader, nullptr);
// インプットエレメント
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
// ルートシグニチャ
D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = {};
rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
ComPtr<ID3DBlob> signature;
ComPtr<ID3DBlob> error;
D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error);
ComPtr<ID3D12RootSignature> rootSignature;
device->CreateRootSignature(0, signature->GetBufferPointer(), signature->GetBufferSize(), IID_PPV_ARGS(&rootSignature));
// パイプラインステートオブジェクト
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) }; // 入力レイアウト
psoDesc.pRootSignature = rootSignature.Get(); // ルートシグネチャ
psoDesc.VS = { vertexShader->GetBufferPointer(), vertexShader->GetBufferSize() }; // 頂点シェーダー
psoDesc.PS = { pixelShader->GetBufferPointer(), pixelShader->GetBufferSize() }; // ピクセルシェーダー
// デフォルトのラスタライザ設定
D3D12_RASTERIZER_DESC rasterizerDesc = {};
rasterizerDesc.FillMode = D3D12_FILL_MODE_SOLID;
rasterizerDesc.CullMode = D3D12_CULL_MODE_BACK;
rasterizerDesc.FrontCounterClockwise = FALSE;
rasterizerDesc.DepthClipEnable = TRUE;
psoDesc.RasterizerState = rasterizerDesc;
// デフォルトのブレンド設定
D3D12_BLEND_DESC blendDesc = {};
blendDesc.RenderTarget[0].BlendEnable = FALSE;
blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
psoDesc.BlendState = blendDesc;
psoDesc.DepthStencilState.DepthEnable = FALSE;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.SampleMask = UINT_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
ComPtr<ID3D12PipelineState> pipelineState;
device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&pipelineState));
// ビューポートとシザー矩形の設定
D3D12_VIEWPORT viewport = {};
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = 800;
viewport.Height = 600;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
D3D12_RECT scissorRect = {};
scissorRect.left = 0;
scissorRect.top = 0;
scissorRect.right = 800;
scissorRect.bottom = 600;
// メインループ
MSG msg = {};
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
// コマンドリストの記録と実行
commandAllocator->Reset();
commandList->Reset(commandAllocator.Get(), pipelineState.Get());
// バリア遷移
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = renderTargets[swapChain->GetCurrentBackBufferIndex()].Get();
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
commandList->ResourceBarrier(1, &barrier);
rtvHandle = rtvHeap->GetCPUDescriptorHandleForHeapStart();
rtvHandle.ptr += swapChain->GetCurrentBackBufferIndex() * rtvDescriptorSize;
// RenderTargetをセットして、クリア
commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
commandList->ClearRenderTargetView(rtvHandle, DirectX::Colors::Black, 0, nullptr);
// パイプライン
commandList->SetPipelineState(pipelineState.Get());
commandList->SetGraphicsRootSignature(rootSignature.Get());
commandList->RSSetViewports(1, &viewport);
commandList->RSSetScissorRects(1, &scissorRect);
// 描画コマンドの実行
commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
commandList->IASetVertexBuffers(0, 1, &vertexBufferView);
commandList->DrawInstanced(3, 1, 0, 0);
// プレゼントへのバリア遷移
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
commandList->ResourceBarrier(1, &barrier);
commandList->Close();
ID3D12CommandList* commandLists[] = { commandList.Get() };
commandQueue->ExecuteCommandLists(_countof(commandLists), commandLists);
// スワップチェーンの表示
swapChain->Present(1, 0);
}
}
UnregisterClass(wcex.lpszClassName, hInstance);
return 0;
}
ごめんなさい。短く書けませんでした。長いですね。
d3dx12.h をインクルードしてユーティリティを使えば、もう少し短くなるかもしれませんが、d3dx12.h は Windows SDK に入っていないので使いませんでした。
d3dx12.hというのは、d3d12を簡単に設定できるようにしてくれるユーティリティライブラリなのですが、マイクロソフトさんの扱いが微妙で、どこからインストールすればいいのか、 はっきり書かれていません。さらに、インストールされているフォルダ名が違ったりして、正直扱いにくいです。本来、サンプルプログラム用に作られたものなんだけど、d3d12.hだけだと 使いにくいので、未だに使われています。
ちなみに、「NuGetパッケージの管理」から「Microsoft.Direct3D.D3D12」というパッケージをインストールすると、d3dx12.hも含んだDirect3D12がインストールされました。 よくわからないですね。Direct3D12を単体でインストールできても、Windows SDKのファイルと競合しますよね。 nugetはd3dx12だけインストールできるようにしてくれればいいのに。
ちょっと長文になってしまったので、今回はここまでにして、次回以降、このプログラムの内容を説明したいと思います。