Command Buffer

Vulkan

In Vulkan, the memory of the command buffer is managed by the driver. Usually, the application allocates "frame_throttling_count" command pools in advance, and uses the "frame_throttling_index" to select the command pool from which the command buffers are allocated in each frame. Note that the "frame_throttling_count" should be distinguished from the "swapchain_image_count".

For example, in RADV, the memory is stored in the list radv_amdgpu_cs::handles. Evidently, we can NOT know in advance how much memory we should allocate for the command buffer at the begining of each frame. Hence, when we are out of memory, we need to allocate a new buffer object and insert the new buffer object to the list (see cs_grow, radv_reset_cmd_buffer, radv_cmd_buffer_resize_upload_buf and cs_add_buffer for more information). Thus, the memory of the command buffer is NOT continuous, and is composed by several buffer objects in the list.

Particularly, the RADV follows the Mesa3D common command pool framework where the vkResetCommandPool is implemented by the vkResetCommandBuffer. This means that the memory of the command buffer is managed by the command buffer rather than the command pool in RADV.

Console

In console, the memory of the command buffer is managed by the application rather than the driver. When the command buffer is initialized, a memory buffer can be provided by the application. And the application can also provide an out-of-memory callback, which is invoked when the command buffer is out of the memory.

Perhaps, it is too difficult for the application to manage the memory of the command buffer, the ring buffer class is provided to help the application. The ring buffer provides a function which can be used by the application as the out-of-memory callback of the command buffer, and the application does NOT need to provide the memory buffer any more when initializing the command buffer.

The ring buffer maintains a list of several segments, which is analogous to the list of the "memory buffer objects" in RADV, and the ring buffer inserts the "jump" commands to connect these separated segments. When the ring buffer is initialized, several segments can be added to the ring buffer by the application. And the application can also provide an out-of-memory callback, which is invoked when there are no free segments in the ring buffer. Note that the out-of-memory callback of the ring buffer should be distinguished from the out-of-memory callback of the command buffer.

By default, no out-of-memory callback is provided, and the ring buffer waits until the GPU has stopped using the submitted segments.

Actually, this default waiting behavior of the the ring buffer is analogous to the legacy Direct3D11 constant buffer which waits for the GPU when the constant buffer is out of memory. To improve the performance, the memory of the constant buffer is visible to the application in Vulkan and Direct3D12, and thus the more efficient strategy, which is NOT compatible with the Direct3D11 specification, can be adopted by the appilcation. For example, the application may simply ignore the subsequent objects when the constant buffer is out of memory, and thus the application does NOT need to wait any more.

Evidently, the out-of-memory callback of the ring buffer can be provided by the application to avoid the waiting. The application can allocate the memory for another new segment and add the new segment to the ring buffer in the out-of-memory callback of the ring buffer, and the application does NOT need to provide the segments any more when initializing the ring buffer. Optionally, the application may retrieve the free segments of the ring buffer and free the memory of the segments when resetting the command buffer. This is analogous to the behavior of "radv_reset_cmd_buffer" in RADV, but evidently we can keep using the existing segments without freeing them for better performance.