#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "vk_funcs.h"
#include "ktxvulkan.h"
#include "ktxint.h"
#include "unused.h"
#include "texture1.h"
#include "texture2.h"
#include "vk_format.h"
#if defined(_DEBUG)
#define VK_CHECK_RESULT(f) \
{ \
VkResult res = (f); \
if (res != VK_SUCCESS) \
{ \
\
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(
VkCommandBuffer cmdBuffer,
VkImage image,
VkImageLayout oldLayout,
VkImageLayout newLayout,
VkImageSubresourceRange subresourceRange);
static void
generateMipmaps(ktxVulkanTexture* vkTexture, ktxVulkanDeviceInfo* vdi,
VkFilter filter, VkImageLayout initialLayout);
ktxVulkanDeviceInfo*
ktxVulkanDeviceInfo_Create(VkPhysicalDevice physicalDevice, VkDevice device,
VkQueue queue, VkCommandPool cmdPool,
const VkAllocationCallbacks* pAllocator)
{
ktxVulkanDeviceInfo* newvdi;
newvdi = (ktxVulkanDeviceInfo*)malloc(sizeof(ktxVulkanDeviceInfo));
if (newvdi != NULL) {
if (ktxVulkanDeviceInfo_Construct(newvdi, physicalDevice, device,
queue, cmdPool, pAllocator) != KTX_SUCCESS)
{
free(newvdi);
newvdi = 0;
}
}
return newvdi;
}
KTX_error_code
ktxVulkanDeviceInfo_Construct(ktxVulkanDeviceInfo* This,
VkPhysicalDevice physicalDevice, VkDevice device,
VkQueue queue, VkCommandPool cmdPool,
const VkAllocationCallbacks* pAllocator)
{
VkCommandBufferAllocateInfo cmdBufInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO
};
VkResult result;
#if defined(KTX_USE_FUNCPTRS_FOR_VULKAN)
if (!ktxVulkanModuleHandle) {
ktx_error_code_e kresult = ktxLoadVulkanLibrary();
if (kresult != KTX_SUCCESS)
return kresult;
}
#endif
This->physicalDevice = physicalDevice;
This->device = device;
This->queue = queue;
This->cmdPool = cmdPool;
This->pAllocator = pAllocator;
vkGetPhysicalDeviceMemoryProperties(physicalDevice,
&This->deviceMemoryProperties);
cmdBufInfo.commandPool = cmdPool;
cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cmdBufInfo.commandBufferCount = 1;
result = vkAllocateCommandBuffers(device, &cmdBufInfo, &This->cmdBuffer);
if (result != VK_SUCCESS) {
return KTX_OUT_OF_MEMORY; }
return KTX_SUCCESS;
}
void
ktxVulkanDeviceInfo_Destruct(ktxVulkanDeviceInfo* This)
{
vkFreeCommandBuffers(This->device, This->cmdPool, 1,
&This->cmdBuffer);
}
void
ktxVulkanDeviceInfo_Destroy(ktxVulkanDeviceInfo* This)
{
assert(This != NULL);
ktxVulkanDeviceInfo_Destruct(This);
free(This);
}
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;
}
return 0;
}
typedef struct user_cbdata_optimal {
VkBufferImageCopy* region; VkDeviceSize offset; ktx_uint32_t numFaces;
ktx_uint32_t numLayers;
ktx_uint8_t* dest; ktx_uint32_t elementSize;
ktx_uint32_t numDimensions;
#if defined(_DEBUG)
VkBufferImageCopy* regionsArrayEnd;
#endif
} user_cbdata_optimal;
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);
#if defined(_DEBUG)
assert(ud->region < ud->regionsArrayEnd);
#endif
ud->region->bufferOffset = ud->offset;
ud->offset += faceLodSize;
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);
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;
#if defined(_DEBUG)
assert(ud->region < ud->regionsArrayEnd);
#endif
ud->region->bufferOffset = ud->offset;
if (_KTX_PAD_UNPACK_ALIGN_LEN(rowPitch) == 0) {
memcpy(ud->dest + ud->offset, pixels, faceLodSize);
ud->offset += faceLodSize;
} else {
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;
}
}
}
if (ud->offset % ud->elementSize != 0 || ud->offset % 4 != 0) {
assert(ud->elementSize < 4 && ud->elementSize > 0);
ktx_uint32_t lcm = lcm4(ud->elementSize);
ud->offset = _KTX_PADN(lcm, ud->offset);
}
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 {
VkImage destImage;
VkDevice device;
uint8_t* dest; ktxTexture* texture;
} user_cbdata_linear;
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;
VkImageSubresource subRes = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = miplevel,
.arrayLayer = face
};
UNUSED(width);
UNUSED(height);
UNUSED(depth);
vkGetImageSubresourceLayout(ud->device, ud->destImage, &subRes,
&subResLayout);
memcpy(ud->dest + subResLayout.offset, pixels, faceLodSize);
return KTX_SUCCESS;
}
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;
VkSubresourceLayout subResLayout;
VkImageSubresource subRes = {
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.mipLevel = miplevel,
.arrayLayer = face
};
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;
UNUSED(width);
vkGetImageSubresourceLayout(ud->device, ud->destImage, &subRes,
&subResLayout);
srcRowPitch = ktxTexture_GetRowPitch(ud->texture, miplevel);
if (subResLayout.rowPitch != srcRowPitch)
rowIterations = height;
else
rowIterations = 1;
imageIterations = 1;
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) {
if (subResLayout.rowPitch < srcRowPitch)
copySize = subResLayout.rowPitch;
else
copySize = srcRowPitch;
} else if (imageIterations > 1)
copySize = faceLodSize / imageIterations;
else
copySize = faceLodSize;
offset = subResLayout.offset;
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;
}
KTX_error_code
ktxTexture_VkUploadEx(ktxTexture* This, ktxVulkanDeviceInfo* vdi,
ktxVulkanTexture* vkTexture,
VkImageTiling tiling,
VkImageUsageFlags usageFlags,
VkImageLayout finalLayout)
{
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;
if (!vdi || !This || !vkTexture) {
return KTX_INVALID_VALUE;
}
if (!This->pData && !ktxTexture_isActiveStream(This)) {
return KTX_INVALID_OPERATION;
}
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: 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;
assert(!This->isArray);
viewType = VK_IMAGE_VIEW_TYPE_3D;
break;
}
vkFormat = ktxTexture_GetVkFormat(This);
if (vkFormat == VK_FORMAT_UNDEFINED) {
return KTX_INVALID_OPERATION;
}
if (tiling == VK_IMAGE_TILING_OPTIMAL) {
usageFlags |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
}
if (This->generateMipmaps) {
usageFlags |= (VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT);
}
vResult = 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;
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;
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 (elementSize % 4 == 0
|| (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;
VK_CHECK_RESULT(vkBeginCommandBuffer(vdi->cmdBuffer, &cmdBufBeginInfo));
if (tiling == VK_IMAGE_TILING_OPTIMAL)
{
VkBuffer stagingBuffer;
VkDeviceMemory stagingMemory;
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) {
numCopyRegions = This->numLevels;
} else {
numCopyRegions = This->isArray ? This->numLevels
: This->numLevels * This->numFaces;
bufferCreateInfo.size += numCopyRegions * elementSize * 4;
}
copyRegions = (VkBufferImageCopy*)malloc(sizeof(VkBufferImageCopy)
* numCopyRegions);
if (copyRegions == NULL) {
return KTX_OUT_OF_MEMORY;
}
bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VK_CHECK_RESULT(vkCreateBuffer(vdi->device, &bufferCreateInfo,
vdi->pAllocator, &stagingBuffer));
vkGetBufferMemoryRequirements(vdi->device, stagingBuffer, &memReqs);
memAllocInfo.allocationSize = memReqs.size;
memAllocInfo.memoryTypeIndex = ktxVulkanDeviceInfo_getMemoryType(
vdi,
memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
| VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
);
vResult = vkAllocateMemory(vdi->device, &memAllocInfo,
vdi->pAllocator, &stagingMemory);
if (vResult != VK_SUCCESS) {
return KTX_OUT_OF_MEMORY;
}
VK_CHECK_RESULT(vkBindBufferMemory(vdi->device, stagingBuffer,
stagingMemory, 0));
VK_CHECK_RESULT(vkMapMemory(vdi->device, stagingMemory, 0,
memReqs.size, 0,
(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) {
if (This->pData) {
assert(This->dataSize <= memAllocInfo.allocationSize);
memcpy(pMappedStagingBuffer, This->pData, This->dataSize);
} else {
kResult = ktxTexture_LoadImageData(This,
pMappedStagingBuffer,
(ktx_size_t)memAllocInfo.allocationSize);
if (kResult != KTX_SUCCESS)
return kResult;
}
kResult = ktxTexture_IterateLevels(This,
optimalTilingCallback,
&cbData);
} else {
if (This->pData) {
kResult = ktxTexture_IterateLevelFaces(
This,
optimalTilingPadCallback,
&cbData);
} else {
kResult = ktxTexture_IterateLoadLevelFaces(
This,
optimalTilingPadCallback,
&cbData);
}
}
vkUnmapMemory(vdi->device, stagingMemory);
imageCreateInfo.imageType = imageType;
imageCreateInfo.flags = createFlags;
imageCreateInfo.format = vkFormat;
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(vkCreateImage(vdi->device, &imageCreateInfo,
vdi->pAllocator, &vkTexture->image));
vkGetImageMemoryRequirements(vdi->device, vkTexture->image, &memReqs);
memAllocInfo.allocationSize = memReqs.size;
memAllocInfo.memoryTypeIndex = ktxVulkanDeviceInfo_getMemoryType(
vdi, memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VK_CHECK_RESULT(vkAllocateMemory(vdi->device, &memAllocInfo,
vdi->pAllocator,
&vkTexture->deviceMemory));
VK_CHECK_RESULT(vkBindImageMemory(vdi->device, vkTexture->image,
vkTexture->deviceMemory, 0));
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = This->numLevels;
subresourceRange.baseArrayLayer = 0;
subresourceRange.layerCount = numImageLayers;
setImageLayout(
vdi->cmdBuffer,
vkTexture->image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
subresourceRange);
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 {
setImageLayout(
vdi->cmdBuffer,
vkTexture->image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
finalLayout,
subresourceRange);
}
VK_CHECK_RESULT(vkEndCommandBuffer(vdi->cmdBuffer));
VK_CHECK_RESULT(vkCreateFence(vdi->device, &fenceCreateInfo,
vdi->pAllocator, ©Fence));
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &vdi->cmdBuffer;
VK_CHECK_RESULT(vkQueueSubmit(vdi->queue, 1, &submitInfo, copyFence));
VK_CHECK_RESULT(vkWaitForFences(vdi->device, 1, ©Fence,
VK_TRUE, DEFAULT_FENCE_TIMEOUT));
vkDestroyFence(vdi->device, copyFence, vdi->pAllocator);
vkFreeMemory(vdi->device, stagingMemory, vdi->pAllocator);
vkDestroyBuffer(vdi->device, stagingBuffer, vdi->pAllocator);
}
else
{
VkImage mappableImage;
VkDeviceMemory mappableMemory;
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;
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;
VK_CHECK_RESULT(vkCreateImage(vdi->device, &imageCreateInfo,
vdi->pAllocator, &mappableImage));
vkGetImageMemoryRequirements(vdi->device, mappableImage, &memReqs);
memAllocInfo.allocationSize = memReqs.size;
memAllocInfo.memoryTypeIndex = ktxVulkanDeviceInfo_getMemoryType(
vdi,
memReqs.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
vResult = vkAllocateMemory(vdi->device, &memAllocInfo, vdi->pAllocator,
&mappableMemory);
if (vResult != VK_SUCCESS) {
return KTX_OUT_OF_MEMORY;
}
VK_CHECK_RESULT(vkBindImageMemory(vdi->device, mappableImage,
mappableMemory, 0));
cbData.destImage = mappableImage;
cbData.device = vdi->device;
cbData.texture = This;
callback = canUseFasterPath ?
linearTilingCallback : linearTilingPadCallback;
VK_CHECK_RESULT(vkMapMemory(vdi->device, mappableMemory, 0,
memReqs.size, 0, (void **)&cbData.dest));
if (ktxTexture_isActiveStream(This)) {
kResult = ktxTexture_IterateLoadLevelFaces(This,
callback,
&cbData);
} else {
kResult = ktxTexture_IterateLevelFaces(This,
callback,
&cbData);
}
vkUnmapMemory(vdi->device, mappableMemory);
vkTexture->image = mappableImage;
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;
setImageLayout(
vdi->cmdBuffer,
vkTexture->image,
VK_IMAGE_LAYOUT_PREINITIALIZED,
finalLayout,
subresourceRange);
}
VK_CHECK_RESULT(vkEndCommandBuffer(vdi->cmdBuffer));
submitInfo.waitSemaphoreCount = 0;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &vdi->cmdBuffer;
VK_CHECK_RESULT(vkQueueSubmit(vdi->queue, 1, &submitInfo, nullFence));
VK_CHECK_RESULT(vkQueueWaitIdle(vdi->queue));
}
return KTX_SUCCESS;
}
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);
}
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);
}
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);
}
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);
}
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);
}
VkFormat
ktxTexture1_GetVkFormat(ktxTexture1* This)
{
VkFormat vkFormat;
vkFormat = vkGetFormatFromOpenGLInternalFormat(This->glInternalformat);
if (vkFormat == VK_FORMAT_UNDEFINED) {
vkFormat = vkGetFormatFromOpenGLFormat(This->glFormat,
This->glType);
}
return vkFormat;
}
VkFormat
ktxTexture2_GetVkFormat(ktxTexture2* This)
{
return This->vkFormat;
}
VkFormat
ktxTexture_GetVkFormat(ktxTexture* This)
{
if (This->classId == ktxTexture2_c)
return ktxTexture2_GetVkFormat((ktxTexture2*)This);
else
return ktxTexture1_GetVkFormat((ktxTexture1*)This);
}
static void
setImageLayout(
VkCommandBuffer cmdBuffer,
VkImage image,
VkImageLayout oldLayout,
VkImageLayout newLayout,
VkImageSubresourceRange subresourceRange)
{
VkImageMemoryBarrier imageMemoryBarrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = NULL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED
};
imageMemoryBarrier.oldLayout = oldLayout;
imageMemoryBarrier.newLayout = newLayout;
imageMemoryBarrier.image = image;
imageMemoryBarrier.subresourceRange = subresourceRange;
switch (oldLayout)
{
case VK_IMAGE_LAYOUT_UNDEFINED:
imageMemoryBarrier.srcAccessMask = 0;
break;
case VK_IMAGE_LAYOUT_PREINITIALIZED:
imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
imageMemoryBarrier.srcAccessMask
= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
break;
default:
assert(KTX_FALSE);
}
switch (newLayout)
{
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
imageMemoryBarrier.srcAccessMask |= VK_ACCESS_TRANSFER_READ_BIT;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
imageMemoryBarrier.dstAccessMask
= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
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:
assert(KTX_FALSE);
}
VkPipelineStageFlags srcStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
VkPipelineStageFlags destStageFlags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
vkCmdPipelineBarrier(
cmdBuffer,
srcStageFlags,
destStageFlags,
0,
0, NULL,
0, NULL,
1, &imageMemoryBarrier);
}
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;
setImageLayout(
vdi->cmdBuffer,
vkTexture->image,
initialLayout,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
subresourceRange);
for (uint32_t i = 1; i < vkTexture->levelCount; i++)
{
VkImageBlit imageBlit;
memset(&imageBlit, 0, sizeof(imageBlit));
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));;
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;
setImageLayout(
vdi->cmdBuffer,
vkTexture->image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
mipSubRange);
vkCmdBlitImage(
vdi->cmdBuffer,
vkTexture->image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
vkTexture->image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
1,
&imageBlit,
blitFilter);
setImageLayout(
vdi->cmdBuffer,
vkTexture->image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
mipSubRange);
}
subresourceRange.levelCount = vkTexture->levelCount;
setImageLayout(
vdi->cmdBuffer,
vkTexture->image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
vkTexture->imageLayout,
subresourceRange);
}
void
ktxVulkanTexture_Destruct(ktxVulkanTexture* vkTexture, VkDevice device,
const VkAllocationCallbacks* pAllocator)
{
vkDestroyImage(device, vkTexture->image, pAllocator);
vkFreeMemory(device, vkTexture->deviceMemory, pAllocator);
}