iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
Software Development

因為 AI 看不懂老舊程式,只好乖乖從零開始學 DirectX 12 與 HLSL系列 第 8

Day 8 :Graphics Pipeline 介紹(4) - ResourceBarrier Fence Frame

  • 分享至 

  • xImage
  •  

FPS

因為要操作 GPU 前一定要經過 CPU 才能操控,所以需要有個流程,把從設定要處理的資料格式、到等待 GPU 處理完畢回傳結果,最後把結果呈現這些全部串在一起。

CPU
│
│ 1. 更新遊戲邏輯
│ 2. 計算物件狀態
│ 3. 準備 Draw Call / Command List
▼
Command Queue
│
▼
GPU
│
│ 4. 執行繪圖指令
│ 5. Vertex Shader
│ 6. Rasterization
│ 7. Pixel Shader
│ 8. 寫入 Render Target
▼
Back Buffer
│
│ Present
▼
螢幕顯示

...大概這種概念

我們常說的 FPS,其實也就是這整體在一秒可以被完成多少次數。

而在這中間要開始做效能優化的話,就會先去確定他是 CPU Bound 還是 GPU Bound,然後更進一步的去檢查其中的問題。

雖然稍微有點超出這章的內容範圍,不過可以先簡單提個例子。

假設我今天要畫一個非常大片的顏色格子、上面會有紅色、藍色、綠色…等等高達上萬的顏色要繪製在場景上讓玩家走跳。

大概可以想像成這樣

然後如果真的照字面上面的意思,去創建 10000 個 Mesh ,讓每個 Mesh 都顯示不同顏色的話,流程可能會變成

// CPU 要先一一傳遞資料進 GPU 去創建 Mesh
for (int i = 0; i < 10000; ++i)
{
    SetVertexBuffer(mesh[i]);
    SetIndexBuffer(mesh[i]);
    SetColor(mesh[i].color);

    DrawIndexedInstanced(...);
}
//從 CPU 到 GPU 的流程
CPU
 ↓
Draw Mesh 0
Draw Mesh 1
Draw Mesh 2
Draw Mesh 3
...
Draw Mesh 9999
 ↓
Command Queue
 ↓
GPU

而當然,創建 1 個 Mesh 的問題不只如此,一個 Mesh 會帶著很多資料,除了一般的 Vertex 跟 Index,還有 CIIndex、Color、Normal 、TexCoord、Lightmap…等等一大堆的資料。

就算可能最小 1 個 Mesh 他生這些預設值只會佔 32 KB,但 10000 個 Mesh 就會 320000KB。就算 CPU GPU 在怎麼強也無法真的很好處理這樣數量集的資料。

而另一種做法就是只生一個 Mesh,然後用 StructuredBuffer 傳資料進 Shader,讓他直接在 GPU 裡繪製顏色。

// 先用 struct 定義好要傳的顏色
struct PlaneData
{
    float3 position;
    float4 color;
};

// StructuredBuffer 的概念之後會提到,現在理解成也是一個可以傳資料的格式就好
StructuredBuffer<PlaneData> planeData : register(t0);

//寫一個類似這樣的程式一次傳完
DrawIndexedInstanced(
    6,        // 每個 Quad 6 個 index
    10000,    // 10000 instances
    0,
    0,
    0
);
// 在 GPU 繪製時直接在 Shader 設定其資料與之後的顏色
PlaneData data = planeData[instanceID];
// 最後的流程大概是這種感覺
CPU

設定 Mesh
設定 StructuredBuffer
DrawIndexedInstanced(... 10000 instances ...)

        ↓

GPU

Instance 0 → data[0]
Instance 1 → data[1]
Instance 2 → data[2]
...
Instance 9999 → data[9999]

這樣就能解決 Mesh 造成過度佔據記憶體的問題。

最後顯示其設定相關的程式設定與實際程式

typedef struct D3D12_RESOURCE_BARRIER {
  D3D12_RESOURCE_BARRIER_TYPE  Type;  // 指定此 Resource Barrier 的類型,例如 Transition、Aliasing 或 UAV
  D3D12_RESOURCE_BARRIER_FLAGS Flags; // 設定此 Resource Barrier 的額外控制旗標

  union {
    D3D12_RESOURCE_TRANSITION_BARRIER Transition; // 描述 Resource 從一種 State 切換到另一種 State 的資訊
    D3D12_RESOURCE_ALIASING_BARRIER   Aliasing;   // 描述共用同一塊 GPU 記憶體的不同 Resource 之間的切換關係
    D3D12_RESOURCE_UAV_BARRIER        UAV;        // 確保 UAV 的前一次讀寫完成後,後續 UAV 存取才繼續執行
  };

} D3D12_RESOURCE_BARRIER;
graphics_engine.h

#pragma once

#include <array>

#include "my_engine.h"
#include <d3d12.h>
#include <dxgi1_6.h>
#include <memory>
#include <directxtk12//GraphicsMemory.h>

#include "render_context.h"

class GraphicsEngine
{
public:
    GraphicsEngine() = default;
    ~GraphicsEngine() noexcept;

    GraphicsEngine(const GraphicsEngine&) = delete;
    GraphicsEngine& operator=(const GraphicsEngine&) = delete;
    GraphicsEngine(GraphicsEngine&&) = delete;
    GraphicsEngine& operator=(GraphicsEngine&&) = delete;

    bool init(HWND hwnd, UINT frameBufferWidth, UINT frameBufferHeight);
    void resize(UINT frameBufferWidth, UINT frameBufferHeight);

    [[nodiscard]]
      ComPtr<IDXGIFactory7> createDXGIFactory();

    void beginRender();
    void endRender();
    void waitDraw();

    [[nodiscard]]
    ID3D12Device14* getD3DDevice() const noexcept
    {
        return m_d3dDevice.Get();
    }

    [[nodiscard]]
    UINT getBackBufferIndex() const noexcept
    {
        return m_frameIndex;
    }

    [[nodiscard]]
    UINT getFrameBufferWidth() const noexcept
    {
        return m_frameBufferWidth;
    }
    [[nodiscard]]
    UINT GetFrameBufferHeight() const noexcept
    {
        return m_frameBufferHeight;
    }

    [[nodiscard]]
    ID3D12Resource* getDepthStencilBuffer() const noexcept
    {
        return m_depthStencilBuffer.Get();
    }

    [[nodiscard]]
    D3D12_CPU_DESCRIPTOR_HANDLE getCurrentRenderTargetView() const;
    [[nodiscard]]
    D3D12_CPU_DESCRIPTOR_HANDLE getDepthStencilView() const;

    enum : UINT
    {
        FRAME_BUFFER_COUNT = 2
    };

private:
    bool createD3DDevice(IDXGIFactory7* dxgiFactory);

    bool createSwapChain(
        HWND hwnd,
        UINT frameBufferWidth,
        UINT frameBufferHeight,
        IDXGIFactory7* dxgiFactory
    );
    bool createSynchronizationWithGPUObject();
    void createBackBufferViews();
    void createDepthStencilBuffer();
   
    ComPtr<IDXGISwapChain4> m_swapChain;
    std::array<ComPtr<ID3D12Resource>, FRAME_BUFFER_COUNT> m_renderTargets;
    ComPtr<ID3D12Fence> m_fence;

    int m_currentBackBufferIndex = 0;
    UINT m_rtvDescriptorSize = 0;
    UINT m_frameIndex = 0;
    HANDLE m_fenceEvent = nullptr;
    UINT64 m_fenceValue = 0;
    UINT m_frameBufferWidth = 0;
    UINT m_frameBufferHeight = 0;

    RenderContext m_renderContext;
    std::unique_ptr<DirectX::GraphicsMemory> m_directXTKGfxMemory;
};

extern GraphicsEngine* g_graphicsEngine;

graphics_engine.cpp

//...其他程式

void GraphicsEngine::beginRender()
{
    // 每一幀開始時,向 Swap Chain 查詢目前可寫入的 Back Buffer
    m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();

    // 重設 Command Allocator,回收上一輪命令記錄使用的記憶體
    if (FAILED(m_commandAllocator->Reset()))
        throw std::runtime_error("GraphicsEngine: Failed to reset the command allocator.");
    if (FAILED(m_commandList->Reset(m_commandAllocator.Get(), nullptr)))
        throw std::runtime_error("GraphicsEngine: Failed to reset the command list.");

    //使用 D3D12_RESOURCE_BARRIER 去做資源狀態轉換
    D3D12_RESOURCE_BARRIER barrier{};
    barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    barrier.Transition.pResource = m_renderTargets[m_frameIndex].Get();
    barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
    barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
    barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
    m_commandList->ResourceBarrier(1, &barrier);

    // Viewport 將結果映射到畫面像素範圍
    const D3D12_VIEWPORT viewport{
        0.0f,
        0.0f,
        static_cast<float>(m_frameBufferWidth),
        static_cast<float>(m_frameBufferHeight),
        0.0f,
        1.0f};

    // Scissor Rect 限制實際上顯示的範圍
    const D3D12_RECT scissorRect{
        0,
        0,
        static_cast<LONG>(m_frameBufferWidth),
        static_cast<LONG>(m_frameBufferHeight)};
    m_commandList->RSSetViewports(1, &viewport);
    m_commandList->RSSetScissorRects(1, &scissorRect);

    // 取得目前 Back Buffer 的 RTV,以及深度緩衝區的 DSV
    const D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = getCurrentRenderTargetView();
    const D3D12_CPU_DESCRIPTOR_HANDLE dsvHandle = getDepthStencilView();

    // 將目前 RTV 與 DSV 綁定到 Output Merger
    m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, &dsvHandle);

    constexpr float clearColor[] = {0.08f, 0.10f, 0.14f, 1.0f};
    m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
    m_commandList->ClearDepthStencilView(
        dsvHandle,
        D3D12_CLEAR_FLAG_DEPTH,
        1.0f,
        0,
        0,
        nullptr);
}

void GraphicsEngine::endRender()
{
    //繪圖完畢後,把 Back Buffer 從 RENDER_TARGET 狀態轉回 PRESENT
    D3D12_RESOURCE_BARRIER barrier{};
    barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
    barrier.Transition.pResource = m_renderTargets[m_frameIndex].Get();
    barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
    barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
    barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
    m_commandList->ResourceBarrier(1, &barrier);

    if (FAILED(m_commandList->Close()))
        throw std::runtime_error("GraphicsEngine: Failed to close the command list.");

    ID3D12CommandList* commandLists[] = {m_commandList.Get()};
    m_commandQueue->ExecuteCommandLists(1, commandLists);
    if (FAILED(m_swapChain->Present(1, 0)))
        throw std::runtime_error("GraphicsEngine: Failed to present the frame.");

    // 等待 GPU 完成本幀工作
    waitDraw();
}

參考資料

D3D12_RESOURCE_BARRIER (d3d12.h) - Win32 apps

Game Performance Optimization

ChatGPT 生範例圖


上一篇
Day 7:Graphics Pipeline 介紹(3) - Command List / Command Queue
下一篇
Day 9 :Graphics Pipeline 介紹(5) - Shader 設定
系列文
因為 AI 看不懂老舊程式,只好乖乖從零開始學 DirectX 12 與 HLSL13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言