From 93a3b559073943dcbc13254401d15525a86d87df Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Fri, 31 Jul 2026 16:15:50 -0400 Subject: [PATCH] [Feat] (DirectVulkan): upload combined depth-stencil texture data UploadDirtyMipLevels used to skip D24S8/D32FS8 textures outright, leaving glTexImage-supplied depth-stencil data unuploaded (KHR-GL33 texture_repeat_mode depth24_stencil8 and texture_swizzle depth-stencil cases all sampled zeros). De-interleave the shadow's GL wire format into a depth plane (X8_D24 word / float) and a stencil byte plane and record one copy per aspect, with cross-conversion when the device backs the texture with the other depth-stencil format. Depth32FStencil8's shadow byte size also claimed 16 bytes/texel while the stored wire format (GL_FLOAT_32_UNSIGNED_INT_24_8_REV) is 8; that mismatch truncated every upload of it. Also route a multisample-texture sample-count request through the device's supported counts (round up, GL promises at-least semantics). --- .../Renderer/VkTextureManager.cpp | 159 ++++++++++++++---- .../DirectVulkan/Renderer/VkTextureManager.h | 3 + MobileGL/MG_Util/Metrics/TextureMetrics.cpp | 4 +- 3 files changed, 135 insertions(+), 31 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 2a0dab92..da5bd7bf 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -120,31 +120,21 @@ namespace MobileGL::MG_Backend::DirectVulkan { } static Bool TryResolveSampleCountFlagBits(Int requestedSamples, VkSampleCountFlagBits& outSampleCount) { - switch (requestedSamples) { - case 1: + // GL promises "at least the requested samples", so a non-power-of-two + // request (legal in GL, e.g. 3) rounds up to the next Vulkan bit. + if (requestedSamples <= 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: + } + if (requestedSamples > 64) { return false; } + Uint32 bit = 1; + while (bit < static_cast(requestedSamples)) { + bit <<= 1; + } + outSampleCount = static_cast(bit); + return true; } static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) { @@ -1536,6 +1526,44 @@ namespace MobileGL::MG_Backend::DirectVulkan { desiredUsage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; } + // Round a multisample request up to a count the device supports for this + // format (GL only promises "at least"), mirroring the renderbuffer path. + if (isMultisampleTexture && resolvedSampleCount != VK_SAMPLE_COUNT_1_BIT) { + auto supportedIt = m_multisampleCountsByFormat.find(format); + if (supportedIt == m_multisampleCountsByFormat.end()) { + VkImageFormatProperties imageFormatProperties{}; + VkSampleCountFlags supported = VK_SAMPLE_COUNT_1_BIT; + if (vkGetPhysicalDeviceImageFormatProperties(m_physicalDevice, format, shapeInfo.imageType, + VK_IMAGE_TILING_OPTIMAL, desiredUsage, imageCreateFlags, + &imageFormatProperties) == VK_SUCCESS) { + supported = imageFormatProperties.sampleCounts; + } + supportedIt = m_multisampleCountsByFormat.emplace(format, supported).first; + } + const VkSampleCountFlags supported = supportedIt->second; + if ((supported & resolvedSampleCount) == 0) { + Uint32 rounded = 0; + for (Uint32 bit = static_cast(resolvedSampleCount) << 1; bit <= VK_SAMPLE_COUNT_64_BIT; + bit <<= 1) { + if ((supported & bit) != 0) { + rounded = bit; + break; + } + } + if (rounded == 0) { + for (Uint32 bit = static_cast(resolvedSampleCount) >> 1; bit != 0; bit >>= 1) { + if ((supported & bit) != 0) { + rounded = bit; + break; + } + } + } + if (rounded != 0) { + resolvedSampleCount = static_cast(rounded); + } + } + } + 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()) && @@ -1957,17 +1985,74 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - // Combined depth-stencil images need per-aspect de-interleaved copies (VkBufferImageCopy - // aspectMask must have exactly one bit set). Until that is implemented, skip the upload - // instead of recording an invalid command buffer that kills the process. + // Combined depth-stencil images need per-aspect copies (VkBufferImageCopy aspectMask + // must have exactly one bit set), so de-interleave the shadow's GL wire format into + // a depth plane followed by a stencil plane per upload item. const VkImageAspectFlags uploadAspectMask = GetAspectMaskForFormat(outResource.format); - if ((uploadAspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) && (uploadAspectMask & VK_IMAGE_ASPECT_STENCIL_BIT)) { - MGLOG_E("UploadDirtyMipLevels: skipping unimplemented depth-stencil data upload for textureId=%d", - mipmapTexture.GetExternalIndex()); - for (const auto& item : uploadItems) { - mipmapTexture.MarkStorageDirty(item.target, item.level, false); + const Bool isCombinedDepthStencil = + (uploadAspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) && (uploadAspectMask & VK_IMAGE_ASPECT_STENCIL_BIT); + if (isCombinedDepthStencil) { + const Bool srcIsD24S8 = outResource.format == VK_FORMAT_D24_UNORM_S8_UINT; + const Bool srcIsD32FS8 = outResource.format == VK_FORMAT_D32_SFLOAT_S8_UINT; + if (!srcIsD24S8 && !srcIsD32FS8) { + MGLOG_E("UploadDirtyMipLevels: unsupported combined depth-stencil format %d for textureId=%d", + static_cast(outResource.format), mipmapTexture.GetExternalIndex()); + for (const auto& item : uploadItems) { + mipmapTexture.MarkStorageDirty(item.target, item.level, false); + } + return true; + } + 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); + MOBILEGL_ASSERT(shadowTexelSize == 4 || shadowTexelSize == 8, + "UploadDirtyMipLevels: unexpected depth-stencil shadow texel size %zu for textureId=%d", + shadowTexelSize, mipmapTexture.GetExternalIndex()); + // Depth plane as the aspect's buffer-copy format (32-bit word for + // D24: low 24 bits; float for D32F), then one stencil byte per texel. + Vector deinterleaved(texelCount * 4 + texelCount); + Uint8* depthPlane = deinterleaved.data(); + Uint8* stencilPlane = deinterleaved.data() + texelCount * 4; + const Uint8* shadow = static_cast(item.source); + for (SizeT t = 0; t < texelCount; ++t) { + if (shadowTexelSize == 8) { + // GL_FLOAT_32_UNSIGNED_INT_24_8_REV: float depth, then a word + // with stencil in its low 8 bits. + float depthValue; + Uint32 stencilWord; + std::memcpy(&depthValue, shadow + t * 8, sizeof(depthValue)); + std::memcpy(&stencilWord, shadow + t * 8 + 4, sizeof(stencilWord)); + if (srcIsD32FS8) { + std::memcpy(depthPlane + t * 4, &depthValue, sizeof(depthValue)); + } else { + const float clamped = std::min(std::max(depthValue, 0.0f), 1.0f); + const Uint32 depthWord = static_cast(clamped * 16777215.0f + 0.5f); + std::memcpy(depthPlane + t * 4, &depthWord, sizeof(depthWord)); + } + stencilPlane[t] = static_cast(stencilWord & 0xFFu); + } else { + // GL_UNSIGNED_INT_24_8: depth in the high 24 bits, stencil low 8. + Uint32 packed; + std::memcpy(&packed, shadow + t * 4, sizeof(packed)); + if (srcIsD24S8) { + const Uint32 depthWord = packed >> 8; + std::memcpy(depthPlane + t * 4, &depthWord, sizeof(depthWord)); + } else { + const float depthValue = static_cast(packed >> 8) / 16777215.0f; + std::memcpy(depthPlane + t * 4, &depthValue, sizeof(depthValue)); + } + stencilPlane[t] = static_cast(packed & 0xFFu); + } + } + item.expandedData = Move(deinterleaved); + item.source = item.expandedData.data(); + item.uploadByteSize = item.expandedData.size(); + item.offset = stagingSize; + stagingSize += static_cast(item.uploadByteSize); } - return true; } VkBuffer stagingBuffer = VK_NULL_HANDLE; @@ -2039,6 +2124,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { copy.imageOffset = {0, 0, 0}; copy.imageExtent = {static_cast(item.texelSize.x()), static_cast(item.texelSize.y()), depthSelectsArrayLayer ? 1u : depthOrLayers}; + if (isCombinedDepthStencil) { + const SizeT texelCount = static_cast(item.texelSize.x()) * + static_cast(item.texelSize.y()) * + static_cast(std::max(item.texelSize.z(), 1)); + VkBufferImageCopy depthCopy = copy; + depthCopy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + VkBufferImageCopy stencilCopy = copy; + stencilCopy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; + stencilCopy.bufferOffset = item.offset + static_cast(texelCount) * 4; + const VkBufferImageCopy copies[2] = {depthCopy, stencilCopy}; + vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 2, copies); + continue; + } vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index b375bc05..c1566f61 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -481,6 +481,9 @@ private: std::unordered_map m_textureResources; // Textures that have been bound to a GL image unit (see MarkStorageImageTexture). std::unordered_set m_storageImageTextures; + // Supported multisample counts per format, so repeat texture syncs do not + // re-query vkGetPhysicalDeviceImageFormatProperties. + std::unordered_map m_multisampleCountsByFormat; Vector> m_deferredReleases; Vector> m_deferredViewReleases; // Texture uploads are submitted out-of-band but NOT waited on (waiting diff --git a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp index cbeb8b92..2075cf08 100644 --- a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp +++ b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp @@ -91,6 +91,9 @@ namespace MobileGL { case TextureInternalFormat::RG32F: case TextureInternalFormat::RG32I: case TextureInternalFormat::RG32UI: + // Shadow bytes hold the GL_FLOAT_32_UNSIGNED_INT_24_8_REV wire format + // (float depth + a word whose low 8 bits are stencil), 8 bytes/texel. + case TextureInternalFormat::Depth32FStencil8: return 8; case TextureInternalFormat::RGB32F: @@ -101,7 +104,6 @@ namespace MobileGL { case TextureInternalFormat::RGBA32F: case TextureInternalFormat::RGBA32I: case TextureInternalFormat::RGBA32UI: - case TextureInternalFormat::Depth32FStencil8: return 16; case TextureInternalFormat::R11FG11FB10F: