Udostępnij przez


zarządzanie zasobami Fence-Based

Pokazuje, jak zarządzać okresem życia danych zasobów przez śledzenie postępu procesora GPU za pośrednictwem ogrodzeń. Pamięć może być skutecznie ponownie używana z ogrodzeniami starannie zarządzając dostępnością wolnego miejsca w pamięci, na przykład w implementacji buforu pierścieniowego dla sterty przekazywania.

Scenariusz buforu pierścieniowego

Poniżej przedstawiono przykład, w którym aplikacja doświadcza rzadkiego zapotrzebowania na pamięć sterty przekazywania.

Bufor pierścieniowy to jeden ze sposobów zarządzania stertą przekazywania. Bufor pierścieniowy przechowuje dane wymagane przez następne kilka ramek. Aplikacja utrzymuje bieżący wskaźnik wejściowy danych i kolejkę przesunięcia ramki w celu zarejestrowania każdej ramki i początkowego przesunięcia danych zasobów dla tej ramki.

Aplikacja tworzy bufor pierścieniowy na podstawie buforu w celu przekazania danych do procesora GPU dla każdej ramki. Obecnie ramka 2 została renderowana, bufor pierścieniowy owija się wokół danych dla ramki 4, wszystkie dane wymagane dla ramki 5 są obecne, a duży stały bufor wymagany dla ramki 6 musi być przydzielony podrzędnie.

Rysunek 1: aplikacja próbuje przydzielić podrzędną dla buforu stałego, ale znajduje niewystarczającą ilość wolnej pamięci.

niewystarczającą ilość wolnej pamięci w tym buforze pierścieniowym

Rysunek 2: za pośrednictwem sondowania ogrodzenia aplikacja odkrywa, że ramka 3 została renderowana, kolejka przesunięcia ramki jest następnie aktualizowana, a bieżący stan buforu pierścienia jest jednak nadal zbyt duży, aby pomieścić stały bufor.

nadal niewystarczająca ilość pamięci po renderowaniu ramki 3

Rysunek 3 : biorąc pod uwagę sytuację, bloki procesora CPU (przez oczekiwanie na ogrodzenie) do momentu renderowania ramki 4, co zwalnia podprzydzielonej pamięci dla ramki 4.

ramka renderowania 4 zwalnia więcej buforu pierścienia

Rysunek 4: teraz wolna pamięć jest wystarczająco duża, aby bufor stały, a alokacja podrzędna powiodła się; aplikacja kopiuje dane buforu stałego big do pamięci używanej wcześniej przez dane zasobów dla ramek 3 i 4. Bieżący wskaźnik wejściowy zostanie ostatecznie zaktualizowany.

teraz jest miejsce z ramy 6 w buforze pierścieniowym

Jeśli aplikacja implementuje bufor pierścieniowy, bufor pierścieniowy musi być wystarczająco duży, aby poradzić sobie z gorszym scenariuszem rozmiarów danych zasobów.

Przykład buforu pierścieniowego

Poniższy przykładowy kod pokazuje, jak można zarządzać buforem pierścieniowym, zwracając uwagę na procedurę podrzędną alokacji, która obsługuje sondowanie ogrodzenia i oczekiwanie. Dla uproszczenia przykład używa NOT_SUFFICIENT_MEMORY do ukrywania szczegółów "niewystarczającej ilości wolnej pamięci znalezionej w stercie", ponieważ ta logika (oparta na m_pDataCur i przesunięciach wewnątrz FrameOffsetQueue) nie jest ściśle związana z stertami lub ogrodzeniami. Przykład jest uproszczony w celu poświęcania szybkości klatek na klatkę zamiast wykorzystania pamięci.

Należy pamiętać, że obsługa buforu pierścieniowego powinna być popularnym scenariuszem; Jednak projekt stert nie wyklucza innego użycia, takiego jak parametryzacja listy poleceń i ponowne użycie.

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

podlokalizacji w ramach