Поделиться через


управление ресурсами Fence-Based

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

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

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

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

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

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

недостаточно свободного памяти в этом буфере кольца

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

по-прежнему недостаточно памяти после отрисовки кадров 3

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

кадр отрисовки 4 освобождает больше буфера кольца

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

теперь в буфере кольцевого буфера

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

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

В следующем примере кода показано, как управлять кольцевым буфером, обращая внимание на подпрограмму подраспределения, которая обрабатывает опросы и ожидание забора. Для простоты в примере используется 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

подлокации в буферах