diff --git a/MobileGL/Config.h b/MobileGL/Config.h index e8715b67..a99cd7a9 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -80,6 +80,10 @@ namespace MobileGL::MG_Config { // rewrites the recognized workgroup prefix-scan template on Qualcomm devices with // subgroups wider than 32 lanes (see ShaderSourceProcessor's quirk registry). QuirkOverride SubgroupPrefixScanQuirk = QuirkOverride::Auto; + // MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS: leave the Vulkan robustBufferAccess device + // feature off. It is enabled by default to match GL's defined out-of-range fetch + // behavior; this escape hatch exists to measure or dodge its GPU cost on a device. + Bool DisableRobustBufferAccess = false; }; extern FeaturesTable Features; } // namespace MobileGL::MG_Config diff --git a/MobileGL/ConfigLoader.cpp b/MobileGL/ConfigLoader.cpp index 00a3f9b4..59aff28f 100644 --- a/MobileGL/ConfigLoader.cpp +++ b/MobileGL/ConfigLoader.cpp @@ -135,6 +135,7 @@ namespace MobileGL::MG_ConfigLoader { features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING"); features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS"); features.SubgroupPrefixScanQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN"); + features.DisableRobustBufferAccess = QueryEnvFlag("MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS"); } inline void InitBackendType() { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 4cd818e8..2b52e333 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -1341,7 +1341,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 && (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0; VkImageCreateFlags imageCreateFlags = shapeInfo.imageFlags; - if (supportsStorageImage && IsMutableStorageImageFormat(format)) { + if (supportsStorageImage && IsMutableStorageImageFormat(format) && + m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) { imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; } @@ -1409,9 +1410,27 @@ namespace MobileGL::MG_Backend::DirectVulkan { imageInfo.samples = resolvedSampleCount; if (isMultisampleTexture || (imageInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) { VkImageFormatProperties imageFormatProperties{}; - const VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( + VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, imageInfo.flags, &imageFormatProperties); + if (imageFormatResult != VK_SUCCESS && !isMultisampleTexture && + (imageInfo.flags & VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT) != 0) { + // Losing reinterpreted views only degrades the formatless-image feature for + // this texture; failing creation would lose the texture entirely, so retry + // as a plain immutable-format image. + MGLOG_W("%s: mutable image format=%d is unsupported for textureId=%d; creating " + "without VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT (format reinterpretation " + "will be unavailable for it)", + __func__, static_cast(format), texture.GetExternalIndex()); + // Remember the verdict so later syncs of same-format textures neither retry + // the probe nor flag-mismatch against this image and recreate it. + m_mutableFormatUnsupported.insert(format); + imageInfo.flags &= ~VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + imageCreateFlags = imageInfo.flags; + imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( + m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, + imageInfo.flags, &imageFormatProperties); + } if (imageFormatResult != VK_SUCCESS || (isMultisampleTexture && (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0)) { MGLOG_D("%s: image flags=0x%x sampleCount=%d are unsupported for textureId=%d target=%s " diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index a3b13f52..79ce1c54 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -13,6 +13,7 @@ #include #include #include +#include namespace MobileGL::MG_State::GLState { class ITextureObject; @@ -384,6 +385,9 @@ private: TextureResource* resource = nullptr; }; Vector m_drawSyncedThisDraw; + // Formats whose mutable-image probe failed on this device; their images are created + // without MUTABLE_FORMAT_BIT so repeat syncs neither re-probe nor flag-mismatch. + std::unordered_set m_mutableFormatUnsupported; std::unordered_map, TextureIdentityHash> m_aliveObjects; std::unordered_map m_textureResources; Vector> m_deferredReleases; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 3d604b5a..cf8edc47 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -1622,6 +1622,63 @@ void main() { case VK_FORMAT_R32G32B32A32_SFLOAT: Memcpy(rgba, source, sizeof(Float) * 4); return true; + // Single- and dual-channel formats the reinterpretation feature makes common + // as readback sources (iterationRP custom images are R32F/R32UI-class). + // Missing channels take GL's defaults: 0 for GB, 1 for alpha. + case VK_FORMAT_R32_SFLOAT: { + Float value = 0.0f; + Memcpy(&value, source, sizeof(value)); + rgba[0] = value; + rgba[1] = 0.0f; + rgba[2] = 0.0f; + rgba[3] = 1.0f; + return true; + } + case VK_FORMAT_R32G32_SFLOAT: { + Float values[2] = {0.0f, 0.0f}; + Memcpy(values, source, sizeof(values)); + rgba[0] = values[0]; + rgba[1] = values[1]; + rgba[2] = 0.0f; + rgba[3] = 1.0f; + return true; + } + case VK_FORMAT_R32_UINT: { + Uint32 value = 0; + Memcpy(&value, source, sizeof(value)); + rgba[0] = static_cast(value); + rgba[1] = 0.0f; + rgba[2] = 0.0f; + rgba[3] = 1.0f; + return true; + } + case VK_FORMAT_R32_SINT: { + Int32 value = 0; + Memcpy(&value, source, sizeof(value)); + rgba[0] = static_cast(value); + rgba[1] = 0.0f; + rgba[2] = 0.0f; + rgba[3] = 1.0f; + return true; + } + case VK_FORMAT_R16_SFLOAT: { + Uint16 value = 0; + Memcpy(&value, source, sizeof(value)); + rgba[0] = MG_Util::DecodeHalfBitsToFloat(value); + rgba[1] = 0.0f; + rgba[2] = 0.0f; + rgba[3] = 1.0f; + return true; + } + case VK_FORMAT_R16G16_SFLOAT: + for (SizeT component = 0; component < 2; ++component) { + Uint16 value = 0; + Memcpy(&value, source + component * sizeof(value), sizeof(value)); + rgba[component] = MG_Util::DecodeHalfBitsToFloat(value); + } + rgba[2] = 0.0f; + rgba[3] = 1.0f; + return true; default: return false; } @@ -7166,7 +7223,10 @@ void main() { // Match GL's robust buffer-fetch behavior where the Vulkan device supports it. This covers // out-of-range fetches; arbitrary GL vertex strides/offsets still need the explicit tight // repack in VertexInputStateFactory when they violate Vulkan's address-alignment rules. - deviceFeatures.robustBufferAccess = supportedDeviceFeatures.robustBufferAccess; + // MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS leaves it off to measure or dodge its GPU cost. + deviceFeatures.robustBufferAccess = MG_Config::Features.DisableRobustBufferAccess + ? VK_FALSE + : supportedDeviceFeatures.robustBufferAccess; deviceFeatures.geometryShader = supportedDeviceFeatures.geometryShader; deviceFeatures.independentBlend = supportedDeviceFeatures.independentBlend; m_independentBlendFeatureEnabled = deviceFeatures.independentBlend == VK_TRUE; @@ -7196,6 +7256,15 @@ void main() { if (m_unformattedFloatStorageImagesEnabled) { deviceFeatures.shaderStorageImageReadWithoutFormat = VK_TRUE; deviceFeatures.shaderStorageImageWriteWithoutFormat = VK_TRUE; + } else { + // Surface the degradation instead of failing silently: shader packs that bind a + // float storage image with a format different from its declaration (e.g. + // iterationRP) will render incorrectly on this device. + MGLOG_W("CreateLogicalDeviceAndQueues: shaderStorageImage*WithoutFormat unavailable " + "(read=%d write=%d); float storage-image format reinterpretation is disabled " + "and packs relying on it may misrender", + supportedDeviceFeatures.shaderStorageImageReadWithoutFormat, + supportedDeviceFeatures.shaderStorageImageWriteWithoutFormat); } deviceFeatures.drawIndirectFirstInstance = supportedDeviceFeatures.drawIndirectFirstInstance; deviceFeatures.multiDrawIndirect = supportedDeviceFeatures.multiDrawIndirect; diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index c93038aa..615a4724 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -794,6 +794,33 @@ TEST(DirectVulkanSanity, ReadbackConvertsRgba8AndRgba16fPixels) { EXPECT_FLOAT_EQ(rgba16fFloatResult[3], 1.0f); } +TEST(DirectVulkanSanity, ReadbackDecodesSingleChannel32BitFormats) { + using MobileGL::MG_Backend::DirectVulkan::VulkanRenderer; + + // The reinterpretation feature makes R32F/R32UI-class images common readback sources + // (iterationRP custom images). Missing channels take GL defaults: 0 for GB, 1 for alpha. + const MobileGL::Float r32f[] = {0.75f, -2.0f}; + MobileGL::Float r32fResult[8]{}; + ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels( + reinterpret_cast(r32f), VK_FORMAT_R32_SFLOAT, + 2, 1, GL_RGBA, GL_FLOAT, sizeof(MobileGL::Float) * 8, + reinterpret_cast(r32fResult))); + EXPECT_FLOAT_EQ(r32fResult[0], 0.75f); + EXPECT_FLOAT_EQ(r32fResult[1], 0.0f); + EXPECT_FLOAT_EQ(r32fResult[2], 0.0f); + EXPECT_FLOAT_EQ(r32fResult[3], 1.0f); + EXPECT_FLOAT_EQ(r32fResult[4], -2.0f); + + const MobileGL::Uint32 r32ui[] = {12345u}; + MobileGL::Float r32uiResult[4]{}; + ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels( + reinterpret_cast(r32ui), VK_FORMAT_R32_UINT, + 1, 1, GL_RGBA, GL_FLOAT, sizeof(MobileGL::Float) * 4, + reinterpret_cast(r32uiResult))); + EXPECT_FLOAT_EQ(r32uiResult[0], 12345.0f); + EXPECT_FLOAT_EQ(r32uiResult[3], 1.0f); +} + TEST(DirectVulkanSanity, DrawIndexedIndirectCommandMatchesGlAndVulkanLayout) { using namespace MobileGL::MG_Backend::DirectVulkan;