フェンスを使用して GPU の進行状況を追跡することで、リソース データの有効期間を管理する方法を示します。 メモリは、アップロード ヒープのリング バッファー実装など、メモリ内の空き領域の可用性を慎重に管理するフェンスで効果的に再利用できます。
リング バッファーのシナリオ
次の例では、アプリでアップロード ヒープ メモリの需要がまれに発生します。
リング バッファーは、アップロード ヒープを管理する 1 つの方法です。 リング バッファーには、次のいくつかのフレームに必要なデータが保持されます。 アプリは、現在のデータ入力ポインターと、各フレームを記録するフレーム オフセット キューと、そのフレームのリソース データの開始オフセットを保持します。
アプリは、フレームごとに GPU にデータをアップロードするバッファーに基づいてリング バッファーを作成します。 現在、フレーム 2 がレンダリングされ、リング バッファーはフレーム 4 のデータをラップし、フレーム 5 に必要なすべてのデータが存在し、フレーム 6 に必要な大きな定数バッファーをサブ割り当てする必要があります。
図 1 : アプリは定数バッファーのサブ割り当てを試みますが、空きメモリが不足しています。
このリング バッファー
図 2 : フェンス ポーリングによって、アプリはフレーム 3 がレンダリングされたことを検出し、フレーム オフセット キューが更新され、リング バッファーの現在の状態が続きます。ただし、空きメモリは、定数バッファーに対応するのに十分な大きさではありません。
フレーム 3 
図 3 : 状況を考えると、フレーム 4 がレンダリングされるまで CPU ブロック自体が (フェンス待機を介して) ブロックされ、フレーム 4 に割り当てられたメモリが解放されます。
が解放されます
図 4 : 空きメモリが定数バッファーに十分な大きさになり、サブ割り当てが成功しました。アプリは、フレーム 3 と 4 の両方のリソース データで以前に使用されていたメモリにビッグ定数バッファー データをコピーします。 現在の入力ポインターが最終的に更新されます。
にフレーム 6 からのスペースが存在します
アプリがリング バッファーを実装する場合、リング バッファーはリソース データのサイズの悪化シナリオに対応するのに十分な大きさである必要があります。
リング バッファーのサンプル
次のサンプル コードは、フェンスのポーリングと待機を処理するサブ割り当てルーチンに注目して、リング バッファーを管理する方法を示しています。 わかりやすくするために、このサンプルでは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();
}
}
関連トピック