HelperVk.h#

Parent directory (Vulkan)

Contains helper functions for several common complicated Vulkan tasks, such as swapchain creation and texture uploading.

Includes#

  • ConvertToPVRVkTypes.h (ConvertToPVRVkTypes.h)

  • PVRAssets/Model.h

  • PVRAssets/PVRAssets.h

  • PVRCore/PVRCore.h

  • PVRCore/stream/FileStream.h

  • PVRCore/texture/TextureLoad.h

  • PVRUtils/MultiObject.h

  • PVRUtils/Vulkan/MemoryAllocator.h

  • PVRVk/CommandBufferVk.h

  • PVRVk/CommandPoolVk.h

  • PVRVk/DeviceVk.h

  • PVRVk/ExtensionsVk.h

  • PVRVk/FramebufferVk.h

  • PVRVk/ImageVk.h

  • PVRVk/InstanceVk.h

  • PVRVk/PhysicalDeviceVk.h

  • PVRVk/QueueVk.h

  • PVRVk/RenderPassVk.h

  • PVRVk/SurfaceVk.h

  • PVRVk/SwapchainVk.h

Included By#

Namespaces#

Classes#

Functions#

Variables#

Source Code#

#pragma once
#include "PVRCore/PVRCore.h"
#include "PVRCore/stream/FileStream.h"
#include "PVRAssets/Model.h"
#include "PVRAssets/PVRAssets.h"
#include "PVRCore/texture/TextureLoad.h"
#include "PVRVk/DeviceVk.h"
#include "PVRVk/PhysicalDeviceVk.h"
#include "PVRVk/InstanceVk.h"
#include "PVRVk/CommandBufferVk.h"
#include "PVRVk/ImageVk.h"
#include "PVRVk/SurfaceVk.h"
#include "ConvertToPVRVkTypes.h"
#include "PVRVk/CommandPoolVk.h"
#include "PVRVk/QueueVk.h"
#include "PVRVk/RenderPassVk.h"
#include "PVRVk/FramebufferVk.h"
#include "PVRVk/SwapchainVk.h"
#include "PVRUtils/Vulkan/MemoryAllocator.h"
#include "PVRUtils/MultiObject.h"
#include "PVRVk/ExtensionsVk.h"
namespace pvr {
namespace utils {
extern bool PVRUtils_Throw_On_Validation_Error;

#pragma region
inline bool isFormatDepthStencil(pvrvk::Format format) { return format >= pvrvk::Format::e_D16_UNORM && format <= pvrvk::Format::e_D32_SFLOAT_S8_UINT; }

void getMemoryTypeIndex(const pvrvk::PhysicalDevice& physicalDevice, const uint32_t allowedMemoryTypeBits, const pvrvk::MemoryPropertyFlags requiredMemoryProperties,
    const pvrvk::MemoryPropertyFlags optimalMemoryProperties, uint32_t& outMemoryTypeIndex, pvrvk::MemoryPropertyFlags& outMemoryPropertyFlags);

inline void populateClearValues(const pvrvk::RenderPass& renderpass, const pvrvk::ClearValue& clearColor, const pvrvk::ClearValue& clearDepthStencilValue, pvrvk::ClearValue* outClearValues)
{
    for (uint32_t i = 0; i < renderpass->getCreateInfo().getNumAttachmentDescription(); ++i)
    {
        const pvrvk::Format& format = renderpass->getCreateInfo().getAttachmentDescription(i).getFormat();
        if (pvr::utils::isFormatDepthStencil(format)) { outClearValues[i] = clearDepthStencilValue; }
        else
        {
            outClearValues[i] = clearColor;
        }
    }
}
inline uint8_t getNumSamplesFromSampleCountFlags(pvrvk::SampleCountFlags sampleCountFlags)
{
    uint8_t numSamples = 0;
    if (static_cast<uint32_t>(sampleCountFlags & pvrvk::SampleCountFlags::e_1_BIT) != 0) { numSamples += 1; }
    if (static_cast<uint32_t>(sampleCountFlags & pvrvk::SampleCountFlags::e_2_BIT) != 0) { numSamples += 2; }
    if (static_cast<uint32_t>(sampleCountFlags & pvrvk::SampleCountFlags::e_4_BIT) != 0) { numSamples += 4; }
    if (static_cast<uint32_t>(sampleCountFlags & pvrvk::SampleCountFlags::e_8_BIT) != 0) { numSamples += 8; }
    if (static_cast<uint32_t>(sampleCountFlags & pvrvk::SampleCountFlags::e_16_BIT) != 0) { numSamples += 16; }
    if (static_cast<uint32_t>(sampleCountFlags & pvrvk::SampleCountFlags::e_32_BIT) != 0) { numSamples += 32; }
    if (static_cast<uint32_t>(sampleCountFlags & pvrvk::SampleCountFlags::e_64_BIT) != 0) { numSamples += 64; }

    return numSamples;
}

pvrvk::ImageAspectFlags inferAspectFromFormat(pvrvk::Format format, uint32_t planeIndex = 0);

void getColorBits(pvrvk::Format format, uint32_t& redBits, uint32_t& greenBits, uint32_t& blueBits, uint32_t& alphaBits);

void getDepthStencilBits(pvrvk::Format format, uint32_t& depthBits, uint32_t& stencilBits);

#pragma endregion

#pragma region

struct DebugUtilsCallbacks
{
public:
    pvrvk::DebugUtilsMessenger debugUtilsMessengers[2];
    pvrvk::DebugReportCallback debugCallbacks[2];
};

DebugUtilsCallbacks createDebugUtilsCallbacks(pvrvk::Instance& instance, void* pUserData = nullptr);

inline void beginQueueDebugLabel(pvrvk::Queue queue, const pvrvk::DebugUtilsLabel& labelInfo)
{
    // if the VK_EXT_debug_utils extension is supported then start the queue label region
    if (queue->getDevice()->getPhysicalDevice()->getInstance()->getEnabledExtensionTable().extDebugUtilsEnabled) { queue->beginDebugUtilsLabel(labelInfo); }
}

inline void endQueueDebugLabel(pvrvk::Queue queue)
{
    // if the VK_EXT_debug_utils extension is supported then end the queue label region
    if (queue->getDevice()->getPhysicalDevice()->getInstance()->getEnabledExtensionTable().extDebugUtilsEnabled) { queue->endDebugUtilsLabel(); }
}

inline void beginCommandBufferDebugLabel(pvrvk::CommandBufferBase commandBufferBase, const pvrvk::DebugUtilsLabel& labelInfo)
{
    // if the VK_EXT_debug_utils extension is supported then start the queue label region
    if (commandBufferBase->getDevice()->getPhysicalDevice()->getInstance()->getEnabledExtensionTable().extDebugUtilsEnabled) { commandBufferBase->beginDebugUtilsLabel(labelInfo); }
    // else if the VK_EXT_debug_marker extension is supported then start the debug marker region
    else if (commandBufferBase->getDevice()->getEnabledExtensionTable().extDebugMarkerEnabled)
    {
        pvrvk::DebugMarkerMarkerInfo markerInfo(labelInfo.getLabelName(), labelInfo.getR(), labelInfo.getG(), labelInfo.getB(), labelInfo.getA());
        commandBufferBase->debugMarkerBeginEXT(markerInfo);
    }
}

inline void endCommandBufferDebugLabel(pvrvk::CommandBufferBase commandBufferBase)
{
    // if the VK_EXT_debug_utils extension is supported then end the command buffer label region
    if (commandBufferBase->getDevice()->getPhysicalDevice()->getInstance()->getEnabledExtensionTable().extDebugUtilsEnabled) { commandBufferBase->endDebugUtilsLabel(); }
    // else if the VK_EXT_debug_marker extension is supported then end the debug marker region
    else if (commandBufferBase->getDevice()->getEnabledExtensionTable().extDebugMarkerEnabled)
    {
        commandBufferBase->debugMarkerEndEXT();
    }
}

inline void insertDebugUtilsLabel(pvrvk::CommandBufferBase commandBufferBase, const pvrvk::DebugUtilsLabel& labelInfo)
{
    // if the VK_EXT_debug_utils extension is supported then insert the queue label region
    if (commandBufferBase->getDevice()->getPhysicalDevice()->getInstance()->getEnabledExtensionTable().extDebugUtilsEnabled)
    {
        commandBufferBase->insertDebugUtilsLabel(labelInfo);
    } // else if the VK_EXT_debug_marker extension is supported then insert the debug marker
    else if (commandBufferBase->getDevice()->getEnabledExtensionTable().extDebugMarkerEnabled)
    {
        pvrvk::DebugMarkerMarkerInfo markerInfo(labelInfo.getLabelName(), labelInfo.getR(), labelInfo.getG(), labelInfo.getB(), labelInfo.getA());
        commandBufferBase->debugMarkerInsertEXT(markerInfo);
    }
}

inline void beginCommandBufferDebugLabel(pvrvk::CommandBuffer& commandBuffer, const pvrvk::DebugUtilsLabel& labelInfo)
{
    beginCommandBufferDebugLabel(static_cast<pvrvk::CommandBufferBase>(commandBuffer), labelInfo);
}

inline void beginCommandBufferDebugLabel(pvrvk::SecondaryCommandBuffer& secondaryCommandBuffer, const pvrvk::DebugUtilsLabel& labelInfo)
{
    beginCommandBufferDebugLabel(static_cast<pvrvk::CommandBufferBase>(secondaryCommandBuffer), labelInfo);
}

inline void endCommandBufferDebugLabel(pvrvk::CommandBuffer& commandBuffer) { endCommandBufferDebugLabel(static_cast<pvrvk::CommandBufferBase>(commandBuffer)); }

inline void endCommandBufferDebugLabel(pvrvk::SecondaryCommandBuffer& secondaryCommandBuffer)
{
    endCommandBufferDebugLabel(static_cast<pvrvk::CommandBufferBase>(secondaryCommandBuffer));
}

inline void insertDebugUtilsLabel(pvrvk::CommandBuffer& commandBuffer, const pvrvk::DebugUtilsLabel& labelInfo)
{
    insertDebugUtilsLabel(static_cast<pvrvk::CommandBufferBase>(commandBuffer), labelInfo);
}

inline void insertDebugUtilsLabel(pvrvk::SecondaryCommandBuffer& secondaryCommandBuffer, const pvrvk::DebugUtilsLabel& labelInfo)
{
    insertDebugUtilsLabel(static_cast<pvrvk::CommandBufferBase>(secondaryCommandBuffer), labelInfo);
}

VKAPI_ATTR VkBool32 VKAPI_CALL throwOnErrorDebugUtilsMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes,
    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData);

VKAPI_ATTR VkBool32 VKAPI_CALL logMessageDebugUtilsMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes,
    const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData);

