// Copyright 2019-2023, Collabora, Ltd.
// SPDX-License-Identifier: BSL-1.0
/*!
 * @file
 * @brief  Command pool helpers.
 * @author Jakob Bornecrantz <jakob@collabora.com>
 * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
 * @ingroup aux_vk
 */

#pragma once

#include "vk/vk_helpers.h"
#include "vk/vk_cmd.h"

#ifdef __cplusplus
extern "C" {
#endif


/*
 *
 * Struct(s)
 *
 */

/*!
 * Small helper to manage lock around a command pool.
 *
 * @ingroup aux_vk
 */
struct vk_cmd_pool
{
	//! The command pool for command buffers
	VkCommandPool pool;

	/*!
	 * @brief Queue (family) associated with @ref vk_cmd_pool::pool,
	 *
	 * weak reference to any queue in @ref vk_bundle (e.g. vk_bundle::[graphics|compute]_queue)
	 * should not live longer than the @ref vk_bundle instance.
	 */
	struct vk_bundle_queue *queue;

	//! Command Pool mutex
	struct os_mutex mutex;
};


/*
 *
 * Functions.
 *
 */

/*!
 * Create a command buffer pool.
 *
 * @public @memberof vk_cmd_pool
 */
XRT_CHECK_RESULT VkResult
vk_cmd_pool_init_for_queue(struct vk_bundle *vk,
                           struct vk_cmd_pool *pool,
                           VkCommandPoolCreateFlags flags,
                           struct vk_bundle_queue *queue);

/*!
 * Create a command buffer pool.
 *
 * @public @memberof vk_cmd_pool
 */
static inline XRT_CHECK_RESULT VkResult
vk_cmd_pool_init(struct vk_bundle *vk, struct vk_cmd_pool *pool, VkCommandPoolCreateFlags flags)
{
	return vk_cmd_pool_init_for_queue(vk, pool, flags, vk->main_queue);
}

/*!
 * Destroy a command buffer pool, lock must not be held, externally
 * synchronizable with all other pool commands.
 *
 * @public @memberof vk_cmd_pool
 */
void
vk_cmd_pool_destroy(struct vk_bundle *vk, struct vk_cmd_pool *pool);

/*!
 * Create a command buffer, call with the pool mutex held.
 *
 * @pre Command pool lock must be held, see @ref vk_cmd_pool_lock.
 *
 * @public @memberof vk_cmd_pool
 */
XRT_CHECK_RESULT VkResult
vk_cmd_pool_create_cmd_buffer_locked(struct vk_bundle *vk, struct vk_cmd_pool *pool, VkCommandBuffer *out_cmd_buffer);

/*!
 * Create a command buffer and also begin it, call with the pool mutex held.
 *
 * @pre Command pool lock must be held.
 *
 * @public @memberof vk_cmd_pool
 */
XRT_CHECK_RESULT VkResult
vk_cmd_pool_create_and_begin_cmd_buffer_locked(struct vk_bundle *vk,
                                               struct vk_cmd_pool *pool,
                                               VkCommandBufferUsageFlags flags,
                                               VkCommandBuffer *out_cmd_buffer);

/*!
 * Submit to the vulkan queue, will take the queue mutex.
 *
 * @pre Command pool lock must be held, see @ref vk_cmd_pool_lock.
 *
 * @public @memberof vk_cmd_pool
 */
XRT_CHECK_RESULT VkResult
vk_cmd_pool_submit_cmd_buffer_locked(struct vk_bundle *vk, struct vk_cmd_pool *pool, VkCommandBuffer cmd_buffer);

/*!
 * A do everything submit function, will take the queue mutex. Will create a
 * fence and wait on the commands to complete. Will also end and destroy the
 * passed in command buffer.
 *
 * @pre Command pool lock must be held, see @ref vk_cmd_pool_lock.
 *
 * Calls:
 * * vkEndCommandBuffer
 * * vkCreateFence
 * * vkWaitForFences
 * * vkDestroyFence
 * * vkFreeCommandBuffers
 *
 * @public @memberof vk_cmd_pool
 */
XRT_CHECK_RESULT static inline VkResult
vk_cmd_pool_end_submit_wait_and_free_cmd_buffer_locked(struct vk_bundle *vk,
                                                       struct vk_cmd_pool *pool,
                                                       VkCommandBuffer cmd_buffer)
{
	return vk_cmd_end_submit_wait_and_free_cmd_buffer_locked(vk, pool->queue, pool->pool, cmd_buffer);
}

/*!
 * Lock the command pool, needed for creating command buffers, filling out
 * commands on any command buffers created from this pool and submitting any
 * command buffers created from this pool to a VkQueue.
 *
 * @public @memberof vk_cmd_pool
 */
static inline void
vk_cmd_pool_lock(struct vk_cmd_pool *pool)
{
	os_mutex_lock(&pool->mutex);
}

/*!
 * Unlock the command pool.
 *
 * @public @memberof vk_cmd_pool
 */
static inline void
vk_cmd_pool_unlock(struct vk_cmd_pool *pool)
{
	os_mutex_unlock(&pool->mutex);
}

/*!
 * Locks, calls @ref vk_cmd_pool_create_cmd_buffer_locked, and then unlocks the
 * command pool.
 *
 * @public @memberof vk_cmd_pool
 */
XRT_CHECK_RESULT static inline VkResult
vk_cmd_pool_create_cmd_buffer(struct vk_bundle *vk, struct vk_cmd_pool *pool, VkCommandBuffer *out_cmd_buffer)
{
	vk_cmd_pool_lock(pool);
	VkResult ret = vk_cmd_pool_create_cmd_buffer_locked(vk, pool, out_cmd_buffer);
	vk_cmd_pool_unlock(pool);
	return ret;
}

/*!
 * Locks, calls @ref vk_cmd_pool_create_and_begin_cmd_buffer_locked, and then
 * unlocks the command pool.
 *
 * @public @memberof vk_cmd_pool
 */
XRT_CHECK_RESULT static inline VkResult
vk_cmd_pool_create_and_begin_cmd_buffer(struct vk_bundle *vk,
                                        struct vk_cmd_pool *pool,
                                        VkCommandBufferUsageFlags flags,
                                        VkCommandBuffer *out_cmd_buffer)
{
	vk_cmd_pool_lock(pool);
	VkResult ret = vk_cmd_pool_create_and_begin_cmd_buffer_locked(vk, pool, flags, out_cmd_buffer);
	vk_cmd_pool_unlock(pool);
	return ret;
}

/*!
 * Locks, calls @ref vk_cmd_submit_locked, and then unlocks the command
 * pool. Will, during the call, take the queue lock and release it.
 *
 * @public @memberof vk_cmd_pool
 */
XRT_CHECK_RESULT static inline VkResult
vk_cmd_pool_submit(
    struct vk_bundle *vk, struct vk_cmd_pool *pool, uint32_t count, const VkSubmitInfo *infos, VkFence fence)
{
	vk_cmd_pool_lock(pool);
	VkResult ret = vk_cmd_submit_locked(vk, pool->queue, count, infos, fence);
	vk_cmd_pool_unlock(pool);
	return ret;
}

/*!
 * Locks, calls @ref vk_cmd_pool_submit_cmd_buffer_locked, and then unlocks the
 * command pool. Will during the call take the queue lock and release it.
 *
 * @public @memberof vk_cmd_pool
 */
XRT_CHECK_RESULT static inline VkResult
vk_cmd_pool_submit_cmd_buffer(struct vk_bundle *vk, struct vk_cmd_pool *pool, VkCommandBuffer cmd_buffer)
{
	vk_cmd_pool_lock(pool);
	VkResult ret = vk_cmd_pool_submit_cmd_buffer_locked(vk, pool, cmd_buffer);
	vk_cmd_pool_unlock(pool);
	return ret;
}

/*!
 * Locks, calls @ref vk_cmd_pool_end_submit_wait_and_free_cmd_buffer_locked, and
 * then unlocks the command pool. Will during the call take the queue lock and
 * release it.
 *
 * @public @memberof vk_cmd_pool
 */
XRT_CHECK_RESULT static inline VkResult
vk_cmd_pool_end_submit_wait_and_free_cmd_buffer(struct vk_bundle *vk,
                                                struct vk_cmd_pool *pool,
                                                VkCommandBuffer cmd_buffer)
{
	vk_cmd_pool_lock(pool);
	VkResult ret = vk_cmd_pool_end_submit_wait_and_free_cmd_buffer_locked(vk, pool, cmd_buffer);
	vk_cmd_pool_unlock(pool);
	return ret;
}

#ifdef VK_EXT_debug_utils
/*!
 * Small helper function that creates a command buffer and begins it,
 * ends it after inserting a debug label into it.
 *
 * @pre Command pool lock must be held.
 *
 * @public @memberof vk_cmd_pool
 */
XRT_CHECK_RESULT VkResult
vk_cmd_pool_create_begin_insert_label_and_end_cmd_buffer_locked(struct vk_bundle *vk,
                                                                struct vk_cmd_pool *pool,
                                                                const char *label_name,
                                                                VkCommandBuffer *out_cmd_buffer);
#endif

#ifdef __cplusplus
}
#endif
