In order to create a fence, the ID3D12Device::CreateFence
function is to be called. This function call includes an initial value and a flag field, which can specify whether or not the fence is to be shared between multiple GPU adapters.
In order to wait for the fence on the CPU, a Win32 event handle created by CreateEvent
is required, after which the ID3D12Fence::SetEventOnCompletion
function can be called to set the event when the fence reaches a specific value. After that, WaitForSingleObject
or WaitForMultipleObjects
can be used to wait for fence completion.
In order to wait on the fence on the GPU, the ID3D12CommandQueue::Wait
method can be invoked to push a wait command to the command queue. The command queue will keep executing commands in order as they are submitted to it, and upon reaching the Wait
command will hold executing other commands until the fence reaches the value specified in the Wait
command.
The fence can be signalled (set to a specific value) by either the CPU or the GPU. On the CPU, use ID3D12Fence::Signal
to immediately set the fence value to the value specified. On the GPU, use ID3D12CommandQueue::Signal
to push a command to set the current fence value at that point in the command queue.
For the current example, the only thing we use the fence for is in order to queue multiple frames of commands and render targets at the same time, and making sure we are not using the same command buffer and render target while they are still in use by a previous submitted frame. In this scenario, the GPU signals, and the CPU waits. As such, we are creating both the fence and an event.
result = renderer->device->lpVtbl->CreateFence(
renderer->device,
0xFFFFFFFFFFFFFFFF,
D3D12_FENCE_FLAG_NONE,
&IID_ID3D12Fence,
(void**)&renderer->frameFence
);
if ( !SUCCEEDED ( result ) )
RETURN_ERROR(-1, "CreateFence failed (0x%08X)", result );
renderer->eofEvent = CreateEventEx ( NULL, FALSE, FALSE, EVENT_ALL_ACCESS );
if ( renderer->eofEvent == NULL )
RETURN_ERROR(-1, "CreateEventEx failed (0x%08X)", GetLastError() );