Управление ресурсами на основе границ
Показывает, как управлять жизненным циклом данных ресурсов путем отслеживания хода выполнения GPU через ограждения. Память можно эффективно повторно использовать с ограждениями, тщательно управляя доступностью свободного пространства в памяти, например в реализации кольцевого буфера для кучи отправки.
Сценарий кольцевого буфера
Ниже приведен пример, в котором приложение испытывает редкий спрос на память кучи отправки.
Кольцевой буфер — это один из способов управления кучей отправки. Кольцевой буфер содержит данные, необходимые для следующих нескольких кадров. Приложение поддерживает текущий указатель ввода данных и очередь смещения кадра для записи каждого кадра и начального смещения данных ресурса для этого кадра.
Приложение создает кольцевой буфер на основе буфера для передачи данных в GPU для каждого кадра. В настоящее время кадр 2 отображается, кольцевой буфер обтекает данные кадра 4, все данные, необходимые для кадра 5, присутствуют, а большой буфер констант, необходимый для кадра 6, должен быть выделен вложен.
Рис. 1 . Приложение пытается выделить вложенное выделение для буфера констант, но находит недостаточно свободной памяти.
Рис. 2 . При опросе забора приложение обнаруживает, что кадр 3 был отрисован, очередь смещения кадра обновляется, а текущее состояние кольцевого буфера по-прежнему остается недостаточно большим для размещения буфера констант.
Рис. 3 . Учитывая ситуацию, ЦП блокирует себя (через ожидание забора) до отрисовки кадра 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();
}
}
Связанные темы