/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */

/*
 * Copyright 2018-2020 Mark Callow.
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @internal
 * @file
 * @~English
 *
 * @brief Functions for instantiating Vulkan textures from KTX files.
 *
 * @author Mark Callow, Edgewise Consulting
 */

#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif


#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>

#include "vk_funcs.h"   // Must be included before ktxvulkan.h.
#include "ktxvulkan.h"
#include "ktxint.h"
#include "unused.h"
#include "texture1.h"
#include "texture2.h"
#include "vk_format.h"

/*
 * N.B. See comment at top of ./glloader.c regarding references to
 * "non0class" members in Doxygen comments.
 */

// Macro to check and display Vulkan return results.
// Use when the only possible errors are caused by invalid usage by this loader.
#if defined(_DEBUG)
#define VK_CHECK_RESULT(f)                                                  \
{                                                                           \
    VkResult res = (f);                                                     \
    if (res != VK_SUCCESS)                                                  \
    {                                                                       \
        /* XXX Find an errorString function. */                             \
        fprintf(stderr, "Fatal error in ktxLoadVkTexture*: "                \
                "VkResult is \"%d\" in %s at line %d\n",                    \
                res, __FILE__, __LINE__);                                   \
        assert(res == VK_SUCCESS);                                          \
    }                                                                       \
}
#else
#define VK_CHECK_RESULT(f) ((void)f)
#endif

#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))

#define DEFAULT_FENCE_TIMEOUT 100000000000
#define VK_FLAGS_NONE 0

static void
setImageLayout(
    ktxVulkanFunctions vkFuncs,
    VkCommandBuffer cmdBuffer,
    VkImage image,
    VkImageLayout oldLayout,
    VkImageLayout newLayout,
    VkImageSubresourceRange subresourceRange);

static void
generateMipmaps(ktxVulkanTexture* vkTexture, ktxVulkanDeviceInfo* vdi,
                VkFilter filter, VkImageLayout initialLayout);

/**
 * @defgroup ktx_vkloader Vulkan Texture Image Loader
 * @brief Create texture images on a Vulkan device.
 * @{
 */

/**
 * @example vkload.cpp
 * This shows how to create and load a Vulkan image using the Vulkan texture
 * image loading functions.
 */

/**
 * @memberof ktxVulkanDeviceInfo
 * @~English
 * @brief Create a ktxVulkanDeviceInfo object.
 *
 * Allocates CPU memory for a ktxVulkanDeviceInfo object then calls
 * ktxVulkanDeviceInfo\_Construct(). See it for documentation of the
 * parameters.
 *
 * @return a pointer to the constructed ktxVulkanDeviceInfo.
 *
 * @sa ktxVulkanDeviceInfo\_Construct()
 * @sa ktxVulkanDeviceInfo\_Destroy()
 */
ktxVulkanDeviceInfo*
ktxVulkanDeviceInfo_Create(VkPhysicalDevice physicalDevice, VkDevice device,
                           VkQueue queue, VkCommandPool cmdPool,
                           const VkAllocationCallbacks* pAllocator)
{
    return ktxVulkanDeviceInfo_CreateEx(VK_NULL_HANDLE, physicalDevice, device,
                                        queue, cmdPool, pAllocator, NULL);
}

/**
 * @memberof ktxVulkanDeviceInfo
 * @~English
 * @brief Create a ktxVulkanDeviceInfo object.
 *
 * Allocates CPU memory for a ktxVulkanDeviceInfo object then calls
 * ktxVulkanDeviceInfo\_Construct(). See it for documentation of the
 * parameters.
 *
 * @return a pointer to the constructed ktxVulkanDeviceInfo.
 *
 * @sa ktxVulkanDeviceInfo\_Construct()
 * @sa ktxVulkanDeviceInfo\_Destroy()
 */
ktxVulkanDeviceInfo*
ktxVulkanDeviceInfo_CreateEx(VkInstance instance,
                             VkPhysicalDevice physicalDevice, VkDevice device,
                             VkQueue queue, VkCommandPool cmdPool,
                             const VkAllocationCallbacks* pAllocator,
                             const ktxVulkanFunctions* pFuncs)
{
    ktxVulkanDeviceInfo* newvdi;
    newvdi = (ktxVulkanDeviceInfo*)malloc(sizeof(ktxVulkanDeviceInfo));
    if (newvdi != NULL) {
        if (ktxVulkanDeviceInfo_ConstructEx(newvdi, instance, physicalDevice,
                                            device, queue, cmdPool, pAllocator,
                                            pFuncs) != KTX_SUCCESS)
        {
            free(newvdi);
            newvdi = 0;
        }
    }
    return newvdi;
}

/**
 * @memberof ktxVulkanDeviceInfo
 * @~English
 * @brief Construct a ktxVulkanDeviceInfo object.
 *
 * Records the device information, allocates a command buffer that will be
 * used to transfer image data to the Vulkan device and retrieves the physical
 * device memory properties for ease of use when allocating device memory for
 * the images.
 *
 * If @c VK_IMAGE_TILING_OPTIMAL will be passed to ktxTexture\_VkUploadEx(), the
 * family of the @a queue parameter must support transfers. This is true if
 * any of @c VK_QUEUE_GRAPHICS_BIT, @c VK_QUEUE_COMPUTE_BIT or
 * @c VK_QUEUE_TRANSFER_BIT is set in the @c queueFlags property of the queue's
 * @c VkQueueFamilyProperties. If protected memory is being used, i.e
 * @c queueFlags has the @c VK_QUEUE_PROTECTED_BIT set, then
 * @c VK_IMAGE_TILING_OPTIMAL must be passed to ktxTexture\_VkUploadEx().
 *
 * VkImages created in @ref ktxTexture::ktxTexture\_VkUploadEx()
 * "ktxTexture_VkUploadEx()" will have @c VK_SHARING_MODE_EXCLUSIVE set.
 * Thus the resulting image will be usable only with queues of the same family
 * as @a queue.
 *
 * Pass a valid ktxVulkanDeviceInfo\* to any Vulkan KTX image loading
 * function to provide it with the information.
 *
 * @returns KTX\_SUCCESS on success, other  KTX\_\* enum values on error.
 *
 * @exception KTX_NOT_FOUND   A dynamically loaded Vulkan function
 *                            required by the loader was not found.
 *
 * @exception KTX_OUT_OF_MEMORY A command buffer could not be allocated.
 *
 * @sa ktxVulkanDeviceInfo\_Destruct()
 *
 * @param  This            pointer to the ktxVulkanDeviceInfo object to
 *                        initialize.
 * @param  physicalDevice handle of the Vulkan physical device.
 * @param  device         handle of the Vulkan logical device.
 * @param  queue          handle of the Vulkan queue.
 * @param  cmdPool        handle of the Vulkan command pool.
 * @param  pAllocator     pointer to the allocator to use for the image
 *                        memory. If NULL, the default allocator will be used.
 *
 */
KTX_error_code
ktxVulkanDeviceInfo_Construct(ktxVulkanDeviceInfo* This,
                              VkPhysicalDevice physicalDevice, VkDevice device,
                              VkQueue queue, VkCommandPool cmdPool,
                              const VkAllocationCallbacks* pAllocator)
{
   return ktxVulkanDeviceInfo_ConstructEx(This, VK_NULL_HANDLE, physicalDevice, device, queue,
                                          cmdPool, pAllocator, NULL);
}

/**
 * @memberof ktxVulkanDeviceInfo
 * @copydoc ktxVulkanDeviceInfo_Construct
 *
 * @~English
 * @param  instance       handle of the Vulkan instance. If @c VK_NULL_HANDLE,
 *                        which is not recommended, the function will attempt
 *                        to initialize the instance-level functions via the
 *                        platform's standard dynamic library symbol loading
 *                        mechanisms.
 * @param  pFunctions     pointer to the struct of functions to use for vulkan
 *                        operations. Can be NULL in which case the function
 *                        will retrieve the proc addresses itself.
 */
KTX_error_code
ktxVulkanDeviceInfo_ConstructEx(ktxVulkanDeviceInfo* This,
                              VkInstance instance,
                              VkPhysicalDevice physicalDevice, VkDevice device,
                              VkQueue queue, VkCommandPool cmdPool,
                              const VkAllocationCallbacks* pAllocator,
                              const ktxVulkanFunctions* pFunctions)
{

#define LOAD_EXT_FUNC(member, fun) \
do {                             \
  if ((member).fun == NULL) {            \
    (member).fun = (PFN_##fun)ktxLoadVulkanFunction(#fun); \
    if ((member).fun == NULL) {          \
        return KTX_NOT_FOUND;                         \
    }\
  } \
} while (0)

#define LOAD_INST_FUNC(member, instance, fun) \
do {                             \
  if ((member).fun == NULL) {            \
    (member).fun = (PFN_##fun)((member).vkGetInstanceProcAddr)((instance), #fun); \
    if ((member).fun == NULL) {          \
        return KTX_NOT_FOUND;                         \
    }\
  } \
} while (0)

#define LOAD_DEVICE_FUNC(member, device, fun) \
do {                             \
  if ((member).fun == NULL) {            \
    (member).fun = (PFN_##fun)((member).vkGetDeviceProcAddr)((device), #fun); \
    if ((member).fun == NULL) {          \
        return KTX_NOT_FOUND;                         \
    }\
  } \
} while (0)


    This->instance = instance;
    This->physicalDevice = physicalDevice;
    This->device = device;
    This->queue = queue;
    This->cmdPool = cmdPool;
    This->pAllocator = pAllocator;

    ktxVulkanFunctions funcs;
    memset(&funcs, 0, sizeof(ktxVulkanFunctions));

    if (pFunctions != NULL)
        funcs = *pFunctions;

    if (instance == VK_NULL_HANDLE || pFunctions == NULL) {
        // This is the old behavior, where no functions where specified. We
        // take a shot at loading them dynamically.

        // The reason we check this here, is that ktxLoadVulkanFunction doesn't
        // give us a proper error code.
        ktx_error_code_e kresult = ktxLoadVulkanLibrary();
        if (kresult != KTX_SUCCESS)
            return kresult;

        // If pFunctions are null, then we try to load the minimum number of
        // required functions.
        if (pFunctions == NULL) {
            LOAD_EXT_FUNC(funcs, vkGetInstanceProcAddr);
        }

        // If we have no instance, we will need to bind the instance-level
        // functions, as we won't be able to load them through
        // vkGetInstanceProcAddr, and we need to load vkGetDeviceProcAddr.
        if (instance == VK_NULL_HANDLE) {
            LOAD_EXT_FUNC(funcs, vkGetDeviceProcAddr);

            // These require an instance, which we don't have.
            LOAD_EXT_FUNC(funcs, vkGetPhysicalDeviceFormatProperties);
            LOAD_EXT_FUNC(funcs, vkGetPhysicalDeviceImageFormatProperties);
            LOAD_EXT_FUNC(funcs, vkGetPhysicalDeviceMemoryProperties);
        }
    }

    // We now load the rest of the functions in.
    LOAD_INST_FUNC(funcs, instance, vkGetPhysicalDeviceFormatProperties);
    LOAD_INST_FUNC(funcs, instance, vkGetPhysicalDeviceMemoryProperties);
    LOAD_INST_FUNC(funcs, instance, vkGetPhysicalDeviceImageFormatProperties);


    // We now get to device functions.
    LOAD_INST_FUNC(funcs, instance, vkGetDeviceProcAddr);

    LOAD_DEVICE_FUNC(funcs, device, vkAllocateCommandBuffers);
    LOAD_DEVICE_FUNC(funcs, device, vkFreeCommandBuffers);
    LOAD_DEVICE_FUNC(funcs, device, vkAllocateMemory);
    LOAD_DEVICE_FUNC(funcs, device, vkFreeMemory);
    LOAD_DEVICE_FUNC(funcs, device, vkBeginCommandBuffer);
    LOAD_DEVICE_FUNC(funcs, device, vkEndCommandBuffer);
    LOAD_DEVICE_FUNC(funcs, device, vkBindBufferMemory);
    LOAD_DEVICE_FUNC(funcs, device, vkBindImageMemory);
    LOAD_DEVICE_FUNC(funcs, device, vkCmdBlitImage);
    LOAD_DEVICE_FUNC(funcs, device, vkCmdCopyBufferToImage);
    LOAD_DEVICE_FUNC(funcs, device, vkCmdPipelineBarrier);
    LOAD_DEVICE_FUNC(funcs, device, vkCreateImage);
    LOAD_DEVICE_FUNC(funcs, device, vkDestroyImage);
    LOAD_DEVICE_FUNC(funcs, device, vkCreateBuffer);
    LOAD_DEVICE_FUNC(funcs, device, vkDestroyBuffer);
    LOAD_DEVICE_FUNC(funcs, device, vkCreateFence);
    LOAD_DEVICE_FUNC(funcs, device, vkDestroyFence);
    LOAD_DEVICE_FUNC(funcs, device, vkWaitForFences);
    LOAD_DEVICE_FUNC(funcs, device, vkMapMemory);
    LOAD_DEVICE_FUNC(funcs, device, vkUnmapMemory);
    LOAD_DEVICE_FUNC(funcs, device, vkQueueSubmit);
    LOAD_DEVICE_FUNC(funcs, device, vkQueueWaitIdle);
    LOAD_DEVICE_FUNC(funcs, device, vkGetBufferMemoryRequirements);
    LOAD_DEVICE_FUNC(funcs, device, vkGetImageMemoryRequirements);
    LOAD_DEVICE_FUNC(funcs, device, vkGetImageSubresourceLayout);


    This->vkFuncs = funcs;

    VkCommandBufferAllocateInfo cmdBufInfo = {
            .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO
    };
    VkResult result;

    This->vkFuncs.vkGetPhysicalDeviceMemoryProperties(physicalDevice,
                                        &This->deviceMemoryProperties);

    // Use a separate command buffer for texture loading. Needed for
    // submitting image barriers and converting tilings.
    cmdBufInfo.commandPool = cmdPool;
    cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
    cmdBufInfo.commandBufferCount = 1;
    result = This->vkFuncs.vkAllocateCommandBuffers(device, &cmdBufInfo,
                                                    &This->cmdBuffer);
    if (result != VK_SUCCESS) {
        return KTX_OUT_OF_MEMORY; // TODO: Consider an equivalent to pGlError
    }
    return KTX_SUCCESS;

#undef LOAD_EXT_FUNC
#undef LOAD_INST_FUNC
#undef LOAD_DEVICE_FUNC
}

/**
 * @memberof ktxVulkanDeviceInfo
 * @~English
 * @brief Destruct a ktxVulkanDeviceInfo object.
 *
 * Frees the command buffer.
 *
 * @param This pointer to the ktxVulkanDeviceInfo to destruct.
 */
void
ktxVulkanDeviceInfo_Destruct(ktxVulkanDeviceInfo* This)
{
    This->vkFuncs.vkFreeCommandBuffers(This->device, This->cmdPool, 1,
                         &This->cmdBuffer);
}

/**
 * @memberof ktxVulkanDeviceInfo
 * @~English
 * @brief Destroy a ktxVulkanDeviceInfo object.
 *
 * Calls ktxVulkanDeviceInfo\_Destruct() then frees the ktxVulkanDeviceInfo.
 *
 * @param This pointer to the ktxVulkanDeviceInfo to destroy.
 */
void
ktxVulkanDeviceInfo_Destroy(ktxVulkanDeviceInfo* This)
{
    assert(This != NULL);
    ktxVulkanDeviceInfo_Destruct(This);
    free(This);
}

/* Get appropriate memory type index for a memory allocation. */
static uint32_t
ktxVulkanDeviceInfo_getMemoryType(ktxVulkanDeviceInfo* This,
                                  uint32_t typeBits, VkFlags properties)
{
    for (uint32_t i = 0; i < 32; i++)
    {
        if ((typeBits & 1) == 1)
        {
            if ((This->deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
            {
                return i;
            }
        }
        typeBits >>= 1;
    }

    // XXX : throw error
    return 0;
}

//======================================================================
//  ReadImages callbacks
//======================================================================

typedef struct user_cbdata_optimal {
    VkBufferImageCopy* region; // Specify destination region in final image.
    VkDeviceSize offset;       // Offset of current level in staging buffer
    ktx_uint32_t numFaces;
    ktx_uint32_t numLayers;
    // The following are used only by optimalTilingPadCallback
    ktx_uint8_t* dest;         // Pointer to mapped staging buffer.
    ktx_uint32_t elementSize;
    ktx_uint32_t numDimensions;
#if defined(_DEBUG)
    VkBufferImageCopy* regionsArrayEnd;
#endif
} user_cbdata_optimal;

/**
 * @internal
 * @~English
 * @brief Callback for optimally tiled textures with no source row padding.
 *
 * Images must be preloaded into the staging buffer. Each iteration, i.e.
 * the value of @p faceLodSize must be for a complete mip level, regardless of
 * texture type. This should be used only with @c ktx_Texture_IterateLevels.
 *
 * Sets up a region to copy the data from the staging buffer to the final
 * image.
 *
 * @note @p pixels is not used.
 *
 * @copydetails PFNKTXITERCB
 */
static KTX_error_code
optimalTilingCallback(int miplevel, int face,
                      int width, int height, int depth,
                      ktx_uint64_t faceLodSize,
                      void* pixels, void* userdata)
{
    user_cbdata_optimal* ud = (user_cbdata_optimal*)userdata;
    UNUSED(pixels);

    // Set up copy to destination region in final image
#if defined(_DEBUG)
    assert(ud->region < ud->regionsArrayEnd);
#endif
    ud->region->bufferOffset = ud->offset;
    ud->offset += faceLodSize;
    // These 2 are expressed in texels.
    ud->region->bufferRowLength = 0;
    ud->region->bufferImageHeight = 0;
    ud->region->imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    ud->region->imageSubresource.mipLevel = miplevel;
    ud->region->imageSubresource.baseArrayLayer = face;
    ud->region->imageSubresource.layerCount = ud->numLayers * ud->numFaces;
    ud->region->imageOffset.x = 0;
    ud->region->imageOffset.y = 0;
    ud->region->imageOffset.z = 0;
    ud->region->imageExtent.width = width;
    ud->region->imageExtent.height = height;
    ud->region->imageExtent.depth = depth;

    ud->region += 1;

    return KTX_SUCCESS;
}

uint32_t lcm4(uint32_t a);

/**
 * @internal
 * @~English
 * @brief Callback for optimally tiled textures with possible source row
 *        padding.
 *
 * Copies data to the staging buffer removing row padding, if necessary.
 * Increments the offset for destination of the next copy increasing it to an
 * appropriate common multiple of the element size and 4 to comply with Vulkan
 * valid usage. Finally sets up a region to copy the face/lod from the staging
 * buffer to the final image.
 *
 * This longer method is needed because row padding is different between
 * KTX (pad to 4) and Vulkan (none). Also region->bufferOffset, i.e. the start
 * of each image, has to be a multiple of 4 and also a multiple of the
 * element size.
 *
 * This should be used with @c ktx_Texture_IterateFaceLevels or
 * @c ktx_Texture_IterateLoadLevelFaces. Face-level iteration has been
 * selected to minimize the buffering needed between reading the file and
 * copying the data into the staging buffer. Obviously when
 * @c ktx_Texture_IterateFaceLevels is being used, this is a moot point.
*
 * @copydetails PFNKTXITERCB
 */
KTX_error_code
optimalTilingPadCallback(int miplevel, int face,
                         int width, int height, int depth,
                         ktx_uint64_t faceLodSize,
                         void* pixels, void* userdata)
{
    user_cbdata_optimal* ud = (user_cbdata_optimal*)userdata;
    ktx_uint32_t rowPitch = width * ud->elementSize;

    // Set bufferOffset in destination region in final image
#if defined(_DEBUG)
    assert(ud->region < ud->regionsArrayEnd);
#endif
    ud->region->bufferOffset = ud->offset;

    // Copy data into staging buffer
    if (_KTX_PAD_UNPACK_ALIGN_LEN(rowPitch) == 0) {
        // No padding. Can copy in bulk.
        memcpy(ud->dest + ud->offset, pixels, faceLodSize);
        ud->offset += faceLodSize;
    } else {
        // Must remove padding. Copy a row at a time.
		ktx_uint32_t image, imageIterations;
		ktx_int32_t row;
        ktx_uint32_t paddedRowPitch;

        if (ud->numDimensions == 3)
            imageIterations = depth;
        else if (ud->numLayers > 1)
            imageIterations = ud->numLayers * ud->numFaces;
        else
            imageIterations = 1;
        rowPitch = paddedRowPitch = width * ud->elementSize;
        paddedRowPitch = _KTX_PAD_UNPACK_ALIGN(paddedRowPitch);
        for (image = 0; image < imageIterations; image++) {
            for (row = 0; row < height; row++) {
                memcpy(ud->dest + ud->offset, pixels, rowPitch);
                ud->offset += rowPitch;
                pixels = (ktx_uint8_t*)pixels + paddedRowPitch;
            }
        }
    }

    // Round to needed multiples for next region, if necessary.
    if (ud->offset % ud->elementSize != 0 || ud->offset % 4 != 0) {
        // Only elementSizes of 1,2 and 3 will bring us here.
        assert(ud->elementSize < 4 && ud->elementSize > 0);
        ktx_uint32_t lcm = lcm4(ud->elementSize);
        ud->offset = _KTX_PADN(lcm, ud->offset);
    }
    // These 2 are expressed in texels; not suitable for dealing with padding.
    ud->region->bufferRowLength = 0;
    ud->region->bufferImageHeight = 0;
    ud->region->imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    ud->region->imageSubresource.mipLevel = miplevel;
    ud->region->imageSubresource.baseArrayLayer = face;
    ud->region->imageSubresource.layerCount = ud->numLayers * ud->numFaces;
    ud->region->imageOffset.x = 0;
    ud->region->imageOffset.y = 0;
    ud->region->imageOffset.z = 0;
    ud->region->imageExtent.width = width;
    ud->region->imageExtent.height = height;
    ud->region->imageExtent.depth = depth;

    ud->region += 1;

    return KTX_SUCCESS;
}

typedef struct user_cbdata_linear {
    ktxVulkanFunctions vkFuncs;
    VkImage destImage;
    VkDevice device;
    uint8_t* dest;   // Pointer to mapped Image memory
    ktxTexture* texture;
} user_cbdata_linear;

/**
 * @internal
 * @~English
 * @brief Callback for linear tiled textures with no source row padding.
 *
 * Copy the image data into the mapped Vulkan image.
 */
KTX_error_code
linearTilingCallback(int miplevel, int face,
                      int width, int height, int depth,
                      ktx_uint64_t faceLodSize,
                      void* pixels, void* userdata)
{
    user_cbdata_linear* ud = (user_cbdata_linear*)userdata;
    VkSubresourceLayout subResLayout;
#if !defined(_MSC_VER) || _MSC_VER >= 1920
    VkImageSubresource subRes = {
      .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
      .mipLevel = miplevel,
      .arrayLayer = face
    };
#else
    VkImageSubresource subRes = {0};
    subRes.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    subRes.mipLevel = miplevel;
    subRes.arrayLayer = face;
#endif

    UNUSED(width);
    UNUSED(height);
    UNUSED(depth);

    // Get sub resources layout. Includes row pitch, size,
    // offsets, etc.
    ud->vkFuncs.vkGetImageSubresourceLayout(ud->device, ud->destImage, &subRes,
                                &subResLayout);
    // Copies all images of the miplevel (for array & 3d) or a single face.
    memcpy(ud->dest + subResLayout.offset, pixels, faceLodSize);
    return KTX_SUCCESS;
}

/**
 * @internal
 * @~English
 * @brief Callback for linear tiled textures with possible source row
 *        padding.
 *
 * Need to use this long method as row padding is different
 * between KTX (pad to 4) and Vulkan (none).
 *
 * In theory this should work for the no-padding case too but it is much
 * clearer and a bit faster to use the simple callback above. It also avoids
 * potential Vulkan implementation bugs.
 *
 * I have seen weird subResLayout results with a BC2_UNORM texture in the only
 * real Vulkan implementation I have available (Mesa). The reported row & image
 * strides appears to be for an R8G8B8A8_UNORM of the same texel size.
 */
KTX_error_code
linearTilingPadCallback(int miplevel, int face,
                      int width, int height, int depth,
                      ktx_uint64_t faceLodSize,
                      void* pixels, void* userdata)
{
    user_cbdata_linear* ud = (user_cbdata_linear*)userdata;
    VkDeviceSize offset;
    ktx_size_t   imageSize = 0;
    VkDeviceSize imagePitch = 0;
    ktx_uint32_t srcRowPitch;
    ktx_uint32_t rowIterations;
    ktx_uint32_t imageIterations;
    ktx_uint32_t row, image;
    ktx_uint8_t* pSrc;
    ktx_size_t   copySize;
    VkSubresourceLayout subResLayout;
#if !defined(_MSC_VER) || _MSC_VER >= 1920
    VkImageSubresource subRes = {
      .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
      .mipLevel = miplevel,
      .arrayLayer = face
    };
#else
    VkImageSubresource subRes = {0};
    subRes.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    subRes.mipLevel = miplevel;
    subRes.arrayLayer = face;
#endif

    UNUSED(width);

    // Get sub resources layout. Includes row pitch, size,
    // offsets, etc.
    ud->vkFuncs.vkGetImageSubresourceLayout(ud->device, ud->destImage, &subRes,
                                &subResLayout);

    srcRowPitch = ktxTexture_GetRowPitch(ud->texture, miplevel);

    if (subResLayout.rowPitch != srcRowPitch)
        rowIterations = height;
    else
        rowIterations = 1;

    imageIterations = 1;
    // Arrays, including cube map arrays, or 3D textures
    // Note from the Vulkan spec:
    //  *  arrayPitch is undefined for images that were not
    //     created as arrays.
    //  *  depthPitch is defined only for 3D images.
    if (ud->texture->numLayers > 1 || ud->texture->numDimensions == 3) {
        imageSize = ktxTexture_GetImageSize(ud->texture, miplevel);
        if (ud->texture->numLayers > 1) {
            imagePitch = subResLayout.arrayPitch;
            if (imagePitch != imageSize)
                imageIterations
                        = ud->texture->numLayers * ud->texture->numFaces;
        } else {
            imagePitch = subResLayout.depthPitch;
            if (imagePitch != imageSize)
                imageIterations = depth;
        }
        assert(imageSize <= imagePitch);
    }

    if (rowIterations > 1) {
        // Copy the minimum of srcRowPitch, the GL_UNPACK_ALIGNMENT padded size,
        // and subResLayout.rowPitch.
        if (subResLayout.rowPitch < srcRowPitch)
            copySize = subResLayout.rowPitch;
        else
            copySize = srcRowPitch;
    } else if (imageIterations > 1)
        copySize = faceLodSize / imageIterations;
    else
        copySize = faceLodSize;

    offset = subResLayout.offset;
    // Copy image data to destImage via its mapped memory.
    for (image = 0; image < imageIterations; image++) {
        pSrc = (ktx_uint8_t*)pixels + imageSize * image;
        for (row = 0; row < rowIterations; row++) {
            memcpy(ud->dest + offset, pSrc, copySize);
            offset += subResLayout.rowPitch;
            pSrc += srcRowPitch;
          }
        offset += imagePitch;
    }
    return KTX_SUCCESS;
}

/**
 * @memberof ktxTexture
 * @~English
 * @brief Create a Vulkan image object from a ktxTexture object.
 *
 * Creates a VkImage with @c VkFormat etc. matching the KTX data and uploads
 * the images.  Mipmaps will be generated if the @c ktxTexture's
 * @c generateMipmaps flag is set. Returns the handles of the created objects
 * and information about the texture in the @c ktxVulkanTexture pointed at by
 * @p vkTexture.
 *
 * The created VkImage will have @c VK_SHARING_MODE_EXCLUSIVE set thus the
 * resulting image will be usable only with queues of the same family as
 * the @c queue in the ktxVulkanDeviceInfo pointed to by @a vdi.
 *
 * @p usageFlags and thus acceptable usage of the created image may be
 * augmented as follows:
 * - with @c VK_IMAGE_USAGE_TRANSFER_DST_BIT if @p tiling is
 *   @c VK_IMAGE_TILING_OPTIMAL
 * - with <code>VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT</code>
 *   if @c generateMipmaps is set in the @c ktxTexture.
 *
 * Most Vulkan implementations support @c VK_IMAGE_TILING_LINEAR only for a very
 * limited number of formats and features. Generally @c VK_IMAGE_TILING_OPTIMAL
 * is preferred. The latter requires a staging buffer so will use more memory
 * during loading.
 * 
 * If a pointer to a set of suballocator callbacks is provided, they
 * will be used instead of manual allocation of VkDeviceMemory. A 64 bit uint
 * that references the suballocated page(s) is returned on memory procurement 
 * and saved in the @c allocationId field of the structure pointed to by @a vkTexture.
 *
 * @param[in] This                        pointer to the ktxTexture from which to upload.
 * @param [in] vdi                        pointer to a ktxVulkanDeviceInfo structure providing
 *                                        information about the Vulkan device onto which to
 *                                        load the texture.
 * @param [in,out] vkTexture              pointer to a ktxVulkanTexture structure into which
 *                                        the function writes information about the created
 *                                        VkImage.
 * @param [in] tiling                     type of tiling to use in the destination image
 *                                        on the Vulkan device.
 * @param [in] usageFlags                 a set of VkImageUsageFlags bits indicating the
 *                                        intended usage of the destination image.
 * @param [in] finalLayout                a VkImageLayout value indicating the desired
 *                                        final layout of the created image.
 * @param [in] subAllocatorCallbacks      pointer to a set of suballocator callbacks 
 *                                        that wrap around suballocator calls: alloc, 
 *                                        bindbuffer, bindimage, map, unmap and free.
 *                                        They use a uint64_t stored in the @c allocationId
 *                                        field of the structure pointed at by @a vkTexture
 *                                        to reference allocated page(s).
 *
 * @return  KTX_SUCCESS on success, other KTX_* enum values on error.
 *
 * @exception KTX_INVALID_VALUE         An incomplete set of callbacks are provided in 
 *                                      subAllocatorCallbacks.
 * @exception KTX_INVALID_VALUE         @p This, @p vdi or @p vkTexture is @c NULL.
 * @exception KTX_INVALID_OPERATION     The ktxTexture contains neither images nor
 *                                      an active stream from which to read them.
 * @exception KTX_INVALID_OPERATION     The combination of the ktxTexture's format,
 *                                      @p tiling and @p usageFlags is not supported
 *                                      by the physical device.
 * @exception KTX_INVALID_OPERATION     Requested mipmap generation is not supported
 *                                      by the physical device for the combination
 *                                      of the ktxTexture's format and @p tiling.
 * @exception KTX_INVALID_OPERATION     Number of mip levels or array layers exceeds
 *                                      the maximums supported for the ktxTexture's
 *                                      format and @p tiling.
 * @exception KTX_OUT_OF_MEMORY         Sufficient memory could not be allocated on
 *                                      either the CPU or the Vulkan device.
 * @exception KTX_UNSUPPORTED_FEATURE   Attempting to sparsely bind KTX textures
 *                                      for the time being will report this error.
 *
 * @sa @ref ktxVulkanDeviceInfo::ktxVulkanDeviceInfo\_Construct "ktxVulkanDeviceInfo_Construct()"
 */
KTX_error_code
ktxTexture_VkUploadEx_WithSuballocator(ktxTexture* This, ktxVulkanDeviceInfo* vdi,
                                       ktxVulkanTexture* vkTexture,
                                       VkImageTiling tiling,
                                       VkImageUsageFlags usageFlags,
                                       VkImageLayout finalLayout,
                                       ktxVulkanTexture_subAllocatorCallbacks* subAllocatorCallbacks)
{
    KTX_error_code           kResult;
    VkFilter                 blitFilter = VK_FILTER_LINEAR;
    VkFormat                 vkFormat;
    VkImageType              imageType;
    VkImageViewType          viewType;
    VkImageCreateFlags       createFlags = 0;
    VkImageFormatProperties  imageFormatProperties;
    VkResult                 vResult;
    VkCommandBufferBeginInfo cmdBufBeginInfo = {
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
        .pNext = NULL
    };
    VkImageCreateInfo        imageCreateInfo = {
         .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
         .pNext = NULL
    };
    VkMemoryAllocateInfo     memAllocInfo = {
        .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
        .pNext = NULL,
        .allocationSize = 0,
        .memoryTypeIndex = 0
    };
    VkMemoryRequirements     memReqs;
    ktx_uint32_t             numImageLayers, numImageLevels;
    ktx_uint32_t elementSize = ktxTexture_GetElementSize(This);
    ktx_bool_t               canUseFasterPath;
    ktx_bool_t               useSuballocator = false;
    if (subAllocatorCallbacks) {
        if (subAllocatorCallbacks->allocMemFuncPtr &&
            subAllocatorCallbacks->bindBufferFuncPtr &&
            subAllocatorCallbacks->bindImageFuncPtr &&
            subAllocatorCallbacks->memoryMapFuncPtr &&
            subAllocatorCallbacks->memoryUnmapFuncPtr &&
            subAllocatorCallbacks->freeMemFuncPtr)
            useSuballocator = true;
        else
            return KTX_INVALID_VALUE;
    }

    if (!vdi || !This || !vkTexture) {
        return KTX_INVALID_VALUE;
    }

    if (!This->pData && !ktxTexture_isActiveStream(This)) {
        /* Nothing to upload. */
        return KTX_INVALID_OPERATION;
    }

    /* _ktxCheckHeader should have caught this. */
    assert(This->numFaces == 6 ? This->numDimensions == 2 : VK_TRUE);

    numImageLayers = This->numLayers;
    if (This->isCubemap) {
        numImageLayers *= 6;
        createFlags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
    }

    assert(This->numDimensions >= 1 && This->numDimensions <= 3);
    switch (This->numDimensions) {
      case 1:
        imageType = VK_IMAGE_TYPE_1D;
        viewType = This->isArray ?
                        VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D;
        break;
      case 2:
      default: // To keep compilers happy.
        imageType = VK_IMAGE_TYPE_2D;
        if (This->isCubemap)
            viewType = This->isArray ?
                        VK_IMAGE_VIEW_TYPE_CUBE_ARRAY : VK_IMAGE_VIEW_TYPE_CUBE;
        else
            viewType = This->isArray ?
                        VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D;
        break;
      case 3:
        imageType = VK_IMAGE_TYPE_3D;
        /* 3D array textures not supported in Vulkan. Attempts to create or
         * load them should have been trapped long before this.
         */
        assert(!This->isArray);
        viewType = VK_IMAGE_VIEW_TYPE_3D;
        break;
    }

    vkFormat = ktxTexture_GetVkFormat(This);
    if (vkFormat == VK_FORMAT_UNDEFINED) {
        return KTX_INVALID_OPERATION;
    }

    /* Get device properties for the requested image format */
    if (tiling == VK_IMAGE_TILING_OPTIMAL) {
        // Ensure we can copy from staging buffer to image.
        usageFlags |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
    }
    if (This->generateMipmaps) {
        // Ensure we can blit between levels.
        usageFlags |= (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
    }
    vResult = vdi->vkFuncs.vkGetPhysicalDeviceImageFormatProperties(vdi->physicalDevice,
                                                      vkFormat,
                                                      imageType,
                                                      tiling,
                                                      usageFlags,
                                                      createFlags,
                                                      &imageFormatProperties);
    if (vResult == VK_ERROR_FORMAT_NOT_SUPPORTED) {
        return KTX_INVALID_OPERATION;
    }
    if (This->numLayers > imageFormatProperties.maxArrayLayers) {
        return KTX_INVALID_OPERATION;
    }

    if (This->generateMipmaps) {
        uint32_t max_dim;
        VkFormatProperties    formatProperties;
        VkFormatFeatureFlags  formatFeatureFlags;
        VkFormatFeatureFlags  neededFeatures
            = VK_FORMAT_FEATURE_BLIT_DST_BIT | VK_FORMAT_FEATURE_BLIT_SRC_BIT;
        vdi->vkFuncs.vkGetPhysicalDeviceFormatProperties(vdi->physicalDevice,
                                            vkFormat,
                                            &formatProperties);
        assert(vResult == VK_SUCCESS);
        if (tiling == VK_IMAGE_TILING_OPTIMAL)
            formatFeatureFlags = formatProperties.optimalTilingFeatures;
        else
            formatFeatureFlags = formatProperties.linearTilingFeatures;

        if ((formatFeatureFlags & neededFeatures) != neededFeatures)
            return KTX_INVALID_OPERATION;

        if (formatFeatureFlags & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)
            blitFilter = VK_FILTER_LINEAR;
        else
            blitFilter = VK_FILTER_NEAREST; // XXX INVALID_OP?

        max_dim = MAX(MAX(This->baseWidth, This->baseHeight), This->baseDepth);
        numImageLevels = (uint32_t)floor(log2(max_dim)) + 1;
    } else {
        numImageLevels = This->numLevels;
    }

    if (numImageLevels > imageFormatProperties.maxMipLevels) {
        return KTX_INVALID_OPERATION;
    }

    if (This->classId == ktxTexture2_c) {
        canUseFasterPath = KTX_TRUE;
    } else {
        ktx_uint32_t actualRowPitch = ktxTexture_GetRowPitch(This, 0);
        ktx_uint32_t tightRowPitch = elementSize * This->baseWidth;
        // If the texture's images do not have any row padding, we can use a
        // faster path. Only uncompressed textures might have padding.
        //
        // The first test in the if will match compressed textures, because
        // they all have a block size that is a multiple of 4, as well as
        // a class of uncompressed textures that will never need padding.
        //
        // The second test matches textures whose level 0 has no padding. Any
        // texture whose block size is not a multiple of 4 will need padding
        // at some miplevel even if level 0 does not. So, if more than 1 level
        // exists, we must use the slower path.
        //
        // Note all elementSizes > 4 Will be a multiple of 4, so only
        // elementSizes of 1, 2 & 3 are a concern here.
        if (elementSize % 4 == 0  /* There'll be no padding at any level. */
               /* There is no padding at level 0 and no other levels. */
            || (This->numLevels == 1 && actualRowPitch == tightRowPitch))
            canUseFasterPath = KTX_TRUE;
        else
            canUseFasterPath = KTX_FALSE;
    }

    vkTexture->width = This->baseWidth;
    vkTexture->height = This->baseHeight;
    vkTexture->depth = This->baseDepth;
    vkTexture->imageLayout = finalLayout;
    vkTexture->imageFormat = vkFormat;
    vkTexture->levelCount = numImageLevels;
    vkTexture->layerCount = numImageLayers;
    vkTexture->viewType = viewType;
    vkTexture->vkDestroyImage = vdi->vkFuncs.vkDestroyImage;
    vkTexture->vkFreeMemory = vdi->vkFuncs.vkFreeMemory;

    VK_CHECK_RESULT(
            vdi->vkFuncs.vkBeginCommandBuffer(vdi->cmdBuffer, &cmdBufBeginInfo)
            );

    if (tiling == VK_IMAGE_TILING_OPTIMAL)
    {
        // Create a host-visible staging buffer that contains the raw image data
        VkBuffer stagingBuffer;
        VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
        VkBufferImageCopy* copyRegions;
        VkDeviceSize textureSize;
        VkBufferCreateInfo bufferCreateInfo = {
          .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
          .pNext = NULL
        };
        VkImageSubresourceRange subresourceRange;
        VkFence copyFence;
        VkFenceCreateInfo fenceCreateInfo = {
            .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
            .pNext = NULL,
            .flags = VK_FLAGS_NONE
        };
        VkSubmitInfo submitInfo = {
            .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
            .pNext = NULL
        };
        ktx_uint8_t* pMappedStagingBuffer;
        ktx_uint32_t numCopyRegions;
        user_cbdata_optimal cbData;


        textureSize = ktxTexture_GetDataSizeUncompressed(This);
        bufferCreateInfo.size = textureSize;
        if (canUseFasterPath) {
            /*
             * Because all array layers and faces are the same size they can
             * be copied in a single operation so there'll be 1 copy per mip
             * level.
             */
            numCopyRegions = This->numLevels;
        } else {
            /*
             * Have to copy all images individually into the staging
             * buffer so we can place them at correct multiples of
             * elementSize and 4 and also need a copy region per image
             * in case they end up with padding between them.
             */
            numCopyRegions = This->isArray ? This->numLevels
                                  : This->numLevels * This->numFaces;
            /*
             * Add extra space to allow for possible padding described
             * above. A bit ad-hoc but it's only a small amount of
             * memory.
             */
            bufferCreateInfo.size += numCopyRegions * elementSize * 4;
        }
        copyRegions = (VkBufferImageCopy*)malloc(sizeof(VkBufferImageCopy)
                                                   * numCopyRegions);
        if (copyRegions == NULL) {
            return KTX_OUT_OF_MEMORY;
        }

        // This buffer is used as a transfer source for the buffer copy
        bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
        bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

        VK_CHECK_RESULT(
                vdi->vkFuncs.vkCreateBuffer(vdi->device, &bufferCreateInfo,
                                       vdi->pAllocator, &stagingBuffer));

        // Get memory requirements for the staging buffer (alignment,
        // memory type bits)
        vdi->vkFuncs.vkGetBufferMemoryRequirements(vdi->device, stagingBuffer, &memReqs);

        memAllocInfo.allocationSize = memReqs.size;
        // Get memory type index for a host visible buffer
        memAllocInfo.memoryTypeIndex = ktxVulkanDeviceInfo_getMemoryType(
                vdi,
                memReqs.memoryTypeBits,
                VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
              | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
        );

        uint64_t stagingAllocId = 0ull;
        if (!useSuballocator) {
            vResult = vdi->vkFuncs.vkAllocateMemory(vdi->device, &memAllocInfo,
                vdi->pAllocator, &stagingMemory);
            if (vResult != VK_SUCCESS) {
                return KTX_OUT_OF_MEMORY;
            }
            VK_CHECK_RESULT(
                    vdi->vkFuncs.vkBindBufferMemory(vdi->device, stagingBuffer,
                        stagingMemory, 0));

            VK_CHECK_RESULT(
                    vdi->vkFuncs.vkMapMemory(vdi->device, stagingMemory, 0,
                        memReqs.size, 0,
                        (void**)&pMappedStagingBuffer));
        }
        else {
            uint64_t numPages = 0ull;
            stagingAllocId = subAllocatorCallbacks->allocMemFuncPtr(&memAllocInfo, &memReqs, &numPages);
            if (stagingAllocId == 0ull) {
                return KTX_OUT_OF_MEMORY;
            }
            if (numPages > 1ull) { // Sparse binding of KTX textures is unsupported for the moment
                return KTX_UNSUPPORTED_FEATURE;
            }
            VK_CHECK_RESULT(
                subAllocatorCallbacks->bindBufferFuncPtr(stagingBuffer, stagingAllocId));
            VK_CHECK_RESULT(
                subAllocatorCallbacks->memoryMapFuncPtr(stagingAllocId, 0ull, 
                &memReqs.size,
                (void**)&pMappedStagingBuffer));
        }

        cbData.offset = 0;
        cbData.region = copyRegions;
        cbData.numFaces = This->numFaces;
        cbData.numLayers = This->numLayers;
        cbData.dest = pMappedStagingBuffer;
        cbData.elementSize = elementSize;
        cbData.numDimensions = This->numDimensions;
#if defined(_DEBUG)
        cbData.regionsArrayEnd = copyRegions + numCopyRegions;
#endif
        if (canUseFasterPath) {
            // Bulk load the data to the staging buffer and iterate
            // over levels.

            if (This->pData) {
                // Image data has already been loaded. Copy to staging
                // buffer.
                assert(This->dataSize <= memAllocInfo.allocationSize);
                memcpy(pMappedStagingBuffer, This->pData, This->dataSize);
            } else {
                /* Load the image data directly into the staging buffer. */
                /* The strange cast quiets an Xcode warning when building
                 * for the Generic iOS Device where size_t is 32-bit even
                 * when building for arm64. */
                kResult = ktxTexture_LoadImageData(This,
                                      pMappedStagingBuffer,
                                      (ktx_size_t)memAllocInfo.allocationSize);
                if (kResult != KTX_SUCCESS)
                    return kResult;
            }

            // Iterate over mip levels to set up the copy regions.
            kResult = ktxTexture_IterateLevels(This,
                                               optimalTilingCallback,
                                               &cbData);
            // XXX Check for possible errors.
        } else {
            // Iterate over face-levels with callback that copies the
            // face-levels to Vulkan-valid offsets in the staging buffer while
            // removing padding. Using face-levels minimizes pre-staging-buffer
            // buffering, in the event the data is not already loaded.
            if (This->pData) {
                kResult = ktxTexture_IterateLevelFaces(
                                            This,
                                            optimalTilingPadCallback,
                                            &cbData);
            } else {
                kResult = ktxTexture_IterateLoadLevelFaces(
                                            This,
                                            optimalTilingPadCallback,
                                            &cbData);
                // XXX Check for possible errors.
            }
        }

        if (!useSuballocator)
            vdi->vkFuncs.vkUnmapMemory(vdi->device, stagingMemory);
        else
            subAllocatorCallbacks->memoryUnmapFuncPtr(stagingAllocId, 0ull);

        // Create optimal tiled target image
        imageCreateInfo.imageType = imageType;
        imageCreateInfo.flags = createFlags;
        imageCreateInfo.format = vkFormat;
        // numImageLevels ensures enough levels for generateMipmaps.
        imageCreateInfo.mipLevels = numImageLevels;
        imageCreateInfo.arrayLayers = numImageLayers;
        imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
        imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
        imageCreateInfo.usage = usageFlags;
        imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
        imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        imageCreateInfo.extent.width = vkTexture->width;
        imageCreateInfo.extent.height = vkTexture->height;
        imageCreateInfo.extent.depth = vkTexture->depth;

        VK_CHECK_RESULT(
                vdi->vkFuncs.vkCreateImage(vdi->device, &imageCreateInfo,
                                      vdi->pAllocator, &vkTexture->image));

        vdi->vkFuncs.vkGetImageMemoryRequirements(vdi->device, vkTexture->image, &memReqs);

        memAllocInfo.allocationSize = memReqs.size;
        memAllocInfo.memoryTypeIndex = ktxVulkanDeviceInfo_getMemoryType(
            vdi, memReqs.memoryTypeBits,
            VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);

        if (!useSuballocator) {
            VK_CHECK_RESULT(
                vdi->vkFuncs.vkAllocateMemory(vdi->device, &memAllocInfo,
                    vdi->pAllocator,
                    &vkTexture->deviceMemory));
            VK_CHECK_RESULT(
                vdi->vkFuncs.vkBindImageMemory(vdi->device, vkTexture->image,
                    vkTexture->deviceMemory, 0));
        }
        else {
            uint64_t numPages = 0ull;
            vkTexture->allocationId = subAllocatorCallbacks->allocMemFuncPtr(&memAllocInfo, &memReqs, &numPages);
            if (vkTexture->allocationId == 0ull) {
                return KTX_OUT_OF_MEMORY;
            }
            if(numPages > 1ull) { // Sparse binding of KTX textures is unsupported for the moment
                return KTX_UNSUPPORTED_FEATURE;
            }
            VK_CHECK_RESULT(
                subAllocatorCallbacks->bindImageFuncPtr(vkTexture->image, vkTexture->allocationId));
        }

        subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        subresourceRange.baseMipLevel = 0;
        subresourceRange.levelCount = This->numLevels;
        subresourceRange.baseArrayLayer = 0;
        subresourceRange.layerCount = numImageLayers;

        // Image barrier to transition, possibly only the base level, image
        // layout to TRANSFER_DST_OPTIMAL so it can be used as the copy
        // destination.
        setImageLayout(
            vdi->vkFuncs,
            vdi->cmdBuffer,
            vkTexture->image,
            VK_IMAGE_LAYOUT_UNDEFINED,
            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
            subresourceRange);

        // Copy mip levels from staging buffer
        vdi->vkFuncs.vkCmdCopyBufferToImage(
            vdi->cmdBuffer, stagingBuffer,
            vkTexture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
            numCopyRegions, copyRegions
            );

        free(copyRegions);

        if (This->generateMipmaps) {
            generateMipmaps(vkTexture, vdi,
                            blitFilter, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
        } else {
            // Transition image layout to finalLayout after all mip levels
            // have been copied.
            // In this case numImageLevels == This->numLevels
            //subresourceRange.levelCount = numImageLevels;
            setImageLayout(
                vdi->vkFuncs,
                vdi->cmdBuffer,
                vkTexture->image,
                VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                finalLayout,
                subresourceRange);
        }

        // Submit command buffer containing copy and image layout commands
        VK_CHECK_RESULT(
                vdi->vkFuncs.vkEndCommandBuffer(vdi->cmdBuffer));

        // Create a fence to make sure that the copies have finished before
        // continuing
        VK_CHECK_RESULT(
                vdi->vkFuncs.vkCreateFence(vdi->device, &fenceCreateInfo,
                                      vdi->pAllocator, &copyFence));

        submitInfo.commandBufferCount = 1;
        submitInfo.pCommandBuffers = &vdi->cmdBuffer;

        VK_CHECK_RESULT(
                vdi->vkFuncs.vkQueueSubmit(vdi->queue, 1, &submitInfo, copyFence));

        VK_CHECK_RESULT(
                vdi->vkFuncs.vkWaitForFences(vdi->device, 1, &copyFence,
                                        VK_TRUE, DEFAULT_FENCE_TIMEOUT));

        vdi->vkFuncs.vkDestroyFence(vdi->device, copyFence, vdi->pAllocator);

        // Clean up staging resources
        if (!useSuballocator)
            vdi->vkFuncs.vkFreeMemory(vdi->device, stagingMemory, vdi->pAllocator);
        else
            subAllocatorCallbacks->freeMemFuncPtr(stagingAllocId);
        vdi->vkFuncs.vkDestroyBuffer(vdi->device, stagingBuffer, vdi->pAllocator);
    }
    else
    {
        VkImage mappableImage;
        VkDeviceMemory mappableMemory = VK_NULL_HANDLE;
        VkFence nullFence = { VK_NULL_HANDLE };
        VkSubmitInfo submitInfo = {
            .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
            .pNext = NULL
        };
        user_cbdata_linear cbData;
        PFNKTXITERCB callback;

        imageCreateInfo.imageType = imageType;
        imageCreateInfo.flags = createFlags;
        imageCreateInfo.format = vkFormat;
        imageCreateInfo.extent.width = vkTexture->width;
        imageCreateInfo.extent.height = vkTexture->height;
        imageCreateInfo.extent.depth = vkTexture->depth;
        // numImageLevels ensures enough levels for generateMipmaps.
        imageCreateInfo.mipLevels = numImageLevels;
        imageCreateInfo.arrayLayers = numImageLayers;
        imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
        imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
        imageCreateInfo.usage = usageFlags;
        imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
        imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED;

        // Load mip map level 0 to linear tiling image
        VK_CHECK_RESULT(
                vdi->vkFuncs.vkCreateImage(vdi->device, &imageCreateInfo,
                                      vdi->pAllocator, &mappableImage));

        // Get memory requirements for this image
        // like size and alignment
        vdi->vkFuncs.vkGetImageMemoryRequirements(vdi->device, mappableImage, &memReqs);
        // Set memory allocation size to required memory size
        memAllocInfo.allocationSize = memReqs.size;

        // Get memory type that can be mapped to host memory
        memAllocInfo.memoryTypeIndex = ktxVulkanDeviceInfo_getMemoryType(
                vdi,
                memReqs.memoryTypeBits,
                VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);

        // Allocate host memory
        if (!useSuballocator) {
            vResult = vdi->vkFuncs.vkAllocateMemory(vdi->device, &memAllocInfo, vdi->pAllocator,
                &mappableMemory);
            if (vResult != VK_SUCCESS) {
                return KTX_OUT_OF_MEMORY;
            }
            VK_CHECK_RESULT(
                vdi->vkFuncs.vkBindImageMemory(vdi->device, mappableImage,
                    mappableMemory, 0));
        }
        else {
            uint64_t numPages = 0ull;
            vkTexture->allocationId = subAllocatorCallbacks->allocMemFuncPtr(&memAllocInfo, &memReqs, &numPages);
            if (vkTexture->allocationId == 0ull) {
                return KTX_OUT_OF_MEMORY;
            }
            if (numPages > 1ull) { // Sparse binding of KTX textures is unsupported for the moment
                return KTX_UNSUPPORTED_FEATURE;
            }
            VK_CHECK_RESULT(
                subAllocatorCallbacks->bindImageFuncPtr(mappableImage, vkTexture->allocationId));
        }

        cbData.vkFuncs = vdi->vkFuncs;
        cbData.destImage = mappableImage;
        cbData.device = vdi->device;
        cbData.texture = This;
        callback = canUseFasterPath ?
                         linearTilingCallback : linearTilingPadCallback;

        // Map image memory
        if (!useSuballocator) {
            VK_CHECK_RESULT(
                vdi->vkFuncs.vkMapMemory(vdi->device, mappableMemory, 0,
                    memReqs.size, 0, (void**)&cbData.dest));
        }
        else {
            VK_CHECK_RESULT(
                subAllocatorCallbacks->memoryMapFuncPtr(vkTexture->allocationId, 0ull,
                    &memReqs.size, (void**)&cbData.dest));
        }

        // Iterate over images to copy texture data into mapped image memory.
        if (ktxTexture_isActiveStream(This)) {
            kResult = ktxTexture_IterateLoadLevelFaces(This,
                                                       callback,
                                                       &cbData);
        } else {
            kResult = ktxTexture_IterateLevelFaces(This,
                                                   callback,
                                                   &cbData);
        }
        // XXX Check for possible errors
        if (!useSuballocator)
            vdi->vkFuncs.vkUnmapMemory(vdi->device, mappableMemory);
        else
            subAllocatorCallbacks->memoryUnmapFuncPtr(vkTexture->allocationId, 0ull);

        // Linear tiled images can be directly used as textures.
        vkTexture->image = mappableImage;
        if (!useSuballocator) vkTexture->deviceMemory = mappableMemory;

        if (This->generateMipmaps) {
            generateMipmaps(vkTexture, vdi,
                            blitFilter,
                            VK_IMAGE_LAYOUT_PREINITIALIZED);
        } else {
            VkImageSubresourceRange subresourceRange;
            subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
            subresourceRange.baseMipLevel = 0;
            subresourceRange.levelCount = numImageLevels;
            subresourceRange.baseArrayLayer = 0;
            subresourceRange.layerCount = numImageLayers;

           // Transition image layout to finalLayout.
            setImageLayout(
                vdi->vkFuncs,
                vdi->cmdBuffer,
                vkTexture->image,
                VK_IMAGE_LAYOUT_PREINITIALIZED,
                finalLayout,
                subresourceRange);
        }

        // Submit command buffer containing image layout commands
        VK_CHECK_RESULT(vdi->vkFuncs.vkEndCommandBuffer(vdi->cmdBuffer));

        submitInfo.waitSemaphoreCount = 0;
        submitInfo.commandBufferCount = 1;
        submitInfo.pCommandBuffers = &vdi->cmdBuffer;

        VK_CHECK_RESULT(vdi->vkFuncs.vkQueueSubmit(vdi->queue, 1, &submitInfo, nullFence));
        VK_CHECK_RESULT(vdi->vkFuncs.vkQueueWaitIdle(vdi->queue));
    }
    return KTX_SUCCESS;
}

/** @memberof ktxTexture
 * @~English
 * @brief Create a Vulkan image object from a ktxTexture object.
 *
 * Calls ktxTexture\_VkUploadEx\_WithSuballocator() with no supplied
 * suballocator callbacks. Use that for complete control.
 */
KTX_error_code
ktxTexture_VkUploadEx(ktxTexture* This, ktxVulkanDeviceInfo* vdi,
    ktxVulkanTexture* vkTexture,
    VkImageTiling tiling,
    VkImageUsageFlags usageFlags,
    VkImageLayout finalLayout)
{
    return ktxTexture_VkUploadEx_WithSuballocator(This, vdi, vkTexture,
                                                  tiling, usageFlags, finalLayout, NULL);
}

/**
 * @memberof ktxTexture
 * @~English
 * @brief Create a Vulkan image object from a ktxTexture object.
 *
 * Calls ktxTexture\_VkUploadEx() with the most commonly used options:
 * @c VK_IMAGE_TILING_OPTIMAL,
 * @c VK_IMAGE_USAGE_SAMPLED_BIT and
 * @c VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. Use that for complete
 * control.
 */
KTX_error_code
ktxTexture_VkUpload(ktxTexture* texture, ktxVulkanDeviceInfo* vdi,
                    ktxVulkanTexture *vkTexture)
{
    return ktxTexture_VkUploadEx(ktxTexture(texture), vdi, vkTexture,
                                 VK_IMAGE_TILING_OPTIMAL,
                                 VK_IMAGE_USAGE_SAMPLED_BIT,
                                 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}

/** @memberof ktxTexture1
 * @~English
 * @brief Create a Vulkan image object from a ktxTexture1 object.
 *
 * This simply calls @ref ktxTexture::ktxTexture\_VkUploadEx_WithSuballocator
 * "ktxTexture_VkUploadEx_WithSuballocator()"
 *
 * @copydetails ktxTexture::ktxTexture_VkUploadEx_WithSuballocator
 */
KTX_error_code
ktxTexture1_VkUploadEx_WithSuballocator(ktxTexture1* This, ktxVulkanDeviceInfo* vdi,
                                        ktxVulkanTexture* vkTexture,
                                        VkImageTiling tiling,
                                        VkImageUsageFlags usageFlags,
                                        VkImageLayout finalLayout,
                                        ktxVulkanTexture_subAllocatorCallbacks* subAllocatorCallbacks)
{
    return ktxTexture_VkUploadEx_WithSuballocator(ktxTexture(This), vdi, vkTexture,
                                                  tiling, usageFlags, finalLayout,
                                                  subAllocatorCallbacks);
}

/** @memberof ktxTexture1
 * @~English
 * @brief Create a Vulkan image object from a ktxTexture1 object.
 *
 * This simply calls @ref ktxTexture::ktxTexture\_VkUploadEx "ktxTexture_VkUploadEx()".
 */
KTX_error_code
ktxTexture1_VkUploadEx(ktxTexture1* This, ktxVulkanDeviceInfo* vdi,
                       ktxVulkanTexture* vkTexture,
                       VkImageTiling tiling,
                       VkImageUsageFlags usageFlags,
                       VkImageLayout finalLayout)
{
    return ktxTexture_VkUploadEx(ktxTexture(This), vdi, vkTexture,
                                 tiling, usageFlags, finalLayout);
}

/** @memberof ktxTexture1
 * @~English
 * @brief Create a Vulkan image object from a ktxTexture1 object.
 *
 * Calls @ref ktxTexture::ktxTexture\_VkUploadEx "ktxTexture_VkUploadEx()" with
 * the most commonly used options: @c VK_IMAGE_TILING_OPTIMAL,
 * @c VK_IMAGE_USAGE_SAMPLED_BIT and
 * @c VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. Use that for complete control.
 */
KTX_error_code
ktxTexture1_VkUpload(ktxTexture1* texture, ktxVulkanDeviceInfo* vdi,
                     ktxVulkanTexture *vkTexture)
{
    return ktxTexture_VkUploadEx(ktxTexture(texture), vdi, vkTexture,
                                 VK_IMAGE_TILING_OPTIMAL,
                                 VK_IMAGE_USAGE_SAMPLED_BIT,
                                 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}

/** @memberof ktxTexture2
 * @~English
 * @brief Create a Vulkan image object from a ktxTexture2 object.
 *
 * This simplly calls @ref ktxTexture::ktxTexture_VkUploadEx_WithSuballocator
 * "ktxTexture_VkUploadEx_WithSuballocator()".
 *
 * @copydetails ktxTexture::ktxTexture_VkUploadEx_WithSuballocator
 */
KTX_error_code
ktxTexture2_VkUploadEx_WithSuballocator(ktxTexture2* This, ktxVulkanDeviceInfo* vdi,
                                        ktxVulkanTexture* vkTexture,
                                        VkImageTiling tiling,
                                        VkImageUsageFlags usageFlags,
                                        VkImageLayout finalLayout,
                                        ktxVulkanTexture_subAllocatorCallbacks* subAllocatorCallbacks)
{
    return ktxTexture_VkUploadEx_WithSuballocator(ktxTexture(This), vdi, vkTexture,
                                                  tiling, usageFlags, finalLayout,
                                                  subAllocatorCallbacks);
}

/** @memberof ktxTexture2
 * @~English
 * @brief Create a Vulkan image object from a ktxTexture2 object.
 *
 * This simply calls @ref ktxTexture::ktxTexture\_VkUploadEx "ktxTexture_VkUploadEx()".
 */
KTX_error_code
ktxTexture2_VkUploadEx(ktxTexture2* This, ktxVulkanDeviceInfo* vdi,
                       ktxVulkanTexture* vkTexture,
                       VkImageTiling tiling,
                       VkImageUsageFlags usageFlags,
                       VkImageLayout finalLayout)
{
    return ktxTexture_VkUploadEx(ktxTexture(This), vdi, vkTexture,
                                 tiling, usageFlags, finalLayout);
}

/** @memberof ktxTexture2
 * @~English
 * @brief Create a Vulkan image object from a ktxTexture2 object.
 *
 * Calls @ref ktxTexture::ktxTexture\_VkUploadEx "ktxTexture_VkUploadEx()" with
 * the most commonly used options: @c VK_IMAGE_TILING_OPTIMAL,
 * @c VK_IMAGE_USAGE_SAMPLED_BIT and
 * @c VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL. Use that for complete control.
 */
KTX_error_code
ktxTexture2_VkUpload(ktxTexture2* This, ktxVulkanDeviceInfo* vdi,
                     ktxVulkanTexture *vkTexture)
{
    return ktxTexture_VkUploadEx(ktxTexture(This), vdi, vkTexture,
                                 VK_IMAGE_TILING_OPTIMAL,
                                 VK_IMAGE_USAGE_SAMPLED_BIT,
                                 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
}

/** @memberof ktxTexture1
 * @~English
 * @brief Return the VkFormat enum of a ktxTexture1 object.
 *
 * @return The VkFormat of the texture object. May return VK_FORMAT_UNDEFINED if
 *         there is no mapping from the GL internalformat and format.
 */
VkFormat
ktxTexture1_GetVkFormat(ktxTexture1* This)
{
    VkFormat vkFormat;

    vkFormat = vkGetFormatFromOpenGLInternalFormat(This->glInternalformat);
    if (vkFormat == VK_FORMAT_UNDEFINED) {
        vkFormat = vkGetFormatFromOpenGLFormat(This->glFormat,
            This->glType);
    }
    return vkFormat;
}

/** @memberof ktxTexture2
 * @~English
 * @brief Return the VkFormat enum of a ktxTexture2 object.
 *
 * @return The VkFormat of the texture object.
 */
VkFormat
ktxTexture2_GetVkFormat(ktxTexture2* This)
{
    return This->vkFormat;
}

/** @memberof ktxTexture
 * @~English
 * @brief Return the VkFormat enum of a ktxTexture object.
 *
 * In ordert to ensure that the Vulkan uploader is not linked into an application unless explicitly called,
 * this is not a virtual function. It determines the texture type then dispatches to the correct function.
 * @sa @ref ktxTexture1::ktxTexture1_GetVkFormat "ktxTexture1_GetVkFormat()"
 * @sa @ref ktxTexture2::ktxTexture2_GetVkFormat "ktxTexture2_GetVkFormat()"
 */
VkFormat
ktxTexture_GetVkFormat(ktxTexture* This)
{
    if (This->classId == ktxTexture2_c)
        return ktxTexture2_GetVkFormat((ktxTexture2*)This);
    else
        return ktxTexture1_GetVkFormat((ktxTexture1*)This);
}

//======================================================================
//  Utilities
//======================================================================

/**
 * @internal
 * @~English
 * @brief Create an image memory barrier for changing the layout of an image.
 *
 * The barrier is placed in the passed command buffer. See the Vulkan spec.
 * chapter 11.4 "Image Layout" for details.
 */
static void
setImageLayout(
    ktxVulkanFunctions vkFuncs,
    VkCommandBuffer cmdBuffer,
    VkImage image,
    VkImageLayout oldLayout,
    VkImageLayout newLayout,
    VkImageSubresourceRange subresourceRange)
{
    // Create an image barrier object
    VkImageMemoryBarrier imageMemoryBarrier = {
        .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
        .pNext = NULL,
         // Some default values
        .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
        .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED
    };

    imageMemoryBarrier.oldLayout = oldLayout;
    imageMemoryBarrier.newLayout = newLayout;
    imageMemoryBarrier.image = image;
    imageMemoryBarrier.subresourceRange = subresourceRange;

    // Source layouts (old)
    // The source access mask controls actions to be finished on the old
    // layout before it will be transitioned to the new layout.
    switch (oldLayout)
    {
    case VK_IMAGE_LAYOUT_UNDEFINED:
        // Image layout is undefined (or does not matter).
        // Only valid as initial layout. No flags required.
        imageMemoryBarrier.srcAccessMask = 0;
        break;

    case VK_IMAGE_LAYOUT_PREINITIALIZED:
        // Image is preinitialized.
        // Only valid as initial layout for linear images; preserves memory
        // contents. Make sure host writes have finished.
        imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
        break;

    case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
        // Image is a color attachment.
        // Make sure writes to the color buffer have finished
        imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
        break;

    case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
        // Image is a depth/stencil attachment.
        // Make sure any writes to the depth/stencil buffer have finished.
        imageMemoryBarrier.srcAccessMask
                                = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
        break;

    case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
        // Image is a transfer source.
        // Make sure any reads from the image have finished
        imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
        break;

    case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
        // Image is a transfer destination.
        // Make sure any writes to the image have finished.
        imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        break;

    case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
        // Image is read by a shader.
        // Make sure any shader reads from the image have finished
        imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
        break;

    default:
        /* Value not used by callers, so not supported. */
        assert(KTX_FALSE);
    }

    // Target layouts (new)
    // The destination access mask controls the dependency for the new image
    // layout.
    switch (newLayout)
    {
    case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
        // Image will be used as a transfer destination.
        // Make sure any writes to the image have finished.
        imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        break;

    case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
        // Image will be used as a transfer source.
        // Make sure any reads from and writes to the image have finished.
        imageMemoryBarrier.srcAccessMask |= VK_ACCESS_TRANSFER_READ_BIT;
        imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
        break;

    case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
        // Image will be used as a color attachment.
        // Make sure any writes to the color buffer have finished.
        imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
        imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
        break;

    case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
        // Image layout will be used as a depth/stencil attachment.
        // Make sure any writes to depth/stencil buffer have finished.
        imageMemoryBarrier.dstAccessMask
                                = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
        break;

    case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
        // Image will be read in a shader (sampler, input attachment).
        // Make sure any writes to the image have finished.
        if (imageMemoryBarrier.srcAccessMask == 0)
        {
            imageMemoryBarrier.srcAccessMask
                    = VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT;
        }
        imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
        break;
    default:
        /* Value not used by callers, so not supported. */
        assert(KTX_FALSE);
    }

    // Put barrier on top of pipeline.
    VkPipelineStageFlags srcStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
    VkPipelineStageFlags destStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;

    // Add the barrier to the passed command buffer
    vkFuncs.vkCmdPipelineBarrier(
        cmdBuffer,
        srcStageFlags,
        destStageFlags,
        0,
        0, NULL,
        0, NULL,
        1, &imageMemoryBarrier);
}

/** @internal
 * @~English
 * @brief Generate mipmaps from base using @c VkCmdBlitImage.
 *
 * Mipmaps are generated by blitting level n from level n-1 as it should
 * be faster than the alternative of blitting all levels from the base level.
 *
 * After generation, the image is transitioned to the layout indicated by
 * @c vkTexture->imageLayout.
 *
 * @param[in] vkTexture     pointer to an object with information about the
 *                          image for which to generate mipmaps.
 * @param[in] vdi           pointer to an object with information about the
 *                          Vulkan device and command buffer to use.
 * @param[in] blitFilter    the type of filter to use in the @c VkCmdBlitImage.
 * @param[in] initialLayout the layout of the image on entry to the function.
 */
static void
generateMipmaps(ktxVulkanTexture* vkTexture, ktxVulkanDeviceInfo* vdi,
                VkFilter blitFilter, VkImageLayout initialLayout)
{
    VkImageSubresourceRange subresourceRange;
    memset(&subresourceRange, 0, sizeof(subresourceRange));
    subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    subresourceRange.baseMipLevel = 0;
    subresourceRange.levelCount = 1;
    subresourceRange.baseArrayLayer = 0;
    subresourceRange.layerCount = vkTexture->layerCount;

    // Transition base level to SRC_OPTIMAL for blitting.
    setImageLayout(
        vdi->vkFuncs,
        vdi->cmdBuffer,
        vkTexture->image,
        initialLayout,
        VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
        subresourceRange);

    // Generate the mip chain
    // ----------------------
    // Blit level n from level n-1.
    for (uint32_t i = 1; i < vkTexture->levelCount; i++)
    {
        VkImageBlit imageBlit;
        memset(&imageBlit, 0, sizeof(imageBlit));

        // Source
        imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        imageBlit.srcSubresource.layerCount = vkTexture->layerCount;
        imageBlit.srcSubresource.mipLevel = i-1;
        imageBlit.srcOffsets[1].x = MAX(1, vkTexture->width >> (i - 1));
        imageBlit.srcOffsets[1].y = MAX(1, vkTexture->height >> (i - 1));
        imageBlit.srcOffsets[1].z = MAX(1, vkTexture->depth >> (i - 1));;

        // Destination
        imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        imageBlit.dstSubresource.layerCount = 1;
        imageBlit.dstSubresource.mipLevel = i;
        imageBlit.dstOffsets[1].x = MAX(1, vkTexture->width >> i);
        imageBlit.dstOffsets[1].y = MAX(1, vkTexture->height >> i);
        imageBlit.dstOffsets[1].z = MAX(1, vkTexture->depth >> i);

        VkImageSubresourceRange mipSubRange;
        memset(&mipSubRange, 0, sizeof(mipSubRange));

        mipSubRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        mipSubRange.baseMipLevel = i;
        mipSubRange.levelCount = 1;
        mipSubRange.layerCount = vkTexture->layerCount;

        // Transiton current mip level to transfer dest
        setImageLayout(
            vdi->vkFuncs,
            vdi->cmdBuffer,
            vkTexture->image,
            VK_IMAGE_LAYOUT_UNDEFINED,
            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
            mipSubRange);

        // Blit from previous level
        vdi->vkFuncs.vkCmdBlitImage(
            vdi->cmdBuffer,
            vkTexture->image,
            VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
            vkTexture->image,
            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
            1,
            &imageBlit,
            blitFilter);

        // Transiton current mip level to transfer source for read in
        // next iteration.
        setImageLayout(
            vdi->vkFuncs,
            vdi->cmdBuffer,
            vkTexture->image,
            VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
            VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
            mipSubRange);
    }

    // After the loop, all mip layers are in TRANSFER_SRC layout.
    // Transition all to final layout.
    subresourceRange.levelCount = vkTexture->levelCount;
    setImageLayout(
        vdi->vkFuncs,
        vdi->cmdBuffer,
        vkTexture->image,
        VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
        vkTexture->imageLayout,
        subresourceRange);
}

//======================================================================
//  ktxVulkanTexture utilities
//======================================================================

/**
 * @memberof ktxVulkanTexture
 * @~English
 * @brief Destructor for the object returned when loading a texture image.
 *
 * Frees the Vulkan resources created when the texture image was loaded.
 * If a complete set of suballocator callbacks
 * are provided, it will use the free call from those instead.
 *
 * @param vkTexture                pointer to the ktxVulkanTexture to be destructed.
 * @param device                   handle to the Vulkan logical device to which the texture was
 *                                 loaded.
 * @param pAllocator               pointer to the allocator used during loading.
 * @param subAllocatorCallbacks    pointer to a structure of suballocator callbacks.
 *                                 Pass NULL if a suballocator was not used.
 * @return                         KTX_SUCCESS on success, KTX_INVALID_VALUE if the
 *                                 supplied subAllocatorCallbacks structure is incomplete.
 */
ktx_error_code_e
ktxVulkanTexture_Destruct_WithSuballocator(ktxVulkanTexture* vkTexture, VkDevice device,
                                           const VkAllocationCallbacks* pAllocator,
                                           ktxVulkanTexture_subAllocatorCallbacks* subAllocatorCallbacks)
{
    ktx_bool_t useSuballocator = false;
    if (subAllocatorCallbacks) {
        if (subAllocatorCallbacks->allocMemFuncPtr &&
            subAllocatorCallbacks->bindBufferFuncPtr &&
            subAllocatorCallbacks->bindImageFuncPtr &&
            subAllocatorCallbacks->memoryMapFuncPtr &&
            subAllocatorCallbacks->memoryUnmapFuncPtr &&
            subAllocatorCallbacks->freeMemFuncPtr)
            useSuballocator = true;
        else
            return KTX_INVALID_VALUE;
    }

    vkTexture->vkDestroyImage(device, vkTexture->image, pAllocator);
    if (useSuballocator)
        subAllocatorCallbacks->freeMemFuncPtr(vkTexture->allocationId);
    else
        vkTexture->vkFreeMemory(device, vkTexture->deviceMemory, pAllocator);

    return KTX_SUCCESS;
}

/**
 * @memberof ktxVulkanTexture
 * @~English
 * @brief Destructor for the object returned when loading a texture image.
 *
 * Calls ktxVulkanTexture_Destruct_WithSuballocator() without a
 * set of suballocator callbacks.
 *
 * @sa ktxVulkanTexture_Destruct_WithSuballocator() for details and use that for complete
 *     control.
 */
void
ktxVulkanTexture_Destruct(ktxVulkanTexture* vkTexture, VkDevice device,
                          const VkAllocationCallbacks* pAllocator)
{
    (void)ktxVulkanTexture_Destruct_WithSuballocator(vkTexture, device, pAllocator, NULL);
}


/** @} */