VKAPI_ATTR VkBool32 VKAPI_CALL throwOnErrorDebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
    int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData);

VKAPI_ATTR VkBool32 VKAPI_CALL logMessageDebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location,
    int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData);

inline LogLevel mapDebugUtilsMessageSeverityFlagsToLogLevel(pvrvk::DebugUtilsMessageSeverityFlagsEXT flags)
{
    if ((flags & pvrvk::DebugUtilsMessageSeverityFlagsEXT::e_INFO_BIT_EXT) != 0) { return LogLevel::Information; }
    if ((flags & pvrvk::DebugUtilsMessageSeverityFlagsEXT::e_WARNING_BIT_EXT) != 0) { return LogLevel::Warning; }
    if ((flags & pvrvk::DebugUtilsMessageSeverityFlagsEXT::e_VERBOSE_BIT_EXT) != 0) { return LogLevel::Debug; }
    if ((flags & pvrvk::DebugUtilsMessageSeverityFlagsEXT::e_ERROR_BIT_EXT) != 0) { return LogLevel::Error; }
    return LogLevel::Information;
}

inline LogLevel mapDebugReportFlagsToLogLevel(pvrvk::DebugReportFlagsEXT flags)
{
    if ((flags & pvrvk::DebugReportFlagsEXT::e_INFORMATION_BIT_EXT) != 0) { return LogLevel::Information; }
    if ((flags & pvrvk::DebugReportFlagsEXT::e_WARNING_BIT_EXT) != 0) { return LogLevel::Warning; }
    if ((flags & pvrvk::DebugReportFlagsEXT::e_PERFORMANCE_WARNING_BIT_EXT) != 0) { return LogLevel::Performance; }
    if ((flags & pvrvk::DebugReportFlagsEXT::e_ERROR_BIT_EXT) != 0) { return LogLevel::Error; }
    if ((flags & pvrvk::DebugReportFlagsEXT::e_DEBUG_BIT_EXT) != 0) { return LogLevel::Debug; }
    return LogLevel::Information;
}

#pragma endregion

#pragma region

pvrvk::Buffer createBuffer(const pvrvk::Device& device, const pvrvk::BufferCreateInfo& createInfo, pvrvk::MemoryPropertyFlags requiredMemoryFlags,
    pvrvk::MemoryPropertyFlags optimalMemoryFlags = pvrvk::MemoryPropertyFlags::e_NONE, const vma::Allocator& bufferAllocator = nullptr,
    vma::AllocationCreateFlags vmaAllocationCreateFlags = vma::AllocationCreateFlags::e_MAPPED_BIT, pvrvk::MemoryAllocateFlags memoryAllocateFlags = pvrvk::MemoryAllocateFlags::e_NONE);

pvrvk::Image createImage(const pvrvk::Device& device, const pvrvk::ImageCreateInfo& createInfo,
    pvrvk::MemoryPropertyFlags requiredMemoryFlags = pvrvk::MemoryPropertyFlags::e_DEVICE_LOCAL_BIT, pvrvk::MemoryPropertyFlags optimalMemoryFlags = pvrvk::MemoryPropertyFlags::e_NONE,
    const vma::Allocator& imageAllocator = nullptr, vma::AllocationCreateFlags vmaAllocationCreateFlags = vma::AllocationCreateFlags::e_NONE);

#pragma endregion

#pragma region
bool isSupportedFormat(const pvrvk::PhysicalDevice& pdev, pvrvk::Format fmt);

void setImageLayoutAndQueueFamilyOwnership(pvrvk::CommandBufferBase srccmd, pvrvk::CommandBufferBase dstcmd, uint32_t srcQueueFamily, uint32_t dstQueueFamily,
    pvrvk::ImageLayout oldLayout, pvrvk::ImageLayout newLayout, pvrvk::Image& image, uint32_t baseMipLevel, uint32_t numMipLevels, uint32_t baseArrayLayer, uint32_t numArrayLayers,
    pvrvk::ImageAspectFlags aspect);

inline void setImageLayout(pvrvk::Image& image, pvrvk::ImageLayout oldLayout, pvrvk::ImageLayout newLayout, pvrvk::CommandBufferBase transitionCmdBuffer)
{
    setImageLayoutAndQueueFamilyOwnership(transitionCmdBuffer, pvrvk::CommandBufferBase(), static_cast<uint32_t>(-1), static_cast<uint32_t>(-1), oldLayout, newLayout, image, 0,
        image->getNumMipLevels(), 0, static_cast<uint32_t>(image->getNumArrayLayers()), inferAspectFromFormat(image->getFormat()));
}

pvrvk::ImageView uploadImageAndViewSubmit(pvrvk::Device& device, const Texture& texture, bool allowDecompress, pvrvk::CommandPool& commandPool, pvrvk::Queue& queue,
    pvrvk::ImageUsageFlags usageFlags = pvrvk::ImageUsageFlags::e_SAMPLED_BIT, pvrvk::ImageLayout finalLayout = pvrvk::ImageLayout::e_SHADER_READ_ONLY_OPTIMAL,
    vma::Allocator stagingBufferAllocator = nullptr, vma::Allocator imageAllocator = nullptr,
    vma::AllocationCreateFlags imageAllocationCreateFlags = vma::AllocationCreateFlags::e_NONE);

pvrvk::ImageView uploadImageAndView(pvrvk::Device& device, const Texture& texture, bool allowDecompress, pvrvk::SecondaryCommandBuffer& commandBuffer,
    pvrvk::ImageUsageFlags usageFlags = pvrvk::ImageUsageFlags::e_SAMPLED_BIT, pvrvk::ImageLayout finalLayout = pvrvk::ImageLayout::e_SHADER_READ_ONLY_OPTIMAL,
    vma::Allocator stagingBufferAllocator = nullptr, vma::Allocator imageAllocator = nullptr,
    vma::AllocationCreateFlags imageAllocationCreateFlags = vma::AllocationCreateFlags::e_NONE);

pvrvk::ImageView uploadImageAndView(pvrvk::Device& device, const Texture& texture, bool allowDecompress, pvrvk::CommandBuffer& commandBuffer,
    pvrvk::ImageUsageFlags usageFlags = pvrvk::ImageUsageFlags::e_SAMPLED_BIT, pvrvk::ImageLayout finalLayout = pvrvk::ImageLayout::e_SHADER_READ_ONLY_OPTIMAL,
    vma::Allocator stagingBufferAllocator = nullptr, vma::Allocator imageAllocator = nullptr,
    vma::AllocationCreateFlags imageAllocationCreateFlags = vma::AllocationCreateFlags::e_NONE);

pvrvk::Image uploadImage(pvrvk::Device& device, const Texture& texture, bool allowDecompress, pvrvk::CommandBuffer& commandBuffer,
    pvrvk::ImageUsageFlags usageFlags = pvrvk::ImageUsageFlags::e_SAMPLED_BIT, pvrvk::ImageLayout finalLayout = pvrvk::ImageLayout::e_SHADER_READ_ONLY_OPTIMAL,
    vma::Allocator stagingBufferAllocator = nullptr, vma::Allocator imageAllocator = nullptr,
    vma::AllocationCreateFlags imageAllocationCreateFlags = vma::AllocationCreateFlags::e_NONE);

