// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp // Copyright (c) 2025-2026 MobileGL-Dev // Licensed under the GNU Lesser General Public License v3.0: // https://www.gnu.org/licenses/gpl-3.0.txt // https://www.gnu.org/licenses/lgpl-3.0.txt // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header #include "VkTextureManager.h" #include "MG_State/GLState/Core.h" #include "MG_Util/Converters/MGToStr/TextureEnumConverter.h" #include "MG_Util/Converters/MGToVk/TextureEnumConverter.h" #include #include #include #include #include namespace MobileGL::MG_Backend::DirectVulkan { // Compute shaders may legally sample framebuffer-attached textures (the GL feedback-loop rule // only covers rendering commands; e.g. Flywheel's Hi-Z depth pyramid downsample samples the // depth attachment of the bound draw framebuffer), so sampled-read barriers must cover the // compute stage in addition to the graphics stages. static constexpr VkPipelineStageFlags kSampledReadStages = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; static Uint32 ComputeFullMipLevelCount(const IntVec3& baseTexelSize) { Int maxDimension = std::max(baseTexelSize.x(), std::max(baseTexelSize.y(), std::max(baseTexelSize.z(), 1))); Uint32 mipLevelCount = 1; while (maxDimension > 1) { maxDimension = std::max(maxDimension / 2, 1); ++mipLevelCount; } return mipLevelCount; } struct TextureFormatInfo { VkFormat format = VK_FORMAT_UNDEFINED; Bool expandRgbToRgba = false; Uint32 componentByteCount = 0; Array alphaBytes = {0, 0, 0, 0}; }; struct TextureShapeInfo { VkImageType imageType = VK_IMAGE_TYPE_2D; VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D; VkImageCreateFlags imageFlags = 0; Uint32 depth = 1; Uint32 arrayLayers = 1; }; static Bool IsR11G11B10FFallbackEnabled() { return MG_Config::Features.VulkanR11G11B10FFallback; } static Bool IsMultisampleTextureUploadTarget(TextureUploadTarget target) { return target == TextureUploadTarget::Texture2DMultisample || target == TextureUploadTarget::ProxyTexture2DMultisample || target == TextureUploadTarget::Texture2DMultisampleArray || target == TextureUploadTarget::ProxyTexture2DMultisampleArray; } static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) { switch (requestedSamples) { case 1: outSampleCount = VK_SAMPLE_COUNT_1_BIT; return true; case 2: outSampleCount = VK_SAMPLE_COUNT_2_BIT; return true; case 4: outSampleCount = VK_SAMPLE_COUNT_4_BIT; return true; case 8: outSampleCount = VK_SAMPLE_COUNT_8_BIT; return true; case 16: outSampleCount = VK_SAMPLE_COUNT_16_BIT; return true; case 32: outSampleCount = VK_SAMPLE_COUNT_32_BIT; return true; case 64: outSampleCount = VK_SAMPLE_COUNT_64_BIT; return true; default: return false; } } static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) { return target >= TextureUploadTarget::CubeMapPositiveX && target <= TextureUploadTarget::CubeMapNegativeZ; } static Uint32 ResolveUploadArrayLayer(TextureUploadTarget target) { if (!IsCubeMapFaceUploadTarget(target)) { return 0; } return static_cast(target) - static_cast(TextureUploadTarget::CubeMapPositiveX); } static Bool IsValidSampledImageLayout(VkImageLayout layout) { switch (layout) { case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: case VK_IMAGE_LAYOUT_GENERAL: case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: return true; default: return false; } } static VkImageLayout ResolveSampledReadOnlyLayout(VkImageAspectFlags aspectMask) { return (aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0 ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } static void GetImageTransitionSourceState(VkImageLayout oldLayout, VkPipelineStageFlags& outSrcStageMask, VkAccessFlags& outSrcAccessMask) { switch (oldLayout) { case VK_IMAGE_LAYOUT_UNDEFINED: outSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; outSrcAccessMask = 0; return; case VK_IMAGE_LAYOUT_GENERAL: outSrcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; outSrcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; return; case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: outSrcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; outSrcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; return; case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: outSrcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; outSrcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; return; case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: outSrcStageMask = kSampledReadStages; outSrcAccessMask = VK_ACCESS_SHADER_READ_BIT; return; case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: outSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; outSrcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; return; case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: outSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; outSrcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; return; default: MOBILEGL_ASSERT(false, "GetImageTransitionSourceState: unsupported layout=%d", static_cast(oldLayout)); outSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; outSrcAccessMask = 0; return; } } VkTextureManager::TextureIdentity VkTextureManager::MakeTextureIdentity( MG_State::GLState::ITextureObject* texture) { return TextureIdentity{ .texture = texture, .lifetimeId = texture ? texture->GetLifetimeId() : 0, }; } static void GetImageTransitionDestinationState(VkImageLayout newLayout, VkPipelineStageFlags& outDstStageMask, VkAccessFlags& outDstAccessMask) { switch (newLayout) { case VK_IMAGE_LAYOUT_GENERAL: outDstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; outDstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; return; case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: outDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; outDstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; return; case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: outDstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; outDstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; return; case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: outDstStageMask = kSampledReadStages; outDstAccessMask = VK_ACCESS_SHADER_READ_BIT; return; case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: outDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; outDstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; return; case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: outDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; outDstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; return; default: MOBILEGL_ASSERT(false, "GetImageTransitionDestinationState: unsupported layout=%d", static_cast(newLayout)); outDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; outDstAccessMask = 0; return; } } static Bool PreserveTextureContentsOnRecreate(VkDevice device, VkCommandPool commandPool, VkQueue graphicsQueue, const VkTextureManager::TextureResource& oldResource, VkTextureManager::TextureResource& newResource) { MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: device is null"); MOBILEGL_ASSERT(commandPool != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: commandPool is null"); MOBILEGL_ASSERT(graphicsQueue != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: graphicsQueue is null"); MOBILEGL_ASSERT(oldResource.image != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: old image is null"); MOBILEGL_ASSERT(newResource.image != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: new image is null"); const Uint32 preservedMipLevels = std::min(oldResource.mipLevels, newResource.mipLevels); if (preservedMipLevels == 0 || oldResource.layout == VK_IMAGE_LAYOUT_UNDEFINED) { return true; } VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = 1; VkCommandBuffer commandBuffer = VK_NULL_HANDLE; VK_VERIFY(vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer), "vkAllocateCommandBuffers(texture preserve)"); VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; VK_VERIFY(vkBeginCommandBuffer(commandBuffer, &beginInfo), "vkBeginCommandBuffer(texture preserve)"); Bool ok = VkTextureManager::TransitionImageLayout( commandBuffer, newResource.image, newResource.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_WRITE_BIT, newResource.aspect, 0, newResource.mipLevels, newResource.arrayLayers); MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to prepare destination image"); VkImageLayout srcTrackedLayout = oldResource.layout; VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkAccessFlags srcAccessMask = 0; GetImageTransitionSourceState(srcTrackedLayout, srcStageMask, srcAccessMask); ok = VkTextureManager::TransitionImageLayout( commandBuffer, oldResource.image, srcTrackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, oldResource.aspect, 0, preservedMipLevels, oldResource.arrayLayers); MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to prepare source image"); Vector copyRegions; copyRegions.reserve(preservedMipLevels); for (Uint32 level = 0; level < preservedMipLevels; ++level) { VkImageCopy copy{}; copy.srcSubresource.aspectMask = oldResource.aspect; copy.srcSubresource.mipLevel = level; copy.srcSubresource.baseArrayLayer = 0; copy.srcSubresource.layerCount = oldResource.arrayLayers; copy.dstSubresource.aspectMask = newResource.aspect; copy.dstSubresource.mipLevel = level; copy.dstSubresource.baseArrayLayer = 0; copy.dstSubresource.layerCount = newResource.arrayLayers; copy.extent.width = std::max(oldResource.extent.width >> level, 1u); copy.extent.height = std::max(oldResource.extent.height >> level, 1u); copy.extent.depth = std::max(oldResource.depth >> level, 1u); copyRegions.push_back(copy); } vkCmdCopyImage(commandBuffer, oldResource.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, newResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(copyRegions.size()), copyRegions.data()); VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkAccessFlags dstAccessMask = 0; GetImageTransitionDestinationState(oldResource.layout, dstStageMask, dstAccessMask); ok = VkTextureManager::TransitionImageLayout( commandBuffer, newResource.image, newResource.layout, oldResource.layout, VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessMask, newResource.aspect, 0, newResource.mipLevels, newResource.arrayLayers); MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to restore destination layout"); VK_VERIFY(vkEndCommandBuffer(commandBuffer), "vkEndCommandBuffer(texture preserve)"); VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; VkFenceCreateInfo fenceInfo{}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; VkFence fence = VK_NULL_HANDLE; VK_VERIFY(vkCreateFence(device, &fenceInfo, nullptr, &fence), "vkCreateFence(texture preserve)"); VK_VERIFY(vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence), "vkQueueSubmit(texture preserve)"); VK_VERIFY(vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX), "vkWaitForFences(texture preserve)"); vkDestroyFence(device, fence, nullptr); vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); return true; } static TextureFormatInfo ResolveTextureFormatInfo(TextureInternalFormat format) { switch (format) { case TextureInternalFormat::RGB: case TextureInternalFormat::RGB8: return {VK_FORMAT_R8G8B8A8_UNORM, true, 1, {0xFF, 0x00, 0x00, 0x00}}; case TextureInternalFormat::SRGB8: return {VK_FORMAT_R8G8B8A8_SRGB, true, 1, {0xFF, 0x00, 0x00, 0x00}}; case TextureInternalFormat::RGB8Snorm: return {VK_FORMAT_R8G8B8A8_SNORM, true, 1, {0x7F, 0x00, 0x00, 0x00}}; case TextureInternalFormat::RGB16: return {VK_FORMAT_R16G16B16A16_UNORM, true, 2, {0xFF, 0xFF, 0x00, 0x00}}; case TextureInternalFormat::RGB16Snorm: return {VK_FORMAT_R16G16B16A16_SNORM, true, 2, {0xFF, 0x7F, 0x00, 0x00}}; case TextureInternalFormat::RGB16F: return {VK_FORMAT_R16G16B16A16_SFLOAT, true, 2, {0x00, 0x3C, 0x00, 0x00}}; case TextureInternalFormat::R11FG11FB10F: if (IsR11G11B10FFallbackEnabled()) { return {VK_FORMAT_R16G16B16A16_SFLOAT, true, 2, {0x00, 0x3C, 0x00, 0x00}}; } return {MG_Util::ConvertTextureInternalFormatToVkEnum(format), false, 0, {0, 0, 0, 0}}; case TextureInternalFormat::RGB32F: return {VK_FORMAT_R32G32B32A32_SFLOAT, true, 4, {0x00, 0x00, 0x80, 0x3F}}; case TextureInternalFormat::RGB8I: return {VK_FORMAT_R8G8B8A8_SINT, true, 1, {0x01, 0x00, 0x00, 0x00}}; case TextureInternalFormat::RGB8UI: return {VK_FORMAT_R8G8B8A8_UINT, true, 1, {0x01, 0x00, 0x00, 0x00}}; case TextureInternalFormat::RGB16I: return {VK_FORMAT_R16G16B16A16_SINT, true, 2, {0x01, 0x00, 0x00, 0x00}}; case TextureInternalFormat::RGB16UI: return {VK_FORMAT_R16G16B16A16_UINT, true, 2, {0x01, 0x00, 0x00, 0x00}}; case TextureInternalFormat::RGB32I: return {VK_FORMAT_R32G32B32A32_SINT, true, 4, {0x01, 0x00, 0x00, 0x00}}; case TextureInternalFormat::RGB32UI: return {VK_FORMAT_R32G32B32A32_UINT, true, 4, {0x01, 0x00, 0x00, 0x00}}; default: return {MG_Util::ConvertTextureInternalFormatToVkEnum(format), false, 0, {0, 0, 0, 0}}; } } static Bool ExpandRgbSourceToRgba(const void* source, SizeT sourceByteSize, const IntVec3& texelSize, const TextureFormatInfo& formatInfo, Vector& outExpandedData) { MOBILEGL_ASSERT(source != nullptr, "ExpandRgbSourceToRgba: source is null"); MOBILEGL_ASSERT(formatInfo.expandRgbToRgba, "ExpandRgbSourceToRgba: format does not require RGB expansion"); MOBILEGL_ASSERT(formatInfo.componentByteCount > 0, "ExpandRgbSourceToRgba: invalid component size for expanded RGB format"); const SizeT depth = static_cast(std::max(texelSize.z(), 1)); const SizeT pixelCount = static_cast(texelSize.x()) * static_cast(texelSize.y()) * depth; MOBILEGL_ASSERT(pixelCount > 0, "ExpandRgbSourceToRgba: invalid texel size (%d, %d, %d)", texelSize.x(), texelSize.y(), texelSize.z()); MOBILEGL_ASSERT(sourceByteSize == pixelCount * formatInfo.componentByteCount * 3, "ExpandRgbSourceToRgba: unexpected source byte size=%zu for pixelCount=%zu componentBytes=%u", sourceByteSize, pixelCount, formatInfo.componentByteCount); outExpandedData.resize(pixelCount * formatInfo.componentByteCount * 4); const auto* src = static_cast(source); auto* dst = outExpandedData.data(); const SizeT srcPixelSize = static_cast(formatInfo.componentByteCount) * 3; const SizeT dstPixelSize = static_cast(formatInfo.componentByteCount) * 4; for (SizeT pixel = 0; pixel < pixelCount; ++pixel) { const SizeT srcOffset = pixel * srcPixelSize; const SizeT dstOffset = pixel * dstPixelSize; std::memcpy(dst + dstOffset, src + srcOffset, srcPixelSize); std::memcpy(dst + dstOffset + srcPixelSize, formatInfo.alphaBytes.data(), formatInfo.componentByteCount); } return true; } static VkComponentSwizzle ToVkComponentSwizzle(TextureSwizzleParam swizzle) { switch (swizzle) { case TextureSwizzleParam::Red: return VK_COMPONENT_SWIZZLE_R; case TextureSwizzleParam::Green: return VK_COMPONENT_SWIZZLE_G; case TextureSwizzleParam::Blue: return VK_COMPONENT_SWIZZLE_B; case TextureSwizzleParam::Alpha: return VK_COMPONENT_SWIZZLE_A; case TextureSwizzleParam::Zero: return VK_COMPONENT_SWIZZLE_ZERO; case TextureSwizzleParam::One: return VK_COMPONENT_SWIZZLE_ONE; default: MOBILEGL_ASSERT(false, "ToVkComponentSwizzle: unsupported swizzle=%d", static_cast(swizzle)); return VK_COMPONENT_SWIZZLE_IDENTITY; } } static VkComponentSwizzle ToVkSampledComponentSwizzle(TextureSwizzleParam swizzle, Bool alphaIsImplicitOne) { if (alphaIsImplicitOne && swizzle == TextureSwizzleParam::Alpha) { return VK_COMPONENT_SWIZZLE_ONE; } return ToVkComponentSwizzle(swizzle); } static VkComponentMapping ResolveSampledViewComponents(const MG_State::GLState::ITextureObject& texture, const TextureFormatInfo& formatInfo) { const auto& swizzles = texture.GetAllSwizzleParams(); const Bool alphaIsImplicitOne = formatInfo.expandRgbToRgba; VkComponentMapping components{ ToVkSampledComponentSwizzle(swizzles.x(), alphaIsImplicitOne), ToVkSampledComponentSwizzle(swizzles.y(), alphaIsImplicitOne), ToVkSampledComponentSwizzle(swizzles.z(), alphaIsImplicitOne), ToVkSampledComponentSwizzle(swizzles.w(), alphaIsImplicitOne), }; return components; } static Bool TryResolveTextureShapeInfo(const MG_State::GLState::ITextureObject& texture, TextureUploadTarget uploadTarget, const IntVec3& texelSize, TextureShapeInfo& outShape) { switch (uploadTarget) { case TextureUploadTarget::Texture1D: case TextureUploadTarget::ProxyTexture1D: outShape = {}; outShape.imageType = VK_IMAGE_TYPE_1D; outShape.viewType = VK_IMAGE_VIEW_TYPE_1D; return true; case TextureUploadTarget::Texture1DArray: case TextureUploadTarget::ProxyTexture1DArray: MOBILEGL_ASSERT(texelSize.z() > 0, "TryResolveTextureShapeInfo: invalid 1D array depth=%d for textureId=%d", texelSize.z(), texture.GetExternalIndex()); outShape.imageType = VK_IMAGE_TYPE_1D; outShape.viewType = VK_IMAGE_VIEW_TYPE_1D_ARRAY; outShape.depth = 1; outShape.arrayLayers = static_cast(texelSize.z()); return true; case TextureUploadTarget::Texture2D: case TextureUploadTarget::ProxyTexture2D: case TextureUploadTarget::TextureRectangle: case TextureUploadTarget::ProxyTextureRectangle: outShape = {}; return true; case TextureUploadTarget::Texture2DMultisample: case TextureUploadTarget::ProxyTexture2DMultisample: outShape = {}; return true; case TextureUploadTarget::Texture2DArray: case TextureUploadTarget::ProxyTexture2DArray: MOBILEGL_ASSERT(texelSize.z() > 0, "TryResolveTextureShapeInfo: invalid 2D array depth=%d for textureId=%d", texelSize.z(), texture.GetExternalIndex()); outShape.imageType = VK_IMAGE_TYPE_2D; outShape.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; outShape.depth = 1; outShape.arrayLayers = static_cast(texelSize.z()); return true; case TextureUploadTarget::Texture2DMultisampleArray: case TextureUploadTarget::ProxyTexture2DMultisampleArray: MOBILEGL_ASSERT(texelSize.z() > 0, "TryResolveTextureShapeInfo: invalid 2D multisample array depth=%d for textureId=%d", texelSize.z(), texture.GetExternalIndex()); outShape.imageType = VK_IMAGE_TYPE_2D; outShape.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; outShape.depth = 1; outShape.arrayLayers = static_cast(texelSize.z()); return true; case TextureUploadTarget::Texture3D: case TextureUploadTarget::ProxyTexture3D: MOBILEGL_ASSERT(texelSize.z() > 0, "TryResolveTextureShapeInfo: invalid 3D texture depth=%d for textureId=%d", texelSize.z(), texture.GetExternalIndex()); outShape.imageType = VK_IMAGE_TYPE_3D; outShape.viewType = VK_IMAGE_VIEW_TYPE_3D; outShape.depth = static_cast(texelSize.z()); return true; case TextureUploadTarget::CubeMapPositiveX: case TextureUploadTarget::CubeMapNegativeX: case TextureUploadTarget::CubeMapPositiveY: case TextureUploadTarget::CubeMapNegativeY: case TextureUploadTarget::CubeMapPositiveZ: case TextureUploadTarget::CubeMapNegativeZ: case TextureUploadTarget::ProxyCubeMap: MOBILEGL_ASSERT(texture.GetTarget() == TextureTarget::TextureCubeMap, "TryResolveTextureShapeInfo: cube upload target on non-cube textureId=%d target=%s", texture.GetExternalIndex(), MG_Util::ConvertTextureTargetToString(texture.GetTarget()).c_str()); MOBILEGL_ASSERT(texelSize.x() == texelSize.y(), "TryResolveTextureShapeInfo: cube map textureId=%d is not square (%d x %d)", texture.GetExternalIndex(), texelSize.x(), texelSize.y()); outShape.imageType = VK_IMAGE_TYPE_2D; outShape.viewType = VK_IMAGE_VIEW_TYPE_CUBE; outShape.imageFlags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; outShape.depth = 1; outShape.arrayLayers = 6; return true; default: return false; } } Bool VkTextureManager::Initialize(const InitInfo& initInfo) { Shutdown(); m_device = initInfo.device; m_physicalDevice = initInfo.physicalDevice; m_allocator = initInfo.allocator; m_commandPool = initInfo.commandPool; m_graphicsQueue = initInfo.graphicsQueue; m_currentFrameIndex = 0; m_deferredReleases.clear(); m_deferredReleases.resize(initInfo.frameCount); m_deferredViewReleases.clear(); m_deferredViewReleases.resize(initInfo.frameCount); MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_physicalDevice != VK_NULL_HANDLE && m_allocator != nullptr && m_commandPool != VK_NULL_HANDLE && m_graphicsQueue != VK_NULL_HANDLE, "VkTextureManager::Initialize failed: invalid initialization info"); MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkTextureManager::Initialize failed: frameCount must be > 0"); TextureResource::s_device = m_device; TextureResource::s_allocator = m_allocator; return true; } void VkTextureManager::Shutdown() { DestroyDeferredReleases(); m_textureResources.clear(); m_aliveObjects.clear(); m_device = VK_NULL_HANDLE; m_physicalDevice = VK_NULL_HANDLE; m_allocator = nullptr; m_commandPool = VK_NULL_HANDLE; m_graphicsQueue = VK_NULL_HANDLE; m_currentFrameIndex = 0; } void VkTextureManager::BeginFrame(Uint32 frameIndex) { MOBILEGL_ASSERT(frameIndex < m_deferredReleases.size(), "VkTextureManager::BeginFrame invalid frame index %u (size=%zu)", frameIndex, m_deferredReleases.size()); MOBILEGL_ASSERT(frameIndex < m_deferredViewReleases.size(), "VkTextureManager::BeginFrame invalid deferred-view frame index %u (size=%zu)", frameIndex, m_deferredViewReleases.size()); m_currentFrameIndex = frameIndex; CollectDeferredReleases(frameIndex); } void VkTextureManager::EraseTrackedTexture(const TextureIdentity& identity) { auto resourceIt = m_textureResources.find(identity); if (resourceIt != m_textureResources.end()) { DeferResourceRelease(Move(resourceIt->second)); m_textureResources.erase(resourceIt); } m_aliveObjects.erase(identity); } void VkTextureManager::PruneStaleTextureAliases(MG_State::GLState::ITextureObject* texture) { if (texture == nullptr) { return; } Vector staleAliases; for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) { if (it->first.texture != texture) { continue; } const auto liveTexture = it->second.lock(); if (!liveTexture || liveTexture.get() != texture || liveTexture->GetLifetimeId() != it->first.lifetimeId) { staleAliases.emplace_back(it->first); } } for (const auto& identity : staleAliases) { EraseTrackedTexture(identity); } } void VkTextureManager::BeginDrawSyncScope() { m_drawSyncedThisDraw.clear(); m_drawSyncScopeActive = true; } void VkTextureManager::EndDrawSyncScope() { m_drawSyncScopeActive = false; m_drawSyncedThisDraw.clear(); } VkTextureManager::TextureResource* VkTextureManager::SyncTextureAndGetDescriptor(MG_State::GLState::ITextureObject& texture) { MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "SyncTextureAndGetDescriptor: m_device == VK_NULL_HANDLE"); const TextureIdentity identity = MakeTextureIdentity(&texture); // Per-draw memo fast path (see BeginDrawSyncScope): a texture already fully // synced earlier in this draw cannot have changed since (no GL mutation runs // mid-SetupDraw), so skip the heavy SyncTexture work and hand back the // already-synced resource. Layout lives on the resource and is updated by the // transition path, so the short-circuited resource still reflects the truth. const Bool memoActive = m_drawSyncScopeActive; if (memoActive) { for (const DrawSyncedTexture& synced : m_drawSyncedThisDraw) { if (synced.identity == identity) { if (synced.resource != nullptr && synced.resource->image != VK_NULL_HANDLE) { return synced.resource; } break; // resource unexpectedly gone -> fall through to a full sync } } } auto aliveIt = m_aliveObjects.find(identity); if (aliveIt != m_aliveObjects.end() && aliveIt->second.expired()) { EraseTrackedTexture(aliveIt->first); aliveIt = m_aliveObjects.end(); } // Only (re)register and prune when this (texture, lifetime) pair is new: stale // aliases can only come into existence through an address reuse, which by // construction introduces a new identity. Doing this unconditionally made every // sampled-texture sync scan the entire alive-texture map per draw. if (aliveIt == m_aliveObjects.end()) { const auto& liveTexture = MG_State::pGLContext->GetTextureObject(texture.GetExternalIndex()); if (liveTexture && liveTexture.get() == &texture) { m_aliveObjects[identity] = WeakPtr(liveTexture); PruneStaleTextureAliases(&texture); } } auto it = m_textureResources.find(identity); if (it == m_textureResources.end()) { TextureResource initial{}; auto [insertIt, _] = m_textureResources.emplace(identity, Move(initial)); it = insertIt; } if (!SyncTexture(texture, it->second)) { MGLOG_D("%s: Syncing texture %d failed", __func__, texture.GetExternalIndex()); return nullptr; } if (memoActive) { Bool recorded = false; for (const DrawSyncedTexture& synced : m_drawSyncedThisDraw) { if (synced.identity == identity) { recorded = true; break; } } if (!recorded) { m_drawSyncedThisDraw.push_back({identity, &(it->second)}); } } return &(it->second); } VkImageView VkTextureManager::GetOrCreateViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel) { TextureResource* resource = SyncTextureAndGetDescriptor(texture); if (resource == nullptr || resource->image == VK_NULL_HANDLE || mipLevel >= resource->mipLevels) { return VK_NULL_HANDLE; } if (resource->perMipViews.size() != resource->mipLevels) { resource->perMipViews.resize(resource->mipLevels, VK_NULL_HANDLE); } VkImageView& perMipView = resource->perMipViews[mipLevel]; if (perMipView != VK_NULL_HANDLE) { return perMipView; } perMipView = CreateImageView(resource->image, resource->format, resource->aspect, resource->viewType, mipLevel, 1, 0, resource->arrayLayers); if (perMipView == VK_NULL_HANDLE) { MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u", __func__, texture.GetExternalIndex(), mipLevel); return VK_NULL_HANDLE; } return perMipView; } VkImageView VkTextureManager::GetOrCreateAttachmentViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel, Uint32 baseArrayLayer, Uint32 layerCount, VkImageViewType viewType) { TextureResource* resource = SyncTextureAndGetDescriptor(texture); if (resource == nullptr || resource->image == VK_NULL_HANDLE || mipLevel >= resource->mipLevels) { return VK_NULL_HANDLE; } if (layerCount == 0 || baseArrayLayer >= resource->arrayLayers || baseArrayLayer + layerCount > resource->arrayLayers) { MGLOG_D("%s: invalid layer span [%u, %u) for textureId=%d arrayLayers=%u", __func__, baseArrayLayer, baseArrayLayer + layerCount, texture.GetExternalIndex(), resource->arrayLayers); return VK_NULL_HANDLE; } if (baseArrayLayer == 0 && layerCount == resource->arrayLayers && viewType == resource->viewType) { return GetOrCreateViewAtMipLevel(texture, mipLevel); } const TextureResource::AttachmentViewKey key{ .mipLevel = mipLevel, .baseArrayLayer = baseArrayLayer, .layerCount = layerCount, .viewType = viewType, }; auto it = resource->attachmentViews.find(key); if (it == resource->attachmentViews.end()) { it = resource->attachmentViews.emplace(key, VK_NULL_HANDLE).first; } VkImageView& attachmentView = it->second; if (attachmentView != VK_NULL_HANDLE) { return attachmentView; } attachmentView = CreateImageView(resource->image, resource->format, resource->aspect, viewType, mipLevel, 1, baseArrayLayer, layerCount); if (attachmentView == VK_NULL_HANDLE) { MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u baseArrayLayer=%u layerCount=%u viewType=%d", __func__, texture.GetExternalIndex(), mipLevel, baseArrayLayer, layerCount, static_cast(viewType)); resource->attachmentViews.erase(it); return VK_NULL_HANDLE; } return attachmentView; } VkImageView VkTextureManager::GetOrCreateSampledViewAtMipLevel(MG_State::GLState::ITextureObject& texture, Uint32 mipLevel) { TextureResource* resource = SyncTextureAndGetDescriptor(texture); if (resource == nullptr || resource->image == VK_NULL_HANDLE || mipLevel >= resource->mipLevels) { return VK_NULL_HANDLE; } if (resource->perMipSampledViews.size() != resource->mipLevels) { resource->perMipSampledViews.resize(resource->mipLevels, VK_NULL_HANDLE); } VkImageView& perMipSampledView = resource->perMipSampledViews[mipLevel]; if (perMipSampledView != VK_NULL_HANDLE) { return perMipSampledView; } const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat()); const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture, formatInfo); const VkImageAspectFlags sampledAspect = ResolveSampledImageViewAspectMask(resource->aspect); perMipSampledView = CreateImageView(resource->image, resource->format, sampledAspect, resource->viewType, mipLevel, 1, 0, resource->arrayLayers, &sampledComponents); if (perMipSampledView == VK_NULL_HANDLE) { MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u", __func__, texture.GetExternalIndex(), mipLevel); return VK_NULL_HANDLE; } return perMipSampledView; } void VkTextureManager::UpdateTrackedImageLayout(MG_State::GLState::ITextureObject* texture, VkImageLayout newLayout) { MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayout: texture is null"); auto it = m_textureResources.find(MakeTextureIdentity(texture)); MOBILEGL_ASSERT(it != m_textureResources.end(), "UpdateTrackedImageLayout: textureId=%d has no tracked resource", texture->GetExternalIndex()); MOBILEGL_ASSERT(it->second.image != VK_NULL_HANDLE, "UpdateTrackedImageLayout: textureId=%d has null image", texture->GetExternalIndex()); it->second.layout = newLayout; } void VkTextureManager::UpdateTrackedImageLayoutAfterAttachmentWrite(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject* texture, Uint32 writtenMipLevel, VkImageLayout newLayout) { MOBILEGL_ASSERT(texture != nullptr, "UpdateTrackedImageLayoutAfterAttachmentWrite: texture is null"); auto it = m_textureResources.find(MakeTextureIdentity(texture)); MOBILEGL_ASSERT(it != m_textureResources.end(), "UpdateTrackedImageLayoutAfterAttachmentWrite: textureId=%d has no tracked resource", texture->GetExternalIndex()); auto& resource = it->second; MOBILEGL_ASSERT(resource.image != VK_NULL_HANDLE, "UpdateTrackedImageLayoutAfterAttachmentWrite: textureId=%d has null image", texture->GetExternalIndex()); MOBILEGL_ASSERT(writtenMipLevel < resource.mipLevels, "UpdateTrackedImageLayoutAfterAttachmentWrite: textureId=%d mipLevel=%u out of range %u", texture->GetExternalIndex(), writtenMipLevel, resource.mipLevels); if (resource.layout != newLayout && resource.mipLevels > 1) { VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkAccessFlags srcAccessMask = 0; GetImageTransitionSourceState(resource.layout, srcStageMask, srcAccessMask); VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkAccessFlags dstAccessMask = 0; GetImageTransitionDestinationState(newLayout, dstStageMask, dstAccessMask); if (writtenMipLevel > 0) { VkImageLayout lowerMipLayout = resource.layout; const Bool lowerTransitioned = TransitionImageLayout( commandBuffer, resource.image, lowerMipLayout, newLayout, srcStageMask, dstStageMask, srcAccessMask, dstAccessMask, resource.aspect, 0, writtenMipLevel, resource.arrayLayers); MOBILEGL_ASSERT(lowerTransitioned, "UpdateTrackedImageLayoutAfterAttachmentWrite: failed to transition lower mip levels for textureId=%d", texture->GetExternalIndex()); } const Uint32 upperBaseMipLevel = writtenMipLevel + 1; if (upperBaseMipLevel < resource.mipLevels) { VkImageLayout upperMipLayout = resource.layout; const Bool upperTransitioned = TransitionImageLayout( commandBuffer, resource.image, upperMipLayout, newLayout, srcStageMask, dstStageMask, srcAccessMask, dstAccessMask, resource.aspect, upperBaseMipLevel, resource.mipLevels - upperBaseMipLevel, resource.arrayLayers); MOBILEGL_ASSERT(upperTransitioned, "UpdateTrackedImageLayoutAfterAttachmentWrite: failed to transition upper mip levels for textureId=%d", texture->GetExternalIndex()); } } resource.layout = newLayout; } Bool VkTextureManager::TransitionTextureForSampling(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture) { TextureResource* resource = SyncTextureAndGetDescriptor(texture); if (resource == nullptr) { return false; } if (IsValidSampledImageLayout(resource->layout)) { return true; } if (resource->layout == VK_IMAGE_LAYOUT_UNDEFINED) { MGLOG_W("TransitionTextureForSampling: textureId=%d is still in VK_IMAGE_LAYOUT_UNDEFINED before sampling", texture.GetExternalIndex()); } VkImageLayout targetLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkAccessFlags srcAccessMask = 0; if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { MOBILEGL_ASSERT(resource->layout == VK_IMAGE_LAYOUT_UNDEFINED || resource->layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, "TransitionTextureForSampling: unsupported color layout=%d for textureId=%d", static_cast(resource->layout), texture.GetExternalIndex()); if (resource->layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; } targetLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } else if ((resource->aspect & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0) { MOBILEGL_ASSERT(resource->layout == VK_IMAGE_LAYOUT_UNDEFINED || resource->layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, "TransitionTextureForSampling: unsupported depth/stencil layout=%d for textureId=%d", static_cast(resource->layout), texture.GetExternalIndex()); if (resource->layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { srcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; } targetLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; } else { MOBILEGL_ASSERT(false, "TransitionTextureForSampling: unsupported aspect mask=0x%x for textureId=%d", static_cast(resource->aspect), texture.GetExternalIndex()); } const Bool ok = TransitionImageLayout(commandBuffer, resource->image, resource->layout, targetLayout, srcStageMask, kSampledReadStages, srcAccessMask, VK_ACCESS_SHADER_READ_BIT, resource->aspect, 0, resource->mipLevels, resource->arrayLayers); MOBILEGL_ASSERT(ok, "TransitionTextureForSampling: transition failed for textureId=%d", texture.GetExternalIndex()); return ok; } Bool VkTextureManager::TransitionTextureForStorageImage(VkCommandBuffer commandBuffer, MG_State::GLState::ITextureObject& texture) { TextureResource* resource = SyncTextureAndGetDescriptor(texture); if (resource == nullptr) { return false; } if (resource->sampleCount != VK_SAMPLE_COUNT_1_BIT) { MGLOG_D("TransitionTextureForStorageImage: multisample textureId=%d is not exposed as a storage image", texture.GetExternalIndex()); return false; } if (resource->layout == VK_IMAGE_LAYOUT_GENERAL) { return true; } VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; VkAccessFlags srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_MEMORY_READ_BIT; GetImageTransitionSourceState(resource->layout, srcStageMask, srcAccessMask); const Bool ok = TransitionImageLayout(commandBuffer, resource->image, resource->layout, VK_IMAGE_LAYOUT_GENERAL, srcStageMask, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, srcAccessMask, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, resource->aspect, 0, resource->mipLevels, resource->arrayLayers); MOBILEGL_ASSERT(ok, "TransitionTextureForStorageImage: transition failed for textureId=%d", texture.GetExternalIndex()); return ok; } Bool VkTextureManager::TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout, VkImageLayout newLayout, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask, VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask, Uint32 baseMipLevel, Uint32 levelCount, Uint32 layerCount) { MOBILEGL_ASSERT(image != VK_NULL_HANDLE, "TransitionImageLayout: m_image == VK_NULL_HANDLE"); MOBILEGL_ASSERT(!((dstAccessMask & VK_ACCESS_TRANSFER_READ_BIT) != 0 && (dstStageMask & VK_PIPELINE_STAGE_TRANSFER_BIT) == 0), "TransitionImageLayout: invalid dstAccess/dstStage pair (dstAccess=0x%x, dstStage=0x%x, oldLayout=%d, newLayout=%d)", static_cast(dstAccessMask), static_cast(dstStageMask), static_cast(trackedLayout), static_cast(newLayout)); if (trackedLayout == newLayout) { return true; } VkImageMemoryBarrier barrier{}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.srcAccessMask = srcAccessMask; barrier.dstAccessMask = dstAccessMask; barrier.oldLayout = trackedLayout; barrier.newLayout = newLayout; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = image; barrier.subresourceRange.aspectMask = aspectMask; barrier.subresourceRange.baseMipLevel = baseMipLevel; barrier.subresourceRange.levelCount = levelCount; barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = layerCount; vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &barrier); trackedLayout = newLayout; return true; } SizeT VkTextureManager::CollectGarbage() { m_gcCounter++; if (m_gcCounter != 0) { return 0; } Vector expiredTextures; expiredTextures.reserve(m_aliveObjects.size()); for (auto it = m_aliveObjects.begin(); it != m_aliveObjects.end(); ++it) { if (it->second.expired()) { expiredTextures.emplace_back(it->first.texture); } } for (auto* texture : expiredTextures) { PruneStaleTextureAliases(texture); } return expiredTextures.size(); } Bool VkTextureManager::SyncTexture(MG_State::GLState::ITextureObject &texture, TextureResource &outResource) { // Cross-draw fast path: if the resource is already built and neither the texture's // pixel content (bumped in MarkStorageDirty) nor its params changed since the last // sync, there is nothing to re-check or re-upload - skip CheckMipmapCompleteness, // SyncTextureResource, SyncTextureViews and the per-level dirty scan. Layout is // maintained separately by the transition path, so the resource still reflects truth. const Uint64 syncingContentVersion = texture.GetContentVersion(); const auto* syncingMipTexture = MG_State::GLState::AsMipmapTexture(&texture); const Uint32 syncingMipLevelCount = syncingMipTexture != nullptr ? syncingMipTexture->GetMipmapLevelCount() : 0u; if (outResource.image != VK_NULL_HANDLE && outResource.syncedContentVersion == syncingContentVersion && outResource.syncedTextureParamsVersion == texture.GetTextureParamsVersion() && outResource.syncedMipLevelCount == syncingMipLevelCount) { return true; } TextureUploadTarget uploadTarget = TextureUploadTarget::Unknown; IntVec3 texelSize{0, 0, 0}; SizeT byteSize = 0; Uint32 mipLevelCount = 0; if (!CheckMipmapCompleteness(texture, uploadTarget, texelSize, byteSize, mipLevelCount)) { MGLOG_D("%s: mipmap not complete", __func__); return false; } auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture); if (!mipTexture) { MGLOG_D("%s: not TextureObjectMipmap", __func__); return false; } if (!SyncTextureResource(texture, uploadTarget, texelSize, byteSize, mipLevelCount, outResource)) { MGLOG_D("%s: SyncTextureResource failed", __func__); return false; } if (!SyncTextureViews(texture, outResource)) { MGLOG_D("%s: SyncTextureViews failed", __func__); return false; } Vector dirtyTargets; if (outResource.viewType == VK_IMAGE_VIEW_TYPE_CUBE) { dirtyTargets = mipTexture->GetUploadTargets(); } else { dirtyTargets.push_back(uploadTarget); } Bool hasDirtyMipLevel = false; for (const TextureUploadTarget target : dirtyTargets) { const Uint32 targetMipLevelCount = std::min(mipLevelCount, GetUploadMipLevelCount(*mipTexture, target)); for (Uint32 level = 0; level < targetMipLevelCount; ++level) { if (mipTexture->IsStorageDirty(target, level)) { hasDirtyMipLevel = true; break; } } if (hasDirtyMipLevel) { break; } } if (!hasDirtyMipLevel) { outResource.syncedContentVersion = syncingContentVersion; outResource.syncedMipLevelCount = syncingMipLevelCount; return true; } if (!UploadDirtyMipLevels(*mipTexture, uploadTarget, outResource)) { MGLOG_D("%s: UploadDirtyMipLevels failed", __func__); return false; } outResource.syncedContentVersion = syncingContentVersion; outResource.syncedMipLevelCount = syncingMipLevelCount; return true; } Bool VkTextureManager::SyncTextureResource(const MG_State::GLState::ITextureObject &texture, TextureUploadTarget uploadTarget, const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels, TextureResource &resource) { const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat()); const VkFormat format = formatInfo.format; if (format == VK_FORMAT_UNDEFINED) { MGLOG_D("%s: format == VK_FORMAT_UNDEFINED", __func__); return false; } if (texelSize.x() <= 0 || texelSize.y() <= 0 /*|| byteSize == 0*/) { MGLOG_D("%s: texelSize or byteSize is zero", __func__); return false; } if (mipLevels == 0) { MGLOG_D("%s: no mip levels", __func__); return false; } const Bool isMultisampleTexture = IsMultisampleTextureUploadTarget(uploadTarget); const Uint32 backingMipLevels = isMultisampleTexture ? 1u : std::max(mipLevels, ComputeFullMipLevelCount(texelSize)); TextureShapeInfo shapeInfo{}; const Bool supportedShape = TryResolveTextureShapeInfo(texture, uploadTarget, texelSize, shapeInfo); MOBILEGL_ASSERT(supportedShape, "SyncTextureResource: unsupported uploadTarget=%s textureTarget=%s textureId=%d size=(%d,%d,%d) " "mipLevels=%u vkViewType=%d", MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(), MG_Util::ConvertTextureTargetToString(texture.GetTarget()).c_str(), texture.GetExternalIndex(), texelSize.x(), texelSize.y(), texelSize.z(), mipLevels, static_cast(MG_Util::ConvertTextureUploadTargetToVkEnum(uploadTarget))); if (!supportedShape) { MGLOG_D("%s: not Texture2D, unsupported", __func__); return false; } VkSampleCountFlagBits resolvedSampleCount = VK_SAMPLE_COUNT_1_BIT; if (isMultisampleTexture && !TryResolveSampleCountFlagBits(texture.GetSamples(), resolvedSampleCount)) { MGLOG_D("%s: unsupported multisample count=%d for textureId=%d target=%s", __func__, texture.GetSamples(), texture.GetExternalIndex(), MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str()); return false; } const Bool compatible = resource.image != VK_NULL_HANDLE && resource.format == format && resource.extent.width == static_cast(texelSize.x()) && resource.extent.height == static_cast(texelSize.y()) && resource.depth == shapeInfo.depth && resource.arrayLayers == shapeInfo.arrayLayers && resource.viewType == shapeInfo.viewType && resource.sampleCount == resolvedSampleCount && resource.mipLevels == backingMipLevels; if (compatible) { if (resource.perMipViews.size() != backingMipLevels) { resource.perMipViews.resize(backingMipLevels, VK_NULL_HANDLE); } if (resource.perMipSampledViews.size() != backingMipLevels) { resource.perMipSampledViews.resize(backingMipLevels, VK_NULL_HANDLE); } return true; } const Bool preserveExistingContent = resource.image != VK_NULL_HANDLE && resource.format == format && resource.extent.width == static_cast(texelSize.x()) && resource.extent.height == static_cast(texelSize.y()) && resource.depth == shapeInfo.depth && resource.arrayLayers == shapeInfo.arrayLayers && resource.viewType == shapeInfo.viewType && resource.sampleCount == resolvedSampleCount && resolvedSampleCount == VK_SAMPLE_COUNT_1_BIT && resource.mipLevels < backingMipLevels && resource.layout != VK_IMAGE_LAYOUT_UNDEFINED; std::unique_ptr preservedResource; if (preserveExistingContent) { preservedResource = std::make_unique(Move(resource)); } else { DeferResourceRelease(Move(resource)); } auto aspect = GetAspectMaskForFormat(format); VkImageCreateInfo imageInfo{}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.flags = shapeInfo.imageFlags; imageInfo.imageType = shapeInfo.imageType; imageInfo.extent.width = static_cast(texelSize.x()); imageInfo.extent.height = static_cast(texelSize.y()); imageInfo.extent.depth = shapeInfo.depth; imageInfo.mipLevels = backingMipLevels; imageInfo.arrayLayers = shapeInfo.arrayLayers; imageInfo.format = format; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; VkFormatProperties formatProperties{}; vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties); const Bool supportsStorageImage = !isMultisampleTexture && (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 && (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0; imageInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | (supportsStorageImage ? VK_IMAGE_USAGE_STORAGE_BIT : 0) | ((aspect & VK_IMAGE_ASPECT_COLOR_BIT) ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0) | (((aspect & VK_IMAGE_ASPECT_DEPTH_BIT) || (aspect & VK_IMAGE_ASPECT_STENCIL_BIT)) ? VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT : 0); if (!isMultisampleTexture) { imageInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; } imageInfo.samples = resolvedSampleCount; if (isMultisampleTexture) { VkImageFormatProperties imageFormatProperties{}; const VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, imageInfo.flags, &imageFormatProperties); if (imageFormatResult != VK_SUCCESS || (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0) { MGLOG_D("%s: sampleCount=%d is unsupported for textureId=%d target=%s format=%d usage=0x%x", __func__, texture.GetSamples(), texture.GetExternalIndex(), MG_Util::ConvertTextureUploadTargetToString(uploadTarget).c_str(), static_cast(format), static_cast(imageInfo.usage)); return false; } } imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VmaAllocationCreateInfo allocationInfo{}; allocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; VK_VERIFY(vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &resource.image, &resource.allocation, nullptr), "vmaCreateImage(texture)"); ++m_textureImageEpoch; // a new attachment image invalidates cached render passes resource.layout = VK_IMAGE_LAYOUT_UNDEFINED; resource.extent = {static_cast(texelSize.x()), static_cast(texelSize.y())}; resource.depth = shapeInfo.depth; resource.arrayLayers = shapeInfo.arrayLayers; resource.mipLevels = backingMipLevels; resource.perMipViews.assign(backingMipLevels, VK_NULL_HANDLE); resource.perMipSampledViews.assign(backingMipLevels, VK_NULL_HANDLE); resource.sampledBaseMipLevel = 0; resource.sampledLevelCount = mipLevels; resource.format = format; resource.aspect = aspect; resource.viewType = shapeInfo.viewType; resource.sampleCount = resolvedSampleCount; resource.syncedTextureParamsVersion = 0; if (preservedResource) { const Bool preserved = PreserveTextureContentsOnRecreate( m_device, m_commandPool, m_graphicsQueue, *preservedResource, resource); MOBILEGL_ASSERT(preserved, "SyncTextureResource: failed to preserve texture contents while growing mip chain"); DeferResourceRelease(Move(*preservedResource)); } return true; } void VkTextureManager::DeferResourceRelease(TextureResource&& resource) { if (resource.image == VK_NULL_HANDLE && resource.fullView == VK_NULL_HANDLE && resource.sampledView == VK_NULL_HANDLE && resource.perMipViews.empty() && resource.perMipSampledViews.empty()) { return; } if (m_deferredReleases.empty()) { resource.Reset(); return; } MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredReleases.size(), "VkTextureManager::DeferResourceRelease invalid current frame index %u (size=%zu)", m_currentFrameIndex, m_deferredReleases.size()); m_deferredReleases[m_currentFrameIndex].push_back(Move(resource)); } void VkTextureManager::CollectDeferredReleases(Uint32 frameIndex) { MOBILEGL_ASSERT(frameIndex < m_deferredReleases.size(), "VkTextureManager::CollectDeferredReleases invalid frame index %u (size=%zu)", frameIndex, m_deferredReleases.size()); m_deferredReleases[frameIndex].clear(); MOBILEGL_ASSERT(frameIndex < m_deferredViewReleases.size(), "VkTextureManager::CollectDeferredReleases invalid deferred-view frame index %u (size=%zu)", frameIndex, m_deferredViewReleases.size()); for (const VkImageView view : m_deferredViewReleases[frameIndex]) { if (view != VK_NULL_HANDLE) { vkDestroyImageView(m_device, view, nullptr); } } m_deferredViewReleases[frameIndex].clear(); } void VkTextureManager::DestroyDeferredReleases() { for (auto& deferredReleases : m_deferredReleases) { deferredReleases.clear(); } m_deferredReleases.clear(); for (auto& deferredViews : m_deferredViewReleases) { for (const VkImageView view : deferredViews) { if (view != VK_NULL_HANDLE) { vkDestroyImageView(m_device, view, nullptr); } } deferredViews.clear(); } m_deferredViewReleases.clear(); } void VkTextureManager::DeferViewRelease(VkImageView view) { if (view == VK_NULL_HANDLE) { return; } if (m_deferredViewReleases.empty()) { vkDestroyImageView(m_device, view, nullptr); return; } MOBILEGL_ASSERT(m_currentFrameIndex < m_deferredViewReleases.size(), "VkTextureManager::DeferViewRelease invalid current frame index %u (size=%zu)", m_currentFrameIndex, m_deferredViewReleases.size()); m_deferredViewReleases[m_currentFrameIndex].push_back(view); } Bool VkTextureManager::SyncTextureViews(const MG_State::GLState::ITextureObject& texture, TextureResource& resource) { MOBILEGL_ASSERT(resource.image != VK_NULL_HANDLE, "SyncTextureViews: image == VK_NULL_HANDLE"); Uint32 baseMipLevel = 0; Uint32 levelCount = 1; ResolveViewMipRange(texture, resource.mipLevels, baseMipLevel, levelCount); const Bool needsRecreate = resource.fullView == VK_NULL_HANDLE || resource.sampledView == VK_NULL_HANDLE || resource.sampledBaseMipLevel != baseMipLevel || resource.sampledLevelCount != levelCount || resource.syncedTextureParamsVersion != texture.GetTextureParamsVersion(); if (!needsRecreate) { return true; } if (resource.fullView != VK_NULL_HANDLE) { DeferViewRelease(resource.fullView); resource.fullView = VK_NULL_HANDLE; } if (resource.sampledView != VK_NULL_HANDLE) { DeferViewRelease(resource.sampledView); resource.sampledView = VK_NULL_HANDLE; } for (auto& sampledView : resource.perMipSampledViews) { if (sampledView != VK_NULL_HANDLE) { DeferViewRelease(sampledView); sampledView = VK_NULL_HANDLE; } } const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat()); const VkComponentMapping sampledComponents = ResolveSampledViewComponents(texture, formatInfo); resource.fullView = CreateImageView(resource.image, resource.format, resource.aspect, resource.viewType, baseMipLevel, levelCount, 0, resource.arrayLayers, &sampledComponents); if (resource.fullView == VK_NULL_HANDLE) { return false; } const VkImageAspectFlags sampledAspect = ResolveSampledImageViewAspectMask(resource.aspect); resource.sampledView = CreateImageView(resource.image, resource.format, sampledAspect, resource.viewType, baseMipLevel, levelCount, 0, resource.arrayLayers, &sampledComponents); if (resource.sampledView == VK_NULL_HANDLE) { return false; } resource.sampledBaseMipLevel = baseMipLevel; resource.sampledLevelCount = levelCount; resource.syncedTextureParamsVersion = texture.GetTextureParamsVersion(); return true; } VkImageView VkTextureManager::CreateImageView(VkImage image, VkFormat format, VkImageAspectFlags aspect, VkImageViewType viewType, Uint32 baseMipLevel, Uint32 levelCount, Uint32 baseArrayLayer, Uint32 layerCount, const VkComponentMapping* components) const { VkImageViewCreateInfo viewInfo{}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = image; viewInfo.viewType = viewType; viewInfo.format = format; viewInfo.components = components != nullptr ? *components : VkComponentMapping{VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A}; viewInfo.subresourceRange.aspectMask = aspect; viewInfo.subresourceRange.baseMipLevel = baseMipLevel; viewInfo.subresourceRange.levelCount = levelCount; viewInfo.subresourceRange.baseArrayLayer = baseArrayLayer; viewInfo.subresourceRange.layerCount = layerCount; VkImageView view = VK_NULL_HANDLE; VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &view), "vkCreateImageView(texture)"); return view; } Bool VkTextureManager::UploadDirtyMipLevels(MG_State::GLState::TextureObjectMipmap &mipmapTexture, TextureUploadTarget uploadTarget, TextureResource &outResource) { struct UploadItem { TextureUploadTarget target = TextureUploadTarget::Unknown; Uint32 level = 0; Uint32 baseArrayLayer = 0; SizeT uploadByteSize = 0; IntVec3 texelSize = {0, 0, 0}; const void* source = nullptr; Vector expandedData; VkDeviceSize offset = 0; }; Vector uploadItems; Vector targets; if (outResource.viewType == VK_IMAGE_VIEW_TYPE_CUBE) { targets = mipmapTexture.GetUploadTargets(); } else { targets.push_back(uploadTarget); } const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(mipmapTexture.GetFormat()); VkDeviceSize stagingSize = 0; for (const TextureUploadTarget target : targets) { const Uint32 definedMipLevels = GetUploadMipLevelCount(mipmapTexture, target); MOBILEGL_ASSERT(definedMipLevels <= outResource.mipLevels, "UploadDirtyMipLevels: defined mip level count %u exceeds backing mip level count %u for textureId=%d target=%s", definedMipLevels, outResource.mipLevels, mipmapTexture.GetExternalIndex(), MG_Util::ConvertTextureUploadTargetToString(target).c_str()); for (Uint32 level = 0; level < definedMipLevels; ++level) { if (!mipmapTexture.IsStorageDirty(target, level)) { continue; } const auto texelSize = mipmapTexture.GetMipmapTexelSize(target, level); const auto byteSize = mipmapTexture.GetMipmapByteSize(target, level); if (texelSize.x() <= 0 || texelSize.y() <= 0 || byteSize == 0) { mipmapTexture.MarkStorageDirty(target, level, false); continue; } const void* source = mipmapTexture.MapMipmapData(target, level); if (source == nullptr) { MGLOG_D("%s: MapmipmapData failed at target %s level %d", __func__, MG_Util::ConvertTextureUploadTargetToString(target).c_str(), level); return false; } UploadItem uploadItem{}; uploadItem.target = target; uploadItem.level = level; uploadItem.baseArrayLayer = ResolveUploadArrayLayer(target); uploadItem.texelSize = texelSize; uploadItem.source = source; uploadItem.offset = stagingSize; uploadItem.uploadByteSize = byteSize; if (formatInfo.expandRgbToRgba) { const Bool expanded = ExpandRgbSourceToRgba(source, byteSize, texelSize, formatInfo, uploadItem.expandedData); MOBILEGL_ASSERT(expanded, "UploadDirtyMipLevels: failed to expand RGB textureId=%d target=%s level=%u to RGBA staging data", mipmapTexture.GetExternalIndex(), MG_Util::ConvertTextureUploadTargetToString(target).c_str(), level); uploadItem.uploadByteSize = uploadItem.expandedData.size(); } uploadItems.push_back(Move(uploadItem)); if (!uploadItems.back().expandedData.empty()) { uploadItems.back().source = uploadItems.back().expandedData.data(); } stagingSize += static_cast(uploadItems.back().uploadByteSize); } } if (uploadItems.empty()) { return true; } VkBuffer stagingBuffer = VK_NULL_HANDLE; VmaAllocation stagingAllocation = nullptr; VkBufferCreateInfo bufferInfo{}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = stagingSize; bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VmaAllocationCreateInfo stagingAllocationInfo{}; stagingAllocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; stagingAllocationInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; stagingAllocationInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; VK_VERIFY(vmaCreateBuffer(m_allocator, &bufferInfo, &stagingAllocationInfo, &stagingBuffer, &stagingAllocation, nullptr), "vmaCreateBuffer(staging texture)"); void* mapped = nullptr; VK_VERIFY(vmaMapMemory(m_allocator, stagingAllocation, &mapped), "vmaMapMemory(staging texture)"); for (const auto& item : uploadItems) { std::memcpy(static_cast(mapped) + item.offset, item.source, item.uploadByteSize); } vmaUnmapMemory(m_allocator, stagingAllocation); VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = m_commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = 1; VkCommandBuffer commandBuffer = VK_NULL_HANDLE; VK_VERIFY(vkAllocateCommandBuffers(m_device, &allocInfo, &commandBuffer), "vkAllocateCommandBuffers(texture)"); VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; VK_VERIFY(vkBeginCommandBuffer(commandBuffer, &beginInfo), "vkBeginCommandBuffer(texture)"); const VkImageAspectFlags aspectMask = GetAspectMaskForFormat(outResource.format); VkPipelineStageFlags uploadSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkAccessFlags uploadSrcAccessMask = 0; GetImageTransitionSourceState(outResource.layout, uploadSrcStageMask, uploadSrcAccessMask); Bool ok = TransitionImageLayout(commandBuffer, outResource.image, outResource.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, uploadSrcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, uploadSrcAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, aspectMask, 0, outResource.mipLevels, outResource.arrayLayers); MOBILEGL_ASSERT(ok, "TransitionImageLayout to VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL failed"); for (const auto& item : uploadItems) { VkBufferImageCopy copy{}; copy.bufferOffset = item.offset; copy.bufferRowLength = 0; copy.bufferImageHeight = 0; copy.imageSubresource.aspectMask = aspectMask; copy.imageSubresource.mipLevel = item.level; copy.imageSubresource.baseArrayLayer = item.baseArrayLayer; copy.imageSubresource.layerCount = 1; copy.imageOffset = {0, 0, 0}; copy.imageExtent = {static_cast(item.texelSize.x()), static_cast(item.texelSize.y()), item.texelSize.z() > 0 ? static_cast(item.texelSize.z()) : 1u}; vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); } const VkImageLayout finalLayout = ResolveSampledReadOnlyLayout(aspectMask); VkImageLayout uploadLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; ok = TransitionImageLayout(commandBuffer, outResource.image, uploadLayout, finalLayout, VK_PIPELINE_STAGE_TRANSFER_BIT, kSampledReadStages, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, aspectMask, 0, outResource.mipLevels, outResource.arrayLayers); MOBILEGL_ASSERT(ok, "TransitionImageLayout to sampled read-only layout failed"); outResource.layout = finalLayout; VK_VERIFY(vkEndCommandBuffer(commandBuffer), "vkEndCommandBuffer(texture)"); VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; VkFenceCreateInfo fenceInfo{}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; VkFence uploadFence = VK_NULL_HANDLE; VK_VERIFY(vkCreateFence(m_device, &fenceInfo, nullptr, &uploadFence), "vkCreateFence(texture upload)"); VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, uploadFence), "vkQueueSubmit(texture)"); VK_VERIFY(vkWaitForFences(m_device, 1, &uploadFence, VK_TRUE, UINT64_MAX), "vkWaitForFences(texture upload)"); vkDestroyFence(m_device, uploadFence, nullptr); vkFreeCommandBuffers(m_device, m_commandPool, 1, &commandBuffer); vmaDestroyBuffer(m_allocator, stagingBuffer, stagingAllocation); if (!ok) { MGLOG_D("%s: texture upload cmd failed", __func__); return false; } for (const auto& item : uploadItems) { mipmapTexture.MarkStorageDirty(item.target, item.level, false); } outResource.layout = finalLayout; return true; } Bool VkTextureManager::CheckMipmapCompleteness(const MG_State::GLState::ITextureObject& texture, TextureUploadTarget& outTarget, IntVec3& outTexelSize, SizeT& outByteSize, Uint32& outMipLevelCount) { const auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture); if (!mipTexture) { MGLOG_D("%s: not TextureObjectMipmap", __func__); return false; } const auto& targets = texture.GetUploadTargets(); if (targets.empty()) { MGLOG_D("%s: upload target empty", __func__); return false; } for (const auto target : targets) { const Uint32 mipLevelCount = GetUploadMipLevelCount(*mipTexture, target); if (mipLevelCount == 0) { MGLOG_D("%s: mipLevelCount == 0", __func__); continue; } // Backing VkImage allocation still uses storage mip 0 as the physical image extent. // GL_TEXTURE_BASE_LEVEL / MAX_LEVEL are applied later when building the sampled view. const auto storageBaseTexelSize = mipTexture->GetMipmapTexelSize(target, 0); const auto storageBaseByteSize = mipTexture->GetMipmapByteSize(target, 0); if (storageBaseTexelSize.x() <= 0 || storageBaseTexelSize.y() <= 0 /*|| storageBaseByteSize == 0*/) { continue; } outTarget = target; outTexelSize = storageBaseTexelSize; outByteSize = storageBaseByteSize; outMipLevelCount = mipLevelCount; return true; } MGLOG_D("%s: no valid target or mipmap", __func__); return false; } Uint32 VkTextureManager::GetUploadMipLevelCount(const MG_State::GLState::TextureObjectMipmap& texture, TextureUploadTarget target) { const Uint totalLevelCount = texture.GetMipmapLevelCount(); if (totalLevelCount == 0) { return 0; } Uint32 validLevelCount = 0; for (Uint level = 0; level < totalLevelCount; ++level) { const auto size = texture.GetMipmapTexelSize(target, level); const auto byteSize = texture.GetMipmapByteSize(target, level); if (size.x() <= 0 || size.y() <= 0 /*|| byteSize == 0*/) { break; } ++validLevelCount; } return validLevelCount; } void VkTextureManager::ResolveViewMipRange(const MG_State::GLState::ITextureObject& texture, Uint32 mipLevels, Uint32& outBaseMipLevel, Uint32& outLevelCount) { MOBILEGL_ASSERT(mipLevels > 0, "ResolveViewMipRange: mipLevels must be > 0"); Uint32 definedMipLevels = mipLevels; if (const auto* mipTexture = MG_State::GLState::AsMipmapTexture(&texture)) { const auto& targets = texture.GetUploadTargets(); for (const auto target : targets) { const Uint32 uploadMipLevels = GetUploadMipLevelCount(*mipTexture, target); if (uploadMipLevels == 0) { continue; } definedMipLevels = std::min(mipLevels, uploadMipLevels); break; } } MOBILEGL_ASSERT(definedMipLevels > 0, "ResolveViewMipRange: texture has no defined mip levels"); const auto& levelRange = texture.GetLevelRange(); const Uint32 maxAvailableMipLevel = definedMipLevels - 1; const Uint32 requestedBaseMipLevel = std::min(static_cast(levelRange.x()), maxAvailableMipLevel); Uint32 requestedMaxMipLevel = std::min(static_cast(levelRange.y()), maxAvailableMipLevel); if (requestedMaxMipLevel < requestedBaseMipLevel) { requestedMaxMipLevel = requestedBaseMipLevel; } outBaseMipLevel = requestedBaseMipLevel; outLevelCount = requestedMaxMipLevel - requestedBaseMipLevel + 1; } VkImageAspectFlags VkTextureManager::GetAspectMaskForFormat(VkFormat format) { switch (format) { case VK_FORMAT_D16_UNORM: case VK_FORMAT_X8_D24_UNORM_PACK32: case VK_FORMAT_D32_SFLOAT: return VK_IMAGE_ASPECT_DEPTH_BIT; case VK_FORMAT_S8_UINT: return VK_IMAGE_ASPECT_STENCIL_BIT; case VK_FORMAT_D16_UNORM_S8_UINT: case VK_FORMAT_D24_UNORM_S8_UINT: case VK_FORMAT_D32_SFLOAT_S8_UINT: return VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; default: return VK_IMAGE_ASPECT_COLOR_BIT; } } VkImageAspectFlags VkTextureManager::ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect) { if ((imageAspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { return VK_IMAGE_ASPECT_COLOR_BIT; } if ((imageAspect & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) { return VK_IMAGE_ASPECT_DEPTH_BIT; } if ((imageAspect & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) { return VK_IMAGE_ASPECT_STENCIL_BIT; } return imageAspect; } } // namespace MobileGL::MG_Backend::DirectVulkan