Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Mostra como gerenciar o tempo de vida dos dados de recursos acompanhando o progresso da GPU por meio de cercas. A memória pode ser efetivamente reutilização com cercas que gerenciam cuidadosamente a disponibilidade de espaço livre na memória, como em uma implementação de buffer de anel para um heap de upload.
- cenário de buffer de anel
- exemplo de buffer de anel
- tópicos relacionados
Cenário de buffer de anel
Veja a seguir um exemplo em que um aplicativo experimenta uma demanda rara para carregar memória de heap.
Um buffer de anel é uma maneira de gerenciar um heap de upload. O buffer de anel contém os dados necessários para os próximos quadros. O aplicativo mantém um ponteiro de entrada de dados atual e uma fila de deslocamento de quadro para registrar cada quadro e iniciar o deslocamento de dados de recurso para esse quadro.
Um aplicativo cria um buffer de anel baseado em um buffer para carregar dados na GPU para cada quadro. No momento, o quadro 2 foi renderizado, o buffer de anel encapsula os dados do quadro 4, todos os dados necessários para o quadro 5 estão presentes e um buffer constante grande necessário para o quadro 6 precisa ser sublocado.
Figura 1: o aplicativo tenta sublocar para o buffer constante, mas encontra memória livre insuficiente.
Figura 2: por meio de sondagem de cerca, o aplicativo descobre que o quadro 3 foi renderizado, a fila de deslocamento de quadro é atualizada e o estado atual do buffer de anéis segue - no entanto, a memória livre ainda não é grande o suficiente para acomodar o buffer constante.
Figura 3: considerando a situação, a CPU se bloqueia (por meio de cerca aguardando) até que o quadro 4 seja renderizado, o que libera a memória sublocada para o quadro 4.
Figura 4: agora a memória livre é grande o suficiente para o buffer constante e a sub-alocação é bem-sucedida; o aplicativo copia os dados de buffer de grande constante para a memória anteriormente usados pelos dados de recurso para ambos os quadros 3 e 4. O ponteiro de entrada atual é finalmente atualizado.
Se um aplicativo implementar um buffer de anel, o buffer de anel deverá ser grande o suficiente para lidar com o cenário pior dos tamanhos dos dados de recurso.
Exemplo de buffer de anel
O código de exemplo a seguir mostra como um buffer de anel pode ser gerenciado, prestando atenção à rotina de sublocação que manipula a sondagem e a espera. Para simplificar, o exemplo usa NOT_SUFFICIENT_MEMORY para ocultar os detalhes de "memória livre não suficiente encontrada no heap", uma vez que essa lógica (baseada em m_pDataCur e deslocamentos dentro de FrameOffsetQueue) não está fortemente relacionada a heaps ou cercas. O exemplo é simplificado para sacrificar a taxa de quadros em vez da utilização da memória.
Observe que, espera-se que o suporte ao buffer de anel seja um cenário popular; no entanto, o design do heap não impede outro uso, como parametrização e reutilização da lista de comandos.
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();
}
}
Tópicos relacionados
-
subalocação dentro de buffers