// Copyright (c) 2017-2019 Tobias Hector

// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do
// so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

//// Simpler Vulkan Synchronization ////
/*
In an effort to make Vulkan synchronization more accessible, I created this
stb-inspired single-header library in order to somewhat simplify the core
synchronization mechanisms in Vulkan - pipeline barriers and events.

Rather than the complex maze of enums and bit flags in Vulkan - many
combinations of which are invalid or nonsensical - this library collapses
this to a much shorter list of 40 distinct usage types, and a couple of
options for handling image layouts.

Use of other synchronization mechanisms such as semaphores, fences and render
passes are not addressed in this API at present.

USAGE

   #define the symbol THSVS_SIMPLER_VULKAN_SYNCHRONIZATION_IMPLEMENTATION in
   *one* C/C++ file before the #include of this file; the implementation
   will be generated in that file.

VERSION

    alpha.8

    Alpha.8 adds a host preinitialization state for linear images, as well as a number of new access sets for extensions released since the last update.


VERSION HISTORY

    alpha.7

    Alpha.7 incorporates a number of fixes from @gwihlidal, and fixes
    handling of pipeline stages in the presence of multiple access types or
    barriers in light of other recent changes.

    alpha.6

    Alpha.6 fixes a typo (VK_ACCESS_TYPE_MEMORY_READ|WRITE_BIT should have been VK_ACCESS_MEMORY_READ|WRITE_BIT), and sets the pipeline stage src and dst flag bits to VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT and VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT during initialization, not 0 as per alpha.5

    alpha.5

    Alpha.5 now correctly zeroes out the pipeline stage flags before trying to incrementally set bits on them... common theme here, whoops.

    alpha.4

    Alpha.4 now correctly zeroes out the access types before trying to incrementally set bits on them (!)

    alpha.3

    Alpha.3 changes the following:

    Uniform and vertex buffer access in one enum, matching D3D12_RESOURCE_STATE_VERTEX_AND_CONSTANT_BUFFER:
     - THSVS_ACCESS_ANY_SHADER_READ_UNIFORM_BUFFER_OR_VERTEX_BUFFER

    Color read *and* write access, matching D3D12_RESOURCE_STATE_RENDER_TARGET:
     - THSVS_ACCESS_COLOR_ATTACHMENT_READ_WRITE

    Also the "THSVS_ACCESS_*_SHADER_READ_SAMPLED_IMAGE" enums have been renamed to the form "THSVS_ACCESS_*_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER"

    alpha.2

    Alpha.2 adds four new resource states for "ANY SHADER ACCESS":
     - THSVS_ACCESS_ANY_SHADER_READ_UNIFORM_BUFFER
     - THSVS_ACCESS_ANY_SHADER_READ_SAMPLED_IMAGE
     - THSVS_ACCESS_ANY_SHADER_READ_OTHER
     - THSVS_ACCESS_ANY_SHADER_WRITE

    alpha.1

    Alpha.1 adds three new resource states:
     - THSVS_ACCESS_GENERAL (Any access on the device)
     - THSVS_ACCESS_DEPTH_ATTACHMENT_WRITE_STENCIL_READ_ONLY (Write access to only the depth aspect of a depth/stencil attachment)
     - THSVS_ACCESS_STENCIL_ATTACHMENT_WRITE_DEPTH_READ_ONLY (Write access to only the stencil aspect of a depth/stencil attachment)

    It also fixes a couple of typos, and adds clarification as to when extensions need to be enabled to use a feature.

    alpha.0

    This is the very first public release of this library; future revisions
    of this API may change the API in an incompatible manner as feedback is
    received.
    Once the version becomes stable, incompatible changes will only be made
    to major revisions of the API - minor revisions will only contain
    bug fixes or minor additions.

MEMORY ALLOCATION

    The thsvsCmdPipelineBarrier and thWaitEvents commands allocate temporary
    storage for the Vulkan barrier equivalents in order to pass them to the
    respective Vulkan commands.

    These use the `THSVS_TEMP_ALLOC(size)` and `THSVS_TEMP_FREE(x)` macros,
    which are by default set to alloca(size) and ((void)(x)), respectively.
    If you don't want to use stack space or would rather use your own
    allocation strategy, these can be overridden by defining these macros
    in before #include-ing the header file with
    THSVS_SIMPLER_VULKAN_SYNCHRONIZATION_IMPLEMENTATION defined.

    I'd rather avoid the need for these allocations in what are likely to be
    high-traffic commands, but currently just want to ship something - may
    revisit this at a future date based on feedback.

EXPRESSIVENESS COMPARED TO RAW VULKAN

    Despite the fact that this API is fairly simple, it expresses 99% of
    what you'd actually ever want to do in practice.
    Adding the missing expressiveness would result in increased complexity
    which didn't seem worth the trade off - however I would consider adding
    something for them in future if it becomes an issue.

    Here's a list of known things you can't express:

    * Execution only dependencies cannot be expressed.
      These are occasionally useful in conjunction with semaphores, or when
      trying to be clever with scheduling - but their usage is both limited
      and fairly tricky to get right anyway.
    * Depth/Stencil Input Attachments can be read in a shader using either
      VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL or
      VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL - this library
      *always* uses VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL.
      It's possible (though highly unlikely) when aliasing images that this
      results in unnecessary transitions.

ERROR CHECKS

    By default, as with the Vulkan API, this library does NOT check for
    errors.
    However, a number of optional error checks (THSVS_ERROR_CHECK_*) can be
    enabled by uncommenting the relevant #defines.
    Currently, error checks simply assert at the point a failure is detected
    and do not output an error message.
    I certainly do not claim they capture *all* possible errors, but they
    capture what should be some of the more common ones.
    Use of the Vulkan Validation Layers in tandem with this library is
    strongly recommended:
        https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers

ISSUES

    This header was clean of warnings using -Wall as of time of publishing
    on both gcc 4.8.4 and clang 3.5, using the c99 standard.

    There's a potential pitfall in thsvsCmdPipelineBarrier and thsvsCmdWaitEvents
    where alloca is used for temporary allocations. See MEMORY ALLOCATION
    for more information.

    Testing of this library is so far extremely limited with no immediate
    plans to add to that - so there's bound to be some amount of bugs.
    Please raise these issues on the repo issue tracker, or provide a fix
    via a pull request yourself if you're so inclined.
*/

#ifndef THSVS_SIMPLER_VULKAN_SYNCHRONIZATION_H
#define THSVS_SIMPLER_VULKAN_SYNCHRONIZATION_H 1

#include <stdint.h>

