Compartilhar via


Gerenciamento de recursos baseados em limites

Mostra como gerenciar o tempo de vida dos dados de recursos acompanhando o progresso da GPU por meio de cercas. A memória pode ser reutilizante com eficiência com cercas que gerenciam cuidadosamente a disponibilidade de espaço livre na memória, como em uma implementação de buffer de anéis para um heap de upload.

Cenário de buffer de anéis

Veja a seguir um exemplo em que um aplicativo experimenta uma demanda rara para carregar memória de heap.

Um buffer de anéis é uma maneira de gerenciar um heap de upload. O buffer de anéis contém os dados necessários para os próximos quadros. O aplicativo mantém um ponteiro de entrada de dados atual e uma fila de deslocamento de quadro para registrar cada quadro e iniciar o deslocamento dos dados de recurso para esse quadro.

Um aplicativo cria um buffer de anéis com base em um buffer para carregar dados na GPU para cada quadro. Atualmente, o quadro 2 foi renderizado, o buffer de anéis encapsula os dados para o quadro 4, todos os dados necessários para o quadro 5 estão presentes e um buffer constante grande necessário para o quadro 6 precisa ser sublocado.

Figura 1 : o aplicativo tenta subalocar para o buffer constante, mas encontra memória livre insuficiente.

memória livre insuficiente neste buffer de anéis

Figura 2 : por meio da sondagem de cerca, o aplicativo descobre que o quadro 3 foi renderizado, a fila de deslocamento de quadro é atualizada e o estado atual do buffer de anéis segue- no entanto, a memória livre ainda não é grande o suficiente para acomodar o buffer constante.

memória ainda insuficiente após a renderização do quadro 3

Figura 3 : dada a situação, a CPU se bloqueia (via cerca aguardando) até que o quadro 4 seja renderizado, o que libera a memória sublocada para o quadro 4.

a renderização do quadro 4 libera mais do buffer de anéis

Figura 4 : agora a memória livre é grande o suficiente para o buffer constante e a sub-alocação é bem-sucedida; o aplicativo copia os grandes dados de buffer constante para a memória usada anteriormente pelos dados de recurso para os quadros 3 e 4. O ponteiro de entrada atual é finalmente atualizado.

agora há espaço do quadro 6 no buffer de anéis

Se um aplicativo implementar um buffer de anéis, o buffer de anéis deverá ser grande o suficiente para lidar com o cenário de pior caso dos tamanhos dos dados de recurso.

Exemplo de buffer de anéis

O código de exemplo a seguir mostra como um buffer de anéis pode ser gerenciado, prestando atenção à rotina de sub-alocação que manipula a sondagem de cerca e a espera. Para simplificar, o exemplo usa NOT_SUFFICIENT_MEMORY para ocultar os detalhes de "memória livre não suficiente encontrada no heap", pois essa lógica (com base em m_pDataCur e deslocamentos dentro de FrameOffsetQueue) não está fortemente relacionada a heaps ou cercas. O exemplo é simplificado para sacrificar a taxa de quadros em vez da utilização da memória.

Observe que o suporte ao buffer de anéis deve ser um cenário popular; no entanto, o design de heap não impede outro uso, como parametrização de lista de comandos e reutilização.

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

Subalocação em buffers