首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C >> 關於C

純 C 語言開發 Direct3D 12 應用:從零繪製三角形完整教學

     編輯:關於C

Direct3D 12 以極低抽象層級暴露 GPU 硬體,官方範例多以 C++ 重度封裝導致晦澀難懂。本文提供純 C 語言、無 D3DX 相依、最小化的 D3D12 三角形渲染範例,適合理解現代圖形 API 核心概念。

專案設定 (Visual Studio 2015+, Windows 10 SDK)

  • Include: $(WindowsSDK_IncludePath)um;$(WindowsSDK_IncludePath)shared
  • Library: $(WindowsSDK_LibraryPath)um\x64
  • Dependencies: d3d12.lib; dxgi.lib; dxguid.lib; d3dcompiler.lib
  • Platform: x64 (D3D12 僅支援 64 位元)
  • Precompiled Headers: Not Using
  • Source: .c 副檔名

核心物件與同步架構

D3D12 明確區分 CPU 端資源建立 與 GPU 端執行,需開發者自行管理:

  • ID3D12Device:邏輯裝置
  • ID3D12CommandQueue + ID3D12CommandAllocator + ID3D12GraphicsCommandList:命令提交管線
  • IDXGISwapChain3:雙緩衝交換鏈 (FRAME_COUNT=2)
  • ID3D12DescriptorHeap (RTV):渲染目標描述符堆疊
  • ID3D12Fence + Event:CPU/GPU 同步
  • ID3D12RootSignature + ID3D12PipelineState:繪圖管線狀態物件 (PSO)

管線建立關鍵步驟

1. 啟用除錯層與建立裝置

ID3D12Debug *dbg; D3D12GetDebugInterface(&IID_ID3D12Debug, &dbg);
dbg->lpVtbl->EnableDebugLayer(dbg);

IDXGIFactory4 *factory; CreateDXGIFactory1(&IID_IDXGIFactory4, &factory);
// 這裡示範 WARP 軟體光柵化,實機請 EnumAdapters1 選擇硬體裝置
IDXGIAdapter *warp; factory->lpVtbl->EnumWarpAdapter(factory, &IID_IDXGIAdapter, &warp);
D3D12CreateDevice((IUnknown*)warp, D3D_FEATURE_LEVEL_11_0, &IID_ID3D12Device, &device);

2. 命令隊列、交換鏈、RTV Heap

D3D12_COMMAND_QUEUE_DESC qd = {D3D12_COMMAND_LIST_TYPE_DIRECT};
device->lpVtbl->CreateCommandQueue(device, &qd, &IID_ID3D12CommandQueue, &cmdQueue);

DXGI_SWAP_CHAIN_DESC scd = {0};
scd.BufferCount = 2; scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; scd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
scd.OutputWindow = hWnd; scd.SampleDesc.Count = 1; scd.Windowed = TRUE;
IDXGISwapChain *sc; factory->lpVtbl->CreateSwapChain(factory, (IUnknown*)cmdQueue, &scd, &sc);
swapChain = (IDXGISwapChain3*)sc;