/*
ThsvsAccessType defines all potential resource usages in the Vulkan API.
*/
typedef enum ThsvsAccessType {
    THSVS_ACCESS_NONE, // No access. Useful primarily for initialization

    // Read access
    // Requires VK_NV_device_generated_commands to be enabled
    THSVS_ACCESS_COMMAND_BUFFER_READ_NV,                                                    // Command buffer read operation as defined by NV_device_generated_commands
    THSVS_ACCESS_INDIRECT_BUFFER,                                                           // Read as an indirect buffer for drawing or dispatch
    THSVS_ACCESS_INDEX_BUFFER,                                                              // Read as an index buffer for drawing
    THSVS_ACCESS_VERTEX_BUFFER,                                                             // Read as a vertex buffer for drawing
    THSVS_ACCESS_VERTEX_SHADER_READ_UNIFORM_BUFFER,                                         // Read as a uniform buffer in a vertex shader
    THSVS_ACCESS_VERTEX_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER,                  // Read as a sampled image/uniform texel buffer in a vertex shader
    THSVS_ACCESS_VERTEX_SHADER_READ_OTHER,                                                  // Read as any other resource in a vertex shader
    THSVS_ACCESS_TESSELLATION_CONTROL_SHADER_READ_UNIFORM_BUFFER,                           // Read as a uniform buffer in a tessellation control shader
    THSVS_ACCESS_TESSELLATION_CONTROL_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER,    // Read as a sampled image/uniform texel buffer  in a tessellation control shader
    THSVS_ACCESS_TESSELLATION_CONTROL_SHADER_READ_OTHER,                                    // Read as any other resource in a tessellation control shader
    THSVS_ACCESS_TESSELLATION_EVALUATION_SHADER_READ_UNIFORM_BUFFER,                        // Read as a uniform buffer in a tessellation evaluation shader
    THSVS_ACCESS_TESSELLATION_EVALUATION_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER, // Read as a sampled image/uniform texel buffer in a tessellation evaluation shader
    THSVS_ACCESS_TESSELLATION_EVALUATION_SHADER_READ_OTHER,                                 // Read as any other resource in a tessellation evaluation shader
    THSVS_ACCESS_GEOMETRY_SHADER_READ_UNIFORM_BUFFER,                                       // Read as a uniform buffer in a geometry shader
    THSVS_ACCESS_GEOMETRY_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER,                // Read as a sampled image/uniform texel buffer  in a geometry shader
    THSVS_ACCESS_GEOMETRY_SHADER_READ_OTHER,                                                // Read as any other resource in a geometry shader
    THSVS_ACCESS_TASK_SHADER_READ_UNIFORM_BUFFER_NV,                                        // Read as a uniform buffer in a task shader
    THSVS_ACCESS_TASK_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER_NV,                 // Read as a sampled image/uniform texel buffer in a task shader
    THSVS_ACCESS_TASK_SHADER_READ_OTHER_NV,                                                 // Read as any other resource in a task shader
    THSVS_ACCESS_MESH_SHADER_READ_UNIFORM_BUFFER_NV,                                        // Read as a uniform buffer in a mesh shader
    THSVS_ACCESS_MESH_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER_NV,                 // Read as a sampled image/uniform texel buffer in a mesh shader
    THSVS_ACCESS_MESH_SHADER_READ_OTHER_NV,                                                 // Read as any other resource in a mesh shader
    THSVS_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_EXT,                                       // Read as a transform feedback counter buffer
    THSVS_ACCESS_FRAGMENT_DENSITY_MAP_READ_EXT,                                             // Read as a fragment density map image
    THSVS_ACCESS_SHADING_RATE_READ_NV,                                                      // Read as a shading rate image
    THSVS_ACCESS_FRAGMENT_SHADER_READ_UNIFORM_BUFFER,                                       // Read as a uniform buffer in a fragment shader
    THSVS_ACCESS_FRAGMENT_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER,                // Read as a sampled image/uniform texel buffer  in a fragment shader
    THSVS_ACCESS_FRAGMENT_SHADER_READ_COLOR_INPUT_ATTACHMENT,                               // Read as an input attachment with a color format in a fragment shader
    THSVS_ACCESS_FRAGMENT_SHADER_READ_DEPTH_STENCIL_INPUT_ATTACHMENT,                       // Read as an input attachment with a depth/stencil format in a fragment shader
    THSVS_ACCESS_FRAGMENT_SHADER_READ_OTHER,                                                // Read as any other resource in a fragment shader
    THSVS_ACCESS_COLOR_ATTACHMENT_READ,                                                     // Read by standard blending/logic operations or subpass load operations
    THSVS_ACCESS_COLOR_ATTACHMENT_ADVANCED_BLENDING_EXT,                                    // Read by advanced blending, standard blending, logic operations, or subpass load operations
    THSVS_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ,                                             // Read by depth/stencil tests or subpass load operations
    THSVS_ACCESS_COMPUTE_SHADER_READ_UNIFORM_BUFFER,                                        // Read as a uniform buffer in a compute shader
    THSVS_ACCESS_COMPUTE_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER,                 // Read as a sampled image/uniform texel buffer in a compute shader
    THSVS_ACCESS_COMPUTE_SHADER_READ_OTHER,                                                 // Read as any other resource in a compute shader
    THSVS_ACCESS_ANY_SHADER_READ_UNIFORM_BUFFER,                                            // Read as a uniform buffer in any shader
    THSVS_ACCESS_ANY_SHADER_READ_UNIFORM_BUFFER_OR_VERTEX_BUFFER,                           // Read as a uniform buffer in any shader, or a vertex buffer
    THSVS_ACCESS_ANY_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER,                     // Read as a sampled image in any shader
    THSVS_ACCESS_ANY_SHADER_READ_OTHER,                                                     // Read as any other resource (excluding attachments) in any shader
    THSVS_ACCESS_TRANSFER_READ,                                                             // Read as the source of a transfer operation
    THSVS_ACCESS_HOST_READ,                                                                 // Read on the host

    // Requires VK_KHR_swapchain to be enabled
    THSVS_ACCESS_PRESENT, // Read by the presentation engine (i.e. vkQueuePresentKHR)

    // Requires VK_EXT_conditional_rendering to be enabled
    THSVS_ACCESS_CONDITIONAL_RENDERING_READ_EXT, // Read by conditional rendering

    // Requires VK_NV_ray_tracing to be enabled
    THSVS_ACCESS_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ_NV, // Read by a ray tracing shader as an acceleration structure
    THSVS_ACCESS_ACCELERATION_STRUCTURE_BUILD_READ_NV,              // Read as an acceleration structure during a build

    // Read accesses end
    THSVS_END_OF_READ_ACCESS,

    // Write access
    // Requires VK_NV_device_generated_commands to be enabled
    THSVS_ACCESS_COMMAND_BUFFER_WRITE_NV,              // Command buffer write operation
    THSVS_ACCESS_VERTEX_SHADER_WRITE,                  // Written as any resource in a vertex shader
    THSVS_ACCESS_TESSELLATION_CONTROL_SHADER_WRITE,    // Written as any resource in a tessellation control shader
    THSVS_ACCESS_TESSELLATION_EVALUATION_SHADER_WRITE, // Written as any resource in a tessellation evaluation shader
    THSVS_ACCESS_GEOMETRY_SHADER_WRITE,                // Written as any resource in a geometry shader

    // Requires VK_NV_mesh_shading to be enabled
    THSVS_ACCESS_TASK_SHADER_WRITE_NV, // Written as any resource in a task shader
    THSVS_ACCESS_MESH_SHADER_WRITE_NV, // Written as any resource in a mesh shader

    // Requires VK_EXT_transform_feedback to be enabled
    THSVS_ACCESS_TRANSFORM_FEEDBACK_WRITE_EXT,         // Written as a transform feedback buffer
    THSVS_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_EXT, // Written as a transform feedback counter buffer

    THSVS_ACCESS_FRAGMENT_SHADER_WRITE,          // Written as any resource in a fragment shader
    THSVS_ACCESS_COLOR_ATTACHMENT_WRITE,         // Written as a color attachment during rendering, or via a subpass store op
    THSVS_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE, // Written as a depth/stencil attachment during rendering, or via a subpass store op

    // Requires VK_KHR_maintenance2 to be enabled
    THSVS_ACCESS_DEPTH_ATTACHMENT_WRITE_STENCIL_READ_ONLY, // Written as a depth aspect of a depth/stencil attachment during rendering, whilst the stencil aspect is read-only
    THSVS_ACCESS_STENCIL_ATTACHMENT_WRITE_DEPTH_READ_ONLY, // Written as a stencil aspect of a depth/stencil attachment during rendering, whilst the depth aspect is read-only

    THSVS_ACCESS_COMPUTE_SHADER_WRITE, // Written as any resource in a compute shader
    THSVS_ACCESS_ANY_SHADER_WRITE,     // Written as any resource in any shader
    THSVS_ACCESS_TRANSFER_WRITE,       // Written as the destination of a transfer operation
    THSVS_ACCESS_HOST_PREINITIALIZED,  // Data pre-filled by host before device access starts
    THSVS_ACCESS_HOST_WRITE,           // Written on the host

    // Requires VK_NV_ray_tracing to be enabled
    THSVS_ACCESS_ACCELERATION_STRUCTURE_BUILD_WRITE_NV, // Written as an acceleration structure during a build

    THSVS_ACCESS_COLOR_ATTACHMENT_READ_WRITE, // Read or written as a color attachment during rendering
                                              // General access
    THSVS_ACCESS_GENERAL,                     // Covers any access - useful for debug, generally avoid for performance reasons

    // Number of access types
    THSVS_NUM_ACCESS_TYPES
} ThsvsAccessType;

