共用方式為


Fence-Based資源管理

示範如何透過柵欄追蹤 GPU 進度來管理資源資料生命週期。 記憶體可以有效地與柵欄搭配使用,以仔細管理記憶體中可用空間的可用性,例如在上傳堆積的通道緩衝區實作中。

環形緩衝區案例

以下是一個範例,其中應用程式遇到上傳堆積記憶體的罕見需求。

環形緩衝區是管理上傳堆積的其中一種方式。 環形緩衝區會保存接下來幾個畫面所需的資料。 應用程式會維護目前的資料輸入指標,以及框架位移佇列,以記錄每個畫面格和開始該畫面的資源資料位移。

應用程式會根據緩衝區建立通道緩衝區,以將資料上傳至每個畫面的 GPU。 目前已轉譯畫面 2,環形緩衝區會繞著畫面 4 的資料換行,畫面格 5 所需的所有資料都存在,而框架 6 所需的大型常數緩衝區必須進行子配置。

圖 1 :應用程式會嘗試針對常數緩衝區進行子配置,但發現可用記憶體不足。

這個通道緩衝區中的可用記憶體不足

圖 2 :透過柵欄輪詢,應用程式發現畫面格 3 已轉譯、畫面位移佇列會接著更新,而信號緩衝區的目前狀態會跟著更新 -不過,可用記憶體仍然不夠大,無法容納常數緩衝區。

在轉譯畫面 3 之後,記憶體仍然不足

圖 3 :假設情況,CPU 會透過等待) 的柵欄來 (,直到轉譯畫面 4 為止,這會釋放配置給畫面 4 的記憶體子元件。

轉譯畫面 4 釋放更多環形緩衝區

圖 4 :現在可用記憶體足以用於常數緩衝區,而子配置成功;應用程式會將巨量常數緩衝區資料複製到資源資料先前用於畫面 3 和 4 的記憶體。 目前輸入指標最後會更新。

現在通道緩衝區中有來自畫面 6 的空間

如果應用程式實作環形緩衝區,信號緩衝區必須夠大,才能處理資源資料大小更糟的情況。

信號緩衝區範例

下列範例程式碼示範如何管理環形緩衝區,並注意處理柵欄輪詢和等候的子配置常式。 為了簡單起見,此範例會使用 NOT_SUFFICIENT_MEMORY 來隱藏「堆積中找到的可用記憶體不足」的詳細資料,因為該 (邏輯會根據FrameOffsetQueue內的m_pDataCur和位移) 與堆積或柵欄緊密相關。 此範例已簡化為犧牲畫面播放速率,而不是記憶體使用率。

請注意,信號緩衝區支援應該是熱門案例;不過,堆積設計不會排除其他用法,例如命令清單參數化和重複使用。

struct FrameResourceOffset
{
    UINT frameIndex;
    UINT8* pResourceOffset;
};
std::queue<FrameResourceOffset> frameOffsetQueue;

void DrawFrame()
{
    float vertices[] = ...;
    UINT verticesOffset = 0;
    ThrowIfFailed(
        SetDataToUploadHeap(
            vertices, sizeof(float), sizeof(vertices) / sizeof(float), 
            4, // Max alignment requirement for vertex data is 4 bytes.
            verticesOffset
            ));

    float constants[] = ...;
    UINT constantsOffset = 0;
    ThrowIfFailed(
        SetDataToUploadHeap(
            constants, sizeof(float), sizeof(constants) / sizeof(float), 
            D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT,
            constantsOffset
            ));

    // Create vertex buffer views for the new binding model. 
    // Create constant buffer views for the new binding model. 
    // ...

    commandQueue->Execute(commandList);
    commandQueue->AdvanceFence();
}

HRESULT SuballocateFromHeap(SIZE_T uSize, UINT uAlign)
{
    if (NOT_SUFFICIENT_MEMORY(uSize, uAlign))
    {
        // Free up resources for frames processed by GPU; see Figure 2.
        UINT lastCompletedFrame = commandQueue->GetLastCompletedFence();
        FreeUpMemoryUntilFrame( lastCompletedFrame );

        while ( NOT_SUFFICIENT_MEMORY(uSize, uAlign)
            && !frameOffsetQueue.empty() )
        {
            // Block until a new frame is processed by GPU, then free up more memory; see Figure 3.
            UINT nextGPUFrame = frameOffsetQueue.front().frameIndex;
            commandQueue->SetEventOnFenceCompletion(nextGPUFrame, hEvent);
            WaitForSingleObject(hEvent, INFINITE);
            FreeUpMemoryUntilFrame( nextGPUFrame );
        }
    }

    if (NOT_SUFFICIENT_MEMORY(uSize, uAlign))
    {
        // Apps need to create a new Heap that is large enough for this resource.
        return E_HEAPNOTLARGEENOUGH;
    }
    else
    {
        // Update current data pointer for the new resource.
        m_pDataCur = reinterpret_cast<UINT8*>(
            Align(reinterpret_cast<SIZE_T>(m_pHDataCur), uAlign)
            );

        // Update frame offset queue if this is the first resource for a new frame; see Figure 4.
        UINT currentFrame = commandQueue->GetCurrentFence();
        if ( frameOffsetQueue.empty()
            || frameOffsetQueue.back().frameIndex < currentFrame )
        {
            FrameResourceOffset offset = {currentFrame, m_pDataCur};
            frameOffsetQueue.push(offset);
        }

        return S_OK;
    }
}

void FreeUpMemoryUntilFrame(UINT lastCompletedFrame)
{
    while ( !frameOffsetQueue.empty() 
        && frameOffsetQueue.first().frameIndex <= lastCompletedFrame )
    {
        frameOffsetQueue.pop();
    }
}

ID3D12Fence

緩衝區內的子位置