pvrvk::ImageView loadAndUploadImageAndView(pvrvk::Device& device, const char* fileName, bool allowDecompress, pvrvk::CommandBuffer& commandBuffer, IAssetProvider& assetProvider,
    pvrvk::ImageUsageFlags usageFlags = pvrvk::ImageUsageFlags::e_SAMPLED_BIT, pvrvk::ImageLayout finalLayout = pvrvk::ImageLayout::e_SHADER_READ_ONLY_OPTIMAL,
    Texture* outAssetTexture = nullptr, vma::Allocator stagingBufferAllocator = nullptr, vma::Allocator imageAllocator = nullptr,
    vma::AllocationCreateFlags imageAllocationCreateFlags = vma::AllocationCreateFlags::e_NONE, const void* pNext = nullptr);

pvrvk::Image loadAndUploadImage(pvrvk::Device& device, const char* fileName, bool allowDecompress, pvrvk::CommandBuffer& commandBuffer, IAssetProvider& assetProvider,
    pvrvk::ImageUsageFlags usageFlags = pvrvk::ImageUsageFlags::e_SAMPLED_BIT, pvrvk::ImageLayout finalLayout = pvrvk::ImageLayout::e_SHADER_READ_ONLY_OPTIMAL,
    Texture* outAssetTexture = nullptr, vma::Allocator stagingBufferAllocator = nullptr, vma::Allocator imageAllocator = nullptr,
    vma::AllocationCreateFlags imageAllocationCreateFlags = vma::AllocationCreateFlags::e_NONE);

pvrvk::Image loadAndUploadImage(pvrvk::Device& device, const std::string& fileName, bool allowDecompress, pvrvk::CommandBuffer& commandBuffer, IAssetProvider& assetProvider,
    pvrvk::ImageUsageFlags usageFlags = pvrvk::ImageUsageFlags::e_SAMPLED_BIT, pvrvk::ImageLayout finalLayout = pvrvk::ImageLayout::e_SHADER_READ_ONLY_OPTIMAL,
    Texture* outAssetTexture = nullptr, vma::Allocator stagingBufferAllocator = nullptr, vma::Allocator imageAllocator = nullptr,
    vma::AllocationCreateFlags imageAllocationCreateFlags = vma::AllocationCreateFlags::e_NONE);

pvrvk::ImageView loadAndUploadImageAndView(pvrvk::Device& device, const char* fileName, bool allowDecompress, pvrvk::SecondaryCommandBuffer& commandBuffer,
    IAssetProvider& assetProvider, pvrvk::ImageUsageFlags usageFlags = pvrvk::ImageUsageFlags::e_SAMPLED_BIT,
    pvrvk::ImageLayout finalLayout = pvrvk::ImageLayout::e_SHADER_READ_ONLY_OPTIMAL, Texture* outAssetTexture = nullptr, vma::Allocator stagingBufferAllocator = nullptr,
    vma::Allocator imageAllocator = nullptr, vma::AllocationCreateFlags imageAllocationCreateFlags = vma::AllocationCreateFlags::e_NONE, const void* pNext = nullptr);

pvrvk::Image loadAndUploadImage(pvrvk::Device& device, const char* fileName, bool allowDecompress, pvrvk::SecondaryCommandBuffer& commandBuffer, IAssetProvider& assetProvider,
    pvrvk::ImageUsageFlags usageFlags = pvrvk::ImageUsageFlags::e_SAMPLED_BIT, pvrvk::ImageLayout finalLayout = pvrvk::ImageLayout::e_SHADER_READ_ONLY_OPTIMAL,
    Texture* outAssetTexture = nullptr, vma::Allocator stagingBufferAllocator = nullptr, vma::Allocator imageAllocator = nullptr,
    vma::AllocationCreateFlags imageAllocationCreateFlags = vma::AllocationCreateFlags::e_NONE);

struct ImageUpdateInfo
{
    // 1D/Array texture and common for rest
    int32_t offsetX;
    uint32_t imageWidth;
    uint32_t dataWidth;
    uint32_t arrayIndex;
    uint32_t mipLevel;
    const void* data;
    uint32_t dataSize;

    // 2D/ Array texture only
    int32_t offsetY;
    uint32_t imageHeight;
    uint32_t dataHeight;

    // cube/ Array Map only. Derive all states above
    uint32_t cubeFace;

    // 3D texture Only. Derive all states above Except arrayIndex
    int32_t offsetZ;
    uint32_t depth;

    // YCbCr texture only
    uint32_t numPlanes;
    uint32_t planeIndex;

    ImageUpdateInfo()
        : offsetX(0), imageWidth(1), dataWidth(1), mipLevel(0), data(nullptr), dataSize(0), offsetY(0), imageHeight(1), dataHeight(1), cubeFace(0), offsetZ(0), depth(1), numPlanes(1)
    {}
};

void updateImage(pvrvk::Device& device, pvrvk::CommandBufferBase transferCommandBuffer, ImageUpdateInfo* updateInfos, uint32_t numUpdateInfos, pvrvk::Format format,
    pvrvk::ImageLayout layout, bool isCubeMap, pvrvk::Image& image, vma::Allocator bufferAllocator = nullptr);

inline void updateHostVisibleBuffer(pvrvk::Buffer& buffer, const void* data, VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE, bool flushMemory = false)
{
    void* mapData = nullptr;
    bool unmap = false;
    if (!buffer->getDeviceMemory()->isMapped())
    {
        mapData = buffer->getDeviceMemory()->map(offset, size);
        unmap = true;
    }
    else
    {
        mapData = static_cast<char*>(buffer->getDeviceMemory()->getMappedData()) + offset;
    }

    memcpy(mapData, data, (size_t)size);

    if (static_cast<uint32_t>(buffer->getDeviceMemory()->getMemoryFlags() & pvrvk::MemoryPropertyFlags::e_HOST_COHERENT_BIT) != 0) { flushMemory = false; }

    if (flushMemory) { buffer->getDeviceMemory()->flushRange(offset, size); }
    if (unmap) { buffer->getDeviceMemory()->unmap(); }
}

inline void updateBufferUsingStagingBuffer(pvrvk::Device& device, pvrvk::Buffer& buffer, pvrvk::CommandBufferBase uploadCmdBuffer, const void* data, VkDeviceSize offset = 0,
    VkDeviceSize size = VK_WHOLE_SIZE, vma::Allocator stagingBufferAllocator = nullptr)
{
    // Updating memory via the use of staging buffers is necessary when memory is not host visible. In this case the buffer memory will be updated indirectly as follows:
    //      1. Create a staging buffer
    //      2. map the staging buffer memory, update the memory, then unmap the buffer memory
    //      3. Copy from the staging buffer to the target buffer

    pvr::utils::beginCommandBufferDebugLabel(uploadCmdBuffer, pvrvk::DebugUtilsLabel("PVRUtilsVk::updateBufferUsingStagingBuffer"));

    // 1. Create a staging buffer
    pvrvk::MemoryPropertyFlags memoryFlags = pvrvk::MemoryPropertyFlags::e_HOST_VISIBLE_BIT;
    pvrvk::Buffer stagingBuffer =
        pvr::utils::createBuffer(device, pvrvk::BufferCreateInfo(size, pvrvk::BufferUsageFlags::e_TRANSFER_SRC_BIT), memoryFlags, memoryFlags, stagingBufferAllocator);

    // 2. map (if required), then update the memory, then finally unmap (if required)
    updateHostVisibleBuffer(stagingBuffer, data, 0, size, true);

    // 3. Copy from the staging buffer to the target buffer
    const pvrvk::BufferCopy bufferCopy(0, offset, size);
    uploadCmdBuffer->copyBuffer(stagingBuffer, buffer, 1, &bufferCopy);

    pvr::utils::endCommandBufferDebugLabel(uploadCmdBuffer);
}

void generateTextureAtlas(pvrvk::Device& device, const pvrvk::Image* inputImages, pvrvk::Rect2Df* outUVs, uint32_t numImages, pvrvk::ImageLayout inputImageLayout,
    pvrvk::ImageView* outImageView, TextureHeader* outDescriptor, pvrvk::CommandBufferBase cmdBuffer, pvrvk::ImageLayout finalLayout = pvrvk::ImageLayout::e_SHADER_READ_ONLY_OPTIMAL,
    vma::Allocator imageAllocator = nullptr, vma::AllocationCreateFlags imageAllocationCreateFlags = vma::AllocationCreateFlags::e_NONE);

#pragma endregion

#pragma region

struct VulkanVersion
{
    uint32_t majorV;
    uint32_t minorV;
    uint32_t patchV;

    VulkanVersion(uint32_t majorV = 1, uint32_t minorV = 0, uint32_t patchV = 0) : majorV(majorV), minorV(minorV), patchV(patchV) {}

    uint32_t toVulkanVersion() { return VK_MAKE_VERSION(majorV, minorV, patchV); }
};

struct InstanceLayers : public pvrvk::VulkanLayerList
{
    InstanceLayers(bool forceLayers =
#ifdef DEBUG
                       true);
#else
                       false);
#endif
};

struct InstanceExtensions : public pvrvk::VulkanExtensionList
{
    InstanceExtensions(VulkanVersion vkVersion = VulkanVersion());
};

struct DeviceExtensions : public pvrvk::VulkanExtensionList
{
    DeviceExtensions(VulkanVersion vkVersion = VulkanVersion());

    DeviceExtensions& addExtensionFeature(pvrvk::ExtensionFeatures& extensionFeature);

    DeviceExtensions& addFragmentShadingRateExtensionAndFeature(pvrvk::PhysicalDevice& physicalDevice);

    // DeviceExtensions& addRayTracingExtensionAndFeature(pvrvk::PhysicalDevice& physicalDevice);

private:
    std::vector<std::shared_ptr<void>> _featureReferences;
};

pvrvk::Instance createInstance(const std::string& applicationName, VulkanVersion apiVersion = VulkanVersion(), const InstanceExtensions& instanceExtensions = InstanceExtensions(),
    const InstanceLayers& instanceLayers = InstanceLayers(),
    const pvrvk::DebugUtilsMessageSeverityFlagsEXT InstanceValidationFlags = pvrvk::DebugUtilsMessageSeverityFlagsEXT::e_WARNING_BIT_EXT |
        pvrvk::DebugUtilsMessageSeverityFlagsEXT::e_ERROR_BIT_EXT);

struct QueuePopulateInfo
{
    pvrvk::QueueFlags queueFlags;

    pvrvk::Surface surface;

    float priority;

    QueuePopulateInfo(pvrvk::QueueFlags queueFlags, float priority = 1.0f) : queueFlags(queueFlags), priority(priority) {}

    QueuePopulateInfo(pvrvk::QueueFlags queueFlags, pvrvk::Surface& surface, float priority = 1.0f) : queueFlags(queueFlags), surface(surface), priority(priority) {}
};

struct QueueAccessInfo
{
    uint32_t familyId;

    uint32_t queueId;

    QueueAccessInfo() : familyId(static_cast<uint32_t>(-1)), queueId(static_cast<uint32_t>(-1)) {}
};

pvrvk::Device createDeviceAndQueues(pvrvk::PhysicalDevice physicalDevice, const QueuePopulateInfo* queueCreateInfos, uint32_t numQueueCreateInfos, QueueAccessInfo* outAccessInfo,
    const DeviceExtensions& deviceExtensions = DeviceExtensions());

#pragma endregion

#pragma region

pvrvk::Surface createSurface(const pvrvk::Instance& instance, const pvrvk::PhysicalDevice& physicalDevice, void* window, void* display, void* connection);

inline bool isImageUsageSupportedBySurface(const pvrvk::SurfaceCapabilitiesKHR& surfaceCapabilities, pvrvk::ImageUsageFlags imageUsage)
{
    return (static_cast<uint32_t>(surfaceCapabilities.getSupportedUsageFlags() & imageUsage) != 0);
}

pvrvk::Swapchain createSwapchain(const pvrvk::Device& device, const pvrvk::Surface& surface, pvr::DisplayAttributes& displayAttributes,
    pvrvk::ImageUsageFlags swapchainImageUsageFlags = pvrvk::ImageUsageFlags::e_COLOR_ATTACHMENT_BIT,
    const std::vector<pvrvk::Format>& preferredColorFormats = std::vector<pvrvk::Format>());

bool isSupportedDepthStencilFormat(const pvrvk::Device& device, pvrvk::Format format);

pvrvk::Format getSupportedDepthStencilFormat(const pvrvk::Device& device, std::vector<pvrvk::Format> preferredDepthFormats = {});
pvrvk::Format getSupportedDepthStencilFormat(const pvrvk::Device& device, pvr::DisplayAttributes& displayAttributes, std::vector<pvrvk::Format> preferredDepthFormats = {});

template<typename ImageContainer>
inline static void createAttachmentImages(ImageContainer& outImages, const pvrvk::Device& device, int32_t imageCount, pvrvk::Format format, const pvrvk::Extent2D& imageExtent,
    const pvrvk::ImageUsageFlags& imageUsageFlags, pvrvk::SampleCountFlags sampleCount, const vma::Allocator& imageAllocator = nullptr,
    vma::AllocationCreateFlags imageAllocationCreateFlags = vma::AllocationCreateFlags::e_DEDICATED_MEMORY_BIT, const std::string& objectName = std::string())
{
    // the required memory property flags
    const pvrvk::MemoryPropertyFlags requiredMemoryProperties = pvrvk::MemoryPropertyFlags::e_DEVICE_LOCAL_BIT;

    // more optimal set of memory property flags
    const pvrvk::MemoryPropertyFlags optimalMemoryProperties = (imageUsageFlags & pvrvk::ImageUsageFlags::e_TRANSIENT_ATTACHMENT_BIT) != 0
        ? pvrvk::MemoryPropertyFlags::e_LAZILY_ALLOCATED_BIT
        : pvrvk::MemoryPropertyFlags::e_DEVICE_LOCAL_BIT;

    for (int32_t i = 0; i < imageCount; ++i)
    {
        pvrvk::Image depthStencilImage = createImage(device,
            pvrvk::ImageCreateInfo(pvrvk::ImageType::e_2D, format, pvrvk::Extent3D(imageExtent.getWidth(), imageExtent.getHeight(), 1u), imageUsageFlags, 1, 1, sampleCount),
            requiredMemoryProperties, optimalMemoryProperties, imageAllocator, imageAllocationCreateFlags);
        depthStencilImage->setObjectName(objectName + " Image [" + std::to_string(i) + std::string("]"));

        outImages[i] = device->createImageView(pvrvk::ImageViewCreateInfo(depthStencilImage));
        outImages[i]->setObjectName(std::string(objectName + " ImageView [" + std::to_string(i) + std::string("]")));
    }
}

struct CreateSwapchainParameters
{
    bool createDepthBuffer = true;
    pvrvk::ImageUsageFlags colorImageUsageFlags = pvrvk::ImageUsageFlags::e_COLOR_ATTACHMENT_BIT;
    pvrvk::ImageUsageFlags depthStencilImageUsageFlags = pvrvk::ImageUsageFlags::e_DEPTH_STENCIL_ATTACHMENT_BIT | pvrvk::ImageUsageFlags::e_TRANSIENT_ATTACHMENT_BIT;
    pvrvk::ImageUsageFlags colorAttachmentFlagsIfMultisampled = pvrvk::ImageUsageFlags::e_COLOR_ATTACHMENT_BIT | pvrvk::ImageUsageFlags::e_TRANSIENT_ATTACHMENT_BIT;
    pvrvk::ImageUsageFlags depthStencilAttachmentFlagsIfMultisampled = pvrvk::ImageUsageFlags::e_DEPTH_STENCIL_ATTACHMENT_BIT | pvrvk::ImageUsageFlags::e_TRANSIENT_ATTACHMENT_BIT;
    vma::Allocator imageAllocator = nullptr;
    vma::AllocationCreateFlags imageAllocatorFlags = vma::AllocationCreateFlags::e_DEDICATED_MEMORY_BIT;

    pvrvk::ImageLayout initialSwapchainLayout = pvrvk::ImageLayout::e_UNDEFINED;
    pvrvk::ImageLayout initialDepthStencilLayout = pvrvk::ImageLayout::e_UNDEFINED;
    pvrvk::AttachmentLoadOp colorLoadOp = pvrvk::AttachmentLoadOp::e_CLEAR;
    pvrvk::AttachmentStoreOp colorStoreOp = pvrvk::AttachmentStoreOp::e_STORE;
    pvrvk::AttachmentLoadOp depthStencilLoadOp = pvrvk::AttachmentLoadOp::e_CLEAR;
    pvrvk::AttachmentStoreOp depthStencilStoreOp = pvrvk::AttachmentStoreOp::e_DONT_CARE;

    void addPreferredColorFormat(pvrvk::Format format) { preferredColorFormats.push_back(format); }
    void addPreferredDepthStencilFormat(pvrvk::Format format) { preferredDepthStencilFormats.push_back(format); }

    std::vector<pvrvk::Format> preferredColorFormats;
    std::vector<pvrvk::Format> preferredDepthStencilFormats;
    CreateSwapchainParameters(bool createDepthBuffer = true, pvrvk::ImageUsageFlags colorBufferImageUsageFlags = pvrvk::ImageUsageFlags::e_COLOR_ATTACHMENT_BIT,
        pvrvk::ImageUsageFlags depthStencilBufferImageUsageFlags = pvrvk::ImageUsageFlags::e_DEPTH_STENCIL_ATTACHMENT_BIT | pvrvk::ImageUsageFlags::e_TRANSIENT_ATTACHMENT_BIT,
        pvrvk::ImageUsageFlags intermediateColorImageUsageFlagsIfMultisampled = pvrvk::ImageUsageFlags::e_COLOR_ATTACHMENT_BIT,
        pvrvk::ImageUsageFlags intermediateDepthStencilImageUsageFlags = pvrvk::ImageUsageFlags::e_DEPTH_STENCIL_ATTACHMENT_BIT | pvrvk::ImageUsageFlags::e_TRANSIENT_ATTACHMENT_BIT,
        vma::Allocator imageAllocator = nullptr, vma::AllocationCreateFlags imageAllocatorFlags = vma::AllocationCreateFlags::e_DEDICATED_MEMORY_BIT)
        : createDepthBuffer(createDepthBuffer), colorImageUsageFlags(colorBufferImageUsageFlags), depthStencilImageUsageFlags(depthStencilBufferImageUsageFlags),
          colorAttachmentFlagsIfMultisampled(intermediateColorImageUsageFlagsIfMultisampled), depthStencilAttachmentFlagsIfMultisampled(intermediateDepthStencilImageUsageFlags),
          imageAllocator(imageAllocator), imageAllocatorFlags(imageAllocatorFlags)
    { //
    }
    CreateSwapchainParameters& setAllocator(vma::Allocator allocator)
    {
        this->imageAllocator = allocator;
        return *this;
    }
    CreateSwapchainParameters& setAllocatorFlags(vma::AllocationCreateFlags flags)
    {
        this->imageAllocatorFlags = flags;
        return *this;
    }
    CreateSwapchainParameters& enableDepthBuffer(bool enableCreateDepthBuffer)
    {
        this->createDepthBuffer = enableCreateDepthBuffer;
        return *this;
    }
    CreateSwapchainParameters& setColorImageUsageFlags(pvrvk::ImageUsageFlags flags)
    {
        this->colorImageUsageFlags = flags;
        return *this;
    }
    CreateSwapchainParameters& setMultisampledColorResolveImageUsageFlags(pvrvk::ImageUsageFlags flags)
    {
        this->colorImageUsageFlags = flags;
        return *this;
    }
    CreateSwapchainParameters& setDepthStencilImageUsageFlags(pvrvk::ImageUsageFlags flags)
    {
        this->depthStencilImageUsageFlags = flags;
        return *this;
    }
    CreateSwapchainParameters& setMultisampledDepthStencilResolveImageUsageFlags(pvrvk::ImageUsageFlags flags)
    {
        this->depthStencilImageUsageFlags = flags;
        return *this;
    }
    CreateSwapchainParameters& setMultisampledDepthStencilAttachmentFlags(pvrvk::ImageUsageFlags flags)
    {
        this->depthStencilAttachmentFlagsIfMultisampled = flags;
        return *this;
    }
    CreateSwapchainParameters& setMultisampledColorAttachmentFlags(pvrvk::ImageUsageFlags flags)
    {
        this->colorAttachmentFlagsIfMultisampled = flags;
        return *this;
    }
    CreateSwapchainParameters& setColorLoadOp(pvrvk::AttachmentLoadOp op)
    {
        this->colorLoadOp = op;
        return *this;
    }
    CreateSwapchainParameters& setColorStoreOp(pvrvk::AttachmentStoreOp op)
    {
        this->colorStoreOp = op;
        return *this;
    }
    CreateSwapchainParameters& setDepthStencilLoadOp(pvrvk::AttachmentLoadOp op)
    {
        this->depthStencilLoadOp = op;
        return *this;
    }
    CreateSwapchainParameters& setDepthStencilStoreOp(pvrvk::AttachmentStoreOp op)
    {
        this->depthStencilStoreOp = op;
        return *this;
    }

    CreateSwapchainParameters& setInitialSwapchainLayout(pvrvk::ImageLayout layout)
    {
        this->initialSwapchainLayout = layout;
        return *this;
    }
    CreateSwapchainParameters& setInitialDepthStencilLayout(pvrvk::ImageLayout layout)
    {
        this->initialDepthStencilLayout = layout;
        return *this;
    }
};

struct OnScreenObjects
{
    pvrvk::RenderPass renderPass;
    std::vector<pvrvk::Framebuffer> framebuffer;
    pvrvk::Swapchain swapchain;
    std::vector<pvrvk::ImageView> depthStencilImages;
    std::vector<pvrvk::ImageView> colorMultisampledAttachmentImages;
    std::vector<pvrvk::ImageView> depthStencilMultisampledAttachmentImages;
    bool isMultisampled() { return colorMultisampledAttachmentImages.size() != 0; }
    bool hasDepthStencil() { return depthStencilImages.size() != 0; }
};

OnScreenObjects createSwapchainRenderpassFramebuffers(
    const pvrvk::Device& device, const pvrvk::Surface& surface, pvr::DisplayAttributes& displayAttributes, const CreateSwapchainParameters& params = CreateSwapchainParameters());

[[deprecated("createDepthStencilImageAndViews is deprecated in v5.5 and will be removed. Superseded by createSwapchainRenderpassFramebuffers which supports multisampling "
             "correctly and "
             "provides an improved interface.")]] void
    createSwapchainAndDepthStencilImageAndViews(const pvrvk::Device& device, const pvrvk::Surface& surface, pvr::DisplayAttributes& displayAttributes, pvrvk::Swapchain& outSwapchain,
        Multi<pvrvk::ImageView>& outDepthStencilImages, const pvrvk::ImageUsageFlags& swapchainImageUsageFlags = pvrvk::ImageUsageFlags::e_COLOR_ATTACHMENT_BIT,
        const pvrvk::ImageUsageFlags& dsImageUsageFlags = pvrvk::ImageUsageFlags::e_DEPTH_STENCIL_ATTACHMENT_BIT | pvrvk::ImageUsageFlags::e_TRANSIENT_ATTACHMENT_BIT,
        const vma::Allocator& imageAllocator = nullptr, vma::AllocationCreateFlags dsImageAllocationCreateFlags = vma::AllocationCreateFlags::e_DEDICATED_MEMORY_BIT);

[[deprecated("createDepthStencilImageAndViews is deprecated. Superseded by createAttachmentImages, which has an improved interface and allows any kind of attachment and "
             "container.")]] inline std::vector<pvrvk::ImageView>
    createDepthStencilImageAndViews(const pvrvk::Device& device, int32_t imageCount, pvrvk::Format depthFormat, const pvrvk::Extent2D& imageExtent,
        const pvrvk::ImageUsageFlags& imageUsageFlags = pvrvk::ImageUsageFlags::e_DEPTH_STENCIL_ATTACHMENT_BIT | pvrvk::ImageUsageFlags::e_TRANSIENT_ATTACHMENT_BIT,
        pvrvk::SampleCountFlags sampleCount = pvrvk::SampleCountFlags::e_1_BIT, vma::Allocator dsImageAllocator = nullptr,
        vma::AllocationCreateFlags dsImageAllocationCreateFlags = vma::AllocationCreateFlags::e_DEDICATED_MEMORY_BIT)
{
    std::vector<pvrvk::ImageView> depthStencilImages(imageCount);
    createAttachmentImages(depthStencilImages, device, imageCount, depthFormat, imageExtent, imageUsageFlags, sampleCount, dsImageAllocator, dsImageAllocationCreateFlags,
        "PVRUtilsVk::DepthStencil ");
    return depthStencilImages;
}

pvrvk::RenderPass createOnScreenRenderPass(const pvrvk::Swapchain& swapchain, bool hasDepthStencil, const pvrvk::Format depthStencilFormat = pvrvk::Format::e_UNDEFINED,
    pvrvk::ImageLayout initialSwapchainLayout = pvrvk::ImageLayout::e_UNDEFINED, pvrvk::ImageLayout initialDepthStencilLayout = pvrvk::ImageLayout::e_UNDEFINED,
    pvrvk::AttachmentLoadOp colorLoadOp = pvrvk::AttachmentLoadOp::e_CLEAR, pvrvk::AttachmentStoreOp colorStoreOp = pvrvk::AttachmentStoreOp::e_STORE,
    pvrvk::AttachmentLoadOp depthStencilLoadOp = pvrvk::AttachmentLoadOp::e_CLEAR, pvrvk::AttachmentStoreOp depthStencilStoreOp = pvrvk::AttachmentStoreOp::e_DONT_CARE,
    pvrvk::SampleCountFlags samples = pvrvk::SampleCountFlags::e_1_BIT);

namespace details {
inline void assignAttachmentIndexes(bool hasDepth, bool isMultisampled, int& outColorIdx, int& outDepthIdx, int& outColorResolveIdx, int& outDepthResolveIdx)
{
    outColorIdx = 0;
    outDepthIdx = hasDepth ? 1 : -1; // Either 1, or unused
    outColorResolveIdx = isMultisampled ? hasDepth ? 2 : 1 : -1; // 1 or 2, or unused
    outDepthResolveIdx = (isMultisampled && hasDepth) ? 3 : -1; // Either 3, or unused
}
} // namespace details

template<typename ContainerType>
inline ContainerType createOnscreenFramebuffers(const pvrvk::Swapchain& swapchain, const pvrvk::RenderPass& renderPass, const pvrvk::ImageView* depthStencilImages,
    pvrvk::ImageView* colorMultisampledImages, pvrvk::ImageView* depthStencilMultisampledImages)
{
    ContainerType framebuffers;
    framebuffers.resize(swapchain->getSwapchainLength());

    bool multisample = (renderPass->getCreateInfo().getSubpass(0).getNumResolveAttachmentReference() > 0);
    if (multisample && !colorMultisampledImages)
    {
        throw InvalidArgumentError("colorResolveImages", "pvr::utils::createOnScreenFramebuffers: On a multisampled swapchain, color resolve images cannot be null be provided");
    }
    if (!multisample && colorMultisampledImages)
    {
        throw InvalidArgumentError("colorResolveImages", "pvr::utils::createOnScreenFramebuffers: On a non-multisampled swapchain, color resolve images must be null");
    }
    if (multisample && depthStencilImages && !depthStencilMultisampledImages)
    {
        throw InvalidArgumentError("depthStencilResolveImages",
            "pvr::utils::createOnScreenFramebuffers: On a multisampled swapchain with a depth buffer, depth resolve images must be provided and cannot be null");
    }
    if (!multisample && depthStencilImages && depthStencilMultisampledImages)
    {
        throw InvalidArgumentError(
            "depthStencilResolveImages", "pvr::utils::createOnScreenFramebuffers: On a non-multisampled swapchain with a depth buffer, depth resolve images must be null");
    }

    if (depthStencilMultisampledImages && !depthStencilImages)
    {
        throw InvalidArgumentError(
            "depthStencilResolveImages", "pvr::utils::createOnScreenFramebuffers: Cannot provide depth resolve images without the corresponding depth images must be null");
    }

    int colorIdx, depthIdx, colorResolveIdx, depthResolveIdx;
    details::assignAttachmentIndexes(depthStencilImages != 0, colorMultisampledImages != 0, colorIdx, depthIdx, colorResolveIdx, depthResolveIdx);

    for (uint32_t i = 0; i < swapchain->getSwapchainLength(); ++i)
    {
        pvrvk::FramebufferCreateInfo framebufferInfo;
        framebufferInfo.setDimensions(swapchain->getDimension());
        if (multisample)
        {
            framebufferInfo.setAttachment(colorResolveIdx, swapchain->getImageView(i));
            framebufferInfo.setAttachment(colorIdx, colorMultisampledImages[i]);
            if (depthStencilImages)
            {
                framebufferInfo.setAttachment(depthIdx, depthStencilMultisampledImages[i]);
                framebufferInfo.setAttachment(depthResolveIdx, depthStencilImages[i]);
            }
        }
        else
        {
            framebufferInfo.setAttachment(colorIdx, swapchain->getImageView(i));
            if (depthStencilImages) { framebufferInfo.setAttachment(depthIdx, depthStencilImages[i]); }
        }

        framebufferInfo.setRenderPass(renderPass);

        framebuffers[i] = swapchain->getDevice()->createFramebuffer(framebufferInfo);
        framebuffers[i]->setObjectName(std::string("PVRUtilsVk::OnScreenFrameBuffer [") + std::to_string(i) + std::string("]"));
    }

    return framebuffers;
}

[[deprecated("This function is deprecated in v5.5 and will be removed. It is superseded by "
             "createSwapchainFramebufferRenderpass, which supports multisampling correctly and has an improved interface.")]] inline pvrvk::RenderPass
    createOnscreenFramebufferAndRenderPass(const pvrvk::Swapchain& swapchain, pvrvk::ImageView* depthStencilImages, Multi<pvrvk::Framebuffer>& outFramebuffers,
        pvrvk::ImageLayout initialSwapchainLayout = pvrvk::ImageLayout::e_UNDEFINED, pvrvk::ImageLayout initialDepthStencilLayout = pvrvk::ImageLayout::e_UNDEFINED,
        pvrvk::AttachmentLoadOp colorLoadOp = pvrvk::AttachmentLoadOp::e_CLEAR, pvrvk::AttachmentStoreOp colorStoreOp = pvrvk::AttachmentStoreOp::e_STORE,
        pvrvk::AttachmentLoadOp depthStencilLoadOp = pvrvk::AttachmentLoadOp::e_CLEAR, pvrvk::AttachmentStoreOp depthStencilStoreOp = pvrvk::AttachmentStoreOp::e_DONT_CARE)
{
    pvrvk::RenderPass outRenderPass =
        createOnScreenRenderPass(swapchain, depthStencilImages != nullptr, depthStencilImages ? depthStencilImages[0]->getFormat() : pvrvk::Format::e_UNDEFINED,
            initialSwapchainLayout, initialDepthStencilLayout, colorLoadOp, colorStoreOp, depthStencilLoadOp, depthStencilStoreOp);
    auto fbos = createOnscreenFramebuffers<Multi<pvrvk::Framebuffer>>(swapchain, outRenderPass, depthStencilImages, nullptr, nullptr);
    std::swap(outFramebuffers, fbos);
    return outRenderPass;
}

inline void populateViewportStateCreateInfo(const pvrvk::Framebuffer& framebuffer, pvrvk::PipelineViewportStateCreateInfo& outCreateInfo)
{
    outCreateInfo.setViewportAndScissor(0,
        pvrvk::Viewport(0.f, 0.f, static_cast<float>(framebuffer->getDimensions().getWidth()), static_cast<float>(framebuffer->getDimensions().getHeight())),
        pvrvk::Rect2D(pvrvk::Offset2D(0, 0), pvrvk::Extent2D(framebuffer->getDimensions().getWidth(), framebuffer->getDimensions().getHeight())));
}

#pragma endregion

#pragma region

struct VertexBindings
{
    std::string semanticName;

    int16_t location;
};

struct VertexBindings_Name
{
    StringHash semantic;

    StringHash variableName;
};

inline void populateInputAssemblyFromMesh(const assets::Mesh& mesh, const VertexBindings* bindingMap, uint16_t numBindings,
    pvrvk::PipelineVertexInputStateCreateInfo& vertexCreateInfo, pvrvk::PipelineInputAssemblerStateCreateInfo& inputAssemblerCreateInfo, uint16_t* numOutBuffers = nullptr)
{
    vertexCreateInfo.clear();
    if (numOutBuffers) { *numOutBuffers = 0; }
    uint16_t current = 0;
    while (current < numBindings)
    {
        auto attr = mesh.getVertexAttributeByName(bindingMap[current].semanticName.c_str());
        if (attr)
        {
            VertexAttributeLayout layout = attr->getVertexLayout();
            uint32_t stride = mesh.getStride(attr->getDataIndex());
            if (numOutBuffers) { *numOutBuffers = std::max(static_cast<uint16_t>(attr->getDataIndex() + 1u), *numOutBuffers); }

            const pvrvk::VertexInputAttributeDescription attribDesc(static_cast<uint32_t>(bindingMap[current].location), attr->getDataIndex(),
                convertToPVRVkVertexInputFormat(layout.dataType, layout.width), static_cast<uint32_t>(layout.offset));

            const pvrvk::VertexInputBindingDescription bindingDesc(attr->getDataIndex(), stride, pvrvk::VertexInputRate::e_VERTEX);
            vertexCreateInfo.addInputAttribute(attribDesc).addInputBinding(bindingDesc);
        }
        else
        {
            Log("Could not find Attribute with Semantic %s in the supplied mesh. Will render without binding it, erroneously.", bindingMap[current].semanticName.c_str());
        }
        ++current;
    }
    inputAssemblerCreateInfo.setPrimitiveTopology(convertToPVRVk(mesh.getMeshInfo().primitiveType));
}

inline void populateInputAssemblyFromMesh(const assets::Mesh& mesh, const VertexBindings_Name* bindingMap, uint32_t numBindings,
    pvrvk::PipelineVertexInputStateCreateInfo& vertexCreateInfo, pvrvk::PipelineInputAssemblerStateCreateInfo& inputAssemblerCreateInfo, uint32_t* numOutBuffers = nullptr)
{
    vertexCreateInfo.clear();
    if (numOutBuffers) { *numOutBuffers = 0; }
    uint32_t current = 0;
    // In this scenario, we will be using our own indexes instead of user provided ones, correlating them by names.
    vertexCreateInfo.clear();
    while (current < numBindings)
    {
        auto attr = mesh.getVertexAttributeByName(bindingMap[current].semantic);
        if (attr)
        {
            VertexAttributeLayout layout = attr->getVertexLayout();
            uint32_t stride = mesh.getStride(attr->getDataIndex());

            if (numOutBuffers) { *numOutBuffers = std::max<uint32_t>(attr->getDataIndex() + 1u, *numOutBuffers); }
            const pvrvk::VertexInputAttributeDescription attribDesc(current, attr->getDataIndex(), convertToPVRVkVertexInputFormat(layout.dataType, layout.width), layout.offset);

            const pvrvk::VertexInputBindingDescription bindingDesc(attr->getDataIndex(), stride, pvrvk::VertexInputRate::e_VERTEX);
            vertexCreateInfo.addInputAttribute(attribDesc).addInputBinding(bindingDesc);
            inputAssemblerCreateInfo.setPrimitiveTopology(convertToPVRVk(mesh.getMeshInfo().primitiveType));
        }
        else
        {
            Log("Could not find Attribute with Semantic %s in the supplied mesh. Will render without binding it, erroneously.", bindingMap[current].semantic.c_str());
        }
        ++current;
    }
}

inline void createSingleBuffersFromMesh(pvrvk::Device& device, const assets::Mesh& mesh, pvrvk::Buffer& outVbo, pvrvk::Buffer& outIbo, pvrvk::CommandBuffer& uploadCmdBuffer,
    bool& requiresCommandBufferSubmission, vma::Allocator bufferAllocator = nullptr, vma::AllocationCreateFlags vmaAllocationCreateFlags = vma::AllocationCreateFlags::e_MAPPED_BIT)
{
    requiresCommandBufferSubmission = false;

    size_t total = 0;
    for (uint32_t i = 0; i < mesh.getNumDataElements(); ++i) { total += mesh.getDataSize(i); }

    pvrvk::MemoryPropertyFlags vboRequiredMemoryFlags = pvrvk::MemoryPropertyFlags::e_DEVICE_LOCAL_BIT;
    pvrvk::MemoryPropertyFlags vboOptimalMemoryFlags = vboRequiredMemoryFlags;
    outVbo = createBuffer(device, pvrvk::BufferCreateInfo(static_cast<uint32_t>(total), pvrvk::BufferUsageFlags::e_VERTEX_BUFFER_BIT | pvrvk::BufferUsageFlags::e_TRANSFER_DST_BIT),
        vboRequiredMemoryFlags, vboOptimalMemoryFlags, bufferAllocator, vmaAllocationCreateFlags);

    bool isVboHostVisible = (outVbo->getDeviceMemory()->getMemoryFlags() & pvrvk::MemoryPropertyFlags::e_HOST_VISIBLE_BIT) != 0;
    if (!isVboHostVisible) { requiresCommandBufferSubmission = true; }

    size_t current = 0;
    for (uint32_t i = 0; i < mesh.getNumDataElements(); ++i)
    {
        if (isVboHostVisible)
        {
            updateHostVisibleBuffer(outVbo, static_cast<const void*>(mesh.getData(i)), static_cast<uint32_t>(current), static_cast<uint32_t>(mesh.getDataSize(i)), true);
        }
        else
        {
            updateBufferUsingStagingBuffer(device, outVbo, pvrvk::CommandBufferBase(uploadCmdBuffer), static_cast<const void*>(mesh.getData(i)), static_cast<uint32_t>(current),
                static_cast<uint32_t>(mesh.getDataSize(i)), bufferAllocator);
        }
        current += mesh.getDataSize(i);
    }

    if (mesh.getNumFaces())
    {
        pvrvk::MemoryPropertyFlags iboRequiredMemoryFlags = pvrvk::MemoryPropertyFlags::e_DEVICE_LOCAL_BIT;
        pvrvk::MemoryPropertyFlags iboOptimalMemoryFlags = iboRequiredMemoryFlags;
        outIbo = createBuffer(device,
            pvrvk::BufferCreateInfo(static_cast<uint32_t>(mesh.getFaces().getDataSize()), pvrvk::BufferUsageFlags::e_INDEX_BUFFER_BIT | pvrvk::BufferUsageFlags::e_TRANSFER_DST_BIT),
            iboRequiredMemoryFlags, iboOptimalMemoryFlags, bufferAllocator, vmaAllocationCreateFlags);

        bool isIboHostVisible = (outIbo->getDeviceMemory()->getMemoryFlags() & pvrvk::MemoryPropertyFlags::e_HOST_VISIBLE_BIT) != 0;
        if (!isIboHostVisible) { requiresCommandBufferSubmission = true; }

        if (isIboHostVisible)
        {
            updateHostVisibleBuffer(outIbo, static_cast<const void*>(mesh.getFaces().getData()), 0, static_cast<uint32_t>(mesh.getFaces().getDataSize()), true);
        }
        else
        {
            updateBufferUsingStagingBuffer(device, outIbo, pvrvk::CommandBufferBase(uploadCmdBuffer), static_cast<const void*>(mesh.getFaces().getData()), 0,
                static_cast<uint32_t>(mesh.getFaces().getDataSize()), bufferAllocator);
        }
    }
    else
    {
        outIbo.reset();
    }
}

inline void createMultipleBuffersFromMesh(pvrvk::Device& device, const assets::Mesh& mesh, std::vector<pvrvk::Buffer>& outVbos, pvrvk::Buffer& outIbo,
    pvrvk::CommandBuffer& uploadCmdBuffer, bool& requiresCommandBufferSubmission, vma::Allocator bufferAllocator,
    vma::AllocationCreateFlags vmaAllocationCreateFlags = vma::AllocationCreateFlags::e_MAPPED_BIT)
{
    requiresCommandBufferSubmission = false;

    for (uint32_t i = 0; i < mesh.getNumDataElements(); ++i)
    {
        pvrvk::MemoryPropertyFlags requiredMemoryFlags = pvrvk::MemoryPropertyFlags::e_DEVICE_LOCAL_BIT;
        pvrvk::MemoryPropertyFlags optimalMemoryFlags = requiredMemoryFlags;
        outVbos.emplace_back(createBuffer(device,
            pvrvk::BufferCreateInfo(static_cast<uint32_t>(mesh.getDataSize(i)), pvrvk::BufferUsageFlags::e_VERTEX_BUFFER_BIT | pvrvk::BufferUsageFlags::e_TRANSFER_DST_BIT),
            requiredMemoryFlags, optimalMemoryFlags, bufferAllocator, vmaAllocationCreateFlags));

        bool isBufferHostVisible = (outVbos.back()->getDeviceMemory()->getMemoryFlags() & pvrvk::MemoryPropertyFlags::e_HOST_VISIBLE_BIT) != 0;
        if (!isBufferHostVisible) { requiresCommandBufferSubmission = true; }

        if (isBufferHostVisible) { updateHostVisibleBuffer(outVbos.back(), static_cast<const void*>(mesh.getData(i)), 0, static_cast<uint32_t>(mesh.getDataSize(i)), true); }
        else
        {
            updateBufferUsingStagingBuffer(device, outVbos.back(), pvrvk::CommandBufferBase(uploadCmdBuffer), static_cast<const void*>(mesh.getData(i)), 0,
                static_cast<uint32_t>(mesh.getDataSize(i)), bufferAllocator);
        }
    }
    if (mesh.getNumFaces())
    {
        pvrvk::MemoryPropertyFlags requiredMemoryFlags = pvrvk::MemoryPropertyFlags::e_DEVICE_LOCAL_BIT;
        pvrvk::MemoryPropertyFlags optimalMemoryFlags = requiredMemoryFlags;
        outIbo = createBuffer(device,
            pvrvk::BufferCreateInfo(static_cast<uint32_t>(mesh.getFaces().getDataSize()), pvrvk::BufferUsageFlags::e_INDEX_BUFFER_BIT | pvrvk::BufferUsageFlags::e_TRANSFER_DST_BIT),
            requiredMemoryFlags, optimalMemoryFlags, bufferAllocator, vmaAllocationCreateFlags);

        bool isBufferHostVisible = (outIbo->getDeviceMemory()->getMemoryFlags() & pvrvk::MemoryPropertyFlags::e_HOST_VISIBLE_BIT) != 0;
        if (!isBufferHostVisible) { requiresCommandBufferSubmission = true; }

        if (isBufferHostVisible)
        {
            updateHostVisibleBuffer(outIbo, static_cast<const void*>(mesh.getFaces().getData()), 0, static_cast<uint32_t>(mesh.getFaces().getDataSize()), true);
        }
        else
        {
            updateBufferUsingStagingBuffer(device, outIbo, pvrvk::CommandBufferBase(uploadCmdBuffer), static_cast<const void*>(mesh.getFaces().getData()), 0,
                static_cast<uint32_t>(mesh.getFaces().getDataSize()), bufferAllocator);
        }
    }
}

template<typename MeshIterator_, typename VboInsertIterator_, typename IboInsertIterator_>
inline void createSingleBuffersFromMeshes(pvrvk::Device& device, MeshIterator_ meshIter, MeshIterator_ meshIterEnd, VboInsertIterator_ outVbos, IboInsertIterator_ outIbos,
    pvrvk::CommandBuffer& uploadCmdBuffer, bool& requiresCommandBufferSubmission, vma::Allocator bufferAllocator = nullptr,
    vma::AllocationCreateFlags vmaAllocationCreateFlags = vma::AllocationCreateFlags::e_MAPPED_BIT)
{
    pvr::utils::beginCommandBufferDebugLabel(uploadCmdBuffer, pvrvk::DebugUtilsLabel("PVRUtilsVk::createSingleBuffersFromMeshes"));
    requiresCommandBufferSubmission = false;

    while (meshIter != meshIterEnd)
    {
        size_t total = 0;
        for (uint32_t ii = 0; ii < meshIter->getNumDataElements(); ++ii) { total += meshIter->getDataSize(ii); }

        pvrvk::Buffer vbo;

        pvrvk::MemoryPropertyFlags vboRequiredMemoryFlags = pvrvk::MemoryPropertyFlags::e_DEVICE_LOCAL_BIT;
        pvrvk::MemoryPropertyFlags vboOptimalMemoryFlags = vboRequiredMemoryFlags;
        vbo = createBuffer(device, pvrvk::BufferCreateInfo(static_cast<uint32_t>(total), pvrvk::BufferUsageFlags::e_VERTEX_BUFFER_BIT | pvrvk::BufferUsageFlags::e_TRANSFER_DST_BIT),
            vboRequiredMemoryFlags, vboOptimalMemoryFlags, bufferAllocator, vmaAllocationCreateFlags);

        bool isVboHostVisible = (vbo->getDeviceMemory()->getMemoryFlags() & pvrvk::MemoryPropertyFlags::e_HOST_VISIBLE_BIT) != 0;
        if (!isVboHostVisible) { requiresCommandBufferSubmission = true; }

        size_t current = 0;
        for (size_t ii = 0; ii < meshIter->getNumDataElements(); ++ii)
        {
            if (isVboHostVisible)
            {
                updateHostVisibleBuffer(vbo, (const void*)meshIter->getData(static_cast<uint32_t>(ii)), static_cast<uint32_t>(current),
                    static_cast<uint32_t>(meshIter->getDataSize(static_cast<uint32_t>(ii))), true);
            }
            else
            {
                updateBufferUsingStagingBuffer(device, vbo, pvrvk::CommandBufferBase(uploadCmdBuffer), (const void*)meshIter->getData(static_cast<uint32_t>(ii)),
                    static_cast<uint32_t>(current), static_cast<uint32_t>(meshIter->getDataSize(static_cast<uint32_t>(ii))), bufferAllocator);
            }
            current += meshIter->getDataSize(static_cast<uint32_t>(ii));
        }

        outVbos = vbo;
        if (meshIter->getNumFaces())
        {
            pvrvk::Buffer ibo;

            pvrvk::MemoryPropertyFlags iboRequiredMemoryFlags = pvrvk::MemoryPropertyFlags::e_DEVICE_LOCAL_BIT;
            pvrvk::MemoryPropertyFlags iboOptimalMemoryFlags = iboRequiredMemoryFlags;
            ibo = createBuffer(device,
                pvrvk::BufferCreateInfo(
                    static_cast<uint32_t>(meshIter->getFaces().getDataSize()), pvrvk::BufferUsageFlags::e_INDEX_BUFFER_BIT | pvrvk::BufferUsageFlags::e_TRANSFER_DST_BIT),
                iboRequiredMemoryFlags, iboOptimalMemoryFlags, bufferAllocator, vmaAllocationCreateFlags);

            bool isIboHostVisible = (ibo->getDeviceMemory()->getMemoryFlags() & pvrvk::MemoryPropertyFlags::e_HOST_VISIBLE_BIT) != 0;
            if (!isIboHostVisible) { requiresCommandBufferSubmission = true; }

            if (isIboHostVisible) { updateHostVisibleBuffer(ibo, static_cast<const void*>(meshIter->getFaces().getData()), 0, meshIter->getFaces().getDataSize(), true); }
            else
            {
                updateBufferUsingStagingBuffer(device, ibo, pvrvk::CommandBufferBase(uploadCmdBuffer), static_cast<const void*>(meshIter->getFaces().getData()), 0,
                    meshIter->getFaces().getDataSize(), bufferAllocator);
            }

            outIbos = ibo;
        }
        else
        {
            outIbos = pvrvk::Buffer();
        }
        ++outVbos;
        ++outIbos;
        ++meshIter;
    }
    pvr::utils::endCommandBufferDebugLabel(uploadCmdBuffer);
}

template<typename MeshIterator_, typename VboContainer_, typename IboContainer_>
inline void createSingleBuffersFromMeshes(pvrvk::Device& device, MeshIterator_ meshIter, MeshIterator_ meshIterEnd, VboContainer_& outVbos,
    typename VboContainer_::iterator vbos_where, IboContainer_& outIbos, typename IboContainer_::iterator ibos_where, pvrvk::CommandBuffer& uploadCmdBuffer,
    bool& requiresCommandBufferSubmission, vma::Allocator bufferAllocator = nullptr, vma::AllocationCreateFlags vmaAllocationCreateFlags = vma::AllocationCreateFlags::e_MAPPED_BIT)
{
    createSingleBuffersFromMeshes(device, meshIter, meshIterEnd, std::inserter(outVbos, vbos_where), std::inserter(outIbos, ibos_where), uploadCmdBuffer,
        requiresCommandBufferSubmission, bufferAllocator, vmaAllocationCreateFlags);
}

template<typename VboInsertIterator_, typename IboInsertIterator_>
inline void createSingleBuffersFromModel(pvrvk::Device& device, const assets::Model& model, VboInsertIterator_ vbos, IboInsertIterator_ ibos, pvrvk::CommandBuffer& uploadCmdBuffer,
    bool& requiresCommandBufferSubmission, vma::Allocator bufferAllocator = nullptr, vma::AllocationCreateFlags vmaAllocationCreateFlags = vma::AllocationCreateFlags::e_MAPPED_BIT)
{
    createSingleBuffersFromMeshes(device, model.beginMeshes(), model.endMeshes(), vbos, ibos, uploadCmdBuffer, requiresCommandBufferSubmission, bufferAllocator, vmaAllocationCreateFlags);
}

template<typename VboContainer_, typename IboContainer_>
inline void appendSingleBuffersFromModel(pvrvk::Device& device, const assets::Model& model, VboContainer_& vbos, IboContainer_& ibos, pvrvk::CommandBuffer& uploadCmdBuffer,
    bool& requiresCommandBufferSubmission, vma::Allocator bufferAllocator = nullptr, vma::AllocationCreateFlags vmaAllocationCreateFlags = vma::AllocationCreateFlags::e_MAPPED_BIT)
{
    createSingleBuffersFromMeshes(device, model.beginMeshes(), model.endMeshes(), std::inserter(vbos, vbos.end()), std::inserter(ibos, ibos.end()), uploadCmdBuffer,
        requiresCommandBufferSubmission, bufferAllocator, vmaAllocationCreateFlags);
}

void create3dPlaneMesh(uint32_t width, uint32_t depth, bool generateTexCoords, bool generateNormalCoords, assets::Mesh& outMesh);
#pragma endregion

#pragma region
std::vector<unsigned char> captureImageRegion(pvrvk::Queue& queue, pvrvk::CommandPool& commandPool, pvrvk::Image& image, pvrvk::Offset3D srcOffset = pvrvk::Offset3D(0, 0, 0),
    pvrvk::Extent3D srcExtent = pvrvk::Extent3D(static_cast<uint32_t>(-1), static_cast<uint32_t>(-1), static_cast<uint32_t>(-1)),
    pvrvk::Format destinationImageFormat = pvrvk::Format::e_UNDEFINED, pvrvk::ImageLayout imageInitialLayout = pvrvk::ImageLayout::e_TRANSFER_SRC_OPTIMAL,
    pvrvk::ImageLayout imageFinalLayout = pvrvk::ImageLayout::e_TRANSFER_DST_OPTIMAL, vma::Allocator bufferAllocator = nullptr, vma::Allocator imageAllocator = nullptr);

void saveImage(pvrvk::Queue& queue, pvrvk::CommandPool& commandPool, pvrvk::Image& image, const pvrvk::ImageLayout imageInitialLayout, const pvrvk::ImageLayout imageFinalLayout,
    const std::string& filename, vma::Allocator bufferAllocator = nullptr, vma::Allocator imageAllocator = nullptr, const uint32_t screenshotScale = 1);

bool takeScreenshot(pvrvk::Queue& queue, pvrvk::CommandPool& commandPool, pvrvk::Swapchain& swapchain, const uint32_t swapIndex, const std::string& screenshotFileName,
    vma::Allocator bufferAllocator = nullptr, vma::Allocator imageAllocator = nullptr, const uint32_t screenshotScale = 1);

std::vector<int> validatePhysicalDeviceExtensions(const pvrvk::Instance instance, const std::vector<std::string>& vectorExtensionNames);

bool formatWithTilingSupportsFeatureFlags(pvrvk::Format imageFormat, pvrvk::ImageTiling imageTiling, pvrvk::FormatFeatureFlags formatFeatureFlags, const pvrvk::Instance instance, pvrvk::PhysicalDevice physicalDevice);

#pragma endregion
} // namespace utils
} // namespace pvr