From 22b749dd37c54d12263c1aa24ad6bc3ac0d1cc0a Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Sat, 1 Aug 2026 01:15:58 -0400 Subject: [PATCH] [Fix] (MG_Util, DirectVulkan): canonical depth shadows with upload conversion Depth textures previously raw-copied whatever the client handed over into the Vulkan image, so any client format other than the image's exact texel layout uploaded garbage (float DEPTH_COMPONENT data read as 16-bit words, GL_TEXTURE_1D/2D alike). The shadow now has a defined canonical layout - unorm16 for DEPTH_COMPONENT16, a full-scale unorm32 word for the 24/32-bit fixed depths, float for DEPTH_COMPONENT32F - produced by the pixel-store unpack converter (new DepthComponent channel mapping + UNorm32 component). GL_DEPTH_COMPONENT client data may also fill packed depth-stencil internals (stencil half zero). The Vulkan uploader converts shadow words to the image texel layout per aspect, and X8_D24_UNORM falls back to D32_SFLOAT where optimal tiling lacks support (lavapipe). texture_size_promotion.functional and packed_depth_stencil.verify_copy_tex_image.* now pass. --- .../Renderer/VkTextureManager.cpp | 77 +++++++++++++++- MobileGL/MG_Util/Metrics/TextureMetrics.cpp | 5 +- .../MG_Util/Texture/PixelStoreProcessor.cpp | 92 ++++++++++++++++++- 3 files changed, 167 insertions(+), 7 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 4784b0ae..73814b6f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -830,7 +830,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { return VK_NULL_HANDLE; } - if (baseArrayLayer == 0 && layerCount == resource->arrayLayers && viewType == resource->viewType) { + const Bool framebufferSrgbEnabled = + MG_State::pGLContext->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb); + const VkFormat attachmentFormat = ResolveSrgbAttachmentWriteFormat(resource->format, framebufferSrgbEnabled); + + if (attachmentFormat == resource->format && baseArrayLayer == 0 && layerCount == resource->arrayLayers && + viewType == resource->viewType) { return GetOrCreateViewAtMipLevel(texture, mipLevel); } @@ -839,6 +844,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { .baseArrayLayer = baseArrayLayer, .layerCount = layerCount, .viewType = viewType, + .viewFormat = attachmentFormat, }; auto it = resource->attachmentViews.find(key); if (it == resource->attachmentViews.end()) { @@ -849,7 +855,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return attachmentView; } - attachmentView = CreateImageView(resource->image, resource->format, resource->aspect, viewType, + attachmentView = CreateImageView(resource->image, attachmentFormat, 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", @@ -1438,11 +1444,23 @@ namespace MobileGL::MG_Backend::DirectVulkan { const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels, TextureResource &resource) { const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat()); - const VkFormat format = formatInfo.format; + VkFormat format = formatInfo.format; if (format == VK_FORMAT_UNDEFINED) { MGLOG_D("%s: format == VK_FORMAT_UNDEFINED", __func__); return false; } + // X8_D24 lacks optimal-tiling support on several drivers (lavapipe included); + // D32_SFLOAT holds every 24-bit depth value exactly, and the upload path + // converts the shadow words to float (see the pure-depth branch below). + if (format == VK_FORMAT_X8_D24_UNORM_PACK32) { + VkFormatProperties formatProperties{}; + vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties); + constexpr VkFormatFeatureFlags kDepthAttachmentAndSample = + VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; + if ((formatProperties.optimalTilingFeatures & kDepthAttachmentAndSample) != kDepthAttachmentAndSample) { + format = VK_FORMAT_D32_SFLOAT; + } + } if (texelSize.x() <= 0 || texelSize.y() <= 0 /*|| byteSize == 0*/) { MGLOG_D("%s: texelSize or byteSize is zero", __func__); return false; @@ -1514,6 +1532,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) { imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; } + // sRGB color images attach through their UNORM twin while GL_FRAMEBUFFER_SRGB is + // disabled (see ResolveSrgbAttachmentWriteFormat), which needs format-reinterpreting + // views - multisample sRGB render targets included. + if (ResolveSrgbAttachmentWriteFormat(format, false) != format && + (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 && + m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) { + imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + } VkImageUsageFlags desiredUsage = VK_IMAGE_USAGE_SAMPLED_BIT | @@ -2068,6 +2094,51 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } + // Pure-depth images whose canonical shadow layout differs from the image texel + // layout (the shadow keeps a full-scale 16/32-bit unorm word or a float; the + // image may be X8_D24 or a D32_SFLOAT fallback) convert per texel here. + if (uploadAspectMask == VK_IMAGE_ASPECT_DEPTH_BIT) { + const TextureInternalFormat depthInternal = mipmapTexture.GetFormat(); + const Bool shadowIsFloat = depthInternal == TextureInternalFormat::DepthComponent32F; + const Bool dstIsFloat = outResource.format == VK_FORMAT_D32_SFLOAT; + const Bool dstIsD24Word = outResource.format == VK_FORMAT_X8_D24_UNORM_PACK32; + stagingSize = 0; + for (auto& item : uploadItems) { + const SizeT texelCount = static_cast(item.texelSize.x()) * + static_cast(item.texelSize.y()) * + static_cast(std::max(item.texelSize.z(), 1)); + const SizeT shadowTexelSize = item.uploadByteSize / std::max(texelCount, 1); + const Bool needsConversion = + (dstIsFloat && !shadowIsFloat) || (dstIsD24Word && shadowTexelSize == 4 && !shadowIsFloat); + if (needsConversion) { + Vector converted(texelCount * 4); + const Uint8* shadow = static_cast(item.source); + for (SizeT t = 0; t < texelCount; ++t) { + Uint32 wide = 0; + if (shadowTexelSize == 2) { + Uint16 raw = 0; + std::memcpy(&raw, shadow + t * 2, sizeof(raw)); + wide = (static_cast(raw) << 16) | raw; + } else { + std::memcpy(&wide, shadow + t * 4, sizeof(wide)); + } + if (dstIsFloat) { + const float value = static_cast(static_cast(wide) / 4294967295.0); + std::memcpy(converted.data() + t * 4, &value, sizeof(value)); + } else { // X8_D24: depth in the low 24 bits of a 32-bit word + const Uint32 word = wide >> 8; + std::memcpy(converted.data() + t * 4, &word, sizeof(word)); + } + } + item.expandedData = Move(converted); + item.source = item.expandedData.data(); + item.uploadByteSize = item.expandedData.size(); + } + item.offset = stagingSize; + stagingSize += static_cast(item.uploadByteSize); + } + } + VkBuffer stagingBuffer = VK_NULL_HANDLE; VmaAllocation stagingAllocation = nullptr; diff --git a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp index 9ce4adce..af7645d0 100644 --- a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp +++ b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp @@ -43,8 +43,11 @@ namespace MobileGL { case TextureInternalFormat::SRGB8: case TextureInternalFormat::RGB8I: case TextureInternalFormat::RGB8UI: - case TextureInternalFormat::DepthComponent24: return 3; + // Canonical depth shadow is a full 32-bit unorm word (see PixelStoreProcessor), + // converted at upload to the image's own 24/32-bit layout. + case TextureInternalFormat::DepthComponent24: + return 4; case TextureInternalFormat::RGBA2: case TextureInternalFormat::RGBA4: diff --git a/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp b/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp index 0da4b2c6..60e9906f 100644 --- a/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp +++ b/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp @@ -95,6 +95,7 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { Int32, Half, Float32, + UNorm32, // 32-bit fixed-point depth shadow }; struct InternalShadowLayout { @@ -123,6 +124,21 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { Bool GetInternalShadowLayout(TextureInternalFormat internal, InternalShadowLayout& out) { switch (internal) { + // Depth shadows follow TextureFormatProcessor::NormalizePixelFormat: 16-bit + // unorm for DEPTH_COMPONENT16, 32-bit unorm for the 24/32-bit fixed-point + // depths, float for DEPTH_COMPONENT32F. + case TextureInternalFormat::DepthComponent16: + out = {1, ShadowComponent::UNorm16, false}; + return true; + case TextureInternalFormat::DepthComponent24: + case TextureInternalFormat::DepthComponent32: + case TextureInternalFormat::DepthComponent: + out = {1, ShadowComponent::UNorm32, false}; + return true; + case TextureInternalFormat::DepthComponent32F: + out = {1, ShadowComponent::Float32, false}; + return true; + case TextureInternalFormat::R8: case TextureInternalFormat::Red: out = {1, ShadowComponent::UNorm8, false}; return true; case TextureInternalFormat::RG8: @@ -299,8 +315,10 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { case TextureInputFormat::RGBAInteger: out = {{0, 1, 2, 3}, 4, true}; return true; case TextureInputFormat::BGRA: out = {{2, 1, 0, 3}, 4, false}; return true; case TextureInputFormat::BGRAInteger: out = {{2, 1, 0, 3}, 4, true}; return true; + // A depth value converts like a single normalized/float channel. + case TextureInputFormat::DepthComponent: out = {{0, -1, -1, -1}, 1, false}; return true; default: - return false; // depth / stencil / unknown + return false; // stencil / packed depth-stencil / unknown } } @@ -346,8 +364,7 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { out = isInteger ? ShadowComponent::Int16 : ShadowComponent::SNorm16; return true; case TexturePixelDataType::UnsignedInt: - if (!isInteger) return false; // no 32-bit normalized shadow layout - out = ShadowComponent::UInt32; + out = isInteger ? ShadowComponent::UInt32 : ShadowComponent::UNorm32; return true; case TexturePixelDataType::Int: if (!isInteger) return false; @@ -617,6 +634,12 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { case ShadowComponent::Float32: Memcpy(dst, &v, sizeof(v)); break; + case ShadowComponent::UNorm32: { + const auto out = static_cast( + std::llround(static_cast(std::clamp(v, 0.0f, 1.0f)) * 4294967295.0)); + Memcpy(dst, &out, sizeof(out)); + break; + } default: break; // integer components never reach the float encoder } @@ -771,6 +794,64 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { const Int effectiveHeight = (params.ImageHeight > 0) ? params.ImageHeight : height; const SizeT inputRowStride = CalculateRowStride(effectiveWidth, pixelSize, params.Alignment); + // GL_DEPTH_COMPONENT client data may populate a packed depth-stencil internal + // format (the stencil half becomes zero); the generic channel converter cannot + // express the packed shadow words, so convert here. + const Bool packedDepthStencilInternal = targetInternalFormat == TextureInternalFormat::Depth24Stencil8 || + targetInternalFormat == TextureInternalFormat::DepthStencil || + targetInternalFormat == TextureInternalFormat::Depth32FStencil8; + if (!isBitmap && packedDepthStencilInternal && textureInputFormat == TextureInputFormat::DepthComponent && + (inputDataType == TexturePixelDataType::Float || inputDataType == TexturePixelDataType::UnsignedInt || + inputDataType == TexturePixelDataType::UnsignedShort)) { + const Bool floatShadow = targetInternalFormat == TextureInternalFormat::Depth32FStencil8; + const SizeT outPixelSize = floatShadow ? 8 : 4; + outSize = static_cast(width) * height * std::max(depth, 1) * outPixelSize; + Uint8* outputPixels = static_cast(malloc(outSize)); + if (!outputPixels) { + outSize = 0; + return nullptr; + } + const Uint8* srcBase = static_cast(inputPixels) + + static_cast(params.SkipImages) * static_cast(effectiveHeight) * inputRowStride + + static_cast(params.SkipRows) * inputRowStride + + static_cast(params.SkipPixels) * pixelSize; + Uint8* dst = outputPixels; + for (Int z = 0; z < std::max(depth, 1); ++z) { + for (Int y = 0; y < height; ++y) { + const Uint8* srcRow = srcBase + + static_cast(z) * static_cast(effectiveHeight) * inputRowStride + + static_cast(y) * inputRowStride; + for (Int x = 0; x < width; ++x) { + Float depthValue = 0.0f; + if (inputDataType == TexturePixelDataType::Float) { + Memcpy(&depthValue, srcRow + static_cast(x) * 4, sizeof(depthValue)); + } else if (inputDataType == TexturePixelDataType::UnsignedInt) { + Uint32 raw = 0; + Memcpy(&raw, srcRow + static_cast(x) * 4, sizeof(raw)); + depthValue = static_cast(static_cast(raw) / 4294967295.0); + } else { + Uint16 raw = 0; + Memcpy(&raw, srcRow + static_cast(x) * 2, sizeof(raw)); + depthValue = static_cast(raw) / 65535.0f; + } + if (floatShadow) { + const Uint32 stencilWord = 0; + Memcpy(dst, &depthValue, sizeof(depthValue)); + Memcpy(dst + 4, &stencilWord, sizeof(stencilWord)); + dst += 8; + } else { + const Uint32 depth24 = static_cast( + std::llround(static_cast(std::clamp(depthValue, 0.0f, 1.0f)) * 16777215.0)); + const Uint32 word = depth24 << 8; + Memcpy(dst, &word, sizeof(word)); + dst += 4; + } + } + } + } + return outputPixels; + } + UnpackConversionSpec conversion{}; const Bool needConversion = !isBitmap && GetUnpackConversionSpec(targetInternalFormat, textureInputFormat, inputDataType, conversion); @@ -972,6 +1053,11 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { Memcpy(&v, p, sizeof(v)); return v; } + case ShadowComponent::UNorm32: { + Uint32 v; + Memcpy(&v, p, sizeof(v)); + return static_cast(static_cast(v) / 4294967295.0); + } default: return 0.0f; }