펜스 기반 리소스 관리

펜스를 통해 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

버퍼 내 하위 할당