//// Created by Bradley Austin Davis on 2016/08/07// Adapted for Vulkan in 2022-2025 by dr Karol Suprynowicz.// Copyright 2013-2018 High Fidelity, Inc.// Copyright 2023-2025 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// SPDX-License-Identifier: Apache-2.0//// Contains parts of Vulkan Samples, Copyright (c) 2018, Sascha Willems, distributed on MIT License.// #include "VKTexture.h" #include <QtCore/QThread>#include <NumericalConstants.h>#include <gl/GLHelpers.h> #include "VKBackend.h"#include "vk/Allocation.h" using namespace gpu;using namespace gpu::vk; /*const uint32_t VKTexture::CUBE_FACE_LAYOUT[VKTexture::TEXTURE_CUBE_NUM_FACES] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z};*/ const uint32_t VKTexture::WRAP_MODES[Sampler::NUM_WRAP_MODES] = { VK_SAMPLER_ADDRESS_MODE_REPEAT, // WRAP_REPEAT, VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, // WRAP_MIRROR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // WRAP_CLAMP, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, // WRAP_BORDER, VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE // WRAP_MIRROR_ONCE,}; const VKFilterMode VKTexture::FILTER_MODES[Sampler::NUM_FILTERS] = { { VK_FILTER_NEAREST, VK_FILTER_NEAREST }, //FILTER_MIN_MAG_POINT, { VK_FILTER_NEAREST, VK_FILTER_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR, { VK_FILTER_LINEAR, VK_FILTER_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT, { VK_FILTER_LINEAR, VK_FILTER_LINEAR }, //FILTER_MIN_MAG_LINEAR, // VKTODO: VkSamplerMipmapMode has some related setting too // VKTODO: This is not really accurate, but hopefully good enough for now { VK_FILTER_NEAREST, VK_FILTER_NEAREST }, //FILTER_MIN_MAG_MIP_POINT, { VK_FILTER_LINEAR, VK_FILTER_NEAREST }, //FILTER_MIN_MAG_POINT_MIP_LINEAR, { VK_FILTER_NEAREST, VK_FILTER_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, { VK_FILTER_LINEAR, VK_FILTER_LINEAR }, //FILTER_MIN_POINT_MAG_MIP_LINEAR, { VK_FILTER_NEAREST, VK_FILTER_NEAREST }, //FILTER_MIN_LINEAR_MAG_MIP_POINT, { VK_FILTER_LINEAR, VK_FILTER_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, { VK_FILTER_NEAREST, VK_FILTER_LINEAR }, //FILTER_MIN_MAG_LINEAR_MIP_POINT, { VK_FILTER_LINEAR, VK_FILTER_LINEAR }, //FILTER_MIN_MAG_MIP_LINEAR, { VK_FILTER_LINEAR, VK_FILTER_LINEAR } //FILTER_ANISOTROPIC,}; static constexpr size_t MAX_PIXEL_BYTE_SIZE{ 4 };static constexpr size_t MAX_TRANSFER_DIMENSION{ 1024 }; /*const uvec3 GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS{ MAX_TRANSFER_DIMENSION, MAX_TRANSFER_DIMENSION, 1 };const uvec3 GLVariableAllocationSupport::INITIAL_MIP_TRANSFER_DIMENSIONS{ 64, 64, 1 };const size_t GLVariableAllocationSupport::MAX_TRANSFER_SIZE = MAX_TRANSFER_DIMENSION * MAX_TRANSFER_DIMENSION * MAX_PIXEL_BYTE_SIZE;const size_t GLVariableAllocationSupport::MAX_BUFFER_SIZE = MAX_TRANSFER_SIZE;*/ /*GLenum VKTexture::getVKTextureType(const Texture& texture) { switch (texture.getType()) { case Texture::TEX_2D: if (!texture.isArray()) { if (!texture.isMultisample()) { return GL_TEXTURE_2D; } else { return GL_TEXTURE_2D_MULTISAMPLE; } } else { if (!texture.isMultisample()) { return GL_TEXTURE_2D_ARRAY; } else { return GL_TEXTURE_2D_MULTISAMPLE_ARRAY; } } break; case Texture::TEX_CUBE: return GL_TEXTURE_CUBE_MAP; break; default: qFatal("Unsupported texture type"); } Q_UNREACHABLE(); return GL_TEXTURE_2D;} const std::vector<GLenum>& VKTexture::getFaceTargets(GLenum target) { static const std::vector<GLenum> cubeFaceTargets { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; static const std::vector<GLenum> face2DTargets { GL_TEXTURE_2D }; static const std::vector<GLenum> face2DMSTargets{ GL_TEXTURE_2D_MULTISAMPLE }; static const std::vector<GLenum> arrayFaceTargets{ GL_TEXTURE_2D_ARRAY }; switch (target) { case GL_TEXTURE_2D: return face2DTargets; case GL_TEXTURE_2D_MULTISAMPLE: return face2DMSTargets; case GL_TEXTURE_2D_ARRAY: return arrayFaceTargets; case GL_TEXTURE_CUBE_MAP: return cubeFaceTargets; default: Q_UNREACHABLE(); break; } Q_UNREACHABLE(); return face2DTargets;}*/ VKTexture::VKTexture(const std::weak_ptr<VKBackend>& backend, const Texture& texture, bool isTransferable) : VKObject(*backend.lock(), texture), _storageStamp(texture.getStamp()), _target(getVKTextureType(texture)), _transferable(isTransferable), _downsampleSource(backend), _vkTexelFormat(evalTexelFormatInternal(texture.getTexelFormat(), backend.lock()->getContext())){ Backend::setGPUObject(texture, this);} VKTexture::~VKTexture() { qDebug() << "VKTexture destroyed: " << this; auto backend = _backend.lock(); backend->getContext().recycler.textureDeleted(this); if (backend && _vkImage == VK_NULL_HANDLE) { // VKTODO // backend->releaseTexture(_id, 0); }} VkImageViewType VKTexture::getVKTextureType(const Texture& texture) { switch (texture.getType()) { case Texture::TEX_2D: if (!texture.isArray()) { if (!texture.isMultisample()) { return VK_IMAGE_VIEW_TYPE_2D; } else { // VKTODO: multisample? return VK_IMAGE_VIEW_TYPE_2D; } } else { if (!texture.isMultisample()) { return VK_IMAGE_VIEW_TYPE_2D_ARRAY; } else { // VKTODO multisample? return VK_IMAGE_VIEW_TYPE_2D_ARRAY; } } break; case Texture::TEX_CUBE: return VK_IMAGE_VIEW_TYPE_CUBE; break; default: qFatal("Unsupported texture type"); } Q_UNREACHABLE(); return VK_IMAGE_VIEW_TYPE_2D;} uint8_t VKTexture::getFaceCount(VkImageViewType target) { switch (target) { case VK_IMAGE_VIEW_TYPE_1D: case VK_IMAGE_VIEW_TYPE_2D: case VK_IMAGE_VIEW_TYPE_3D: case VK_IMAGE_VIEW_TYPE_1D_ARRAY: case VK_IMAGE_VIEW_TYPE_2D_ARRAY: return 1; case VK_IMAGE_VIEW_TYPE_CUBE: case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY: return 6; default: Q_UNREACHABLE(); break; }} Sampler VKTexture::getInvalidSampler() { static Sampler INVALID_SAMPLER; static std::once_flag once; std::call_once(once, [] { Sampler::Desc invalidDesc; invalidDesc._borderColor = vec4(-1.0f); INVALID_SAMPLER = Sampler(invalidDesc); }); return INVALID_SAMPLER;} // From VKSvoid VKAttachmentTexture::createTexture(VKBackend &backend) { VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo(); imageCI.imageType = VK_IMAGE_TYPE_2D; imageCI.format = evalTexelFormatInternal(_gpuObject.getTexelFormat(), backend.getContext()); imageCI.extent.width = _gpuObject.getWidth(); imageCI.extent.height = _gpuObject.getHeight(); imageCI.extent.depth = 1; imageCI.mipLevels = 1; imageCI.arrayLayers = _gpuObject.isArray() ? _gpuObject.getNumSlices() : 1; imageCI.samples = VK_SAMPLE_COUNT_1_BIT; imageCI.tiling = VK_IMAGE_TILING_OPTIMAL; if (_gpuObject.isDepthStencilRenderTarget()) { imageCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; } else { imageCI.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; } VmaAllocationCreateInfo allocationCI = {}; allocationCI.usage = VMA_MEMORY_USAGE_GPU_ONLY; vmaCreateImage(vks::Allocation::getAllocator(), &imageCI, &allocationCI, &_vkImage, &_vmaAllocation, nullptr);} VKAttachmentTexture::~VKAttachmentTexture() { auto backend = _backend.lock(); auto &recycler = backend->getContext().recycler; if (_vkImageView) { recycler.trashVkImageView(_vkImageView); } if (_vkSampler) { recycler.trashVkSampler(_vkSampler); } recycler.trashVkImage(_vkImage); recycler.trashVmaAllocation(_vmaAllocation);} VkDescriptorImageInfo VKAttachmentTexture::getDescriptorImageInfo() { if (_vkSampler == VK_NULL_HANDLE) { auto backend = _backend.lock(); auto device = backend->getContext().device; // Create sampler VkSamplerCreateInfo samplerCreateInfo = {}; samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerCreateInfo.magFilter = VK_FILTER_LINEAR; // VKTODO samplerCreateInfo.minFilter = VK_FILTER_LINEAR; // VKTODO samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerCreateInfo.mipLodBias = 0.0f; samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER; samplerCreateInfo.minLod = 0.0f; samplerCreateInfo.maxLod = 0.0f; samplerCreateInfo.maxAnisotropy = 1.0f; VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &_vkSampler)); } if (_vkImageView == VK_NULL_HANDLE) { auto backend = _backend.lock(); auto device = backend->getContext().device; // Create image view VkImageViewCreateInfo viewCreateInfo = {}; viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewCreateInfo.pNext = nullptr; viewCreateInfo.viewType = getVKTextureType(_gpuObject); viewCreateInfo.format = evalTexelFormatInternal(_gpuObject.getTexelFormat(), backend->getContext()); if (viewCreateInfo.format == VK_FORMAT_D24_UNORM_S8_UINT || viewCreateInfo.format == VK_FORMAT_D32_SFLOAT_S8_UINT || viewCreateInfo.format == VK_FORMAT_D16_UNORM_S8_UINT) { //viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1 }; // VKTODO: both VK_IMAGE_ASPECT_DEPTH_BIT and VK_IMAGE_ASPECT_STENCIL_BIT cannot be set at the same time, but I'm not sure which one to set. viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, _gpuObject.getNumSlices() }; } else if (viewCreateInfo.format == VK_FORMAT_D32_SFLOAT) { viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, _gpuObject.getNumSlices() }; } else { viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, _gpuObject.getNumSlices() }; } //viewCreateInfo.subresourceRange.levelCount = _gpuObject.getNumSlices(); viewCreateInfo.image = _vkImage; VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &_vkImageView)); } VkDescriptorImageInfo result {}; result.sampler = _vkSampler; result.imageLayout = _vkImageLayout; // VKTODO: this needs to be updated on blits and other image writes result.imageView = _vkImageView; return result;}; void VKStrictResourceTexture::createTexture(VKBackend &backend) { VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo(); imageCI.imageType = VK_IMAGE_TYPE_2D; imageCI.format = evalTexelFormatInternal(_gpuObject.getTexelFormat(), backend.getContext()); imageCI.extent.width = _gpuObject.getWidth(); imageCI.extent.height = _gpuObject.getHeight(); imageCI.extent.depth = 1; imageCI.arrayLayers = _gpuObject.isArray() ? _gpuObject.getNumSlices() : 1; imageCI.samples = VK_SAMPLE_COUNT_1_BIT; imageCI.tiling = VK_IMAGE_TILING_OPTIMAL; imageCI.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; if (_gpuObject.getType() == Texture::TEX_CUBE) { imageCI.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; imageCI.arrayLayers = 6; } // We need to lock mip data here so that it doesn't change or get deleted before transfer _transferData.mipLevels = _gpuObject.getNumMips(); _transferData.width = _gpuObject.getWidth(); _transferData.height = _gpuObject.getHeight(); _transferData.buffer_size = 0; for (uint16_t sourceMip = 0; sourceMip < _transferData.mipLevels; ++sourceMip) { if (!_gpuObject.isStoredMipFaceAvailable(sourceMip)) { continue; } _transferData.mips.emplace_back(); //VKTODO: error out if needed size_t face_count = 1; if (_gpuObject.getType() == Texture::TEX_CUBE) { Q_ASSERT(_gpuObject.getNumFaces() == 6); face_count = 6; } else { Q_ASSERT(_gpuObject.getNumFaces() == 1); } // VKTODO: move to a separate function and reuse. // Is conversion from RGB to RGBA needed? bool needsAddingAlpha = false; bool needsBGRToRGB = false; auto storedFormat = _gpuObject.getStoredMipFormat(); auto texelFormat = _gpuObject.getTexelFormat(); if ((storedFormat.getSemantic() == gpu::BGRA || storedFormat.getSemantic() == gpu::SBGRA) && !(texelFormat.getSemantic() == gpu::BGRA || texelFormat.getSemantic() == gpu::SBGRA)) { needsBGRToRGB = true; } auto storedVkFormat = evalTexelFormatInternal(_gpuObject.getStoredMipFormat(), backend.getContext()); auto texelVkFormat = evalTexelFormatInternal(_gpuObject.getTexelFormat(), backend.getContext()); if (storedFormat.getDimension() != texelFormat.getDimension()) { if (storedFormat.getDimension() == gpu::VEC3 && texelFormat.getDimension() == gpu::VEC4) { // It's best to make sure that this is not happening in unexpected cases and causing bugs Q_ASSERT((storedVkFormat == VK_FORMAT_R8G8B8_UNORM && texelVkFormat == VK_FORMAT_R8G8B8A8_UNORM) || (storedVkFormat == VK_FORMAT_R8G8B8_UNORM && texelVkFormat == VK_FORMAT_R8G8B8A8_SRGB)); needsAddingAlpha = true; } else { qDebug() << "Format mismatch, stored: " << storedVkFormat << " texel: " << texelVkFormat; Q_ASSERT(false); } } for (size_t face = 0; face < face_count; face++) { auto dim = _gpuObject.evalMipDimensions(sourceMip); auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face); // VKTODO: only one face for now auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); if (mipData) { TransferData::Mip mip{}; mip.offset = _transferData.buffer_size; mip.size = mipSize; mip.data = mipData; mip.width = dim.x; mip.height = dim.y; mip.needsAddingAlpha = needsAddingAlpha; mip.needsBGRToRGB = needsBGRToRGB; if (needsAddingAlpha) { Q_ASSERT(mipSize % 3 == 0); _transferData.buffer_size += mipSize / 3 * 4; } else { _transferData.buffer_size += mipSize; } _transferData.mips.back().push_back(mip); } else { qCDebug(gpu_vk_logging) << "Missing mipData level=" << sourceMip << " face=" << 0 /*(int)face*/ << " for texture " << _gpuObject.source().c_str(); } } } imageCI.mipLevels = _transferData.mips.size(); VmaAllocationCreateInfo allocationCI = {}; allocationCI.usage = VMA_MEMORY_USAGE_GPU_ONLY; qDebug() << "storedSize: " << _gpuObject.getStoredSize(); VK_CHECK_RESULT(vmaCreateImage(vks::Allocation::getAllocator(), &imageCI, &allocationCI, &_vkImage, &_vmaAllocation, nullptr));} void VKStrictResourceTexture::transfer(VKBackend &backend) { VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); VkMemoryRequirements memReqs; auto device = backend.getContext().device; // From VKS // Use a separate command buffer for texture loading VkCommandBuffer copyCmd = device->createCommandBuffer(device->transferCommandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); // Create a host-visible staging buffer that contains the raw image data VkBuffer stagingBuffer; VkDeviceMemory stagingMemory; VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(); // This buffer is used as a transfer source for the buffer copy bufferCreateInfo.size = _transferData.buffer_size; bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer)); // Get memory requirements for the staging buffer (alignment, memory type bits) vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs); memAllocInfo.allocationSize = memReqs.size; // Get memory type index for a host visible buffer // VKTODO: remove host coherent and synchronize by a command for better performance. memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory)); VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0)); // Copy texture data into staging buffer uint8_t *data; VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data)); for (auto &mip : _transferData.mips) { for (auto &face : mip) { if (face.needsAddingAlpha) { // VKTODO: adding alpha and swapping channels at the same time Q_ASSERT(!face.needsBGRToRGB); size_t pixels = face.size/3; for (size_t i = 0; i < pixels; i++) { size_t sourcePos = face.offset + i * 3; size_t destPos = i * 4; data[destPos] = face.data->data()[face.offset + sourcePos]; data[destPos + 1] = face.data->data()[face.offset + sourcePos + 1]; data[destPos + 2] = face.data->data()[face.offset + sourcePos + 2]; data[destPos + 3] = 255; } } else if (face.needsBGRToRGB) { Q_ASSERT(face.size % 4 == 0); size_t pixels = face.size/4; for (size_t i = 0; i < pixels; i++) { size_t sourcePos = i * 4; size_t destPos = face.offset + i * 4; data[destPos] = face.data->data()[sourcePos + 2]; data[destPos + 1] = face.data->data()[sourcePos + 1]; data[destPos + 2] = face.data->data()[sourcePos]; data[destPos + 3] = face.data->data()[sourcePos + 3]; } } else { memcpy(data + face.offset, face.data->data(), face.data->size()); } } } vkUnmapMemory(device->logicalDevice, stagingMemory); std::vector<VkBufferImageCopy> bufferCopyRegions; for (size_t mipLevel = 0; mipLevel < _transferData.mips.size(); mipLevel++) { for (size_t face = 0; face < _transferData.mips[mipLevel].size(); face++) { VkBufferImageCopy bufferCopyRegion = {}; bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; bufferCopyRegion.imageSubresource.mipLevel = mipLevel; bufferCopyRegion.imageSubresource.baseArrayLayer = face; bufferCopyRegion.imageSubresource.layerCount = 1; bufferCopyRegion.imageExtent.width = _transferData.mips[mipLevel][face].width; bufferCopyRegion.imageExtent.height = _transferData.mips[mipLevel][face].height; bufferCopyRegion.imageExtent.depth = 1; bufferCopyRegion.bufferOffset = _transferData.mips[mipLevel][face].offset; bufferCopyRegions.push_back(bufferCopyRegion); } } // Create optimal tiled target image VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = evalTexelFormatInternal(_gpuObject.getTexelFormat(), backend.getContext()); imageCreateInfo.mipLevels = _transferData.mips.size(); imageCreateInfo.arrayLayers = 1; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageCreateInfo.extent = { _transferData.width, _transferData.height, 1 }; imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT; // Ensure that the TRANSFER_DST bit is set for staging if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT)) { imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; } VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0;Lines 1–500 of 1688. Load more
// // Created by Bradley Austin Davis on 2016/08/07 // Adapted for Vulkan in 2022-2025 by dr Karol Suprynowicz. // Copyright 2013-2018 High Fidelity, Inc. // Copyright 2023-2025 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // // Contains parts of Vulkan Samples, Copyright (c) 2018, Sascha Willems, distributed on MIT License. // #include "VKTexture.h" #include <QtCore/QThread> #include <NumericalConstants.h> #include <gl/GLHelpers.h> #include "VKBackend.h" #include "vk/Allocation.h" using namespace gpu; using namespace gpu::vk; /*const uint32_t VKTexture::CUBE_FACE_LAYOUT[VKTexture::TEXTURE_CUBE_NUM_FACES] = { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z };*/ const uint32_t VKTexture::WRAP_MODES[Sampler::NUM_WRAP_MODES] = { VK_SAMPLER_ADDRESS_MODE_REPEAT, // WRAP_REPEAT, VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT, // WRAP_MIRROR, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // WRAP_CLAMP, VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER, // WRAP_BORDER, VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE // WRAP_MIRROR_ONCE, }; const VKFilterMode VKTexture::FILTER_MODES[Sampler::NUM_FILTERS] = { { VK_FILTER_NEAREST, VK_FILTER_NEAREST }, //FILTER_MIN_MAG_POINT, { VK_FILTER_NEAREST, VK_FILTER_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR, { VK_FILTER_LINEAR, VK_FILTER_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT, { VK_FILTER_LINEAR, VK_FILTER_LINEAR }, //FILTER_MIN_MAG_LINEAR, // VKTODO: VkSamplerMipmapMode has some related setting too // VKTODO: This is not really accurate, but hopefully good enough for now { VK_FILTER_NEAREST, VK_FILTER_NEAREST }, //FILTER_MIN_MAG_MIP_POINT, { VK_FILTER_LINEAR, VK_FILTER_NEAREST }, //FILTER_MIN_MAG_POINT_MIP_LINEAR, { VK_FILTER_NEAREST, VK_FILTER_LINEAR }, //FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT, { VK_FILTER_LINEAR, VK_FILTER_LINEAR }, //FILTER_MIN_POINT_MAG_MIP_LINEAR, { VK_FILTER_NEAREST, VK_FILTER_NEAREST }, //FILTER_MIN_LINEAR_MAG_MIP_POINT, { VK_FILTER_LINEAR, VK_FILTER_NEAREST }, //FILTER_MIN_LINEAR_MAG_POINT_MIP_LINEAR, { VK_FILTER_NEAREST, VK_FILTER_LINEAR }, //FILTER_MIN_MAG_LINEAR_MIP_POINT, { VK_FILTER_LINEAR, VK_FILTER_LINEAR }, //FILTER_MIN_MAG_MIP_LINEAR, { VK_FILTER_LINEAR, VK_FILTER_LINEAR } //FILTER_ANISOTROPIC, }; static constexpr size_t MAX_PIXEL_BYTE_SIZE{ 4 }; static constexpr size_t MAX_TRANSFER_DIMENSION{ 1024 }; /*const uvec3 GLVariableAllocationSupport::MAX_TRANSFER_DIMENSIONS{ MAX_TRANSFER_DIMENSION, MAX_TRANSFER_DIMENSION, 1 }; const uvec3 GLVariableAllocationSupport::INITIAL_MIP_TRANSFER_DIMENSIONS{ 64, 64, 1 }; const size_t GLVariableAllocationSupport::MAX_TRANSFER_SIZE = MAX_TRANSFER_DIMENSION * MAX_TRANSFER_DIMENSION * MAX_PIXEL_BYTE_SIZE; const size_t GLVariableAllocationSupport::MAX_BUFFER_SIZE = MAX_TRANSFER_SIZE;*/ /*GLenum VKTexture::getVKTextureType(const Texture& texture) { switch (texture.getType()) { case Texture::TEX_2D: if (!texture.isArray()) { if (!texture.isMultisample()) { return GL_TEXTURE_2D; } else { return GL_TEXTURE_2D_MULTISAMPLE; } } else { if (!texture.isMultisample()) { return GL_TEXTURE_2D_ARRAY; } else { return GL_TEXTURE_2D_MULTISAMPLE_ARRAY; } } break; case Texture::TEX_CUBE: return GL_TEXTURE_CUBE_MAP; break; default: qFatal("Unsupported texture type"); } Q_UNREACHABLE(); return GL_TEXTURE_2D; } const std::vector<GLenum>& VKTexture::getFaceTargets(GLenum target) { static const std::vector<GLenum> cubeFaceTargets { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; static const std::vector<GLenum> face2DTargets { GL_TEXTURE_2D }; static const std::vector<GLenum> face2DMSTargets{ GL_TEXTURE_2D_MULTISAMPLE }; static const std::vector<GLenum> arrayFaceTargets{ GL_TEXTURE_2D_ARRAY }; switch (target) { case GL_TEXTURE_2D: return face2DTargets; case GL_TEXTURE_2D_MULTISAMPLE: return face2DMSTargets; case GL_TEXTURE_2D_ARRAY: return arrayFaceTargets; case GL_TEXTURE_CUBE_MAP: return cubeFaceTargets; default: Q_UNREACHABLE(); break; } Q_UNREACHABLE(); return face2DTargets; }*/ VKTexture::VKTexture(const std::weak_ptr<VKBackend>& backend, const Texture& texture, bool isTransferable) : VKObject(*backend.lock(), texture), _storageStamp(texture.getStamp()), _target(getVKTextureType(texture)), _transferable(isTransferable), _downsampleSource(backend), _vkTexelFormat(evalTexelFormatInternal(texture.getTexelFormat(), backend.lock()->getContext())) { Backend::setGPUObject(texture, this); } VKTexture::~VKTexture() { qDebug() << "VKTexture destroyed: " << this; auto backend = _backend.lock(); backend->getContext().recycler.textureDeleted(this); if (backend && _vkImage == VK_NULL_HANDLE) { // VKTODO // backend->releaseTexture(_id, 0); } } VkImageViewType VKTexture::getVKTextureType(const Texture& texture) { switch (texture.getType()) { case Texture::TEX_2D: if (!texture.isArray()) { if (!texture.isMultisample()) { return VK_IMAGE_VIEW_TYPE_2D; } else { // VKTODO: multisample? return VK_IMAGE_VIEW_TYPE_2D; } } else { if (!texture.isMultisample()) { return VK_IMAGE_VIEW_TYPE_2D_ARRAY; } else { // VKTODO multisample? return VK_IMAGE_VIEW_TYPE_2D_ARRAY; } } break; case Texture::TEX_CUBE: return VK_IMAGE_VIEW_TYPE_CUBE; break; default: qFatal("Unsupported texture type"); } Q_UNREACHABLE(); return VK_IMAGE_VIEW_TYPE_2D; } uint8_t VKTexture::getFaceCount(VkImageViewType target) { switch (target) { case VK_IMAGE_VIEW_TYPE_1D: case VK_IMAGE_VIEW_TYPE_2D: case VK_IMAGE_VIEW_TYPE_3D: case VK_IMAGE_VIEW_TYPE_1D_ARRAY: case VK_IMAGE_VIEW_TYPE_2D_ARRAY: return 1; case VK_IMAGE_VIEW_TYPE_CUBE: case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY: return 6; default: Q_UNREACHABLE(); break; } } Sampler VKTexture::getInvalidSampler() { static Sampler INVALID_SAMPLER; static std::once_flag once; std::call_once(once, [] { Sampler::Desc invalidDesc; invalidDesc._borderColor = vec4(-1.0f); INVALID_SAMPLER = Sampler(invalidDesc); }); return INVALID_SAMPLER; } // From VKS void VKAttachmentTexture::createTexture(VKBackend &backend) { VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo(); imageCI.imageType = VK_IMAGE_TYPE_2D; imageCI.format = evalTexelFormatInternal(_gpuObject.getTexelFormat(), backend.getContext()); imageCI.extent.width = _gpuObject.getWidth(); imageCI.extent.height = _gpuObject.getHeight(); imageCI.extent.depth = 1; imageCI.mipLevels = 1; imageCI.arrayLayers = _gpuObject.isArray() ? _gpuObject.getNumSlices() : 1; imageCI.samples = VK_SAMPLE_COUNT_1_BIT; imageCI.tiling = VK_IMAGE_TILING_OPTIMAL; if (_gpuObject.isDepthStencilRenderTarget()) { imageCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; } else { imageCI.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; } VmaAllocationCreateInfo allocationCI = {}; allocationCI.usage = VMA_MEMORY_USAGE_GPU_ONLY; vmaCreateImage(vks::Allocation::getAllocator(), &imageCI, &allocationCI, &_vkImage, &_vmaAllocation, nullptr); } VKAttachmentTexture::~VKAttachmentTexture() { auto backend = _backend.lock(); auto &recycler = backend->getContext().recycler; if (_vkImageView) { recycler.trashVkImageView(_vkImageView); } if (_vkSampler) { recycler.trashVkSampler(_vkSampler); } recycler.trashVkImage(_vkImage); recycler.trashVmaAllocation(_vmaAllocation); } VkDescriptorImageInfo VKAttachmentTexture::getDescriptorImageInfo() { if (_vkSampler == VK_NULL_HANDLE) { auto backend = _backend.lock(); auto device = backend->getContext().device; // Create sampler VkSamplerCreateInfo samplerCreateInfo = {}; samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerCreateInfo.magFilter = VK_FILTER_LINEAR; // VKTODO samplerCreateInfo.minFilter = VK_FILTER_LINEAR; // VKTODO samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerCreateInfo.mipLodBias = 0.0f; samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER; samplerCreateInfo.minLod = 0.0f; samplerCreateInfo.maxLod = 0.0f; samplerCreateInfo.maxAnisotropy = 1.0f; VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &_vkSampler)); } if (_vkImageView == VK_NULL_HANDLE) { auto backend = _backend.lock(); auto device = backend->getContext().device; // Create image view VkImageViewCreateInfo viewCreateInfo = {}; viewCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewCreateInfo.pNext = nullptr; viewCreateInfo.viewType = getVKTextureType(_gpuObject); viewCreateInfo.format = evalTexelFormatInternal(_gpuObject.getTexelFormat(), backend->getContext()); if (viewCreateInfo.format == VK_FORMAT_D24_UNORM_S8_UINT || viewCreateInfo.format == VK_FORMAT_D32_SFLOAT_S8_UINT || viewCreateInfo.format == VK_FORMAT_D16_UNORM_S8_UINT) { //viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, 0, 1, 0, 1 }; // VKTODO: both VK_IMAGE_ASPECT_DEPTH_BIT and VK_IMAGE_ASPECT_STENCIL_BIT cannot be set at the same time, but I'm not sure which one to set. viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, _gpuObject.getNumSlices() }; } else if (viewCreateInfo.format == VK_FORMAT_D32_SFLOAT) { viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, _gpuObject.getNumSlices() }; } else { viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, _gpuObject.getNumSlices() }; } //viewCreateInfo.subresourceRange.levelCount = _gpuObject.getNumSlices(); viewCreateInfo.image = _vkImage; VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &_vkImageView)); } VkDescriptorImageInfo result {}; result.sampler = _vkSampler; result.imageLayout = _vkImageLayout; // VKTODO: this needs to be updated on blits and other image writes result.imageView = _vkImageView; return result; }; void VKStrictResourceTexture::createTexture(VKBackend &backend) { VkImageCreateInfo imageCI = vks::initializers::imageCreateInfo(); imageCI.imageType = VK_IMAGE_TYPE_2D; imageCI.format = evalTexelFormatInternal(_gpuObject.getTexelFormat(), backend.getContext()); imageCI.extent.width = _gpuObject.getWidth(); imageCI.extent.height = _gpuObject.getHeight(); imageCI.extent.depth = 1; imageCI.arrayLayers = _gpuObject.isArray() ? _gpuObject.getNumSlices() : 1; imageCI.samples = VK_SAMPLE_COUNT_1_BIT; imageCI.tiling = VK_IMAGE_TILING_OPTIMAL; imageCI.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; if (_gpuObject.getType() == Texture::TEX_CUBE) { imageCI.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; imageCI.arrayLayers = 6; } // We need to lock mip data here so that it doesn't change or get deleted before transfer _transferData.mipLevels = _gpuObject.getNumMips(); _transferData.width = _gpuObject.getWidth(); _transferData.height = _gpuObject.getHeight(); _transferData.buffer_size = 0; for (uint16_t sourceMip = 0; sourceMip < _transferData.mipLevels; ++sourceMip) { if (!_gpuObject.isStoredMipFaceAvailable(sourceMip)) { continue; } _transferData.mips.emplace_back(); //VKTODO: error out if needed size_t face_count = 1; if (_gpuObject.getType() == Texture::TEX_CUBE) { Q_ASSERT(_gpuObject.getNumFaces() == 6); face_count = 6; } else { Q_ASSERT(_gpuObject.getNumFaces() == 1); } // VKTODO: move to a separate function and reuse. // Is conversion from RGB to RGBA needed? bool needsAddingAlpha = false; bool needsBGRToRGB = false; auto storedFormat = _gpuObject.getStoredMipFormat(); auto texelFormat = _gpuObject.getTexelFormat(); if ((storedFormat.getSemantic() == gpu::BGRA || storedFormat.getSemantic() == gpu::SBGRA) && !(texelFormat.getSemantic() == gpu::BGRA || texelFormat.getSemantic() == gpu::SBGRA)) { needsBGRToRGB = true; } auto storedVkFormat = evalTexelFormatInternal(_gpuObject.getStoredMipFormat(), backend.getContext()); auto texelVkFormat = evalTexelFormatInternal(_gpuObject.getTexelFormat(), backend.getContext()); if (storedFormat.getDimension() != texelFormat.getDimension()) { if (storedFormat.getDimension() == gpu::VEC3 && texelFormat.getDimension() == gpu::VEC4) { // It's best to make sure that this is not happening in unexpected cases and causing bugs Q_ASSERT((storedVkFormat == VK_FORMAT_R8G8B8_UNORM && texelVkFormat == VK_FORMAT_R8G8B8A8_UNORM) || (storedVkFormat == VK_FORMAT_R8G8B8_UNORM && texelVkFormat == VK_FORMAT_R8G8B8A8_SRGB)); needsAddingAlpha = true; } else { qDebug() << "Format mismatch, stored: " << storedVkFormat << " texel: " << texelVkFormat; Q_ASSERT(false); } } for (size_t face = 0; face < face_count; face++) { auto dim = _gpuObject.evalMipDimensions(sourceMip); auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face); // VKTODO: only one face for now auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); if (mipData) { TransferData::Mip mip{}; mip.offset = _transferData.buffer_size; mip.size = mipSize; mip.data = mipData; mip.width = dim.x; mip.height = dim.y; mip.needsAddingAlpha = needsAddingAlpha; mip.needsBGRToRGB = needsBGRToRGB; if (needsAddingAlpha) { Q_ASSERT(mipSize % 3 == 0); _transferData.buffer_size += mipSize / 3 * 4; } else { _transferData.buffer_size += mipSize; } _transferData.mips.back().push_back(mip); } else { qCDebug(gpu_vk_logging) << "Missing mipData level=" << sourceMip << " face=" << 0 /*(int)face*/ << " for texture " << _gpuObject.source().c_str(); } } } imageCI.mipLevels = _transferData.mips.size(); VmaAllocationCreateInfo allocationCI = {}; allocationCI.usage = VMA_MEMORY_USAGE_GPU_ONLY; qDebug() << "storedSize: " << _gpuObject.getStoredSize(); VK_CHECK_RESULT(vmaCreateImage(vks::Allocation::getAllocator(), &imageCI, &allocationCI, &_vkImage, &_vmaAllocation, nullptr)); } void VKStrictResourceTexture::transfer(VKBackend &backend) { VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); VkMemoryRequirements memReqs; auto device = backend.getContext().device; // From VKS // Use a separate command buffer for texture loading VkCommandBuffer copyCmd = device->createCommandBuffer(device->transferCommandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); // Create a host-visible staging buffer that contains the raw image data VkBuffer stagingBuffer; VkDeviceMemory stagingMemory; VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(); // This buffer is used as a transfer source for the buffer copy bufferCreateInfo.size = _transferData.buffer_size; bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VK_CHECK_RESULT(vkCreateBuffer(device->logicalDevice, &bufferCreateInfo, nullptr, &stagingBuffer)); // Get memory requirements for the staging buffer (alignment, memory type bits) vkGetBufferMemoryRequirements(device->logicalDevice, stagingBuffer, &memReqs); memAllocInfo.allocationSize = memReqs.size; // Get memory type index for a host visible buffer // VKTODO: remove host coherent and synchronize by a command for better performance. memAllocInfo.memoryTypeIndex = device->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memAllocInfo, nullptr, &stagingMemory)); VK_CHECK_RESULT(vkBindBufferMemory(device->logicalDevice, stagingBuffer, stagingMemory, 0)); // Copy texture data into staging buffer uint8_t *data; VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data)); for (auto &mip : _transferData.mips) { for (auto &face : mip) { if (face.needsAddingAlpha) { // VKTODO: adding alpha and swapping channels at the same time Q_ASSERT(!face.needsBGRToRGB); size_t pixels = face.size/3; for (size_t i = 0; i < pixels; i++) { size_t sourcePos = face.offset + i * 3; size_t destPos = i * 4; data[destPos] = face.data->data()[face.offset + sourcePos]; data[destPos + 1] = face.data->data()[face.offset + sourcePos + 1]; data[destPos + 2] = face.data->data()[face.offset + sourcePos + 2]; data[destPos + 3] = 255; } } else if (face.needsBGRToRGB) { Q_ASSERT(face.size % 4 == 0); size_t pixels = face.size/4; for (size_t i = 0; i < pixels; i++) { size_t sourcePos = i * 4; size_t destPos = face.offset + i * 4; data[destPos] = face.data->data()[sourcePos + 2]; data[destPos + 1] = face.data->data()[sourcePos + 1]; data[destPos + 2] = face.data->data()[sourcePos]; data[destPos + 3] = face.data->data()[sourcePos + 3]; } } else { memcpy(data + face.offset, face.data->data(), face.data->size()); } } } vkUnmapMemory(device->logicalDevice, stagingMemory); std::vector<VkBufferImageCopy> bufferCopyRegions; for (size_t mipLevel = 0; mipLevel < _transferData.mips.size(); mipLevel++) { for (size_t face = 0; face < _transferData.mips[mipLevel].size(); face++) { VkBufferImageCopy bufferCopyRegion = {}; bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; bufferCopyRegion.imageSubresource.mipLevel = mipLevel; bufferCopyRegion.imageSubresource.baseArrayLayer = face; bufferCopyRegion.imageSubresource.layerCount = 1; bufferCopyRegion.imageExtent.width = _transferData.mips[mipLevel][face].width; bufferCopyRegion.imageExtent.height = _transferData.mips[mipLevel][face].height; bufferCopyRegion.imageExtent.depth = 1; bufferCopyRegion.bufferOffset = _transferData.mips[mipLevel][face].offset; bufferCopyRegions.push_back(bufferCopyRegion); } } // Create optimal tiled target image VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = evalTexelFormatInternal(_gpuObject.getTexelFormat(), backend.getContext()); imageCreateInfo.mipLevels = _transferData.mips.size(); imageCreateInfo.arrayLayers = 1; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageCreateInfo.extent = { _transferData.width, _transferData.height, 1 }; imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT; // Ensure that the TRANSFER_DST bit is set for staging if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT)) { imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; } VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0;