1688 lines · 73.3 KB · C++
RawDownload
////  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.