Управление ресурсами на основе границ

Показывает, как управлять жизненным циклом данных ресурсов путем отслеживания хода выполнения GPU через ограждения. Память можно эффективно повторно использовать с ограждениями, тщательно управляя доступностью свободного пространства в памяти, например в реализации кольцевого буфера для кучи отправки.

Сценарий кольцевого буфера

Ниже приведен пример, в котором приложение испытывает редкий спрос на память кучи отправки.

Кольцевой буфер — это один из способов управления кучей отправки. Кольцевой буфер содержит данные, необходимые для следующих нескольких кадров. Приложение поддерживает текущий указатель ввода данных и очередь смещения кадра для записи каждого кадра и начального смещения данных ресурса для этого кадра.

Приложение создает кольцевой буфер на основе буфера для передачи данных в GPU для каждого кадра. В настоящее время кадр 2 отображается, кольцевой буфер обтекает данные кадра 4, все данные, необходимые для кадра 5, присутствуют, а большой буфер констант, необходимый для кадра 6, должен быть выделен вложен.

Рис. 1 . Приложение пытается выделить вложенное выделение для буфера констант, но находит недостаточно свободной памяти.

insufficient free memory in this ring buffer

Рис. 2 . При опросе забора приложение обнаруживает, что кадр 3 был отрисован, очередь смещения кадра обновляется, а текущее состояние кольцевого буфера по-прежнему остается недостаточно большим для размещения буфера констант.

still insufficient memory after frame 3 has rendered

Рис. 3 . Учитывая ситуацию, ЦП блокирует себя (через ожидание забора) до отрисовки кадра 4, что освобождает вложенную память для кадра 4.

rendering frame 4 frees up more of the ring buffer

Рис. 4 . Теперь объем свободной памяти достаточно велик для буфера констант, а вложенное выделение выполняется успешно; приложение копирует данные буфера больших констант в память, которая ранее использовалась данными ресурсов для кадров 3 и 4. Текущий указатель ввода, наконец, обновляется.

now there is room from frame 6 in the ring buffer

Если приложение реализует кольцевой буфер, кольцевой буфер должен быть достаточно большим, чтобы справиться с более плохим сценарием размеров данных ресурсов.

Пример кольцевого буфера

В следующем примере кода показано, как управлять кольцевым буфером, обращая внимание на подпрограмму подраспределения, которая обрабатывает опросы и ожидания забора. Для простоты в примере используется NOT_SUFFICIENT_MEMORY для скрытия сведений о "недостаточной свободной памяти, найденной в куче", так как эта логика (на основе m_pDataCur и смещений внутри FrameOffsetQueue) не тесно связана с кучами или ограждениями. Этот пример упрощен для того, чтобы пожертвовать частотой кадров вместо использования памяти.

Обратите внимание, что поддержка кольцевого буфера, как ожидается, будет популярным сценарием; однако при проектировании кучи не исключается иное использование, например параметризация списка команд и повторное использование.

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

Подраспределение в буферах