Command allocators and lists

While command queues operate on GPU commands submitted to it, they never operate on graphics- or compute commands directly. Instead, ID3D12GraphicsCommandList objects are submitted to command queues, which package a slew of graphics- and compute commands in one go.

The memory required for a command list is allocated by an ID3D12CommandAllocator. This object represents a linear allocator, where every allocation required by the ID3D12GraphicsCommandList will be serviced. Every time a command list a command list is reset, it will allocate a block of memory, and if that block is filled up it will request more from the allocator. Resetting the command list will not clean up the command list's memory block. Memory usage from the command allocator will keep piling up until the ID3D12CommandAllocator::Reset function is called. Upon calling this function, executing ID3D12GraphicsCommandLists with memory previously allocated by that command allocator is not adviced.

The ID3D12CommandAllocator is not thread-safe, so you should have one or more ID3D12CommandAllocator objects per thread involved with populating command lists.

In addition, you might need multiple ID3D12CommandAllocator objects per thread when dealing with dynamic command lists. This is because the Reset function can not be called while a command buffer is in flight, and your target would be to keep a command buffer in flight at all times. Therefore, in this example, we will have a single command allocator per frame buffer, although a more efficient scheme can be devised fairly easily.

NOTE: The following two examples are invoked per frame buffer.

result = renderer->device->lpVtbl->CreateCommandAllocator(
    renderer->device,
    D3D12_COMMAND_LIST_TYPE_DIRECT,
    &IID_ID3D12CommandAllocator,
    (void**)&renderer->frame[i].commandAllocator
);
if ( !SUCCEEDED ( result ) )
    RETURN_ERROR(-1, "CreateCommandAllocator failed (0x%08X)", result );

Here we see the D3D12_COMMAND_LIST_TYPE_DIRECT we have seen when creating the command queue. Similarly, this means the command allocator is only suited to allocating memory for command lists of this specific type.

Now for the command lists:

ID3D12GraphicsCommandList* commandList = NULL;

result = renderer->device->lpVtbl->CreateCommandList (
	renderer->device,
	0,
	D3D12_COMMAND_LIST_TYPE_DIRECT,
	renderer->frame[i].commandAllocator,
	NULL,
	&IID_ID3D12GraphicsCommandList,
	(void**)&commandList
);
if ( !SUCCEEDED(result) )
	RETURN_ERROR(-1, "CreateCommandList failed (0x%08X)", result );

renderer->frame[i].commandList = commandList;

result = renderer->frame[i].commandList->lpVtbl->Close ( renderer->frame[i].commandList );
if ( !SUCCEEDED(result) )
	RETURN_ERROR(-1, "Close failed (0x%08X)", result );

As you might notice, we are calling Close on the ID3D12GraphicsCommandList just after we have created the command list. This is because every command list created by Direct3D 12 is created in an open state. This is also why there is a pipeline state object argument in the call to CreateCommandList.