Fence-Based Gestione risorse
Illustra come gestire l'intervallo di vita dei dati delle risorse monitorando lo stato di avanzamento della GPU tramite recinzioni. La memoria può essere usata in modo efficace con recinzioni che gestiscono attentamente la disponibilità di spazio libero in memoria, ad esempio in un'implementazione del buffer anello per un heap di caricamento.
Scenario del buffer degli anelli
Di seguito è riportato un esempio in cui un'app sperimenta una richiesta rara di caricamento della memoria heap.
Un buffer anello è un modo per gestire un heap di caricamento. Il buffer dell'anello contiene i dati necessari per i successivi fotogrammi. L'app gestisce un puntatore di input dati corrente e una coda di offset frame per registrare ogni frame e l'offset iniziale dei dati delle risorse per tale frame.
Un'app crea un buffer anello basato su un buffer per caricare i dati nella GPU per ogni frame. Attualmente è stato eseguito il rendering del frame 2, il buffer dell'anello esegue il wrapping dei dati per frame 4, tutti i dati necessari per frame 5 sono presenti e un buffer costante elevato necessario per frame 6 deve essere sotto-allocato.
Figura 1 : l'app tenta di allocare sotto-allocare per il buffer costante, ma trova memoria insufficiente.
Figura 2 : tramite il polling di recinzioni, l'app rileva che il frame 3 è stato eseguito il rendering, la coda di offset del frame viene quindi aggiornata e lo stato corrente del buffer dell'anello segue. Tuttavia, la memoria gratuita non è ancora abbastanza grande per supportare il buffer costante.
Figura 3 : in base alla situazione, la CPU si blocca (tramite recinto in attesa) fino a quando il frame 4 non è stato eseguito il rendering, che libera la memoria secondaria allocata per frame 4.
Figura 4 : ora la memoria libera è sufficiente per il buffer costante e l'allocazione secondaria ha esito positivo; l'app copia i dati del buffer costante big in memoria usati in precedenza dai dati delle risorse per entrambi i fotogrammi 3 e 4. Il puntatore di input corrente viene infine aggiornato.
Se un'app implementa un buffer anello, il buffer dell'anello deve essere abbastanza grande per gestire lo scenario peggiore delle dimensioni dei dati delle risorse.
Esempio di buffer anello
Il codice di esempio seguente illustra come può essere gestito un buffer anello, prestando attenzione alla routine di allocazione secondaria che gestisce il polling e l'attesa del recinto. Per semplicità, l'esempio usa NOT_SUFFICIENT_MEMORY per nascondere i dettagli di "memoria insufficiente trovata nell'heap" poiché tale logica (basata su m_pDataCur e offset all'interno di FrameOffsetQueue) non è strettamente correlata agli heaps o alle recinzioni. L'esempio è semplificato per sacrificare la frequenza dei fotogrammi anziché l'utilizzo della memoria.
Si noti che è previsto che il supporto del buffer di anello sia uno scenario popolare; tuttavia, la progettazione dell'heap non impedisce altri usi, ad esempio la parametrizzazione dell'elenco di comandi e la ri-uso.
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();
}
}
Argomenti correlati