// // 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 #include #include #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& VKTexture::getFaceTargets(GLenum target) { static const std::vector 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 face2DTargets { GL_TEXTURE_2D }; static const std::vector face2DMSTargets{ GL_TEXTURE_2D_MULTISAMPLE }; static const std::vector 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& 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 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; subresourceRange.levelCount = _transferData.mips.size(); if (_gpuObject.getType() == Texture::TEX_CUBE) { subresourceRange.layerCount = 6; }else{ subresourceRange.layerCount = 1; } // Image barrier for optimal image (target) // Optimal image will be used as destination for the copy vks::tools::setImageLayout( copyCmd, _vkImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange, device->queueFamilyIndices.transfer, device->queueFamilyIndices.transfer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); // Copy mip levels from staging buffer vkCmdCopyBufferToImage( copyCmd, stagingBuffer, _vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, bufferCopyRegions.size(), bufferCopyRegions.data() ); // Change texture image layout to shader read after all mip levels have been copied // The barrier command needs to be run on both transfer and graphics queues. Only then image layout changes. _vkImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; vks::tools::setImageLayout( copyCmd, _vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange, device->queueFamilyIndices.transfer, device->queueFamilyIndices.graphics, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); // VKTODO: should be VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT but validation layers complain so I'm not sure device->flushCommandBuffer(copyCmd, backend.getContext().transferQueue, device->transferCommandPool); // Clean up staging resources vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr); vkFreeMemory(device->logicalDevice, stagingMemory, nullptr); } void VKStrictResourceTexture::postTransfer(VKBackend &backend) { auto device = backend.getContext().device; // VKTODO: in the future this needs to be streamlined as a part of frame command buffer. VkCommandBuffer graphicsCmd = device->createCommandBuffer(device->graphicsCommandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = _transferData.mips.size(); if (_gpuObject.getType() == Texture::TEX_CUBE) { subresourceRange.layerCount = 6; }else{ subresourceRange.layerCount = 1; } vks::tools::setImageLayout( graphicsCmd, _vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange, device->queueFamilyIndices.transfer, device->queueFamilyIndices.graphics, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); device->flushCommandBuffer(graphicsCmd, backend.getContext().graphicsQueue, device->graphicsCommandPool); // Image is ready to use now. _vkImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; // 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 = static_cast(_transferData.mips.size());//1.0f; samplerCreateInfo.maxAnisotropy = 1.0f; VK_CHECK_RESULT(vkCreateSampler(device->logicalDevice, &samplerCreateInfo, nullptr, &_vkSampler)); // 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()); viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; viewCreateInfo.subresourceRange.levelCount = _transferData.mips.size(); if (_gpuObject.getType() == Texture::TEX_CUBE) { viewCreateInfo.subresourceRange.layerCount = 6; } else { viewCreateInfo.subresourceRange.layerCount = 1; } viewCreateInfo.image = _vkImage; VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &_vkImageView)); }; VKStrictResourceTexture::~VKStrictResourceTexture() { auto backend = _backend.lock(); auto &recycler = backend->getContext().recycler; recycler.trashVkImageView(_vkImageView); if (_vkSampler) { recycler.trashVkSampler(_vkSampler); } recycler.trashVkImage(_vkImage); recycler.trashVmaAllocation(_vmaAllocation); } void VKExternalTexture::createTexture(VKBackend &backend) { auto device = backend.getContext().device; 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.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageCI.samples = VK_SAMPLE_COUNT_1_BIT; if (device->properties.vendorID == VULKAN_VENDOR_ID_AMD) { // VKTODO: this lowers performance, so we should blit it into image with VK_IMAGE_TILING_OPTIMAL while generating mipmaps in the future. // VK_IMAGE_TILING_LINEAR is required on AMD GPUs on Linux. imageCI.tiling = VK_IMAGE_TILING_LINEAR; } else { // VK_IMAGE_TILING_OPTIMAL is required on non-AMD GPUs. 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; } /*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); } // 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()); auto texelVkFormat = evalTexelFormatInternal(_gpuObject.getTexelFormat()); 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); // VKTODO auto texelFormat = evalTexelFormatInternal(_gpuObject.getStoredMipFormat()); //return copyMipFaceLinesFromTexture(targetMip, face, dim, 0, texelFormat.internalFormat, texelFormat.format, texelFormat.type, mipSize, mipData->readData()); } else { qCDebug(gpu_vk_logging) << "Missing mipData level=" << sourceMip << " face=" << 0 << " for texture " << _gpuObject.source().c_str(); } } }*/ imageCI.mipLevels = 1; VkExternalMemoryImageCreateInfo externalMemoryImageCI {}; externalMemoryImageCI.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO; #ifdef WIN32 externalMemoryImageCI.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT; #else externalMemoryImageCI.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; #endif imageCI.pNext = &externalMemoryImageCI; VK_CHECK_RESULT(vkCreateImage(device->logicalDevice, &imageCI, nullptr, &_vkImage)); VkImageMemoryRequirementsInfo2 vkImageMemoryRequirementsInfo2 { }; vkImageMemoryRequirementsInfo2.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2; vkImageMemoryRequirementsInfo2.image = _vkImage; vkImageMemoryRequirementsInfo2.pNext = nullptr; VkMemoryRequirements2 memoryRequirements2{ }; memoryRequirements2.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2; vkGetImageMemoryRequirements2(device->logicalDevice, &vkImageMemoryRequirementsInfo2, &memoryRequirements2); _sharedMemorySize = memoryRequirements2.memoryRequirements.size; VkMemoryDedicatedAllocateInfo vkMemoryDedicatedAllocateInfo{ }; vkMemoryDedicatedAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO; vkMemoryDedicatedAllocateInfo.image = _vkImage; vkMemoryDedicatedAllocateInfo.buffer = VK_NULL_HANDLE; vkMemoryDedicatedAllocateInfo.pNext = nullptr; VkExportMemoryAllocateInfo exportMemoryAllocateInfo {}; exportMemoryAllocateInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO; #ifdef WIN32 exportMemoryAllocateInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_OPAQUE_WIN32_BIT; #else exportMemoryAllocateInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; #endif // This is required on AMD GPUs, but breaks UI on other GPUs. if (device->properties.vendorID == VULKAN_VENDOR_ID_AMD) { exportMemoryAllocateInfo.pNext = &vkMemoryDedicatedAllocateInfo; } VkMemoryAllocateInfo memoryAllocateInfo {}; memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; memoryAllocateInfo.allocationSize = memoryRequirements2.memoryRequirements.size; memoryAllocateInfo.memoryTypeIndex = device->getMemoryType(memoryRequirements2.memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); memoryAllocateInfo.pNext = &exportMemoryAllocateInfo; VK_CHECK_RESULT(vkAllocateMemory(device->logicalDevice, &memoryAllocateInfo, nullptr, &_sharedMemory)); #ifdef WIN32 VkMemoryGetWin32HandleInfoKHR memoryGetWin32HandleInfoKHR {}; memoryGetWin32HandleInfoKHR.sType = VK_STRUCTURE_TYPE_MEMORY_GET_WIN32_HANDLE_INFO_KHR; memoryGetWin32HandleInfoKHR.memory = _sharedMemory, memoryGetWin32HandleInfoKHR.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT_KHR; VK_CHECK_RESULT(vkGetMemoryWin32HandleKHR(device->logicalDevice, &memoryGetWin32HandleInfoKHR, &_sharedHandle)); #else VkMemoryGetFdInfoKHR memoryGetFdInfoKHR {}; memoryGetFdInfoKHR.sType = VK_STRUCTURE_TYPE_MEMORY_GET_FD_INFO_KHR; memoryGetFdInfoKHR.memory = _sharedMemory; memoryGetFdInfoKHR.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT_KHR; VK_CHECK_RESULT(vkGetMemoryFdKHR(device->logicalDevice, &memoryGetFdInfoKHR, &_sharedFd)); #endif VK_CHECK_RESULT(vkBindImageMemory(device->logicalDevice, _vkImage, _sharedMemory, 0)); VkCommandBuffer transferCmd = device->createCommandBuffer(device->graphicsCommandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = 1; // VKTODO if (_gpuObject.getType() == Texture::TEX_CUBE) { subresourceRange.layerCount = 6; }else{ subresourceRange.layerCount = 1; } vks::tools::setImageLayout( transferCmd, _vkImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange, device->queueFamilyIndices.graphics, device->queueFamilyIndices.graphics, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); VkClearColorValue color = { .float32 = {0.5, 0.0, 0.0} }; VkImageSubresourceRange imageSubresourceRange { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; vkCmdClearColorImage(transferCmd, _vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &color, 1, &imageSubresourceRange); _vkImageLayout = VK_IMAGE_LAYOUT_GENERAL; vks::tools::setImageLayout( transferCmd, _vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, subresourceRange, device->queueFamilyIndices.graphics, device->queueFamilyIndices.graphics, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT); device->flushCommandBuffer(transferCmd, backend.getContext().graphicsQueue, device->graphicsCommandPool); } void VKExternalTexture::initGL(gpu::vk::VKBackend& backend) { glCreateMemoryObjectsEXT(1, &_openGLMemoryObject); // VKTODO: clean these up later ::gl::checkGLError("GL to VK"); #ifdef WIN32 glImportMemoryWin32HandleEXT(_openGLMemoryObject, _sharedMemorySize, GL_HANDLE_TYPE_OPAQUE_WIN32_EXT, _sharedHandle); ::gl::checkGLError("GL to VK"); #else glImportMemoryFdEXT(_openGLMemoryObject, _sharedMemorySize, GL_HANDLE_TYPE_OPAQUE_FD_EXT, _sharedFd); _sharedFd = -1; // File descriptor can be used only once? ::gl::checkGLError("GL to VK"); #endif glCreateTextures(GL_TEXTURE_2D, 1, &_openGLId); ::gl::checkGLError("GL to VK"); Q_ASSERT(evalTexelFormatInternal(_gpuObject.getTexelFormat(), backend.getContext()) == VK_FORMAT_R8G8B8A8_UNORM); glTextureStorageMem2DEXT(_openGLId, 1, GL_RGBA8, _gpuObject.getWidth(), _gpuObject.getHeight(), _openGLMemoryObject, 0); ::gl::checkGLError("GL to VK"); // Can be enabled for testing //std::array clearColor {128,128,128,255}; //glClearTexImage(_openGLId, 0, GL_RGBA, GL_UNSIGNED_BYTE, clearColor.data()); ::gl::checkGLError("GL to VK"); } void VKExternalTexture::transferGL(VKBackend &backend) { glCopyImageSubData( _openGLSourceId, GL_TEXTURE_2D , 0, 0, 0, 0 , _openGLId, GL_TEXTURE_2D , 0, 0, 0, 0 , _gpuObject.getWidth(), _gpuObject.getHeight(), 1); ::gl::checkGLError("GL to VK"); // VKTODO: generate mipmaps } void VKExternalTexture::postTransfer(VKBackend &backend) { 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)); // 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()); viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; viewCreateInfo.subresourceRange.levelCount = 1; if (_gpuObject.getType() == Texture::TEX_CUBE) { viewCreateInfo.subresourceRange.layerCount = 6; } else { viewCreateInfo.subresourceRange.layerCount = 1; } viewCreateInfo.image = _vkImage; VK_CHECK_RESULT(vkCreateImageView(device->logicalDevice, &viewCreateInfo, nullptr, &_vkImageView)); }; void VKExternalTexture::releaseExternalTexture() { auto backend = _backend.lock(); if (backend) { auto recycler = _gpuObject.getExternalRecycler(); if (recycler) { backend->releaseExternalTexture(_openGLSourceId, recycler); } else { qWarning() << "No recycler available for texture " << _openGLSourceId << " possible leak"; } const_cast(_openGLSourceId) = 0; } Backend::textureExternalCount.decrement(); } void VKExternalTexture::setSource(GLuint source) { if (_openGLSourceId != source) { releaseExternalTexture(); _openGLSourceId = source; } } VKExternalTexture::~VKExternalTexture() { releaseExternalTexture(); auto backend = _backend.lock(); auto &recycler = backend->getContext().recycler; recycler.trashVkImageView(_vkImageView); if (_vkSampler) { recycler.trashVkSampler(_vkSampler); } recycler.trashVkImage(_vkImage); recycler.trashVkDeviceMemory(_sharedMemory); } VkDescriptorImageInfo VKExternalTexture::getDescriptorImageInfo() { VkDescriptorImageInfo result {}; result.sampler = _vkSampler; result.imageLayout = _vkImageLayout; result.imageView = _vkImageView; return result; } Size VKResourceTexture::copyMipFaceFromTexture(uint16_t sourceMip, uint16_t targetMip, uint8_t face, VkCommandBuffer ©Cmd) { if (!_gpuObject.isStoredMipFaceAvailable(sourceMip)) { return 0; } auto dim = _gpuObject.evalMipDimensions(sourceMip); auto mipData = _gpuObject.accessStoredMipFace(sourceMip, face); auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); if (mipData) { return copyMipFaceLinesFromTexture(targetMip, face, dim, 0, mipSize, mipData->readData()); } else { qCDebug(gpu_vk_logging) << "Missing mipData level=" << sourceMip << " face=" << (int)face << " for texture " << _gpuObject.source().c_str(); } return 0; } //VKTODO: /* GLVariableAllocationSupport::GLVariableAllocationSupport() { Backend::textureResourceCount.increment(); } GLVariableAllocationSupport::~GLVariableAllocationSupport() { Backend::textureResourceCount.decrement(); Backend::textureResourceGPUMemSize.update(_size, 0); Backend::textureResourcePopulatedGPUMemSize.update(_populatedSize, 0); } void GLVariableAllocationSupport::incrementPopulatedSize(Size delta) const { _populatedSize += delta; // Keep the 2 code paths to be able to debug if (_size < _populatedSize) { Backend::textureResourcePopulatedGPUMemSize.update(0, delta); } else { Backend::textureResourcePopulatedGPUMemSize.update(0, delta); } } void GLVariableAllocationSupport::decrementPopulatedSize(Size delta) const { _populatedSize -= delta; // Keep the 2 code paths to be able to debug if (_size < _populatedSize) { Backend::textureResourcePopulatedGPUMemSize.update(delta, 0); } else { Backend::textureResourcePopulatedGPUMemSize.update(delta, 0); } } void GLVariableAllocationSupport::sanityCheck() const { if (_populatedMip < _allocatedMip) { qCWarning(gpugllogging) << "Invalid mip levels"; } }*/ //VKTODO: TransferJob::TransferJob(const Texture& texture, uint16_t sourceMip, uint16_t targetMip, uint8_t face, uint32_t lines, uint32_t lineOffset) { auto transferDimensions = texture.evalMipDimensions(sourceMip); /*GLenum format; GLenum internalFormat; GLenum type; //evalTexelFormatInternal(_gpuObject.getTexelFormat(), backend.getContext()) GLTexelFormat texelFormat = evalTexelFormatInternal(texture.getTexelFormat(), texture.getStoredMipFormat()); format = texelFormat.format; internalFormat = texelFormat.internalFormat; type = texelFormat.type;*/ _transferSize = texture.getStoredMipFaceSize(sourceMip, face); // If we're copying a subsection of the mip, do additional calculations to find the size and offset of the segment if (lines != 0) { transferDimensions.y = lines; auto dimensions = texture.evalMipDimensions(sourceMip); auto bytesPerLine = (uint32_t)_transferSize / dimensions.y; _transferOffset = bytesPerLine * lineOffset; _transferSize = bytesPerLine * lines; } Backend::texturePendingGPUTransferMemSize.update(0, _transferSize); if (_transferSize > VKVariableAllocationSupport::MAX_TRANSFER_SIZE) { qCWarning(gpu_vk_logging) << "Transfer size of " << _transferSize << " exceeds theoretical maximum transfer size"; } // Buffering can invoke disk IO, so it should be off of the main and render threads _bufferingLambda = [=, this](const TexturePointer& texture) { auto mipStorage = texture->accessStoredMipFace(sourceMip, face); if (mipStorage) { _mipData = mipStorage->createView(_transferSize, _transferOffset); } else { qCWarning(gpu_vk_logging) << "Buffering failed because mip could not be retrieved from texture " << texture->source().c_str(); } }; _transferLambda = [=, this](const TexturePointer& texture) { if (_mipData) { auto vkTexture = Backend::getGPUObject(*texture); auto vkResourceTexture = dynamic_cast(vkTexture); vkResourceTexture->copyMipFaceLinesFromTexture(targetMip, face, transferDimensions, lineOffset, _mipData->size(), _mipData->readData()); _mipData.reset(); } else { qCWarning(gpu_vk_logging) << "Transfer failed because mip could not be retrieved from texture " << texture->source().c_str(); } }; } TransferJob::TransferJob(uint16_t sourceMip, const std::function& transferLambda) : _sourceMip(sourceMip), _bufferingRequired(false), _transferLambda([=](const TexturePointer&) { transferLambda(); }) {} TransferJob::~TransferJob() { Backend::texturePendingGPUTransferMemSize.update(_transferSize, 0); } VkDescriptorImageInfo VKStrictResourceTexture::getDescriptorImageInfo() { VkDescriptorImageInfo result {}; result.sampler = _vkSampler; result.imageLayout = _vkImageLayout; result.imageView = _vkImageView; return result; } VKVariableAllocationTexture::VKVariableAllocationTexture(const std::weak_ptr& backend, const Texture& texture) : VKTexture(backend, texture, true) { } VKVariableAllocationTexture::~VKVariableAllocationTexture() { } #if GPU_BINDLESS_TEXTURES const GL45Texture::Bindless& GL45VariableAllocationTexture::getBindless() const { // The parent call may re-initialize the _bindless member, so we need to call it first const auto& result = Parent::getBindless(); // Make sure the referenced structure has the correct minimum available mip value _bindless.minMip = _populatedMip - _allocatedMip; // Now return the result return result; } #endif Size VKResourceTexture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const uvec3& size, uint32_t yOffset, Size sourceSize, const void* sourcePointer) { Size amountCopied = 0; VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); VkMemoryRequirements memReqs; auto backend = _backend.lock(); 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 = sourceSize; 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)); // 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); } } // Copy texture data into staging buffer uint8_t *data; VK_CHECK_RESULT(vkMapMemory(device->logicalDevice, stagingMemory, 0, memReqs.size, 0, (void **)&data)); auto sourceBytes = static_cast(sourcePointer); if (needsAddingAlpha) { // VKTODO: adding alpha and swapping channels at the same time Q_ASSERT(!needsBGRToRGB); size_t pixels = sourceSize/3; for (size_t i = 0; i < pixels; i++) { size_t sourcePos = i * 3; size_t destPos = i * 4; data[destPos] = sourceBytes[sourcePos]; data[destPos + 1] = sourceBytes[sourcePos + 1]; data[destPos + 2] = sourceBytes[sourcePos + 2]; data[destPos + 3] = 255; } } else if (needsBGRToRGB) { Q_ASSERT(sourceSize % 4 == 0); size_t pixels = sourceSize/4; for (size_t i = 0; i < pixels; i++) { size_t sourcePos = i * 4; size_t destPos = i * 4; data[destPos] = sourceBytes[sourcePos + 2]; data[destPos + 1] = sourceBytes[sourcePos + 1]; data[destPos + 2] = sourceBytes[sourcePos]; data[destPos + 3] = sourceBytes[sourcePos + 3]; } } else { memcpy(data, sourceBytes, sourceSize); } vkUnmapMemory(device->logicalDevice, stagingMemory); std::vector bufferCopyRegions; VkBufferImageCopy bufferCopyRegion = {}; bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; bufferCopyRegion.imageSubresource.mipLevel = mip; bufferCopyRegion.imageSubresource.baseArrayLayer = face; bufferCopyRegion.imageSubresource.layerCount = 1; bufferCopyRegion.imageExtent.width = size.x; bufferCopyRegion.imageExtent.height = size.y; bufferCopyRegion.imageExtent.depth = 1; bufferCopyRegion.imageOffset = {0, static_cast(yOffset), 0}; bufferCopyRegion.bufferOffset = 0; bufferCopyRegions.push_back(bufferCopyRegion); VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = mip; subresourceRange.levelCount = 1; subresourceRange.baseArrayLayer = face; subresourceRange.layerCount = 1; /*if (_gpuObject.getType() == Texture::TEX_CUBE) { subresourceRange.layerCount = 6; }else{ subresourceRange.layerCount = 1; }*/ // Image barrier for optimal image (target) // Optimal image will be used as destination for the copy vks::tools::setImageLayout( copyCmd, _vkImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange, device->queueFamilyIndices.transfer, device->queueFamilyIndices.transfer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); // Copy mip levels from staging buffer vkCmdCopyBufferToImage( copyCmd, stagingBuffer, _vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, bufferCopyRegions.size(), bufferCopyRegions.data() ); // Change texture image layout to shader read after all mip levels have been copied // The barrier command needs to be run on both transfer and graphics queues. Only then image layout changes. _vkImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; vks::tools::setImageLayout( copyCmd, _vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange, device->queueFamilyIndices.transfer, device->queueFamilyIndices.graphics, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); device->flushCommandBuffer(copyCmd, backend->getContext().transferQueue, device->transferCommandPool); // Clean up staging resources vkDestroyBuffer(device->logicalDevice, stagingBuffer, nullptr); vkFreeMemory(device->logicalDevice, stagingMemory, nullptr); return amountCopied; } void copyTexGPUMem(const gpu::Texture& texture, VkImageViewType texTarget, VkImage srcImage, VkImage dstImage, uint16_t numMips, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips, VkCommandBuffer ©Cmd) { std::vector imagesCopyRegions; // VKTODO: this could be a single call instead for (uint16_t mipLevel = populatedMips; mipLevel < numMips; mipLevel++) { auto mipDimensions = texture.evalMipDimensions(mipLevel); uint16_t targetMip = mipLevel - destMipOffset; uint16_t sourceMip = mipLevel - srcMipOffset; auto faces = VKTexture::getFaceCount(texTarget); for (uint8_t face = 0; face < faces; ++face) { VkImageCopy imageCopy = {}; imageCopy.srcOffset = VkOffset3D(0, 0, 0); imageCopy.dstOffset = VkOffset3D(0, 0, 0); imageCopy.srcSubresource = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = sourceMip, .baseArrayLayer = face, .layerCount = 1}; imageCopy.dstSubresource = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = targetMip, .baseArrayLayer = face, .layerCount = 1}; imageCopy.extent = { mipDimensions.x, mipDimensions.y, 1 }; imagesCopyRegions.push_back(imageCopy); } } vkCmdCopyImage(copyCmd, srcImage, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, imagesCopyRegions.size(), imagesCopyRegions.data()); } void VKVariableAllocationTexture::copyTextureMipsInGPUMem(VkImage srcImage, VkImage dstImage, uint16_t srcMipOffset, uint16_t destMipOffset, uint16_t populatedMips, VkCommandBuffer ©Cmd) { uint16_t numMips = _gpuObject.getNumMips(); copyTexGPUMem(_gpuObject, _target, srcImage, dstImage, numMips, srcMipOffset, destMipOffset, populatedMips, copyCmd); } void VKVariableAllocationTexture::allocateNewImage(uint16_t targetAllocatedMip, VkCommandBuffer ©Cmd) { auto device = _backend.lock()->getContext().device; _allocatedMip = targetAllocatedMip; VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = evalTexelFormatInternal(_gpuObject.getTexelFormat(), _backend.lock()->getContext()); const auto dimensions = _gpuObject.evalMipDimensions(_allocatedMip); const auto totalMips = _gpuObject.getNumMips(); const auto mips = totalMips - _allocatedMip; imageCreateInfo.mipLevels = mips; imageCreateInfo.extent = { dimensions.x, dimensions.y, 1 }; imageCreateInfo.arrayLayers = _gpuObject.isArray() ? _gpuObject.getNumSlices() : 1; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; if (_gpuObject.getType() == Texture::TEX_CUBE) { imageCreateInfo.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; imageCreateInfo.arrayLayers = 6; } VmaAllocationCreateInfo allocationCI = {}; allocationCI.usage = VMA_MEMORY_USAGE_GPU_ONLY; VK_CHECK_RESULT(vmaCreateImage(vks::Allocation::getAllocator(), &imageCreateInfo, &allocationCI, &_vkImage, &_vmaAllocation, nullptr)); VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = mips; if (_gpuObject.getType() == Texture::TEX_CUBE) { subresourceRange.layerCount = 6; }else{ subresourceRange.layerCount = 1; } vks::tools::setImageLayout( copyCmd, _vkImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange, device->queueFamilyIndices.transfer, device->queueFamilyIndices.transfer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); // I think this is incorrect, it takes into account all mips and not only allocated ones /*for (uint16_t mip = _allocatedMip; mip < mipLevels; ++mip) { _size += _gpuObject.evalMipSize(mip); }*/ // VKTODO: check all places where these occur on OpenGL and add them on Vulkan //Backend::textureResourceGPUMemSize.update(0, _size); } // Managed size resource textures VKResourceTexture::VKResourceTexture(const std::weak_ptr& backend, const Texture& texture) : VKVariableAllocationTexture(backend, texture) { auto backendPointer = backend.lock(); auto mipLevels = texture.getNumMips(); _allocatedMip = mipLevels; _maxAllocatedMip = _populatedMip = mipLevels; _minAllocatedMip = texture.minAvailableMipLevel(); for (uint16_t mip = _minAllocatedMip; mip < mipLevels; ++mip) { if (glm::all(glm::lessThanEqual(texture.evalMipDimensions(mip), INITIAL_MIP_TRANSFER_DIMENSIONS))) { _maxAllocatedMip = _populatedMip = mip; break; } } auto targetMip = _populatedMip - std::min(_populatedMip, 2); uint16_t allocatedMip = std::max(_minAllocatedMip, targetMip); // Create new texture. auto device = backendPointer->getContext().device; VkCommandBuffer copyCmd = device->createCommandBuffer(device->transferCommandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); allocateNewImage(allocatedMip, copyCmd); copyMipsFromTexture(copyCmd); // Change texture image layout to shader read after all mip levels have been copied // The barrier command needs to be run on both transfer and graphics queues. Only then image layout changes. const auto totalMips = _gpuObject.getNumMips(); const auto mips = totalMips - _allocatedMip; VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = mips; if (_gpuObject.getType() == Texture::TEX_CUBE) { subresourceRange.layerCount = 6; }else{ subresourceRange.layerCount = 1; } _vkImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; vks::tools::setImageLayout( copyCmd, _vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange, device->queueFamilyIndices.transfer, device->queueFamilyIndices.graphics, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT); // VKTODO: should be VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT but validation layers complain so I'm not sure device->flushCommandBuffer(copyCmd, backendPointer->getContext().transferQueue, device->transferCommandPool); VKResourceTexture::postTransfer(*backendPointer); VKResourceTexture::syncSampler(texture.getSampler()); } Size VKResourceTexture::copyMipsFromTexture(VkCommandBuffer ©Cmd) { qDebug(gpu_vk_logging) << "VKResourceTexture::copyMipsFromTexture: _allocatedMip: " << _allocatedMip << " _populatedMip: " << _populatedMip; auto mipLevels = _gpuObject.getNumMips(); size_t maxFace = getFaceCount(_target); Size amount = 0; for (uint16_t sourceMip = _populatedMip; sourceMip < mipLevels; ++sourceMip) { uint16_t targetMip = sourceMip - _allocatedMip; for (uint8_t face = 0; face < maxFace; ++face) { amount += copyMipFaceFromTexture(sourceMip, targetMip, face, copyCmd); } } incrementPopulatedSize(amount); return amount; } void VKResourceTexture::syncSampler(const Sampler& sampler) { const auto totalMips = _gpuObject.getNumMips(); const auto mips = totalMips - _allocatedMip; auto backend = _backend.lock(); //Parent::syncSampler(sampler); //glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, _populatedMip - _allocatedMip); if (_cachedSampler == sampler && _vkSampler) { return; } if (_vkSampler) { backend->getContext().recycler.trashVkSampler(_vkSampler); _vkSampler = VK_NULL_HANDLE; } // 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 = static_cast(mips); samplerCreateInfo.maxAnisotropy = 1.0f; VK_CHECK_RESULT(vkCreateSampler(backend->getContext().device->logicalDevice, &samplerCreateInfo, nullptr, &_vkSampler)); updateImageView(); } void VKResourceTexture::updateImageView() { const auto totalMips = _gpuObject.getNumMips(); const auto mips = totalMips - _allocatedMip; auto backend = _backend.lock(); if (_vkImageView) { backend->getContext().recycler.trashVkImageView(_vkImageView); } // 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()); viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; viewCreateInfo.subresourceRange.levelCount = mips; if (_gpuObject.getType() == Texture::TEX_CUBE) { viewCreateInfo.subresourceRange.layerCount = 6; } else { viewCreateInfo.subresourceRange.layerCount = 1; } viewCreateInfo.image = _vkImage; VK_CHECK_RESULT(vkCreateImageView(backend->getContext().device->logicalDevice, &viewCreateInfo, nullptr, &_vkImageView)); } size_t VKResourceTexture::promote() { PROFILE_RANGE(gpu_vk, __FUNCTION__); Q_ASSERT(_allocatedMip > 0); const auto totalMips = _gpuObject.getNumMips(); const auto mips = totalMips - _allocatedMip; auto backend = _backend.lock(); uint16_t targetAllocatedMip = _allocatedMip - std::min(_allocatedMip, 2); targetAllocatedMip = std::max(_minAllocatedMip, targetAllocatedMip); VkImage oldImage = _vkImage; VmaAllocation oldAllocation = _vmaAllocation; auto oldSize = _size; uint16_t oldAllocatedMip = _allocatedMip; // Create new texture. auto device = backend->getContext().device; VkCommandBuffer copyCmd = device->createCommandBuffer(device->transferCommandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); allocateNewImage(targetAllocatedMip, copyCmd); qDebug(gpu_vk_logging) << "VKResourceTexture::promote: oldAllocatedMip: " << oldAllocatedMip << " new _allocatedMip: " << _allocatedMip; // Copy pre-existing mips. copyTextureMipsInGPUMem(oldImage, _vkImage, oldAllocatedMip, _allocatedMip, _populatedMip, copyCmd); VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = mips; if (_gpuObject.getType() == Texture::TEX_CUBE) { subresourceRange.layerCount = 6; }else{ subresourceRange.layerCount = 1; } _vkImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; vks::tools::setImageLayout( copyCmd, _vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange, device->queueFamilyIndices.transfer, device->queueFamilyIndices.graphics, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); // Execute transfer. device->flushCommandBuffer(copyCmd, backend->getContext().transferQueue, device->transferCommandPool); // Destroy the old texture. backend->getContext().recycler.trashVkImage(oldImage); backend->getContext().recycler.trashVmaAllocation(oldAllocation); // Update sampler _cachedSampler = getInvalidSampler(); syncSampler(_gpuObject.getSampler()); // update the memory usage Backend::textureResourceGPUMemSize.update(oldSize, 0); // no change to Backend::textureResourcePopulatedGPUMemSize return (_size - oldSize); } size_t VKResourceTexture::demote() { PROFILE_RANGE(gpu_vk, __FUNCTION__); Q_ASSERT(_allocatedMip < _maxAllocatedMip); const auto totalMips = _gpuObject.getNumMips(); const auto mips = totalMips - _allocatedMip; auto backend = _backend.lock(); VkImage oldImage = _vkImage; VmaAllocation oldAllocation = _vmaAllocation; auto oldSize = _size; auto oldPopulatedMip = _populatedMip; uint16_t oldAllocatedMip = _allocatedMip; qDebug(gpu_vk_logging) << "VKResourceTexture::promote: oldAllocatedMip: " << oldAllocatedMip << " new _allocatedMip: " << _allocatedMip; // Create new texture. auto device = backend->getContext().device; VkCommandBuffer copyCmd = device->createCommandBuffer(device->transferCommandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); allocateNewImage(_allocatedMip + 1, copyCmd); _populatedMip = std::max(_populatedMip, _allocatedMip); // Copy pre-existing mips. copyTextureMipsInGPUMem(oldImage, _vkImage, oldAllocatedMip, _allocatedMip, _populatedMip, copyCmd); VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = mips; if (_gpuObject.getType() == Texture::TEX_CUBE) { subresourceRange.layerCount = 6; }else{ subresourceRange.layerCount = 1; } _vkImageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; vks::tools::setImageLayout( copyCmd, _vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange, device->queueFamilyIndices.transfer, device->queueFamilyIndices.graphics, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); // Execute transfer. device->flushCommandBuffer(copyCmd, backend->getContext().transferQueue, device->transferCommandPool); // Destroy the old texture. backend->getContext().recycler.trashVkImage(oldImage); backend->getContext().recycler.trashVmaAllocation(oldAllocation); // Update sampler. _cachedSampler = getInvalidSampler(); syncSampler(_gpuObject.getSampler()); // Update the memory usage. Backend::textureResourceGPUMemSize.update(oldSize, 0); // Demoting unpopulate the memory delta if (oldPopulatedMip != _populatedMip) { auto numPopulatedDemoted = _populatedMip - oldPopulatedMip; Size amountUnpopulated = 0; for (int i = 0; i < numPopulatedDemoted; i++) { amountUnpopulated += _gpuObject.evalMipSize(oldPopulatedMip + i); } decrementPopulatedSize(amountUnpopulated); } return (oldSize - _size); } void VKResourceTexture::populateTransferQueue(TransferQueue& pendingTransfers) { PROFILE_RANGE(gpu_vk, __FUNCTION__); sanityCheck(); if (_populatedMip <= _allocatedMip) { return; } const uint8_t maxFace = VKTexture::getFaceCount(_target); uint16_t sourceMip = _populatedMip; do { --sourceMip; auto targetMip = sourceMip - _allocatedMip; auto mipDimensions = _gpuObject.evalMipDimensions(sourceMip); for (uint8_t face = 0; face < maxFace; ++face) { if (!_gpuObject.isStoredMipFaceAvailable(sourceMip, face)) { continue; } // If the mip is less than the max transfer size, then just do it in one transfer if (glm::all(glm::lessThanEqual(mipDimensions, MAX_TRANSFER_DIMENSIONS))) { // Can the mip be transferred in one go pendingTransfers.emplace(new TransferJob(_gpuObject, sourceMip, targetMip, face)); continue; } // break down the transfers into chunks so that no single transfer is // consuming more than X bandwidth // For compressed format, regions must be a multiple of the 4x4 tiles, so enforce 4 lines as the minimum block auto mipSize = _gpuObject.getStoredMipFaceSize(sourceMip, face); const auto lines = mipDimensions.y; const uint32_t BLOCK_NUM_LINES{ 4 }; const auto numBlocks = (lines + (BLOCK_NUM_LINES - 1)) / BLOCK_NUM_LINES; auto bytesPerBlock = mipSize / numBlocks; Q_ASSERT(0 == (mipSize % lines)); uint32_t linesPerTransfer = BLOCK_NUM_LINES * (uint32_t)(MAX_TRANSFER_SIZE / bytesPerBlock); uint32_t lineOffset = 0; while (lineOffset < lines) { uint32_t linesToCopy = std::min(lines - lineOffset, linesPerTransfer); pendingTransfers.emplace(new TransferJob(_gpuObject, sourceMip, targetMip, face, linesToCopy, lineOffset)); lineOffset += linesToCopy; } } // queue up the sampler and populated mip change for after the transfer has completed pendingTransfers.emplace(new TransferJob(sourceMip, [=, this] { _populatedMip = sourceMip; incrementPopulatedSize(_gpuObject.evalMipSize(sourceMip)); sanityCheck(); _cachedSampler = getInvalidSampler(); syncSampler(_gpuObject.getSampler()); })); } while (sourceMip != _allocatedMip); } void VKResourceTexture::postTransfer(VKBackend &backend) { const auto totalMips = _gpuObject.getNumMips(); const auto mips = totalMips - _allocatedMip; auto device = backend.getContext().device; // VKTODO: in the future this needs to be streamlined as a part of frame command buffer. VkCommandBuffer graphicsCmd = device->createCommandBuffer(device->graphicsCommandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); VkImageSubresourceRange subresourceRange = {}; subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; subresourceRange.baseMipLevel = 0; subresourceRange.levelCount = mips; if (_gpuObject.getType() == Texture::TEX_CUBE) { subresourceRange.layerCount = 6; }else{ subresourceRange.layerCount = 1; } vks::tools::setImageLayout( graphicsCmd, _vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, subresourceRange, device->queueFamilyIndices.transfer, device->queueFamilyIndices.graphics, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); device->flushCommandBuffer(graphicsCmd, backend.getContext().graphicsQueue, device->graphicsCommandPool); // Image is ready to use now. _vkImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; }; VkDescriptorImageInfo VKResourceTexture::getDescriptorImageInfo() { Q_ASSERT(_vkSampler && _vkImageView); VkDescriptorImageInfo result {}; result.sampler = _vkSampler; result.imageLayout = _vkImageLayout; result.imageView = _vkImageView; return result; }