/*
ThsvsImageLayout defines a handful of layout options for images.
Rather than a list of all possible image layouts, this reduced list is
correlated with the access types to map to the correct Vulkan layouts.
THSVS_IMAGE_LAYOUT_OPTIMAL is usually preferred.
*/
typedef enum ThsvsImageLayout {
    THSVS_IMAGE_LAYOUT_OPTIMAL, // Choose the most optimal layout for each usage. Performs layout transitions as appropriate for the access.
    THSVS_IMAGE_LAYOUT_GENERAL, // Layout accessible by all Vulkan access types on a device - no layout transitions except for presentation

    // Requires VK_KHR_shared_presentable_image to be enabled. Can only be used for shared presentable images (i.e. single-buffered swap chains).
    THSVS_IMAGE_LAYOUT_GENERAL_AND_PRESENTATION // As GENERAL, but also allows presentation engines to access it - no layout transitions
} ThsvsImageLayout;

/*
Global barriers define a set of accesses on multiple resources at once.
If a buffer or image doesn't require a queue ownership transfer, or an image
doesn't require a layout transition (e.g. you're using one of the GENERAL
layouts) then a global barrier should be preferred.
Simply define the previous and next access types of resources affected.
*/
typedef struct ThsvsGlobalBarrier {
    uint32_t prevAccessCount;
    const ThsvsAccessType* pPrevAccesses;
    uint32_t nextAccessCount;
    const ThsvsAccessType* pNextAccesses;
} ThsvsGlobalBarrier;

/*
Buffer barriers should only be used when a queue family ownership transfer
is required - prefer global barriers at all other times.

Access types are defined in the same way as for a global memory barrier, but
they only affect the buffer range identified by buffer, offset and size,
rather than all resources.
srcQueueFamilyIndex and dstQueueFamilyIndex will be passed unmodified into a
VkBufferMemoryBarrier.

A buffer barrier defining a queue ownership transfer needs to be executed
twice - once by a queue in the source queue family, and then once again by a
queue in the destination queue family, with a semaphore guaranteeing
execution order between them.
*/
typedef struct ThsvsBufferBarrier {
    uint32_t prevAccessCount;
    const ThsvsAccessType* pPrevAccesses;
    uint32_t nextAccessCount;
    const ThsvsAccessType* pNextAccesses;
    uint32_t srcQueueFamilyIndex;
    uint32_t dstQueueFamilyIndex;
    VkBuffer buffer;
    VkDeviceSize offset;
    VkDeviceSize size;
} ThsvsBufferBarrier;

/*
Image barriers should only be used when a queue family ownership transfer
or an image layout transition is required - prefer global barriers at all
other times.
In general it is better to use image barriers with THSVS_IMAGE_LAYOUT_OPTIMAL
than it is to use global barriers with images using either of the
THSVS_IMAGE_LAYOUT_GENERAL* layouts.

Access types are defined in the same way as for a global memory barrier, but
they only affect the image subresource range identified by image and
subresourceRange, rather than all resources.
srcQueueFamilyIndex, dstQueueFamilyIndex, image, and subresourceRange will
be passed unmodified into a VkImageMemoryBarrier.

An image barrier defining a queue ownership transfer needs to be executed
twice - once by a queue in the source queue family, and then once again by a
queue in the destination queue family, with a semaphore guaranteeing
execution order between them.

If discardContents is set to true, the contents of the image become
undefined after the barrier is executed, which can result in a performance
boost over attempting to preserve the contents.
This is particularly useful for transient images where the contents are
going to be immediately overwritten. A good example of when to use this is
when an application re-uses a presented image after vkAcquireNextImageKHR.
*/
typedef struct ThsvsImageBarrier {
    uint32_t prevAccessCount;
    const ThsvsAccessType* pPrevAccesses;
    uint32_t nextAccessCount;
    const ThsvsAccessType* pNextAccesses;
    ThsvsImageLayout prevLayout;
    ThsvsImageLayout nextLayout;
    VkBool32 discardContents;
    uint32_t srcQueueFamilyIndex;
    uint32_t dstQueueFamilyIndex;
    VkImage image;
    VkImageSubresourceRange subresourceRange;
} ThsvsImageBarrier;

/*
Mapping function that translates a set of accesses into the corresponding
pipeline stages, VkAccessFlags, and image layout.
*/
void thsvsGetAccessInfo(
    uint32_t accessCount,
    const ThsvsAccessType* pAccesses,
    VkPipelineStageFlags* pStageMask,
    VkAccessFlags* pAccessMask,
    VkImageLayout* pImageLayout,
    bool* pHasWriteAccess);

/*
Mapping function that translates a global barrier into a set of source and
destination pipeline stages, and a VkMemoryBarrier, that can be used with
Vulkan's synchronization methods.
*/
void thsvsGetVulkanMemoryBarrier(
    const ThsvsGlobalBarrier& thBarrier,
    VkPipelineStageFlags* pSrcStages,
    VkPipelineStageFlags* pDstStages,
    VkMemoryBarrier* pVkBarrier);

/*
Mapping function that translates a buffer barrier into a set of source and
destination pipeline stages, and a VkBufferMemoryBarrier, that can be used
with Vulkan's synchronization methods.
*/
void thsvsGetVulkanBufferMemoryBarrier(
    const ThsvsBufferBarrier& thBarrier,
    VkPipelineStageFlags* pSrcStages,
    VkPipelineStageFlags* pDstStages,
    VkBufferMemoryBarrier* pVkBarrier);

/*
Mapping function that translates an image barrier into a set of source and
destination pipeline stages, and a VkBufferMemoryBarrier, that can be used
with Vulkan's synchronization methods.
*/
void thsvsGetVulkanImageMemoryBarrier(
    const ThsvsImageBarrier& thBarrier,
    VkPipelineStageFlags* pSrcStages,
    VkPipelineStageFlags* pDstStages,
    VkImageMemoryBarrier* pVkBarrier);

/*
Simplified wrapper around vkCmdPipelineBarrier.

The mapping functions defined above are used to translate the passed in
barrier definitions into a set of pipeline stages and native Vulkan memory
barriers to be passed to vkCmdPipelineBarrier.

commandBuffer is passed unmodified to vkCmdPipelineBarrier.
*/
void thsvsCmdPipelineBarrier(
    VkCommandBuffer commandBuffer,
    const ThsvsGlobalBarrier* pGlobalBarrier,
    uint32_t bufferBarrierCount,
    const ThsvsBufferBarrier* pBufferBarriers,
    uint32_t imageBarrierCount,
    const ThsvsImageBarrier* pImageBarriers);

/*
Wrapper around vkCmdSetEvent.

Sets an event when the accesses defined by pPrevAccesses are completed.

commandBuffer and event are passed unmodified to vkCmdSetEvent.
*/
void thsvsCmdSetEvent(
    VkCommandBuffer commandBuffer,
    VkEvent event,
    uint32_t prevAccessCount,
    const ThsvsAccessType* pPrevAccesses);

/*
Wrapper around vkCmdResetEvent.

Resets an event when the accesses defined by pPrevAccesses are completed.

commandBuffer and event are passed unmodified to vkCmdResetEvent.
*/
void thsvsCmdResetEvent(
    VkCommandBuffer commandBuffer,
    VkEvent event,
    uint32_t prevAccessCount,
    const ThsvsAccessType* pPrevAccesses);

/*
Simplified wrapper around vkCmdWaitEvents.

The mapping functions defined above are used to translate the passed in
barrier definitions into a set of pipeline stages and native Vulkan memory
barriers to be passed to vkCmdPipelineBarrier.

commandBuffer, eventCount, and pEvents are passed unmodified to
vkCmdWaitEvents.
*/
void thsvsCmdWaitEvents(
    VkCommandBuffer commandBuffer,
    uint32_t eventCount,
    const VkEvent* pEvents,
    const ThsvsGlobalBarrier* pGlobalBarrier,
    uint32_t bufferBarrierCount,
    const ThsvsBufferBarrier* pBufferBarriers,
    uint32_t imageBarrierCount,
    const ThsvsImageBarrier* pImageBarriers);

#endif // THSVS_SIMPLER_VULKAN_SYNCHRONIZATION_H

#ifdef THSVS_SIMPLER_VULKAN_SYNCHRONIZATION_IMPLEMENTATION

#include <stdlib.h>

//// Optional Error Checking ////
/*
Checks for barriers defining multiple usages that have different layouts
*/
// #define THSVS_ERROR_CHECK_MIXED_IMAGE_LAYOUT

/*
Checks if an image/buffer barrier is used when a global barrier would suffice
*/
// #define THSVS_ERROR_CHECK_COULD_USE_GLOBAL_BARRIER

/*
Checks if a write access is listed alongside any other access - if so it
points to a potential data hazard that you need to synchronize separately.
In some cases it may simply be over-synchronization however, but it's usually
worth checking.
*/
// #define THSVS_ERROR_CHECK_POTENTIAL_HAZARD

/*
Checks if a variety of table lookups (like the access map) are within
a valid range.
*/
// #define THSVS_ERROR_CHECK_ACCESS_TYPE_IN_RANGE

//// Temporary Memory Allocation ////
/*
Override these if you can't afford the stack space or just want to use a
custom temporary allocator.
These are currently used exclusively to allocate Vulkan memory barriers in
the API, one for each Buffer or Image barrier passed into the pipeline and
event functions.
May consider other allocation strategies in future.
*/

// Alloca inclusion code below copied from
// https://github.com/nothings/stb/blob/master/stb_vorbis.c

// find definition of alloca if it's not in stdlib.h:
#if defined(_MSC_VER) || defined(__MINGW32__)
    #include <malloc.h>
#endif
#if defined(__linux__) || defined(__linux) || defined(__EMSCRIPTEN__)
    #include <alloca.h>
#endif

#if defined(THSVS_ERROR_CHECK_ACCESS_TYPE_IN_RANGE) ||     \
    defined(THSVS_ERROR_CHECK_COULD_USE_GLOBAL_BARRIER) || \
    defined(THSVS_ERROR_CHECK_MIXED_IMAGE_LAYOUT) ||       \
    defined(THSVS_ERROR_CHECK_POTENTIAL_HAZARD)
    #include <assert.h>
#endif

#if !defined(THSVS_TEMP_ALLOC)
    #define THSVS_TEMP_ALLOC(size) (alloca(size))
#endif

#if !defined(THSVS_TEMP_FREE)
    #define THSVS_TEMP_FREE(x) ((void)(x))
#endif

typedef struct ThsvsVkAccessInfo {
    VkPipelineStageFlags stageMask;
    VkAccessFlags accessMask;
    VkImageLayout imageLayout;
} ThsvsVkAccessInfo;

