// Copyright 2019-2024, Collabora, Ltd.
// Copyright 2024-2026, NVIDIA CORPORATION.
// SPDX-License-Identifier: BSL-1.0
/*!
 * @file
 * @brief  Common Vulkan code header.
 *
 * Note that some functionality in this file is generated by the script
 * `src/xrt/auxiliary/vk/vk_generate_inc_files.py`. The generated parts are
 * included via .h.inc files.
 *
 * @author Jakob Bornecrantz <jakob@collabora.com>
 * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
 * @author Moshi Turner <moshiturner@protonmail.com>
 * @author Korcan Hussein <korcan.hussein@collabora.com>
 * @ingroup aux_vk
 */

#pragma once

#include "xrt/xrt_compiler.h"
#include "xrt/xrt_compositor.h"
#include "xrt/xrt_vulkan_includes.h"
#include "xrt/xrt_handles.h"

#include "util/u_logging.h"
#include "util/u_extension_list.h"

#include "vk/vk_print.h"

#include "os/os_threading.h"


#ifdef __cplusplus
extern "C" {
#endif


#define VK_BUNDLE_MAX_QUEUES 3

/*
 *
 * Structs
 *
 */

struct vk_queue_pair
{
	//! The queue family index
	uint32_t family_index;
	//! The queue (instance) index
	uint32_t index;
};

#define VK_NULL_QUEUE_PAIR                                                                                             \
	XRT_C11_COMPOUND(struct vk_queue_pair)                                                                         \
	{                                                                                                              \
		.family_index = VK_QUEUE_FAMILY_IGNORED, .index = (uint32_t)-1,                                        \
	}

struct vk_bundle_queue
{
	//! The Vulkan queue handle
	VkQueue queue;
	//! The queue family index
	uint32_t family_index;
	//! The queue (instance) index
	uint32_t index;
	//! The queue mutex - @see vk_queue_lock, vk_queue_unlock
	struct os_mutex mutex;
};

/*!
 * A bundle of Vulkan functions and objects, used by both @ref comp and @ref
 * comp_client. Note that they both have different instances of the object, and
 * thus different VkInstance, etc.
 *
 * @ingroup aux_vk
 */
struct vk_bundle
{
	enum u_logging_level log_level;

	VkInstance instance;
	uint32_t version;
	VkPhysicalDevice physical_device;
	int physical_device_index;
	VkDevice device;

	/*!
	 * @brief queues - a free list of **unique** queues
	 *
	 * One per uniquely identifiable vk queue (family x instance index),
	 * duplicate entries must not be stored.
	 *
	 * Should not be used directly, @see main_queue, graphics_queue, encode_queue
	 */
	struct vk_bundle_queue queues[VK_BUNDLE_MAX_QUEUES];

	/*!
	 * @brief Main queue for general work.
	 *
	 * In normal mode, this is a graphics queue.
	 * In compute-only mode, this is a compute queue.
	 *
	 * May alias with graphics_queue.
	 */
	struct vk_bundle_queue *main_queue;

	/*!
	 * @brief Graphics queue.
	 *
	 * Always a graphics queue, even in compute-only mode.
	 * In normal mode, aliases with main_queue.
	 * In compute-only mode, this is a separate graphics queue.
	 */
	struct vk_bundle_queue *graphics_queue;

#if defined(VK_KHR_video_encode_queue)
	/*!
	 * @brief Video encode queue.
	 *
	 * May be NULL if video encoding is not supported.
	 * May alias with other queues.
	 */
	struct vk_bundle_queue *encode_queue;
#endif

	struct
	{
#if defined(XRT_GRAPHICS_BUFFER_HANDLE_IS_WIN32_HANDLE)
		bool color_image_import_opaque_win32;
		bool color_image_export_opaque_win32;
		bool depth_image_import_opaque_win32;
		bool depth_image_export_opaque_win32;

		bool color_image_import_d3d11;
		bool color_image_export_d3d11;
		bool depth_image_import_d3d11;
		bool depth_image_export_d3d11;

#elif defined(XRT_GRAPHICS_BUFFER_HANDLE_IS_FD)
		bool color_image_import_opaque_fd;
		bool color_image_export_opaque_fd;
		bool depth_image_import_opaque_fd;
		bool depth_image_export_opaque_fd;

#elif defined(XRT_GRAPHICS_BUFFER_HANDLE_IS_AHARDWAREBUFFER)
		bool color_image_import_opaque_fd;
		bool color_image_export_opaque_fd;
		bool depth_image_import_opaque_fd;
		bool depth_image_export_opaque_fd;

		bool color_image_import_ahardwarebuffer;
		bool color_image_export_ahardwarebuffer;
		bool depth_image_import_ahardwarebuffer;
		bool depth_image_export_ahardwarebuffer;
#endif

#if defined(XRT_GRAPHICS_SYNC_HANDLE_IS_FD)
		bool fence_sync_fd;
		bool fence_opaque_fd;

		bool binary_semaphore_sync_fd;
		bool binary_semaphore_opaque_fd;

		bool timeline_semaphore_sync_fd;
		bool timeline_semaphore_opaque_fd;
#elif defined(XRT_GRAPHICS_SYNC_HANDLE_IS_WIN32_HANDLE)
		bool fence_win32_handle;

		bool binary_semaphore_d3d12_fence;
		bool binary_semaphore_win32_handle;

		bool timeline_semaphore_d3d12_fence;
		bool timeline_semaphore_win32_handle;
#else
#error "Need port for fence sync handles checkers"
#endif
	} external;

	// Include the generated fields for has_KHR_extension.
#include "vk_helpers_h_ext.h.inc"

	struct
	{
		//! Are timestamps available for compute and graphics queues?
		bool timestamp_compute_and_graphics;

		//! Nanoseconds per gpu tick.
		float timestamp_period;

		//! Valid bits in the queue selected.
		uint32_t timestamp_valid_bits;

		//! Were timeline semaphore requested, available, and enabled?
		bool timeline_semaphore;

		//! Was synchronization2 requested, available, and enabled?
		bool synchronization_2;

		//! Was KHR_present_wait requested, available, and enabled?
		bool present_wait;

		//! Was KHR_video_maintenance1 requested, available, and enabled?
		bool video_maintenance_1;
	} features;

	struct
	{
		//! Maximum number of sampler objects, as created by vkCreateSampler, which can simultaneously exist on
		uint32_t max_sampler_allocation_count;

		//! Maximum number of descriptor sets that can be simultaneously used by a pipeline.
		uint32_t max_bound_descriptor_sets;

		//! Maximum number of samplers that can be included in a pipeline layout.
		uint32_t max_descriptor_set_samplers;

		//! Maximum number of sampled images that can be included in a pipeline layout.
		uint32_t max_descriptor_set_sampled_images;

		//! Maximum number of samplers that can be accessible to a single shader stage in a pipeline layout.
		uint32_t max_per_stage_descriptor_samplers;

		//! Per stage limit on sampled images (includes combined).
		uint32_t max_per_stage_descriptor_sampled_images;

		//! Per stage limit on storage images.
		uint32_t max_per_stage_descriptor_storage_images;
	} limits;

	//! Is the GPU a tegra device.
	bool is_tegra;


	VkDebugReportCallbackEXT debug_report_cb;

	VkPhysicalDeviceMemoryProperties device_memory_props;

	// Loader functions
	PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr;
	PFN_vkCreateInstance vkCreateInstance;
	PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties;

	// Include the generated function pointers fields.
#include "vk_helpers_h_funcs.h.inc"
};

struct vk_buffer
{
	VkBuffer handle;
	VkDeviceMemory memory;
	uint32_t size;
	void *data;
};


/*
 *
 * Helper defines.
 *
 */

/*!
 * This define will print if `RET` is not `VK_SUCCESS`, printing out that the
 * @p FUNC_STR string has failed. The implicit argument @p vk will be used for
 * the @ref vk_print_result call.
 *
 * Use this when you need to do special things after an error,
 * but still want to pirnt the pretty message about the failure.
 *
 * @param RET      The @p VkResult to check.
 * @param FUNC_STR String literal with the function name, used for logging.
 *
 * @ingroup aux_vk
 *
 * @see VK_CHK_AND_RET
 * @see VK_CHK_WITH_RET
 * @see VK_CHK_WITH_GOTO
 */
#define VK_CHK_ONLY_PRINT(RET, FUNC_STR)                                                                               \
	do {                                                                                                           \
		VkResult _ret = RET;                                                                                   \
		if (_ret != VK_SUCCESS) {                                                                              \
			vk_print_result(vk, __FILE__, __LINE__, __func__, _ret, FUNC_STR);                             \
		}                                                                                                      \
	} while (false)

/*!
 * This define will error if `RET` is not `VK_SUCCESS`, printing out that the
 * @p FUNC_STR string has failed, then returns @p RET. The implicit argument
 * @p vk will be used for the @ref vk_print_result call.
 *
 * Use this macro when your function returns a `VkResult`.
 *
 * @param RET      The @p VkResult to check.
 * @param FUNC_STR String literal with the function name, used for logging.
 *
 * @ingroup aux_vk
 *
 * @see VK_CHK_ONLY_PRINT
 * @see VK_CHK_WITH_RET
 * @see VK_CHK_WITH_GOTO
 */
#define VK_CHK_AND_RET(RET, FUNC_STR)                                                                                  \
	do {                                                                                                           \
		VkResult _ret = RET;                                                                                   \
		if (_ret != VK_SUCCESS) {                                                                              \
			vk_print_result(vk, __FILE__, __LINE__, __func__, _ret, FUNC_STR);                             \
			return _ret;                                                                                   \
		}                                                                                                      \
	} while (false)

/*!
 * This define will error if @p RET is not @p VK_SUCCESS, printing out that the
 * @p FUNC_STR string has failed, then returns @p TO_RET. The implicit argument
 * @p vk will be used for the @ref vk_print_result call.
 *
 * Use this macro when your function doesn't return a `VkResult`.
 *
 * @param RET      The @p VkResult to check.
 * @param FUNC_STR String literal with the function name, used for logging.
 * @param TO_RET   Value to return, upon error
 *
 * @ingroup aux_vk
 *
 * @see VK_CHK_ONLY_PRINT
 * @see VK_CHK_AND_RET
 * @see VK_CHK_WITH_GOTO
 */
#define VK_CHK_WITH_RET(RET, FUNC_STR, TO_RET)                                                                         \
	do {                                                                                                           \
		VkResult _ret = RET;                                                                                   \
		if (_ret != VK_SUCCESS) {                                                                              \
			vk_print_result(vk, __FILE__, __LINE__, __func__, _ret, FUNC_STR);                             \
			return TO_RET;                                                                                 \
		}                                                                                                      \
	} while (false)

/*!
 * This define will error if @p RET is not @p VK_SUCCESS, printing out that the
 * @p FUNC_STR string has failed, then goto @p GOTO. The implicit argument @p vk
 * will be used for the @ref vk_print_result call.
 *
 * Use this macro when your function needs to `goto` some cleanup code and
 * return from there.
 *
 * @param RET      The @p VkResult to check.
 * @param FUNC_STR String literal with the function name, used for logging.
 * @param GOTO     Label to jump to, upon error
 *
 * @ingroup aux_vk
 *
 * @see VK_CHK_ONLY_PRINT
 * @see VK_CHK_AND_RET
 * @see VK_CHK_WITH_RET
 */
#define VK_CHK_WITH_GOTO(RET, FUNC_STR, GOTO)                                                                          \
	do {                                                                                                           \
		VkResult _ret = RET;                                                                                   \
		if (_ret != VK_SUCCESS) {                                                                              \
			vk_print_result(vk, __FILE__, __LINE__, __func__, _ret, FUNC_STR);                             \
			goto GOTO;                                                                                     \
		}                                                                                                      \
	} while (false)

//! For xrt_result_t, see @ref VK_CHK_AND_RET.
#define XVK_CHK_AND_RET(...) U_LOG_CHK_AND_RET(vk->log_level, __VA_ARGS__)
//! For xrt_result_t, see @ref VK_CHK_WITH_GOTO.
#define XVK_CHK_WITH_GOTO(...) U_LOG_CHK_WITH_GOTO(vk->log_level, __VA_ARGS__)
//! For xrt_result_t, see @ref VK_CHK_WITH_RET.
#define XVK_CHK_WITH_RET(...) U_LOG_CHK_WITH_RET(vk->log_level, __VA_ARGS__)
//! For xrt_result_t, see @ref VK_CHK_ONLY_PRINT.
#define XVK_CHK_ONLY_PRINT(...) U_LOG_CHK_ONLY_PRINT(vk->log_level, __VA_ARGS__)
//! For xrt_result_t, see @ref VK_CHK_ALWAYS_RET.
#define XVK_CHK_ALWAYS_RET(...) U_LOG_CHK_ALWAYS_RET(vk->log_level, __VA_ARGS__)

static inline void
vk_append_to_pnext_chain(VkBaseInStructure *head, VkBaseInStructure *new_struct)
{
	assert(new_struct->pNext == NULL);
	// Insert ourselves between head and its previous pNext
	new_struct->pNext = head->pNext;
	head->pNext = new_struct;
}


/*
 *
 * Flag bits string functions.
 *
 */

/*!
 * Returns the format feature flag if one valid bit is set,
 * if multiple bits are set, will return 'MULTIPLE BIT SET'.
 */
XRT_CHECK_RESULT const char *
vk_format_feature_flag_string(VkFormatFeatureFlagBits bits, bool null_on_unknown);

/*!
 * Returns the image usage flag if one valid bit is set,
 * if multiple bits are set, will return 'MULTIPLE BIT SET'.
 */
XRT_CHECK_RESULT const char *
vk_image_usage_flag_string(VkImageUsageFlagBits bits, bool null_on_unknown);

/*!
 * Returns the composite alpha flag if one valid bit is set,
 * if multiple bits are set, will return 'MULTIPLE BIT SET'.
 */
XRT_CHECK_RESULT const char *
vk_composite_alpha_flag_string(VkCompositeAlphaFlagBitsKHR bits, bool null_on_unknown);

/*!
 * Returns the surface transform flag if one valid bit is set,
 * if multiple bits are set, will return 'MULTIPLE BIT SET'.
 */
XRT_CHECK_RESULT const char *
vk_surface_transform_flag_string(VkSurfaceTransformFlagBitsKHR bits, bool null_on_unknown);

#ifdef VK_KHR_display
/*!
 * Returns the display plane alpha flag if one valid bit is set,
 * if multiple bits are set, will return 'MULTIPLE BIT SET'.
 */
XRT_CHECK_RESULT const char *
vk_display_plane_alpha_flag_string(VkDisplayPlaneAlphaFlagBitsKHR bits, bool null_on_unknown);
#endif


/*
 *
 * Function and helpers.
 *
 */

#define VK_TRACE(d, ...) U_LOG_IFL_T(d->log_level, __VA_ARGS__)
#define VK_DEBUG(d, ...) U_LOG_IFL_D(d->log_level, __VA_ARGS__)
#define VK_INFO(d, ...) U_LOG_IFL_I(d->log_level, __VA_ARGS__)
#define VK_WARN(d, ...) U_LOG_IFL_W(d->log_level, __VA_ARGS__)
#define VK_ERROR(d, ...) U_LOG_IFL_E(d->log_level, __VA_ARGS__)


/*
 *
 * Debug helper functions, in the vk_debug.c file.
 *
 */

#if defined(VK_EXT_debug_utils) || defined(XRT_DOXYGEN)

/*!
 * Uses VK_EXT_debug_utils to set a name for an object, for easier debugging.
 *
 * @ingroup aux_vk
 */
void
vk_name_object(struct vk_bundle *vk, VkObjectType type, uint64_t object, const char *name);

/*!
 * Small helper for @ref vk_name_object that makes use of pre-process to avoid
 * writing out long type names.
 *
 * @ingroup aux_vk
 */
#define VK_NAME_OBJ(VK, TYPE, SUFFIX, OBJ, NAME)                                                                       \
	do {                                                                                                           \
		if ((VK)->has_EXT_debug_utils) {                                                                       \
			XRT_MAYBE_UNUSED TYPE _thing = (TYPE)(OBJ);                                                    \
			vk_name_object(VK, VK_OBJECT_TYPE_##SUFFIX, (uint64_t)OBJ, NAME);                              \
		}                                                                                                      \
	} while (false)


#else

#define VK_NAME_OBJ(VK, TYPE, SUFFIX, OBJ, NAME) VK_NAME_OBJ_DISABLED(VK, TYPE, OBJ)

#endif

/*!
 * Some combinations of Vulkan implementation and types are broken, we still
 * want type safety so we have this define. Examples of broken combinations:
 *
 * 1. Both Mesa and the Vulkan loader didn't support setting names on the
 *    VkInstance, loader got support in 1.3.261 and Mesa hasn't as of writing.
 * 2. For Mesa drivers we can not name VkSurfaceKHR objects on some systems as
 *    it causes memory corruption, asserts, crashes or functions failing. This
 *    is as of writing broken on the 23.2.1 release, fixed in main and scheduled
 *    for the 23.2.2 release.
 * 3. Mesa RADV leaks the name strings for VkDescriptorSet objects for pools
 *    that we use the reset function.
 *
 * @ingroup aux_vk
 */
#define VK_NAME_OBJ_DISABLED(VK, TYPE, OBJ)                                                                            \
	do {                                                                                                           \
		XRT_MAYBE_UNUSED TYPE _thing = (TYPE)(OBJ);                                                            \
	} while (false)


// clang-format off
// VK_DEFINE_HANDLE types are always pointers
#define VK_NAME_INSTANCE(VK, OBJ, NAME) VK_NAME_OBJ_DISABLED(VK, VkInstance, (uintptr_t)OBJ)
#define VK_NAME_PHYSICAL_DEVICE(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkPhysicalDevice, PHYSICAL_DEVICE, (uintptr_t)OBJ, NAME)
#define VK_NAME_DEVICE(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkDevice, DEVICE, (uintptr_t)OBJ, NAME)
#define VK_NAME_QUEUE(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkQueue, QUEUE, (uintptr_t)OBJ, NAME)
#define VK_NAME_COMMAND_BUFFER(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkCommandBuffer, COMMAND_BUFFER, (uintptr_t)OBJ, NAME)
// VK_DEFINE_NON_DISPATCHABLE_HANDLE types are pointers in 64-bits and uint64_t in 32-bits
#define VK_NAME_SEMAPHORE(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkSemaphore, SEMAPHORE, OBJ, NAME)
#define VK_NAME_FENCE(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkFence, FENCE, OBJ, NAME)
#define VK_NAME_DEVICE_MEMORY(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkDeviceMemory, DEVICE_MEMORY, OBJ, NAME)
#define VK_NAME_BUFFER(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkBuffer, BUFFER, OBJ, NAME)
#define VK_NAME_IMAGE(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkImage, IMAGE, OBJ, NAME)
#define VK_NAME_EVENT(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkEvent, EVENT, OBJ, NAME)
#define VK_NAME_QUERY_POOL(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkQueryPool, QUERY_POOL, OBJ, NAME)
#define VK_NAME_BUFFER_VIEW(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkBufferView, BUFFER_VIEW, OBJ, NAME)
#define VK_NAME_IMAGE_VIEW(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkImageView, IMAGE_VIEW, OBJ, NAME)
#define VK_NAME_SHADER_MODULE(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkShaderModule, SHADER_MODULE, OBJ, NAME)
#define VK_NAME_PIPELINE_CACHE(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkPipelineCache, PIPELINE_CACHE, OBJ, NAME)
#define VK_NAME_PIPELINE_LAYOUT(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkPipelineLayout, PIPELINE_LAYOUT, OBJ, NAME)
#define VK_NAME_RENDER_PASS(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkRenderPass, RENDER_PASS, OBJ, NAME)
#define VK_NAME_PIPELINE(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkPipeline, PIPELINE, OBJ, NAME)
#define VK_NAME_DESCRIPTOR_SET_LAYOUT(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkDescriptorSetLayout, DESCRIPTOR_SET_LAYOUT, OBJ, NAME)
#define VK_NAME_SAMPLER(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkSampler, SAMPLER, OBJ, NAME)
#define VK_NAME_DESCRIPTOR_POOL(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkDescriptorPool, DESCRIPTOR_POOL, OBJ, NAME)
#define VK_NAME_DESCRIPTOR_SET(VK, OBJ, NAME) VK_NAME_OBJ_DISABLED(VK, VkDescriptorSet, OBJ)
#define VK_NAME_FRAMEBUFFER(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkFramebuffer, FRAMEBUFFER, OBJ, NAME)
#define VK_NAME_COMMAND_POOL(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkCommandPool, COMMAND_POOL, OBJ, NAME)

#define VK_NAME_SURFACE(VK, OBJ, NAME) VK_NAME_OBJ_DISABLED(VK, VkSurfaceKHR, OBJ)
#define VK_NAME_SWAPCHAIN(VK, OBJ, NAME) VK_NAME_OBJ(VK, VkSwapchainKHR, SWAPCHAIN_KHR, OBJ, NAME)
// clang-format on


/*
 *
 * Enumeration helpers, in the vk_enumerate.c file.
 *
 */

/*!
 * Return the @p VkExtensionProperties of the given @p layer_name, NULL means
 * the "base" driver instance.
 *
 * @ingroup aux_vk
 */
VkResult
vk_enumerate_instance_extensions_properties(struct vk_bundle *vk,
                                            const char *layer_name,
                                            uint32_t *out_prop_count,
                                            VkExtensionProperties **out_props);

/*!
 * Enumerate the physical devices of the @p VkInstance that has been opened on
 * the given @ref vk_bundle.
 *
 * @ingroup aux_vk
 */
VkResult
vk_enumerate_physical_devices(struct vk_bundle *vk,
                              uint32_t *out_physical_device_count,
                              VkPhysicalDevice **out_physical_devices);

/*!
 * Enumerate the extension properties of the given @p VkPhysicalDevice for the
 * named @p layer_name, NULL means the "base" driver physical device.
 *
 * @ingroup aux_vk
 */
VkResult
vk_enumerate_physical_device_extension_properties(struct vk_bundle *vk,
                                                  VkPhysicalDevice physical_device,
                                                  const char *layer_name,
                                                  uint32_t *out_prop_count,
                                                  VkExtensionProperties **out_props);

/*!
 * Get the queue family properties of the given @p VkPhysicalDevice, note this
 * function does not return a result, it will assert if it can not allocate the
 * array of properties. If there are no queue family properties, the function
 * will return a count of 0 and set the output pointer to NULL.
 *
 * @ingroup aux_vk
 */
void
vk_get_physical_device_queue_family_properties(struct vk_bundle *vk,
                                               VkPhysicalDevice physical_device,
                                               uint32_t *out_prop_count,
                                               VkQueueFamilyProperties **out_props);

#if defined(VK_KHR_surface) || defined(XRT_DOXYGEN)
/*!
 * Enumerate the surface formats of the given @p VkSurfaceKHR,
 * returns a list of @p VkSurfaceFormatKHR, not @p VkFormat.
 *
 * @ingroup aux_vk
 */
VkResult
vk_enumerate_surface_formats(struct vk_bundle *vk,
                             VkSurfaceKHR surface,
                             uint32_t *out_format_count,
                             VkSurfaceFormatKHR **out_formats);

/*!
 * Enumerate the present modes of the given @p VkSurfaceKHR.
 *
 * @ingroup aux_vk
 */
VkResult
vk_enumerate_surface_present_modes(struct vk_bundle *vk,
                                   VkSurfaceKHR surface,
                                   uint32_t *out_present_mode_count,
                                   VkPresentModeKHR **out_present_modes);
#endif

#if defined(VK_KHR_swapchain) || defined(XRT_DOXYGEN)
/*!
 * Enumerate the images of the given @p VkSwapchainKHR.
 *
 * @ingroup aux_vk
 */
VkResult
vk_enumerate_swapchain_images(struct vk_bundle *vk,
                              VkSwapchainKHR swapchain,
                              uint32_t *out_image_count,
                              VkImage **out_images);
#endif

#if defined(VK_USE_PLATFORM_DISPLAY_KHR) || defined(XRT_DOXYGEN)
/*!
 * Enumerate the display properties of the given @p VkPhysicalDevice.
 *
 * @ingroup aux_vk
 */
VkResult
vk_enumerate_physical_device_display_properties(struct vk_bundle *vk,
                                                VkPhysicalDevice physical_device,
                                                uint32_t *out_prop_count,
                                                VkDisplayPropertiesKHR **out_props);

/*!
 * Enumerate the display plane properties of the given @p VkPhysicalDevice.
 *
 * @ingroup aux_vk
 */
VkResult
vk_enumerate_physical_display_plane_properties(struct vk_bundle *vk,
                                               VkPhysicalDevice physical_device,
                                               uint32_t *out_prop_count,
                                               VkDisplayPlanePropertiesKHR **out_props);

/*!
 * Enumerate the mode properties of the given @p VkDisplayKHR, which belongs
 * to the given @p VkPhysicalDevice.
 *
 * @ingroup aux_vk
 */
VkResult
vk_enumerate_display_mode_properties(struct vk_bundle *vk,
                                     VkPhysicalDevice physical_device,
                                     VkDisplayKHR display,
                                     uint32_t *out_prop_count,
                                     VkDisplayModePropertiesKHR **out_props);
#endif


/*
 *
 * Struct init functions, in the vk_function_loaders.c file.
 *
 */

/*!
 * Can be done on a completely bare bundle.
 *
 * @ingroup aux_vk
 */
VkResult
vk_get_loader_functions(struct vk_bundle *vk, PFN_vkGetInstanceProcAddr g);

/*!
 * Requires a instance to have been created and set on the bundle.
 *
 * @ingroup aux_vk
 */
VkResult
vk_get_instance_functions(struct vk_bundle *vk);

/*!
 * Requires a device to have been created and set on the bundle.
 *
 * @ingroup aux_vk
 */
VkResult
vk_get_device_functions(struct vk_bundle *vk);


/*
 *
 * Bundle init functions, in the vk_bundle_init.c file.
 *
 */

/*!
 * Check if the required instance extensions are supported, if not print error
 * message with all extensions missing, returns VK_ERROR_EXTENSION_NOT_PRESENT
 * if not all extensions are supported.
 *
 * @ingroup aux_vk
 */
VkResult
vk_check_required_instance_extensions(struct vk_bundle *vk, struct u_extension_list *required_instance_ext_list);

/*!
 * Build instance extensions from required and optional instance extensions.
 * This function enumerates available instance extensions, checks required ones,
 * and builds a final list. Returns VK_SUCCESS if successful.
 * Only requires @ref vk_get_loader_functions to have been called.
 *
 * @ingroup aux_vk
 */
VkResult
vk_build_instance_extensions(struct vk_bundle *vk,
                             struct u_extension_list *required_instance_ext_list,
                             struct u_extension_list *optional_instance_ext_list,
                             struct u_extension_list **out_instance_ext_list);

/*!
 * Fills in has_* in vk_bundle given a string of prefiltered instance extensions
 */
void
vk_fill_in_has_instance_extensions(struct vk_bundle *vk, struct u_extension_list *ext_list);

/*!
 * Setup the physical device, this is called by vk_create_device but has uses
 * for outside of that.
 *
 * @ingroup aux_vk
 */
VkResult
vk_select_physical_device(struct vk_bundle *vk, int forced_index);

/*!
 * Used to enable device features as a argument @ref vk_create_device.
 *
 * @ingroup aux_vk
 */
struct vk_device_features
{
	bool shader_image_gather_extended;
	bool shader_storage_image_write_without_format;
	bool null_descriptor;
	bool timeline_semaphore;
	bool synchronization_2;
	bool ext_fmt_resolve;
	bool storage_buffer_8bit_access;
	bool present_wait;
	bool video_maintenance_1;
};

/*!
 * Creates a VkDevice and initialises the VkQueue.
 *
 * The @p vk_bundle must have been zero initialized, have the instance functions
 * loaded and a valid instance.
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT VkResult
vk_create_device(struct vk_bundle *vk,
                 int forced_index,
                 bool only_compute,
                 VkQueueGlobalPriorityEXT global_priority,
                 struct u_extension_list *required_device_ext_list,
                 struct u_extension_list *optional_device_ext_list,
                 const struct vk_device_features *optional_device_features);

/*!
 * @brief Initialize mutexes in the @ref vk_bundle.
 *
 * Not required for all uses, but a precondition for some.
 *
 * @ingroup aux_vk
 */
VkResult
vk_init_mutex(struct vk_bundle *vk);

/*!
 * @brief De-initialize mutexes in the @ref vk_bundle.
 *
 * @ingroup aux_vk
 */
VkResult
vk_deinit_mutex(struct vk_bundle *vk);

static inline void
vk_queue_lock(struct vk_bundle_queue *q)
{
	assert(q != NULL);
	os_mutex_lock(&q->mutex);
}

static inline int
vk_queue_trylock(struct vk_bundle_queue *q)
{
	assert(q != NULL);
	return os_mutex_trylock(&q->mutex);
}

static inline void
vk_queue_unlock(struct vk_bundle_queue *q)
{
	assert(q != NULL);
	os_mutex_unlock(&q->mutex);
}

/*!
 * Initialize a bundle with objects given to us by client code,
 * used by @ref client_vk_compositor in @ref comp_client.
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT VkResult
vk_init_from_given(struct vk_bundle *vk,
                   PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr,
                   VkInstance instance,
                   VkPhysicalDevice physical_device,
                   VkDevice device,
                   uint32_t queue_family_index,
                   uint32_t queue_index,
                   bool external_fence_fd_enabled,
                   bool external_semaphore_fd_enabled,
                   bool timeline_semaphore_enabled,
                   bool image_format_list_enabled,
                   bool debug_utils_enabled,
                   enum u_logging_level log_level);


/*
 *
 * Other functions.
 *
 */

/*!
 * @ingroup aux_vk
 */
bool
vk_get_memory_type(struct vk_bundle *vk, uint32_t type_bits, VkMemoryPropertyFlags memory_props, uint32_t *out_type_id);

/*!
 * Allocate memory for an image and bind it to that image.
 *
 * Handles the following steps:
 *
 * - calling vkGetImageMemoryRequirements
 *   - comparing against the max_size
 * - getting the memory type (as dictated by the VkMemoryRequirements and
 *   VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
 * - calling vkAllocateMemory
 * - calling vkBindImageMemory
 * - calling vkDestroyMemory in case of an error.
 *
 * If this fails, it cleans up the VkDeviceMemory.
 *
 * @param vk Vulkan bundle
 * @param image The VkImage to allocate for and bind.
 * @param requirements Memory requirements used for finding the memory type and the size.
 * @param pNext_for_allocate (Optional) a pointer to use in the pNext chain of
 *        VkMemoryAllocateInfo.
 * @param caller_name Used for error printing, this function is called from
 *        various sources and takes next chains that could influence the result
 *        of various calls inside of it. Since it's up to this function to print
 *        any errors it will add the caller name to error messages.
 * @param out_mem Output parameter: will be set to the allocated memory if
 *        everything succeeds. Not modified if there is an error.
 *
 * If this fails, you may want to destroy your VkImage as well, since this
 * routine is usually used in combination with vkCreateImage.
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT VkResult
vk_alloc_and_bind_image_memory(struct vk_bundle *vk,
                               VkImage image,
                               const VkMemoryRequirements *requirements,
                               const void *pNext_for_allocate,
                               const char *caller_name,
                               VkDeviceMemory *out_mem);

/*!
 *
 * @brief Creates a Vulkan device memory and image from a native graphics buffer handle.
 *
 * In case of error, ownership is never transferred and the caller should close the handle themselves.
 *
 * In case of success, the underlying Vulkan functionality's ownership semantics apply: ownership of the @p image_native
 * handle may have transferred, a reference may have been added, or the Vulkan objects may rely on the caller to keep
 * the native handle alive until the Vulkan objects are destroyed. Which option applies depends on the particular native
 * handle type used.
 *
 * See the corresponding specification texts:
 *
 * - Windows:
 *   https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VkImportMemoryWin32HandleInfoKHR
 * - Linux: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VkImportMemoryFdInfoKHR
 * - Android:
 *   https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VkImportAndroidHardwareBufferInfoANDROID
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT VkResult
vk_create_image_from_native(struct vk_bundle *vk,
                            const struct xrt_swapchain_create_info *info,
                            struct xrt_image_native *image_native,
                            VkImage *out_image,
                            VkDeviceMemory *out_mem);

/*!
 * Given a DeviceMemory handle created to be exportable, outputs the native buffer type (FD on desktop Linux)
 * equivalent.
 *
 * Caller assumes ownership of handle which should be unreferenced with @ref u_graphics_buffer_unref when no longer
 * needed.
 *
 * @param vk Vulkan bundle
 * @param device_memory The memory to get the handle of
 * @param[out] out_handle A pointer to the handle to populate
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT VkResult
vk_get_native_handle_from_device_memory(struct vk_bundle *vk,
                                        VkDeviceMemory device_memory,
                                        xrt_graphics_buffer_handle_t *out_handle);

/*!
 * @ingroup aux_vk
 * Helper to create a VkImage
 */
VkResult
vk_create_image_simple(struct vk_bundle *vk,
                       VkExtent2D extent,
                       VkFormat format,
                       VkImageUsageFlags usage,
                       VkDeviceMemory *out_mem,
                       VkImage *out_image);

/*!
 * Helper to create a mutable RG88B8A8 VkImage that specializes in the two
 * UNORM and SRGB variants of that formats.
 *
 * @ingroup aux_vk
 */
VkResult
vk_create_image_mutable_rgba(
    struct vk_bundle *vk, VkExtent2D extent, VkImageUsageFlags usage, VkDeviceMemory *out_mem, VkImage *out_image);

/*!
 * @ingroup aux_vk
 * Helper to create a VkImage, with more options for tiling and memory storage.
 */
VkResult
vk_create_image_advanced(struct vk_bundle *vk,
                         VkExtent3D extent,
                         VkFormat format,
                         VkImageTiling image_tiling,
                         VkImageUsageFlags image_usage_flags,
                         VkMemoryPropertyFlags memory_property_flags,
                         VkDeviceMemory *out_mem,
                         VkImage *out_image);

/*!
 * @ingroup aux_vk
 */
VkResult
vk_create_sampler(struct vk_bundle *vk, VkSamplerAddressMode clamp_mode, VkSampler *out_sampler);


/*
 *
 * Helpers for creating ímage views.
 *
 */

/*!
 * @ingroup aux_vk
 */
VkResult
vk_create_view(struct vk_bundle *vk,
               VkImage image,
               VkImageViewType type,
               VkFormat format,
               VkImageSubresourceRange subresource_range,
               VkImageView *out_view);

/*!
 * @ingroup aux_vk
 */
VkResult
vk_create_view_swizzle(struct vk_bundle *vk,
                       VkImage image,
                       VkImageViewType type,
                       VkFormat format,
                       VkImageSubresourceRange subresource_range,
                       VkComponentMapping components,
                       VkImageView *out_view);

/*!
 * Creates a swizzled image view with a specific subset of usage, useful for a mutable image where one format might
 * not support all usages defined by the image.
 *
 * @ingroup aux_vk
 */
VkResult
vk_create_view_swizzle_usage(struct vk_bundle *vk,
                             VkImage image,
                             VkImageViewType type,
                             VkFormat format,
                             VkImageUsageFlags image_usage,
                             VkImageSubresourceRange subresource_range,
                             VkComponentMapping components,
                             VkImageView *out_view);

/*!
 * Creates a image with a specific subset of usage, useful for a mutable images
 * where one format might not support all usages defined by the image.
 *
 * @ingroup aux_vk
 */
VkResult
vk_create_view_usage(struct vk_bundle *vk,
                     VkImage image,
                     VkImageViewType type,
                     VkFormat format,
                     VkImageUsageFlags image_usage,
                     VkImageSubresourceRange subresource_range,
                     VkImageView *out_view);


/*
 *
 * Helpers for creating descriptor pools and sets.
 *
 */

bool
vk_init_descriptor_pool(struct vk_bundle *vk,
                        const VkDescriptorPoolSize *pool_sizes,
                        uint32_t pool_size_count,
                        uint32_t set_count,
                        VkDescriptorPool *out_descriptor_pool);

bool
vk_allocate_descriptor_sets(struct vk_bundle *vk,
                            VkDescriptorPool descriptor_pool,
                            uint32_t count,
                            const VkDescriptorSetLayout *set_layout,
                            VkDescriptorSet *sets);


/*
 *
 * Helpers for creating buffers.
 *
 */

bool
vk_buffer_init(struct vk_bundle *vk,
               VkDeviceSize size,
               VkBufferUsageFlags usage,
               VkMemoryPropertyFlags properties,
               VkBuffer *out_buffer,
               VkDeviceMemory *out_mem);

void
vk_buffer_destroy(struct vk_buffer *self, struct vk_bundle *vk);

bool
vk_update_buffer(struct vk_bundle *vk, float *buffer, size_t buffer_size, VkDeviceMemory memory);


/*
 *
 * Helpers for writing command buffers, in the vk_helpers.c file.
 *
 */

/*!
 * Inserts a image barrier command, doesn't take any locks, the calling code
 * will need hold the lock for the pool that cmd_buffer is from or ensure it is
 * externally synchronized.
 *
 * @ingroup aux_vk
 */
void
vk_cmd_image_barrier_locked(struct vk_bundle *vk,
                            VkCommandBuffer cmd_buffer,
                            VkImage image,
                            VkAccessFlags src_access_mask,
                            VkAccessFlags dst_access_mask,
                            VkImageLayout old_image_layout,
                            VkImageLayout new_image_layout,
                            VkPipelineStageFlags src_stage_mask,
                            VkPipelineStageFlags dst_stage_mask,
                            VkImageSubresourceRange subresource_range);

/*!
 * Inserts a image barrier command specifically for GPU commands, doesn't take
 * any locks, the calling code will need hold the lock for the pool that
 * cmd_buffer is from or ensure it is externally synchronized.
 *
 * @ingroup aux_vk
 */
void
vk_cmd_image_barrier_gpu_locked(struct vk_bundle *vk,
                                VkCommandBuffer cmd_buffer,
                                VkImage image,
                                VkAccessFlags src_access_mask,
                                VkAccessFlags dst_access_mask,
                                VkImageLayout old_layout,
                                VkImageLayout new_layout,
                                VkImageSubresourceRange subresource_range);

#if defined(VK_EXT_debug_utils) || defined(XRT_DOXYGEN)
/*!
 * Uses VK_EXT_debug_utils to insert debug label into a VkCommandBuffer.
 *
 * In the vk_debug.c file.
 *
 * @ingroup aux_vk
 */
void
vk_cmd_insert_label(struct vk_bundle *vk, VkCommandBuffer cmd_buffer, const char *name);
#endif


/*
 *
 * State creation helpers, in the vk_state_creators.c file.
 *
 */

/*!
 * Arguments to @ref vk_create_descriptor_pool function.
 */
struct vk_descriptor_pool_info
{
	uint32_t uniform_per_descriptor_count;        //!< VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER
	uint32_t sampler_per_descriptor_count;        //!< VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
	uint32_t storage_image_per_descriptor_count;  //!< VK_DESCRIPTOR_TYPE_STORAGE_IMAGE
	uint32_t storage_buffer_per_descriptor_count; //!< VK_DESCRIPTOR_TYPE_STORAGE_BUFFER

	//! The max count of created descriptors.
	uint32_t descriptor_count;

	//! Are descriptors freeable, or must vkResetDescriptorPool be used.
	bool freeable;
};

/*!
 * Creates a descriptor pool, made for a single layout.
 *
 * Does error logging.
 */
VkResult
vk_create_descriptor_pool(struct vk_bundle *vk,
                          const struct vk_descriptor_pool_info *info,
                          VkDescriptorPool *out_descriptor_pool);

/*!
 * Creates a descriptor set.
 *
 * Does error logging.
 */
VkResult
vk_create_descriptor_set(struct vk_bundle *vk,
                         VkDescriptorPool descriptor_pool,
                         VkDescriptorSetLayout descriptor_layout,
                         VkDescriptorSet *out_descriptor_set);

/*!
 * Creates a pipeline layout from a single descriptor set layout.
 *
 * Does error logging.
 */
VkResult
vk_create_pipeline_layout(struct vk_bundle *vk,
                          VkDescriptorSetLayout descriptor_set_layout,
                          VkPipelineLayout *out_pipeline_layout);

/*!
 * Creates a pipeline cache.
 *
 * Does error logging.
 */
VkResult
vk_create_pipeline_cache(struct vk_bundle *vk, VkPipelineCache *out_pipeline_cache);

/*!
 * Creates a compute pipeline, assumes entry function is called 'main'.
 *
 * Does error logging.
 */
VkResult
vk_create_compute_pipeline(struct vk_bundle *vk,
                           VkPipelineCache pipeline_cache,
                           VkShaderModule shader,
                           VkPipelineLayout pipeline_layout,
                           const VkSpecializationInfo *specialization_info,
                           VkPipeline *out_compute_pipeline);


/*
 *
 * Sync objects, in the vk_sync_objects.c file.
 *
 */

/*!
 * Is there a good likelihood that the import/export of a timeline semaphore
 * will succeed, in other words will the below functions work.
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT bool
vk_can_import_and_export_timeline_semaphore(struct vk_bundle *vk);

/*!
 * @brief Creates a Vulkan fence, submits it to the default VkQueue and return
 * its native graphics sync handle.
 *
 * In case of error, out_native is not touched by the function.
 *
 * See @ref vk_create_fence_sync_from_native for ownership semantics on import.
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT VkResult
vk_create_and_submit_fence_native(struct vk_bundle *vk, xrt_graphics_sync_handle_t *out_native);

/*!
 * @brief Creates a Vulkan fence from a native graphics sync handle.
 *
 * In case of error, ownership is never transferred and the caller should close the handle themselves.
 *
 * In case of success, the underlying Vulkan functionality's ownership semantics apply: ownership of the @p native
 * handle may have transferred, a reference may have been added, or the Vulkan object may rely on the caller to keep the
 * native handle alive until the Vulkan object is destroyed. Which option applies depends on the particular native
 * handle type used.
 *
 * See the corresponding Vulkan specification text:
 * https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#synchronization-fences-importing
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT VkResult
vk_create_fence_sync_from_native(struct vk_bundle *vk, xrt_graphics_sync_handle_t native, VkFence *out_fence);

/*!
 * Creates a Vulkan semaphore and a native graphics sync handle.
 *
 * In case of success, the underlying Vulkan functionality's ownership semantics
 * apply: ownership of the @p native handle may have transferred, a reference
 * may have been added, or the Vulkan object may rely on the caller to keep the
 * native handle alive until the Vulkan object is destroyed. Which option
 * applies depends on the particular native handle type used.
 *
 * In case of error, neither @p out_sem and @p out_native is not touched by the
 * function so the caller only becomes responsible for the output on success.
 *
 * See the corresponding Vulkan specification text:
 * https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#synchronization-semaphores
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT VkResult
vk_create_semaphore_and_native(struct vk_bundle *vk, VkSemaphore *out_sem, xrt_graphics_sync_handle_t *out_native);

#if defined(VK_KHR_timeline_semaphore) || defined(XRT_DOXYGEN)
/*
 * Creates a Vulkan timeline semaphore and a native graphics sync
 * handle, see @ref vk_create_semaphore_and_native for more details.
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT VkResult
vk_create_timeline_semaphore_and_native(struct vk_bundle *vk,
                                        VkSemaphore *out_sem,
                                        xrt_graphics_sync_handle_t *out_native);
#endif

/*!
 * @brief Creates a Vulkan semaphore from a native graphics sync handle.
 *
 * In case of error, ownership is never transferred and the caller should close the handle themselves.
 *
 * In case of success, the underlying Vulkan functionality's ownership semantics apply: ownership of the @p native
 * handle may have transferred, a reference may have been added, or the Vulkan object may rely on the caller to keep the
 * native handle alive until the Vulkan object is destroyed. Which option applies depends on the particular native
 * handle type used.
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT VkResult
vk_create_semaphore_from_native(struct vk_bundle *vk, xrt_graphics_sync_handle_t native, VkSemaphore *out_sem);

#if defined(VK_KHR_timeline_semaphore) || defined(XRT_DOXYGEN)
/*!
 * @brief Creates a Vulkan timeline semaphore from a native graphics sync
 * handle, see @ref vk_create_semaphore_from_native for more details.
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT VkResult
vk_create_timeline_semaphore_from_native(struct vk_bundle *vk, xrt_graphics_sync_handle_t native, VkSemaphore *out_sem);
#endif


/*
 *
 * Time function(s), in the vk_time.c file.
 *
 */

#if defined(VK_EXT_calibrated_timestamps) || defined(XRT_DOXYGEN)
/*!
 * Convert timestamps in GPU ticks (as return by VkQueryPool timestamp queries)
 * into host CPU nanoseconds, same time domain as @ref os_monotonic_get_ns.
 *
 * Note the timestamp needs to be in the past and not to old, this is because
 * not all GPU has full 64 bit timer resolution. For instance a Intel GPU "only"
 * have 36 bits of valid timestamp and a tick period 83.3333 nanosecond,
 * equating to an epoch of 5726 seconds before overflowing. The function can
 * handle overflows happening between the given timestamps and when it is called
 * but only for one such epoch overflow, any more will only be treated as one
 * such overflow. So timestamps needs to be converted reasonably soon after they
 * have been captured.
 *
 * @param vk                The Vulkan bundle.
 * @param count             Number of timestamps to be converted.
 * @param[in,out] in_out_timestamps Array of timestamps to be converted, done in place.
 *
 * @ingroup aux_vk
 */
XRT_CHECK_RESULT VkResult
vk_convert_timestamps_to_host_ns(struct vk_bundle *vk, uint32_t count, uint64_t *in_out_timestamps);
#endif


#ifdef __cplusplus
}
#endif
