다음을 통해 공유


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

버퍼 내의 하위 할당