const ThsvsVkAccessInfo ThsvsAccessMap[THSVS_NUM_ACCESS_TYPES] = {
    // THSVS_ACCESS_NONE
    {0,
     0,
     VK_IMAGE_LAYOUT_UNDEFINED},

    // Read Access
    // THSVS_ACCESS_COMMAND_BUFFER_READ_NV
    {VK_PIPELINE_STAGE_COMMAND_PREPROCESS_BIT_NV,
     VK_ACCESS_COMMAND_PREPROCESS_READ_BIT_NV,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_INDIRECT_BUFFER
    {VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT,
     VK_ACCESS_INDIRECT_COMMAND_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},

    // THSVS_ACCESS_INDEX_BUFFER
    {VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
     VK_ACCESS_INDEX_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_VERTEX_BUFFER
    {VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
     VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_VERTEX_SHADER_READ_UNIFORM_BUFFER
    {VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
     VK_ACCESS_UNIFORM_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_VERTEX_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER
    {VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
    // THSVS_ACCESS_VERTEX_SHADER_READ_OTHER
    {VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_GENERAL},

    // THSVS_ACCESS_TESSELLATION_CONTROL_SHADER_READ_UNIFORM_BUFFER
    {VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT,
     VK_ACCESS_UNIFORM_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_TESSELLATION_CONTROL_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER
    {VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
    // THSVS_ACCESS_TESSELLATION_CONTROL_SHADER_READ_OTHER
    {VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_GENERAL},

    // THSVS_ACCESS_TESSELLATION_EVALUATION_SHADER_READ_UNIFORM_BUFFER
    {VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT,
     VK_ACCESS_UNIFORM_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_TESSELLATION_EVALUATION_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER
    {VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
    // THSVS_ACCESS_TESSELLATION_EVALUATION_SHADER_READ_OTHER
    {VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_GENERAL},

    // THSVS_ACCESS_GEOMETRY_SHADER_READ_UNIFORM_BUFFER
    {VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT,
     VK_ACCESS_UNIFORM_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_GEOMETRY_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER
    {VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
    // THSVS_ACCESS_GEOMETRY_SHADER_READ_OTHER
    {VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_GENERAL},

    // THSVS_ACCESS_TASK_SHADER_READ_UNIFORM_BUFFER_NV
    {VK_PIPELINE_STAGE_TASK_SHADER_BIT_NV,
     VK_ACCESS_UNIFORM_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_TASK_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER_NV
    {VK_PIPELINE_STAGE_TASK_SHADER_BIT_NV,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
    // THSVS_ACCESS_TASK_SHADER_READ_OTHER_NV
    {VK_PIPELINE_STAGE_TASK_SHADER_BIT_NV,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_GENERAL},

    // THSVS_ACCESS_MESH_SHADER_READ_UNIFORM_BUFFER_NV
    {VK_PIPELINE_STAGE_MESH_SHADER_BIT_NV,
     VK_ACCESS_UNIFORM_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_MESH_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER_NV
    {VK_PIPELINE_STAGE_MESH_SHADER_BIT_NV,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
    // THSVS_ACCESS_MESH_SHADER_READ_OTHER_NV
    {VK_PIPELINE_STAGE_MESH_SHADER_BIT_NV,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_GENERAL},

    // THSVS_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_EXT
    {VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
     VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_FRAGMENT_DENSITY_MAP_READ_EXT
    {VK_PIPELINE_STAGE_FRAGMENT_DENSITY_PROCESS_BIT_EXT,
     VK_ACCESS_FRAGMENT_DENSITY_MAP_READ_BIT_EXT,
     VK_IMAGE_LAYOUT_FRAGMENT_DENSITY_MAP_OPTIMAL_EXT},
    // THSVS_ACCESS_SHADING_RATE_READ_NV
    {VK_PIPELINE_STAGE_SHADING_RATE_IMAGE_BIT_NV,
     VK_ACCESS_SHADING_RATE_IMAGE_READ_BIT_NV,
     VK_IMAGE_LAYOUT_SHADING_RATE_OPTIMAL_NV},

    // THSVS_ACCESS_FRAGMENT_SHADER_READ_UNIFORM_BUFFER
    {VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
     VK_ACCESS_UNIFORM_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_FRAGMENT_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER
    {VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
    // THSVS_ACCESS_FRAGMENT_SHADER_READ_COLOR_INPUT_ATTACHMENT
    {VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
     VK_ACCESS_INPUT_ATTACHMENT_READ_BIT,
     VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
    // THSVS_ACCESS_FRAGMENT_SHADER_READ_DEPTH_STENCIL_INPUT_ATTACHMENT
    {VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
     VK_ACCESS_INPUT_ATTACHMENT_READ_BIT,
     VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL},
    // THSVS_ACCESS_FRAGMENT_SHADER_READ_OTHER
    {VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_GENERAL},
    // THSVS_ACCESS_COLOR_ATTACHMENT_READ
    {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
     VK_ACCESS_COLOR_ATTACHMENT_READ_BIT,
     VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
    // THSVS_ACCESS_COLOR_ATTACHMENT_ADVANCED_BLENDING_EXT
    {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
     VK_ACCESS_COLOR_ATTACHMENT_READ_NONCOHERENT_BIT_EXT,
     VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
    // THSVS_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ
    {VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
     VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT,
     VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL},

    // THSVS_ACCESS_COMPUTE_SHADER_READ_UNIFORM_BUFFER
    {VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
     VK_ACCESS_UNIFORM_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_COMPUTE_SHADER_READ_SAMPLED_IMAGE_OR_UNIFORM_TEXEL_BUFFER
    {VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
    // THSVS_ACCESS_COMPUTE_SHADER_READ_OTHER
    {VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_GENERAL},

    // THSVS_ACCESS_ANY_SHADER_READ_UNIFORM_BUFFER
    {VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
     VK_ACCESS_UNIFORM_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_ANY_SHADER_READ_UNIFORM_BUFFER_OR_VERTEX_BUFFER
    {VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
     VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_ANY_SHADER_READ_SAMPLED_IMAGE
    {VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL},
    // THSVS_ACCESS_ANY_SHADER_READ_OTHER
    {VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
     VK_ACCESS_SHADER_READ_BIT,
     VK_IMAGE_LAYOUT_GENERAL},

    // THSVS_ACCESS_TRANSFER_READ
    {VK_PIPELINE_STAGE_TRANSFER_BIT,
     VK_ACCESS_TRANSFER_READ_BIT,
     VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL},
    // THSVS_ACCESS_HOST_READ
    {VK_PIPELINE_STAGE_HOST_BIT,
     VK_ACCESS_HOST_READ_BIT,
     VK_IMAGE_LAYOUT_GENERAL},
    // THSVS_ACCESS_PRESENT
    {0,
     0,
     VK_IMAGE_LAYOUT_PRESENT_SRC_KHR},
    // THSVS_ACCESS_CONDITIONAL_RENDERING_READ_EXT
    {VK_PIPELINE_STAGE_CONDITIONAL_RENDERING_BIT_EXT,
     VK_ACCESS_CONDITIONAL_RENDERING_READ_BIT_EXT,
     VK_IMAGE_LAYOUT_UNDEFINED},

    // THSVS_ACCESS_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ_NV
    {VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_NV,
     VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_NV,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_ACCELERATION_STRUCTURE_BUILD_READ_NV
    {VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_NV,
     VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_NV,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_END_OF_READ_ACCESS
    {0,
     0,
     VK_IMAGE_LAYOUT_UNDEFINED},

    // Write access
    // THSVS_ACCESS_COMMAND_BUFFER_WRITE_NV
    {VK_PIPELINE_STAGE_COMMAND_PREPROCESS_BIT_NV,
     VK_ACCESS_COMMAND_PREPROCESS_WRITE_BIT_NV,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_VERTEX_SHADER_WRITE
    {VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
     VK_ACCESS_SHADER_WRITE_BIT,
     VK_IMAGE_LAYOUT_GENERAL},
    // THSVS_ACCESS_TESSELLATION_CONTROL_SHADER_WRITE
    {VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT,
     VK_ACCESS_SHADER_WRITE_BIT,
     VK_IMAGE_LAYOUT_GENERAL},
    // THSVS_ACCESS_TESSELLATION_EVALUATION_SHADER_WRITE
    {VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT,
     VK_ACCESS_SHADER_WRITE_BIT,
     VK_IMAGE_LAYOUT_GENERAL},
    // THSVS_ACCESS_GEOMETRY_SHADER_WRITE
    {VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT,
     VK_ACCESS_SHADER_WRITE_BIT,
     VK_IMAGE_LAYOUT_GENERAL},
    // THSVS_ACCESS_TASK_SHADER_WRITE_NV
    {VK_PIPELINE_STAGE_TASK_SHADER_BIT_NV,
     VK_ACCESS_SHADER_WRITE_BIT,
     VK_IMAGE_LAYOUT_GENERAL},
    // THSVS_ACCESS_MESH_SHADER_WRITE_NV
    {VK_PIPELINE_STAGE_MESH_SHADER_BIT_NV,
     VK_ACCESS_SHADER_WRITE_BIT,
     VK_IMAGE_LAYOUT_GENERAL},
    // THSVS_ACCESS_TRANSFORM_FEEDBACK_WRITE_EXT
    {VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
     VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_EXT
    {VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
     VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT,
     VK_IMAGE_LAYOUT_UNDEFINED},
    // THSVS_ACCESS_FRAGMENT_SHADER_WRITE
    {VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
     VK_ACCESS_SHADER_WRITE_BIT,
     VK_IMAGE_LAYOUT_GENERAL},
    // THSVS_ACCESS_COLOR_ATTACHMENT_WRITE
    {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
     VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
     VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
    // THSVS_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE
    {VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
     VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
     VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL},
    // THSVS_ACCESS_DEPTH_ATTACHMENT_WRITE_STENCIL_READ_ONLY
    {VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
     VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT,
     VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL_KHR},
    // THSVS_ACCESS_STENCIL_ATTACHMENT_WRITE_DEPTH_READ_ONLY
    {VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
     VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT,
     VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL_KHR},

    // THSVS_ACCESS_COMPUTE_SHADER_WRITE
    {VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
     VK_ACCESS_SHADER_WRITE_BIT,
     VK_IMAGE_LAYOUT_GENERAL},

    // THSVS_ACCESS_ANY_SHADER_WRITE
    {VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
     VK_ACCESS_SHADER_WRITE_BIT,
     VK_IMAGE_LAYOUT_GENERAL},

    // THSVS_ACCESS_TRANSFER_WRITE
    {VK_PIPELINE_STAGE_TRANSFER_BIT,
     VK_ACCESS_TRANSFER_WRITE_BIT,
     VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL},
    // THSVS_ACCESS_HOST_PREINITIALIZED
    {VK_PIPELINE_STAGE_HOST_BIT,
     VK_ACCESS_HOST_WRITE_BIT,
     VK_IMAGE_LAYOUT_PREINITIALIZED},
    // THSVS_ACCESS_HOST_WRITE
    {VK_PIPELINE_STAGE_HOST_BIT,
     VK_ACCESS_HOST_WRITE_BIT,
     VK_IMAGE_LAYOUT_GENERAL},
    // THSVS_ACCESS_ACCELERATION_STRUCTURE_BUILD_WRITE_NV
    {VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_NV,
     VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_NV,
     VK_IMAGE_LAYOUT_UNDEFINED},

    // THSVS_ACCESS_COLOR_ATTACHMENT_READ_WRITE
    {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
     VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
     VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL},
    // THSVS_ACCESS_GENERAL
    {VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
     VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
     VK_IMAGE_LAYOUT_GENERAL}};

void thsvsGetAccessInfo(
    uint32_t accessCount,
    const ThsvsAccessType* pAccesses,
    VkPipelineStageFlags* pStageMask,
    VkAccessFlags* pAccessMask,
    VkImageLayout* pImageLayout,
    bool* pHasWriteAccess) {
    *pStageMask = 0;
    *pAccessMask = 0;
    *pImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    *pHasWriteAccess = false;

    for (uint32_t i = 0; i < accessCount; ++i) {
        ThsvsAccessType access = pAccesses[i];
        const ThsvsVkAccessInfo* pAccessInfo = &ThsvsAccessMap[access];

#ifdef THSVS_ERROR_CHECK_ACCESS_TYPE_IN_RANGE
        // Asserts that the previous access index is a valid range for the lookup
        assert(access < THSVS_NUM_ACCESS_TYPES);
#endif

#ifdef THSVS_ERROR_CHECK_POTENTIAL_HAZARD
        // Asserts that the access is a read, else it's a write and it should appear on its own.
        assert(access < THSVS_END_OF_READ_ACCESS || accessCount == 1);
#endif

        *pStageMask |= pAccessInfo->stageMask;

        if (access > THSVS_END_OF_READ_ACCESS)
            *pHasWriteAccess = true;

        *pAccessMask |= pAccessInfo->accessMask;

        VkImageLayout layout = pAccessInfo->imageLayout;

#ifdef THSVS_ERROR_CHECK_MIXED_IMAGE_LAYOUT
        assert(*pImageLayout == VK_IMAGE_LAYOUT_UNDEFINED ||
               *pImageLayout == layout);
#endif

        *pImageLayout = layout;
    }
}

void thsvsGetVulkanMemoryBarrier(
    const ThsvsGlobalBarrier& thBarrier,
    VkPipelineStageFlags* pSrcStages,
    VkPipelineStageFlags* pDstStages,
    VkMemoryBarrier* pVkBarrier) {
    *pSrcStages = 0;
    *pDstStages = 0;
    pVkBarrier->sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
    pVkBarrier->pNext = NULL;
    pVkBarrier->srcAccessMask = 0;
    pVkBarrier->dstAccessMask = 0;

    for (uint32_t i = 0; i < thBarrier.prevAccessCount; ++i) {
        ThsvsAccessType prevAccess = thBarrier.pPrevAccesses[i];
        const ThsvsVkAccessInfo* pPrevAccessInfo = &ThsvsAccessMap[prevAccess];

#ifdef THSVS_ERROR_CHECK_ACCESS_TYPE_IN_RANGE
        // Asserts that the previous access index is a valid range for the lookup
        assert(prevAccess < THSVS_NUM_ACCESS_TYPES);
#endif

#ifdef THSVS_ERROR_CHECK_POTENTIAL_HAZARD
        // Asserts that the access is a read, else it's a write and it should appear on its own.
        assert(prevAccess < THSVS_END_OF_READ_ACCESS || thBarrier.prevAccessCount == 1);
#endif

        *pSrcStages |= pPrevAccessInfo->stageMask;

        // Add appropriate availability operations - for writes only.
        if (prevAccess > THSVS_END_OF_READ_ACCESS)
            pVkBarrier->srcAccessMask |= pPrevAccessInfo->accessMask;
    }

    for (uint32_t i = 0; i < thBarrier.nextAccessCount; ++i) {
        ThsvsAccessType nextAccess = thBarrier.pNextAccesses[i];
        const ThsvsVkAccessInfo* pNextAccessInfo = &ThsvsAccessMap[nextAccess];

#ifdef THSVS_ERROR_CHECK_ACCESS_TYPE_IN_RANGE
        // Asserts that the next access index is a valid range for the lookup
        assert(nextAccess < THSVS_NUM_ACCESS_TYPES);
#endif

#ifdef THSVS_ERROR_CHECK_POTENTIAL_HAZARD
        // Asserts that the access is a read, else it's a write and it should appear on its own.
        assert(nextAccess < THSVS_END_OF_READ_ACCESS || thBarrier.nextAccessCount == 1);
#endif
        *pDstStages |= pNextAccessInfo->stageMask;

        // Add visibility operations as necessary.
        // If the src access mask is zero, this is a WAR hazard (or for some reason a "RAR"),
        // so the dst access mask can be safely zeroed as these don't need visibility.
        if (pVkBarrier->srcAccessMask != 0)
            pVkBarrier->dstAccessMask |= pNextAccessInfo->accessMask;
    }

    // Ensure that the stage masks are valid if no stages were determined
    if (*pSrcStages == 0)
        *pSrcStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    if (*pDstStages == 0)
        *pDstStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
}

void thsvsGetVulkanBufferMemoryBarrier(
    const ThsvsBufferBarrier& thBarrier,
    VkPipelineStageFlags* pSrcStages,
    VkPipelineStageFlags* pDstStages,
    VkBufferMemoryBarrier* pVkBarrier) {
    *pSrcStages = 0;
    *pDstStages = 0;
    pVkBarrier->sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
    pVkBarrier->pNext = NULL;
    pVkBarrier->srcAccessMask = 0;
    pVkBarrier->dstAccessMask = 0;
    pVkBarrier->srcQueueFamilyIndex = thBarrier.srcQueueFamilyIndex;
    pVkBarrier->dstQueueFamilyIndex = thBarrier.dstQueueFamilyIndex;
    pVkBarrier->buffer = thBarrier.buffer;
    pVkBarrier->offset = thBarrier.offset;
    pVkBarrier->size = thBarrier.size;

#ifdef THSVS_ERROR_CHECK_COULD_USE_GLOBAL_BARRIER
    assert(pVkBarrier->srcQueueFamilyIndex != pVkBarrier->dstQueueFamilyIndex);
#endif

    for (uint32_t i = 0; i < thBarrier.prevAccessCount; ++i) {
        ThsvsAccessType prevAccess = thBarrier.pPrevAccesses[i];
        const ThsvsVkAccessInfo* pPrevAccessInfo = &ThsvsAccessMap[prevAccess];

#ifdef THSVS_ERROR_CHECK_ACCESS_TYPE_IN_RANGE
        // Asserts that the previous access index is a valid range for the lookup
        assert(prevAccess < THSVS_NUM_ACCESS_TYPES);
#endif

#ifdef THSVS_ERROR_CHECK_POTENTIAL_HAZARD
        // Asserts that the access is a read, else it's a write and it should appear on its own.
        assert(prevAccess < THSVS_END_OF_READ_ACCESS || thBarrier.prevAccessCount == 1);
#endif

        *pSrcStages |= pPrevAccessInfo->stageMask;

        // Add appropriate availability operations - for writes only.
        if (prevAccess > THSVS_END_OF_READ_ACCESS)
            pVkBarrier->srcAccessMask |= pPrevAccessInfo->accessMask;
    }

    for (uint32_t i = 0; i < thBarrier.nextAccessCount; ++i) {
        ThsvsAccessType nextAccess = thBarrier.pNextAccesses[i];
        const ThsvsVkAccessInfo* pNextAccessInfo = &ThsvsAccessMap[nextAccess];

#ifdef THSVS_ERROR_CHECK_ACCESS_TYPE_IN_RANGE
        // Asserts that the next access index is a valid range for the lookup
        assert(nextAccess < THSVS_NUM_ACCESS_TYPES);
#endif

#ifdef THSVS_ERROR_CHECK_POTENTIAL_HAZARD
        // Asserts that the access is a read, else it's a write and it should appear on its own.
        assert(nextAccess < THSVS_END_OF_READ_ACCESS || thBarrier.nextAccessCount == 1);
#endif

        *pDstStages |= pNextAccessInfo->stageMask;

        // Add visibility operations as necessary.
        // If the src access mask is zero, this is a WAR hazard (or for some reason a "RAR"),
        // so the dst access mask can be safely zeroed as these don't need visibility.
        if (pVkBarrier->srcAccessMask != 0)
            pVkBarrier->dstAccessMask |= pNextAccessInfo->accessMask;
    }

    // Ensure that the stage masks are valid if no stages were determined
    if (*pSrcStages == 0)
        *pSrcStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    if (*pDstStages == 0)
        *pDstStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
}

void thsvsGetVulkanImageMemoryBarrier(
    const ThsvsImageBarrier& thBarrier,
    VkPipelineStageFlags* pSrcStages,
    VkPipelineStageFlags* pDstStages,
    VkImageMemoryBarrier* pVkBarrier) {
    *pSrcStages = 0;
    *pDstStages = 0;
    pVkBarrier->sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    pVkBarrier->pNext = NULL;
    pVkBarrier->srcAccessMask = 0;
    pVkBarrier->dstAccessMask = 0;
    pVkBarrier->srcQueueFamilyIndex = thBarrier.srcQueueFamilyIndex;
    pVkBarrier->dstQueueFamilyIndex = thBarrier.dstQueueFamilyIndex;
    pVkBarrier->image = thBarrier.image;
    pVkBarrier->subresourceRange = thBarrier.subresourceRange;
    pVkBarrier->oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    pVkBarrier->newLayout = VK_IMAGE_LAYOUT_UNDEFINED;

    for (uint32_t i = 0; i < thBarrier.prevAccessCount; ++i) {
        ThsvsAccessType prevAccess = thBarrier.pPrevAccesses[i];
        const ThsvsVkAccessInfo* pPrevAccessInfo = &ThsvsAccessMap[prevAccess];

#ifdef THSVS_ERROR_CHECK_ACCESS_TYPE_IN_RANGE
        // Asserts that the previous access index is a valid range for the lookup
        assert(prevAccess < THSVS_NUM_ACCESS_TYPES);
#endif

#ifdef THSVS_ERROR_CHECK_POTENTIAL_HAZARD
        // Asserts that the access is a read, else it's a write and it should appear on its own.
        assert(prevAccess < THSVS_END_OF_READ_ACCESS || thBarrier.prevAccessCount == 1);
#endif

        *pSrcStages |= pPrevAccessInfo->stageMask;

        // Add appropriate availability operations - for writes only.
        if (prevAccess > THSVS_END_OF_READ_ACCESS)
            pVkBarrier->srcAccessMask |= pPrevAccessInfo->accessMask;

        if (thBarrier.discardContents == VK_TRUE) {
            pVkBarrier->oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        } else {
            VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;

            switch (thBarrier.prevLayout) {
                case THSVS_IMAGE_LAYOUT_GENERAL:
                    if (prevAccess == THSVS_ACCESS_PRESENT)
                        layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
                    else
                        layout = VK_IMAGE_LAYOUT_GENERAL;
                    break;
                case THSVS_IMAGE_LAYOUT_OPTIMAL:
                    layout = pPrevAccessInfo->imageLayout;
                    break;
                case THSVS_IMAGE_LAYOUT_GENERAL_AND_PRESENTATION:
                    layout = VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR;
                    break;
            }

#ifdef THSVS_ERROR_CHECK_MIXED_IMAGE_LAYOUT
            assert(pVkBarrier->oldLayout == VK_IMAGE_LAYOUT_UNDEFINED ||
                   pVkBarrier->oldLayout == layout);
#endif
            pVkBarrier->oldLayout = layout;
        }
    }

    for (uint32_t i = 0; i < thBarrier.nextAccessCount; ++i) {
        ThsvsAccessType nextAccess = thBarrier.pNextAccesses[i];
        const ThsvsVkAccessInfo* pNextAccessInfo = &ThsvsAccessMap[nextAccess];

#ifdef THSVS_ERROR_CHECK_ACCESS_TYPE_IN_RANGE
        // Asserts that the next access index is a valid range for the lookup
        assert(nextAccess < THSVS_NUM_ACCESS_TYPES);
#endif

#ifdef THSVS_ERROR_CHECK_POTENTIAL_HAZARD
        // Asserts that the access is a read, else it's a write and it should appear on its own.
        assert(nextAccess < THSVS_END_OF_READ_ACCESS || thBarrier.nextAccessCount == 1);
#endif

        *pDstStages |= pNextAccessInfo->stageMask;

        // Add visibility operations as necessary.
        // If the src access mask is zero, this is a WAR hazard (or for some reason a "RAR"),
        // so the dst access mask can be safely zeroed as these don't need visibility.
        if (pVkBarrier->srcAccessMask != 0)
            pVkBarrier->dstAccessMask |= pNextAccessInfo->accessMask;

        VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
        switch (thBarrier.nextLayout) {
            case THSVS_IMAGE_LAYOUT_GENERAL:
                if (nextAccess == THSVS_ACCESS_PRESENT)
                    layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
                else
                    layout = VK_IMAGE_LAYOUT_GENERAL;
                break;
            case THSVS_IMAGE_LAYOUT_OPTIMAL:
                layout = pNextAccessInfo->imageLayout;
                break;
            case THSVS_IMAGE_LAYOUT_GENERAL_AND_PRESENTATION:
                layout = VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR;
                break;
        }

#ifdef THSVS_ERROR_CHECK_MIXED_IMAGE_LAYOUT
        assert(pVkBarrier->newLayout == VK_IMAGE_LAYOUT_UNDEFINED ||
               pVkBarrier->newLayout == layout);
#endif
        pVkBarrier->newLayout = layout;
    }

#ifdef THSVS_ERROR_CHECK_COULD_USE_GLOBAL_BARRIER
    assert(pVkBarrier->newLayout != pVkBarrier->oldLayout ||
           pVkBarrier->srcQueueFamilyIndex != pVkBarrier->dstQueueFamilyIndex);
#endif

    // Ensure that the stage masks are valid if no stages were determined
    if (*pSrcStages == 0)
        *pSrcStages = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    if (*pDstStages == 0)
        *pDstStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
}

void thsvsCmdPipelineBarrier(
    VkCommandBuffer commandBuffer,
    const ThsvsGlobalBarrier* pGlobalBarrier,
    uint32_t bufferBarrierCount,
    const ThsvsBufferBarrier* pBufferBarriers,
    uint32_t imageBarrierCount,
    const ThsvsImageBarrier* pImageBarriers) {
    VkMemoryBarrier memoryBarrier;
    // Vulkan pipeline barrier command parameters
    //                     commandBuffer;
    VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    uint32_t memoryBarrierCount = (pGlobalBarrier != NULL) ? 1 : 0;
    VkMemoryBarrier* pMemoryBarriers = (pGlobalBarrier != NULL) ? &memoryBarrier : NULL;
    uint32_t bufferMemoryBarrierCount = bufferBarrierCount;
    VkBufferMemoryBarrier* pBufferMemoryBarriers = NULL;
    uint32_t imageMemoryBarrierCount = imageBarrierCount;
    VkImageMemoryBarrier* pImageMemoryBarriers = NULL;

    // Global memory barrier
    if (pGlobalBarrier != NULL) {
        VkPipelineStageFlags tempSrcStageMask = 0;
        VkPipelineStageFlags tempDstStageMask = 0;
        thsvsGetVulkanMemoryBarrier(*pGlobalBarrier, &tempSrcStageMask, &tempDstStageMask, pMemoryBarriers);
        srcStageMask |= tempSrcStageMask;
        dstStageMask |= tempDstStageMask;
    }

    // Buffer memory barriers
    if (bufferBarrierCount > 0) {
        pBufferMemoryBarriers = (VkBufferMemoryBarrier*)THSVS_TEMP_ALLOC(sizeof(VkBufferMemoryBarrier) * bufferMemoryBarrierCount);

        VkPipelineStageFlags tempSrcStageMask = 0;
        VkPipelineStageFlags tempDstStageMask = 0;
        for (uint32_t i = 0; i < bufferBarrierCount; ++i) {
            thsvsGetVulkanBufferMemoryBarrier(pBufferBarriers[i], &tempSrcStageMask, &tempDstStageMask, &pBufferMemoryBarriers[i]);
            srcStageMask |= tempSrcStageMask;
            dstStageMask |= tempDstStageMask;
        }
    }

    // Image memory barriers
    if (imageBarrierCount > 0) {
        pImageMemoryBarriers = (VkImageMemoryBarrier*)THSVS_TEMP_ALLOC(sizeof(VkImageMemoryBarrier) * imageMemoryBarrierCount);

        VkPipelineStageFlags tempSrcStageMask = 0;
        VkPipelineStageFlags tempDstStageMask = 0;
        for (uint32_t i = 0; i < imageBarrierCount; ++i) {
            thsvsGetVulkanImageMemoryBarrier(pImageBarriers[i], &tempSrcStageMask, &tempDstStageMask, &pImageMemoryBarriers[i]);
            srcStageMask |= tempSrcStageMask;
            dstStageMask |= tempDstStageMask;
        }
    }

    vkCmdPipelineBarrier(
        commandBuffer,
        srcStageMask,
        dstStageMask,
        0,
        memoryBarrierCount,
        pMemoryBarriers,
        bufferMemoryBarrierCount,
        pBufferMemoryBarriers,
        imageMemoryBarrierCount,
        pImageMemoryBarriers);

    THSVS_TEMP_FREE(pBufferMemoryBarriers);
    THSVS_TEMP_FREE(pImageMemoryBarriers);
}

void thsvsCmdSetEvent(
    VkCommandBuffer commandBuffer,
    VkEvent event,
    uint32_t prevAccessCount,
    const ThsvsAccessType* pPrevAccesses) {
    VkPipelineStageFlags stageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;

    for (uint32_t i = 0; i < prevAccessCount; ++i) {
        ThsvsAccessType prevAccess = pPrevAccesses[i];
        const ThsvsVkAccessInfo* pPrevAccessInfo = &ThsvsAccessMap[prevAccess];

#ifdef THSVS_ERROR_CHECK_ACCESS_TYPE_IN_RANGE
        // Asserts that the previous access index is a valid range for the lookup
        assert(prevAccess < THSVS_NUM_ACCESS_TYPES);
#endif

        stageMask |= pPrevAccessInfo->stageMask;
    }

    vkCmdSetEvent(
        commandBuffer,
        event,
        stageMask);
}

void thsvsCmdResetEvent(
    VkCommandBuffer commandBuffer,
    VkEvent event,
    uint32_t prevAccessCount,
    const ThsvsAccessType* pPrevAccesses) {
    VkPipelineStageFlags stageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;

    for (uint32_t i = 0; i < prevAccessCount; ++i) {
        ThsvsAccessType prevAccess = pPrevAccesses[i];
        const ThsvsVkAccessInfo* pPrevAccessInfo = &ThsvsAccessMap[prevAccess];

#ifdef THSVS_ERROR_CHECK_ACCESS_TYPE_IN_RANGE
        // Asserts that the previous access index is a valid range for the lookup
        assert(prevAccess < THSVS_NUM_ACCESS_TYPES);
#endif

        stageMask |= pPrevAccessInfo->stageMask;
    }

    vkCmdResetEvent(
        commandBuffer,
        event,
        stageMask);
}

void thsvsCmdWaitEvents(
    VkCommandBuffer commandBuffer,
    uint32_t eventCount,
    const VkEvent* pEvents,
    const ThsvsGlobalBarrier* pGlobalBarrier,
    uint32_t bufferBarrierCount,
    const ThsvsBufferBarrier* pBufferBarriers,
    uint32_t imageBarrierCount,
    const ThsvsImageBarrier* pImageBarriers) {
    VkMemoryBarrier memoryBarrier;
    // Vulkan pipeline barrier command parameters
    //                     commandBuffer;
    //                     eventCount;
    //                     pEvents;
    VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
    VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    uint32_t memoryBarrierCount = (pGlobalBarrier != NULL) ? 1 : 0;
    VkMemoryBarrier* pMemoryBarriers = (pGlobalBarrier != NULL) ? &memoryBarrier : NULL;
    uint32_t bufferMemoryBarrierCount = bufferBarrierCount;
    VkBufferMemoryBarrier* pBufferMemoryBarriers = NULL;
    uint32_t imageMemoryBarrierCount = imageBarrierCount;
    VkImageMemoryBarrier* pImageMemoryBarriers = NULL;

    // Global memory barrier
    if (pGlobalBarrier != NULL) {
        VkPipelineStageFlags tempSrcStageMask = 0;
        VkPipelineStageFlags tempDstStageMask = 0;
        thsvsGetVulkanMemoryBarrier(*pGlobalBarrier, &tempSrcStageMask, &tempDstStageMask, pMemoryBarriers);
        srcStageMask |= tempSrcStageMask;
        dstStageMask |= tempDstStageMask;
    }

    // Buffer memory barriers
    if (bufferBarrierCount > 0) {
        pBufferMemoryBarriers = (VkBufferMemoryBarrier*)THSVS_TEMP_ALLOC(sizeof(VkBufferMemoryBarrier) * bufferMemoryBarrierCount);

        VkPipelineStageFlags tempSrcStageMask = 0;
        VkPipelineStageFlags tempDstStageMask = 0;
        for (uint32_t i = 0; i < bufferBarrierCount; ++i) {
            thsvsGetVulkanBufferMemoryBarrier(pBufferBarriers[i], &tempSrcStageMask, &tempDstStageMask, &pBufferMemoryBarriers[i]);
            srcStageMask |= tempSrcStageMask;
            dstStageMask |= tempDstStageMask;
        }
    }

    // Image memory barriers
    if (imageBarrierCount > 0) {
        pImageMemoryBarriers = (VkImageMemoryBarrier*)THSVS_TEMP_ALLOC(sizeof(VkImageMemoryBarrier) * imageMemoryBarrierCount);

        VkPipelineStageFlags tempSrcStageMask = 0;
        VkPipelineStageFlags tempDstStageMask = 0;
        for (uint32_t i = 0; i < imageBarrierCount; ++i) {
            thsvsGetVulkanImageMemoryBarrier(pImageBarriers[i], &tempSrcStageMask, &tempDstStageMask, &pImageMemoryBarriers[i]);
            srcStageMask |= tempSrcStageMask;
            dstStageMask |= tempDstStageMask;
        }
    }

    vkCmdWaitEvents(
        commandBuffer,
        eventCount,
        pEvents,
        srcStageMask,
        dstStageMask,
        memoryBarrierCount,
        pMemoryBarriers,
        bufferMemoryBarrierCount,
        pBufferMemoryBarriers,
        imageMemoryBarrierCount,
        pImageMemoryBarriers);

    THSVS_TEMP_FREE(pBufferMemoryBarriers);
    THSVS_TEMP_FREE(pImageMemoryBarriers);
}

#endif // THSVS_SIMPLER_VULKAN_SYNCHRONIZATION_IMPLEMENTATION
