diff --git a/src/vulkan/runtime/vk_command_buffer.c b/src/vulkan/runtime/vk_command_buffer.c index 2bccf6c63da..2be9eeaf840 100644 --- a/src/vulkan/runtime/vk_command_buffer.c +++ b/src/vulkan/runtime/vk_command_buffer.c @@ -72,6 +72,20 @@ vk_command_buffer_finish(struct vk_command_buffer *command_buffer) vk_object_base_finish(&command_buffer->base); } +void +vk_command_buffer_recycle(struct vk_command_buffer *cmd_buffer) +{ + /* Reset, returning resources to the pool. The command buffer object + * itself will be recycled but, if the driver supports returning other + * resources such as batch buffers to the pool, it should do so so they're + * not tied up in recycled command buffer objects. + */ + cmd_buffer->ops->reset(cmd_buffer, + VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT); + + vk_object_base_recycle(&cmd_buffer->base); +} + VKAPI_ATTR VkResult VKAPI_CALL vk_common_ResetCommandBuffer(VkCommandBuffer commandBuffer, VkCommandBufferResetFlags flags) diff --git a/src/vulkan/runtime/vk_command_buffer.h b/src/vulkan/runtime/vk_command_buffer.h index 63864600628..8c02284f310 100644 --- a/src/vulkan/runtime/vk_command_buffer.h +++ b/src/vulkan/runtime/vk_command_buffer.h @@ -176,6 +176,9 @@ vk_command_buffer_reset_render_pass(struct vk_command_buffer *cmd_buffer); void vk_command_buffer_reset(struct vk_command_buffer *command_buffer); +void +vk_command_buffer_recycle(struct vk_command_buffer *command_buffer); + void vk_command_buffer_finish(struct vk_command_buffer *command_buffer); diff --git a/src/vulkan/runtime/vk_command_pool.c b/src/vulkan/runtime/vk_command_pool.c index d4ff898f9b3..b4de2a82d96 100644 --- a/src/vulkan/runtime/vk_command_pool.c +++ b/src/vulkan/runtime/vk_command_pool.c @@ -30,6 +30,23 @@ #include "vk_device.h" #include "vk_log.h" +static bool +should_recycle_command_buffers(struct vk_device *device) +{ + /* They have to be using the common allocation implementation, otherwise + * the recycled command buffers will never actually get re-used + */ + const struct vk_device_dispatch_table *disp = &device->dispatch_table; + if (disp->AllocateCommandBuffers != vk_common_AllocateCommandBuffers) + return false; + + /* We need to be able to reset command buffers */ + if (device->command_buffer_ops->reset == NULL) + return false; + + return true; +} + VkResult MUST_CHECK vk_command_pool_init(struct vk_device *device, struct vk_command_pool *pool, @@ -44,7 +61,9 @@ vk_command_pool_init(struct vk_device *device, pool->queue_family_index = pCreateInfo->queueFamilyIndex; pool->alloc = pAllocator ? *pAllocator : device->alloc; pool->command_buffer_ops = device->command_buffer_ops; + pool->recycle_command_buffers = should_recycle_command_buffers(device); list_inithead(&pool->command_buffers); + list_inithead(&pool->free_command_buffers); return VK_SUCCESS; } @@ -58,6 +77,12 @@ vk_command_pool_finish(struct vk_command_pool *pool) } assert(list_is_empty(&pool->command_buffers)); + list_for_each_entry_safe(struct vk_command_buffer, cmd_buffer, + &pool->free_command_buffers, pool_link) { + cmd_buffer->ops->destroy(cmd_buffer); + } + assert(list_is_empty(&pool->free_command_buffers)); + vk_object_base_finish(&pool->base); } @@ -132,6 +157,38 @@ vk_common_ResetCommandPool(VkDevice device, return VK_SUCCESS; } +static void +vk_command_buffer_recycle_or_destroy(struct vk_command_pool *pool, + struct vk_command_buffer *cmd_buffer) +{ + assert(pool == cmd_buffer->pool); + + if (pool->recycle_command_buffers) { + vk_command_buffer_recycle(cmd_buffer); + + list_del(&cmd_buffer->pool_link); + list_add(&cmd_buffer->pool_link, &pool->free_command_buffers); + } else { + cmd_buffer->ops->destroy(cmd_buffer); + } +} + +static struct vk_command_buffer * +vk_command_pool_find_free(struct vk_command_pool *pool) +{ + if (list_is_empty(&pool->free_command_buffers)) + return NULL; + + struct vk_command_buffer *cmd_buffer = + list_first_entry(&pool->free_command_buffers, + struct vk_command_buffer, pool_link); + + list_del(&cmd_buffer->pool_link); + list_addtail(&cmd_buffer->pool_link, &pool->command_buffers); + + return cmd_buffer; +} + VKAPI_ATTR VkResult VKAPI_CALL vk_common_AllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo *pAllocateInfo, @@ -144,10 +201,12 @@ vk_common_AllocateCommandBuffers(VkDevice device, assert(device == vk_device_to_handle(pool->base.device)); for (i = 0; i < pAllocateInfo->commandBufferCount; i++) { - struct vk_command_buffer *cmd_buffer; - result = pool->command_buffer_ops->create(pool, &cmd_buffer); - if (unlikely(result != VK_SUCCESS)) - goto fail; + struct vk_command_buffer *cmd_buffer = vk_command_pool_find_free(pool); + if (cmd_buffer == NULL) { + result = pool->command_buffer_ops->create(pool, &cmd_buffer); + if (unlikely(result != VK_SUCCESS)) + goto fail; + } cmd_buffer->level = pAllocateInfo->level; @@ -159,7 +218,7 @@ vk_common_AllocateCommandBuffers(VkDevice device, fail: while (i--) { VK_FROM_HANDLE(vk_command_buffer, cmd_buffer, pCommandBuffers[i]); - cmd_buffer->ops->destroy(cmd_buffer); + vk_command_buffer_recycle_or_destroy(pool, cmd_buffer); } for (i = 0; i < pAllocateInfo->commandBufferCount; i++) pCommandBuffers[i] = VK_NULL_HANDLE; @@ -173,14 +232,27 @@ vk_common_FreeCommandBuffers(VkDevice device, uint32_t commandBufferCount, const VkCommandBuffer *pCommandBuffers) { + VK_FROM_HANDLE(vk_command_pool, pool, commandPool); + for (uint32_t i = 0; i < commandBufferCount; i++) { VK_FROM_HANDLE(vk_command_buffer, cmd_buffer, pCommandBuffers[i]); if (cmd_buffer == NULL) continue; + vk_command_buffer_recycle_or_destroy(pool, cmd_buffer); + } +} + +void +vk_command_pool_trim(struct vk_command_pool *pool, + VkCommandPoolTrimFlags flags) +{ + list_for_each_entry_safe(struct vk_command_buffer, cmd_buffer, + &pool->free_command_buffers, pool_link) { cmd_buffer->ops->destroy(cmd_buffer); } + assert(list_is_empty(&pool->free_command_buffers)); } VKAPI_ATTR void VKAPI_CALL @@ -188,5 +260,7 @@ vk_common_TrimCommandPool(VkDevice device, VkCommandPool commandPool, VkCommandPoolTrimFlags flags) { - /* No-op is a valid implementation but may not be optimal */ + VK_FROM_HANDLE(vk_command_pool, pool, commandPool); + + vk_command_pool_trim(pool, flags); } diff --git a/src/vulkan/runtime/vk_command_pool.h b/src/vulkan/runtime/vk_command_pool.h index ca5e6b92fc8..a3ea03384e8 100644 --- a/src/vulkan/runtime/vk_command_pool.h +++ b/src/vulkan/runtime/vk_command_pool.h @@ -46,8 +46,14 @@ struct vk_command_pool { /** Command buffer vtable for command buffers allocated from this pool */ const struct vk_command_buffer_ops *command_buffer_ops; + /** True if we should recycle command buffers */ + bool recycle_command_buffers; + /** List of all command buffers */ struct list_head command_buffers; + + /** List of freed command buffers for trimming. */ + struct list_head free_command_buffers; }; VK_DEFINE_NONDISP_HANDLE_CASTS(vk_command_pool, base, VkCommandPool, @@ -62,6 +68,10 @@ vk_command_pool_init(struct vk_device *device, void vk_command_pool_finish(struct vk_command_pool *pool); +void +vk_command_pool_trim(struct vk_command_pool *pool, + VkCommandPoolTrimFlags flags); + #ifdef __cplusplus } #endif