Fence

In order to await completion of command buffers in the command queue, we will need a synchronization structure known as a fence. A fence has an internal integer counter, and can trigger events on either the CPU or the GPU when the counter reaches a certain value. This can be done to trigger an event on the CPU when a command buffer is done executing, have the GPU wait while the CPU is completing a certain operation first, or to get two command queues on the GPU to wait upon one another.

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() );