Command buffers

As discussed in the render thread manual, the framework uses a multi-threaded architecture where rendering operations are executed on the render thread. All rendering operations in the framework are performed through render::GpuCommandBuffer objects. These command buffers are the primary interface for interacting with the GPU - whether you're drawing geometry, setting pipeline states, or dispatching compute shaders.

Rendering can be a very CPU heavy operation even though GPU does all the rendering - but CPU is still the one submitting all those commands. Command buffers help address this by allowing you to record rendering commands on different threads, distributing the CPU workload more efficiently. Each command buffer can be populated with commands on any thread and submitted from any thread.

Creation

Command buffers are created via a render::GpuCommandBufferPool. The pool is created by calling GpuDevice::CreateGpuCommandBufferPool with render::GpuCommandBufferPoolCreateInformation as the parameter. The pool information specifies:

  • GpuQueueUsage - This determines which commands may be executed on command buffers created from this pool. This can be:
    • GQT_GRAPHICS - Command buffers that support all type of operations (default).
    • GQT_COMPUTE - Command buffers that only support compute operations.
  • Thread ID - The thread on which the pool and its command buffers are allowed to be used.

If you know a command buffer will only execute compute operations it is beneficial to create the pool using GQT_COMPUTE.

GpuDevice& gpuDevice = GetApplication().GetPrimaryGpuDevice();

GpuCommandBufferPoolCreateInformation poolCreateInformation =
	GpuCommandBufferPoolCreateInformation::CreateForThisThread(GQT_GRAPHICS);

SPtr<GpuCommandBufferPool> commandBufferPool = gpuDevice.CreateGpuCommandBufferPool(poolCreateInformation);

Once you have a pool, you can create command buffers by calling render::GpuCommandBufferPool::Create or render::GpuCommandBufferPool::FindOrCreate. The latter will attempt to reuse an existing command buffer from the pool if one is available.

SPtr<GpuCommandBuffer> commandBuffer = commandBufferPool->Create(
	GpuCommandBufferCreateInformation::Create("MyCommandBuffer"));

Command buffers are automatically put into a recording state when created or retrieved from the pool.

Recording commands

Once created, you can record rendering commands by calling methods on the command buffer. These include:

// Begin render pass
commandBuffer->BeginRenderPass(renderTarget, 0, RT_NONE);

// Clear the render target
commandBuffer->ClearRenderTarget(FBT_COLOR | FBT_DEPTH, Color::kBlue, 1.0f, 0, 0xFF);

// Bind pipeline state and resources
commandBuffer->SetGpuGraphicsPipelineState(pipelineState);
commandBuffer->SetVertexBuffers(0, &vertexBuffer, 1);
commandBuffer->SetIndexBuffer(indexBuffer);
commandBuffer->SetVertexDescription(vertexDescription);
commandBuffer->SetDrawOperation(DOT_TRIANGLE_LIST);
commandBuffer->SetGpuParameterSet(parameterSet);

// Draw
commandBuffer->DrawIndexed(0, indexCount, 0, vertexCount, 1, 0);

// End render pass
commandBuffer->EndRenderPass();

Finishing recording

Before a command buffer can be submitted, you must call render::GpuCommandBuffer::End to finish recording and prepare the command buffer for submission.

commandBuffer->End();

Submitting

Commands queued on a command buffer will only get executed after the command buffer is submitted. Submission is done by calling GpuDevice::SubmitCommandBuffer.

GpuDevice& gpuDevice = GetApplication().GetPrimaryGpuDevice();
gpuDevice.SubmitCommandBuffer(commandBuffer);

You must externally synchronize access to GpuCommandBuffer when passing it between threads, as it is not thread safe.

Command buffer state

Command buffers have several states that can be queried via render::GpuCommandBuffer::GetState:

  • render::CommandBufferState::Ready - Buffer is ready to begin recording commands
  • render::CommandBufferState::Recording - Buffer is currently recording commands
  • render::CommandBufferState::Executing - Buffer has been submitted and is executing on the GPU
  • render::CommandBufferState::Done - Buffer has finished executing
CommandBufferState state = commandBuffer->GetState();

if (state == CommandBufferState::Done)
{
	B3D_LOG(Info, LogRenderer, "Command buffer has finished executing");
}