D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {D3D12_DESCRIPTOR_HEAP_TYPE_RTV, 2, D3D12_DESCRIPTOR_HEAP_FLAG_NONE};
device->lpVtbl->CreateDescriptorHeap(device, &rtvHeapDesc, &IID_ID3D12DescriptorHeap, &rtvHeap);
rtvDescriptorSize = device->lpVtbl->GetDescriptorHandleIncrementSize(device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

// 為每個 back buffer 建立 RTV
D3D12_CPU_DESCRIPTOR_HANDLE handle; rtvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(rtvHeap, &handle);
for (int i=0; i<2; i++) {
    swapChain->lpVtbl->GetBuffer(swapChain, i, &IID_ID3D12Resource, &renderTargets[i]);
    device->lpVtbl->CreateRenderTargetView(device, renderTargets[i], NULL, handle);
    handle.ptr += rtvDescriptorSize;
}

3. Root Signature、Shader 編譯、PSO

// 空 Root Signature (允許 IA 輸入佈局)
D3D12_ROOT_SIGNATURE_DESC rsDesc = {0, NULL, 0, NULL, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT};
ID3DBlob *sigBlob, *errBlob;
D3D12SerializeRootSignature(&rsDesc, D3D_ROOT_SIGNATURE_VERSION_1, &sigBlob, &errBlob);
device->lpVtbl->CreateRootSignature(device, 0, sigBlob->lpVtbl->GetBufferPointer(sigBlob), sigBlob->lpVtbl->GetBufferSize(sigBlob), &IID_ID3D12RootSignature, &rootSig);

// 編譯 HLSL
ID3DBlob *vsBlob, *psBlob;
D3DCompileFromFile(L"shaders.hlsl", NULL, NULL, "VSMain", "vs_5_0", D3DCOMPILE_DEBUG, 0, &vsBlob, NULL);
D3DCompileFromFile(L"shaders.hlsl", NULL, NULL, "PSMain", "ps_5_0", D3DCOMPILE_DEBUG, 0, &psBlob, NULL);

// 輸入佈局
D3D12_INPUT_ELEMENT_DESC ied[] = {
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,  D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
    {"COLOR",    0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}
};

// PSO 描述
D3D12_GRAPHICS_PIPELINE_STATE_DESC pso = {0};
pso.InputLayout = (D3D12_INPUT_LAYOUT_DESC){ied, 2};
pso.pRootSignature = rootSig;
pso.VS = (D3D12_SHADER_BYTECODE){vsBlob->lpVtbl->GetBufferPointer(vsBlob), vsBlob->lpVtbl->GetBufferSize(vsBlob)};
pso.PS = (D3D12_SHADER_BYTECODE){psBlob->lpVtbl->GetBufferPointer(psBlob), psBlob->lpVtbl->GetBufferSize(psBlob)};
pso.RasterizerState = (D3D12_RASTERIZER_DESC){D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_NONE, FALSE, 0, 0, 0, TRUE, FALSE, FALSE, 0, D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF};
pso.BlendState = (D3D12_BLEND_DESC){FALSE, FALSE, {{FALSE, FALSE, D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD, D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, D3D12_LOGIC_OP_NOOP, D3D12_COLOR_WRITE_ENABLE_ALL}}};
pso.DepthStencilState.DepthEnable = FALSE; pso.DepthStencilState.StencilEnable = FALSE;
pso.SampleMask = UINT32_MAX; pso.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
pso.NumRenderTargets = 1; pso.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; pso.SampleDesc.Count = 1;
device->lpVtbl->CreateGraphicsPipelineState(device, &pso, &IID_ID3D12PipelineState, &psoObj);

4. 頂點緩衝區 (Upload Heap)

struct Vertex { float pos[4]; float color[4]; };
struct Vertex tri[] = {
    {{0, 0.75f, 0, 1}, {1,0,0,1}},   // 頂點
    {{0.5f, -0.75f, 0, 1}, {0,1,0,1}}, // 右下
    {{-0.5f, -0.75f, 0, 1}, {0,0,1,1}} // 左下
};

D3D12_HEAP_PROPERTIES hp = {D3D12_HEAP_TYPE_UPLOAD};
D3D12_RESOURCE_DESC rd = {D3D12_RESOURCE_DIMENSION_BUFFER, 0, sizeof(tri), 1, 1, 1, DXGI_FORMAT_UNKNOWN, {1,0}, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, D3D12_RESOURCE_FLAG_NONE};
device->lpVtbl->CreateCommittedResource(device, &hp, D3D12_HEAP_FLAG_NONE, &rd, D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &IID_ID3D12Resource, &vb);

void *mapped; D3D12_RANGE rr = {0,0}; vb->lpVtbl->Map(vb, 0, &rr, &mapped);
memcpy(mapped, tri, sizeof(tri)); vb->lpVtbl->Unmap(vb, 0, NULL);

vbView.BufferLocation = vb->lpVtbl->GetGPUVirtualAddress(vb);
vbView.StrideInBytes = sizeof(struct Vertex); vbView.SizeInBytes = sizeof(tri);

5. Fence 與同步

device->lpVtbl->CreateFence(device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, &fence);
fenceEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
fenceValue = 1;

渲染迴圈:Record → Execute → Present → Wait

void RenderFrame(void) {
    // 1. Reset Allocator & Command List
    cmdAlloc->lpVtbl->Reset(cmdAlloc);
    cmdList->lpVtbl->Reset(cmdList, cmdAlloc, psoObj);

    // 2. 設定管線狀態
    cmdList->lpVtbl->SetGraphicsRootSignature(cmdList, rootSig);
    cmdList->lpVtbl->RSSetViewports(cmdList, 1, &viewport);
    cmdList->lpVtbl->RSSetScissorRects(cmdList, 1, &scissorRect);

    // 3. Resource Barrier: Present → Render Target
    D3D12_RESOURCE_BARRIER rb = {D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, 0, .Transition = {renderTargets[frameIdx], D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET}};
    cmdList->lpVtbl->ResourceBarrier(cmdList, 1, &rb);

    // 4. OMSetRenderTargets + Clear
    D3D12_CPU_DESCRIPTOR_HANDLE rtv = rtvHeapStart; rtv.ptr += frameIdx * rtvDescSize;
    cmdList->lpVtbl->OMSetRenderTargets(cmdList, 1, &rtv, FALSE, NULL);
    float clear[] = {0, 0.2f, 0.4f, 1}; cmdList->lpVtbl->ClearRenderTargetView(cmdList, rtv, clear, 0, NULL);

    // 5. Draw
    cmdList->lpVtbl->IASetPrimitiveTopology(cmdList, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    cmdList->lpVtbl->IASetVertexBuffers(cmdList, 0, 1, &vbView);
    cmdList->lpVtbl->DrawInstanced(cmdList, 3, 1, 0, 0);

    // 6. Resource Barrier: Render Target → Present
    rb.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; rb.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
    cmdList->lpVtbl->ResourceBarrier(cmdList, 1, &rb);

    // 7. Close & Execute
    cmdList->lpVtbl->Close(cmdList);
    ID3D12CommandList *lists[] = {(ID3D12CommandList*)cmdList};
    cmdQueue->lpVtbl->ExecuteCommandLists(cmdQueue, 1, lists);

    // 8. Present
    swapChain->lpVtbl->Present(swapChain, 1, 0);

    // 9. Wait for GPU (Fence)
    cmdQueue->lpVtbl->Signal(cmdQueue, fence, fenceValue);
    if (fence->lpVtbl->GetCompletedValue(fence) < fenceValue) {
        fence->lpVtbl->SetEventOnCompletion(fence, fenceValue, fenceEvent);
        WaitForSingleObject(fenceEvent, INFINITE);
    }
    fenceValue++; frameIdx = swapChain->lpVtbl->GetCurrentBackBufferIndex(swapChain);
}

HLSL 著色器

shaders.hlsl (放置於 exe 同目錄):

struct PSInput { float4 pos : SV_POSITION; float4 color : COLOR; };
PSInput VSMain(float4 pos : POSITION, float4 color : COLOR) {
    PSInput o; o.pos = pos; o.color = color; return o;
}
float4 PSMain(PSInput in) : SV_TARGET { return in.color; }

關鍵觀念總結

D3D11D3D12
隱性管理同步/資源狀態開發者顯式管理 Fence、Resource Barrier、Descriptor Heap
單一 Device ContextCommand Queue + Allocator + List 分離,支援多執行緒錄製
Shader 綁定槽位固定Root Signature 定義參數佈局,Root Descriptor/Table 直接存取
PSO 含部分動態狀態PSO 完全不可變,所有狀態編譯期確定

本範例刻意保持最小化,移除錯誤處理與資源釋放程式碼以突顯核心流程。生產環境必須完善 HRESULT 檢查、裝置移除處理、記憶體洩漏檢測與多幀資源管理 (Constant Buffer per-frame 等)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved