Files
how-to-vulkan/ktx/lib/vkloader.c
T
2026-06-14 19:09:18 +01:00

1984 lines
76 KiB
C

/* -*- 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, github.com/MarkCallow
*/
#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) {
free(copyRegions);
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) {
free(copyRegions);
return KTX_OUT_OF_MEMORY;
}
if (numPages > 1ull) { // Sparse binding of KTX textures is unsupported for the moment
free(copyRegions);
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);
}
/** @} */