Freigeben über


Fence-Based Ressourcenverwaltung

Zeigt, wie Sie die Lebensdauer von Ressourcendaten verwalten, indem Sie den GPU-Fortschritt über Zäune nachverfolgen. Speicher kann effektiv mit Zaunen wiederverwendet werden, um die Verfügbarkeit von freiem Speicherplatz im Arbeitsspeicher, z. B. in einer Ringpufferimplementierung für einen Upload-Heap, sorgfältig zu verwalten.

Ringpufferszenario

Im Folgenden sehen Sie ein Beispiel, in dem eine App eine seltene Anforderung für upload-Heap-Speicher aufweist.

Ein Ringpuffer ist eine Möglichkeit zum Verwalten eines Upload-Heaps. Der Ringpuffer enthält Daten, die für die nächsten Frames erforderlich sind. Die App verwaltet einen aktuellen Dateneingabezeiger und eine Frameoffsetwarteschlange, um jeden Frame aufzuzeichnen und den Offset von Ressourcendaten für diesen Frame zu starten.

Eine App erstellt einen Ringpuffer, der auf einem Puffer basiert, um Daten für jeden Frame in die GPU hochzuladen. Derzeit wurde Frame 2 gerendert, der Ringpuffer umschließt die Daten für Frame 4, alle für Frame 5 erforderlichen Daten sind vorhanden, und ein großer Konstantenpuffer, der für Frame 6 erforderlich ist, muss unter zugeordnet werden.

Abbildung 1: Die App versucht, den Konstantenpuffer unterzuordnen, findet jedoch nicht genügend freien Arbeitsspeicher.

unzureichenden freien Arbeitsspeicher in diesem Ringpuffer

Abbildung 2: Durch Zaunabfragung ermittelt die App, dass Frame 3 gerendert wurde, die Frameoffsetwarteschlange dann aktualisiert wird, und der aktuelle Zustand des Ringpuffers folgt . Der freie Arbeitsspeicher ist jedoch immer noch nicht groß genug, um den Konstantenpuffer aufzunehmen.

nach dem Rendern von Frame 3 noch nicht genügend Arbeitsspeicher

Abbildung 3: Angesichts der Situation blockiert sich die CPU-Blöcke (über Zaunwarte), bis Frame 4 gerendert wurde, wodurch der für Frame 4 zugewiesene Speicher freigegeben wird.

Renderframe 4 gibt mehr vom Ringpuffer

Abbildung 4: Jetzt ist freier Arbeitsspeicher groß genug für den Konstantenpuffer, und die Unterzuordnung ist erfolgreich; die App kopiert die großen Konstantenpufferdaten in den Speicher, der zuvor von Ressourcendaten für Frames 3 und 4 verwendet wurde. Der aktuelle Eingabezeiger wird schließlich aktualisiert.

gibt es jetzt Platz aus Frame 6 im Ringpuffer

Wenn eine App einen Ringpuffer implementiert, muss der Ringpuffer groß genug sein, um mit dem schlimmeren Szenario der Ressourcendaten zu umgehen.

Ringpufferbeispiel

Der folgende Beispielcode zeigt, wie ein Ringpuffer verwaltet werden kann, wobei die Unterzuweisungsroutine beachtet wird, die zaunabfragt und wartet. Aus Gründen der Einfachheit verwendet das Beispiel NOT_SUFFICIENT_MEMORY, um die Details von "nicht genügend freiem Arbeitsspeicher im Heap" auszublenden, da diese Logik (basierend auf m_pDataCur und Offsets innerhalb FrameOffsetQueue) nicht eng mit Heaps oder Zaunen verknüpft ist. Das Beispiel wird vereinfacht, um die Framerate anstelle der Speicherauslastung zu opfern.

Beachten Sie, dass die Ringpufferunterstützung ein beliebtes Szenario sein wird; Der Heap-Entwurf schließt jedoch keine andere Verwendung aus, z. B. die Befehlslistenparameterisierung und die erneute Verwendung.

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-

Unterlastung in Puffern