From 796a57a115f6a9b209f5101a1261f9fa8ef5e509 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 12 Aug 2026 06:25:35 -0400 Subject: [PATCH] [Feat, Test] (MG_Backend, MG_Util, MG_Impl): report the fp64 tier at startup, decline 64-bit vertex formats everywhere, advertise GL_ARB_gpu_shader_fp64 only on request --- MobileGL/Config.h | 8 +++ MobileGL/ConfigLoader.cpp | 1 + .../DirectGLES/BackendObject_DirectGLES.cpp | 9 +++ .../BackendObject_DirectVulkan.cpp | 31 ++++++++- .../Scenarios/DoublePrecisionScenario.cpp | 39 +++++++++++ MobileGL/MG_Util/SelfTest/DriverPost.cpp | 67 ++++++++++++++----- 6 files changed, 139 insertions(+), 16 deletions(-) diff --git a/MobileGL/Config.h b/MobileGL/Config.h index ca65bd67..93046299 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -82,6 +82,14 @@ namespace MobileGL::MG_Config { #endif // MOBILEGL_DISABLE_SUBGROUP: force-disable Vulkan shader subgroup support. Bool DisableSubgroup = false; + // MOBILEGL_ADVERTISE_FP64: add GL_ARB_gpu_shader_fp64 to the advertised extension + // string. `double` in a shader always WORKS - it is narrowed to 32 bits before any + // module reaches a backend (ShaderTranspiler::DemoteFloat64Pass) - but the extension + // promises 64-bit precision, and that is the one thing the narrowing cannot deliver. + // Off by default so an application that checks the string before using doubles keeps + // its float path; on for measuring what the conformance suite makes of the demoted + // precision. See the DemoteFloat64Pass header and the "fp64" POST row. + Bool AdvertiseFp64 = false; // MOBILEGL_MAGMA_R11G11B10F_FALLBACK: use fallback format for R11G11B10F on Vulkan. Bool MagmaR11G11B10FFallback = false; // MOBILEGL_MAGMA_FRAMESINFLIGHT: requested Magma frames in flight, defaulting to 3. diff --git a/MobileGL/ConfigLoader.cpp b/MobileGL/ConfigLoader.cpp index b8f4b7dc..2322e8e4 100644 --- a/MobileGL/ConfigLoader.cpp +++ b/MobileGL/ConfigLoader.cpp @@ -167,6 +167,7 @@ namespace MobileGL::MG_ConfigLoader { QueryEnvVariable("MOBILEGL_TRACE_ANGLE_VARIANT", features.TraceAngleVariant, ""); #endif features.DisableSubgroup = QueryEnvFlag("MOBILEGL_DISABLE_SUBGROUP"); + features.AdvertiseFp64 = QueryEnvFlag("MOBILEGL_ADVERTISE_FP64"); features.MagmaR11G11B10FFallback = QueryEnvFlag("MOBILEGL_MAGMA_R11G11B10F_FALLBACK"); features.MagmaFramesInFlight = QueryEnvUint32("MOBILEGL_MAGMA_FRAMESINFLIGHT", 3, 1, 64); features.AvoidSamplerMipmapMinFilter = diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index ba0a75c4..42c0a4e4 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -960,6 +960,15 @@ namespace MobileGL::MG_Backend::DirectGLES { if (MG_Util::Async::AsyncShaderCompileEnabled()) { extensions.push_back(E_GL_KHR_parallel_shader_compile); } + // GL_ARB_gpu_shader_fp64 is opt-in (MOBILEGL_ADVERTISE_FP64). Every `double` in a + // shader compiles and runs already - it is narrowed to 32 bits before the module + // reaches this backend - so an application that simply uses doubles needs nothing + // advertised. What the extension additionally promises is 64-bit PRECISION, which no + // mobile GPU has and the narrowing cannot fake, so advertising it by default would + // make an application that checks the string take a path MobileGL cannot honour. + if (MG_Config::Features.AdvertiseFp64) { + extensions.push_back(E_GL_ARB_gpu_shader_fp64); + } // Only advertised when the device driver actually has usable timer queries // (GL_EXT_disjoint_timer_query plus its entry points) and the // MOBILEGL_DISABLE_TIMERQUERY escape hatch is off. diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 4a25b2aa..da2e639b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -539,6 +539,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (MG_Util::Async::AsyncShaderCompileEnabled()) { extensions.push_back(E_GL_KHR_parallel_shader_compile); } + // GL_ARB_gpu_shader_fp64 is opt-in (MOBILEGL_ADVERTISE_FP64). Every `double` in a + // shader compiles and runs already - it is narrowed to 32 bits before the module + // reaches this backend - so an application that simply uses doubles needs nothing + // advertised. What the extension additionally promises is 64-bit PRECISION, which no + // mobile GPU has and the narrowing cannot fake, so advertising it by default would + // make an application that checks the string take a path MobileGL cannot honour. + if (MG_Config::Features.AdvertiseFp64) { + extensions.push_back(E_GL_ARB_gpu_shader_fp64); + } // GL_ARB_timer_query gates MC's F3 GPU% (LWJGL checks the extension string); // only advertised when the device actually supports timestamp queries and the // MOBILEGL_DISABLE_TIMERQUERY escape hatch is off. @@ -877,7 +886,27 @@ namespace MobileGL::MG_Backend::DirectVulkan { DynParams::PerLayerFramebufferAttachmentBit(TextureTarget::TextureCubeMapArray); } } - m_dynamicParameters.SupportsFloat64VertexAttributes = m_vulkanCaps.SupportsShaderFloat64; + // Never, on any device, and no longer for the reason it used to be. It used to track + // shaderFloat64 because a `dvec3` input needed the Float64 capability to exist in the + // module at all; a 64-bit vertex FETCH was already impossible (VK_FORMAT_R64*_SFLOAT is + // optional and lavapipe reports zero bufferFeatures for all four), so the attribute + // arrived as its 32-bit word pair and PackDoubleVertexInputsPass bitcast it back. + // + // The shader half of that is gone: every 64-bit float is narrowed before any module + // reaches a backend (ShaderTranspiler::DemoteFloat64Pass), so there is no `double` input + // left to bitcast INTO, and feeding a UINT-formatted attribute to what is now a `float` + // input would be silent garbage. Reconstructing the value would mean decoding the + // IEEE-754 double bit pattern in the shader - software fp64, which is precisely what the + // demotion exists to avoid - and on Espryt it would additionally need the ES driver to + // fetch 2N uint components where the application declared N doubles, which a dvec3 or + // dvec4 cannot even express within one attribute location. + // + // So glVertexAttribLFormat / glVertexAttribLPointer are declined here exactly as they + // already were on Espryt and on every real mobile device (Adreno and Mali both report + // shaderFloat64 == VK_FALSE), and for the same visible reason. A `dvec3` INPUT still + // compiles and draws - it is a `vec3` after demotion - as long as the application feeds + // it with glVertexAttribPointer(GL_FLOAT) rather than 64-bit data. + m_dynamicParameters.SupportsFloat64VertexAttributes = false; m_dynamicParameters.MaxShaderStorageBlockSize = std::min(m_vulkanCaps.MaxShaderStorageBlockSize, kMaxAdvertisedShaderStorageBlockSize); if (m_vulkanCaps.SupportsShaderSubgroup) { diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DoublePrecisionScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DoublePrecisionScenario.cpp index 51c37a61..fdac891c 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DoublePrecisionScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DoublePrecisionScenario.cpp @@ -314,5 +314,44 @@ void main() { EXPECT_EQ(glGetError(), static_cast(GL_NO_ERROR)); } + TEST_F(DoublePrecisionScenario, TheFp64ExtensionIsNotAdvertised) { + if (!Ready()) return; + // The shader above compiled, linked and ran without the extension string, which is + // the point: an application does not need GL_ARB_gpu_shader_fp64 advertised to USE + // doubles here. What the string additionally promises is 64-bit precision, and that + // is the one thing the demotion cannot deliver - so it stays off unless + // MOBILEGL_ADVERTISE_FP64 asks for it, and an application that branches on the + // string keeps taking its float path. + GLint extensionCount = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &extensionCount); + ASSERT_GT(extensionCount, 0); + bool advertised = false; + for (GLint i = 0; i < extensionCount; ++i) { + const char* name = reinterpret_cast(glGetStringi(GL_EXTENSIONS, static_cast(i))); + if (name != nullptr && std::string(name) == "GL_ARB_gpu_shader_fp64") advertised = true; + } + EXPECT_FALSE(advertised); + EXPECT_EQ(glGetError(), static_cast(GL_NO_ERROR)); + } + + TEST_F(DoublePrecisionScenario, A64BitVertexFormatIsDeclinedOnEveryBackend) { + if (!Ready()) return; + // The demotion leaves no 64-bit shader input to feed, so there is nothing a 64-bit + // vertex FETCH could be fetched into - on either backend, and no longer only on the + // ones whose device lacks shaderFloat64. Declined loudly rather than accepted and + // drawn as garbage; the matching POST row says the same thing at startup. + GLuint vao = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + while (glGetError() != GL_NO_ERROR) {} + + glVertexAttribLFormat(0, 3, GL_DOUBLE, 0); + EXPECT_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)); + + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); + while (glGetError() != GL_NO_ERROR) {} + } + } // namespace } // namespace MGITest diff --git a/MobileGL/MG_Util/SelfTest/DriverPost.cpp b/MobileGL/MG_Util/SelfTest/DriverPost.cpp index 3f9ccba6..63ddf5c1 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPost.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverPost.cpp @@ -46,6 +46,22 @@ namespace MobileGL::MG_Util::SelfTest { RankMobileGLReported = 5, }; + // Both backends' fp64 rows end the same way, and the sentence they end with depends on + // a config flag rather than on anything either backend probes: the demotion is what + // makes doubles work, but GL_ARB_gpu_shader_fp64 promises the PRECISION the demotion + // cannot deliver, so the string is opt-in and the row has to say which way it went. + String AppendFp64AdvertisementNote(String detail) { + if (MG_Config::Features.AdvertiseFp64) { + return Move(detail) + + ". GL_ARB_gpu_shader_fp64 IS advertised (MOBILEGL_ADVERTISE_FP64): an application " + "that checks the string will believe it has 64-bit precision, and it does not"; + } + return Move(detail) + + ". GL_ARB_gpu_shader_fp64 is not advertised, because the precision it promises is the " + "one thing the demotion cannot provide; set MOBILEGL_ADVERTISE_FP64=1 to advertise it " + "anyway"; + } + struct ReportBuilder { BackendPostReport report; Bool fatalFailed = false; @@ -484,14 +500,24 @@ namespace MobileGL::MG_Util::SelfTest { break; } } - // Reported rather than probed: this one cannot come out any other way. OpenGL ES has no - // double-precision vertex format and ESSL has no fp64 type, so there is no driver and no - // extension that could make it work - the row exists so the loss is named at startup - // instead of discovered as an unexplained GL_INVALID_OPERATION at draw setup. + // Both rows are reported rather than probed: neither can come out any other way. + // ESSL has no 64-bit float type at all, so no driver and no extension could change + // either answer, and the rows exist so the two halves of the loss are named at + // startup instead of discovered as a shader that will not compile or an + // unexplained GL_INVALID_OPERATION at draw setup. + builder.Pass("fp64", AppendFp64AdvertisementNote( + "demoted to fp32 - ESSL has no 64-bit float type, so every double / " + "dvec / dmat in a shader is narrowed to 32 bits before transpilation " + "(DemoteFloat64Pass). Such shaders COMPILE AND RUN, at single " + "precision; a block containing a double is re-laid-out for the " + "narrowed members, so an application that hard-codes std140 offsets " + "computed for doubles must query them instead")); builder.Warn("64-bit vertex attributes", - "not supported on any GLES driver (ES has no GL_DOUBLE vertex format and ESSL has " - "no fp64 type); glVertexAttribLFormat / glVertexArrayAttribLFormat report " - "GL_INVALID_OPERATION - use the Vulkan backend if the application needs them"); + "not supported (ES has no GL_DOUBLE vertex format, and after the fp64 demotion " + "above there is no 64-bit shader input left to feed either); " + "glVertexAttribLFormat / glVertexArrayAttribLFormat report " + "GL_INVALID_OPERATION - feed the attribute with glVertexAttribPointer(GL_FLOAT), " + "which a demoted dvec input reads correctly"); if (glesFuncs.glPatchParameteri != nullptr) { builder.Pass("Tessellation patch parameters", "glPatchParameteri present (GL_PATCH_VERTICES reaches the driver)"); @@ -1843,14 +1869,25 @@ namespace MobileGL::MG_Util::SelfTest { "unsupported; a GL_TEXTURE_CUBE_MAP_ARRAY texture gets no image at all, so sampling " "one reads nothing and glFramebufferTextureLayer on one is declined"); } - if (features.shaderFloat64 == VK_TRUE) { - builder.Pass("shaderFloat64", - "GLSL double/dvec/dmat and 64-bit vertex attributes (glVertexAttribLFormat) supported"); - } else { - builder.Warn("shaderFloat64", - "unsupported; any shader declaring a double fails to create a shader module, and " - "glVertexAttribLFormat reports GL_INVALID_OPERATION instead of feeding the attribute"); - } + // Reported whichever way the device answers, because MobileGL no longer follows the + // device here: every 64-bit float is narrowed to 32 bits before any module reaches this + // backend (DemoteFloat64Pass), so the Float64 capability is never declared and a device + // that HAS the feature gains nothing from it. The device's own answer is still worth + // printing - it is the reason the demotion is unconditional. + builder.Pass("fp64", AppendFp64AdvertisementNote( + format("demoted to fp32 (device shaderFloat64 = {}) - every double / dvec / " + "dmat in a shader is narrowed to 32 bits before pipeline creation, so " + "such shaders BUILD AND RUN at single precision on every device " + "instead of failing to create a shader module on the ones without the " + "feature. A block containing a double is re-laid-out for the narrowed " + "members, so an application that hard-codes std140 offsets computed " + "for doubles must query them instead", + features.shaderFloat64 == VK_TRUE ? "supported" : "unsupported"))); + builder.Warn("64-bit vertex attributes", + "not supported; there is no 64-bit shader input left to feed after the fp64 demotion " + "above, and no VK_FORMAT_R64*_SFLOAT vertex fetch to feed it with on most devices " + "anyway. glVertexAttribLFormat reports GL_INVALID_OPERATION - feed the attribute with " + "glVertexAttribPointer(GL_FLOAT), which a demoted dvec input reads correctly"); Bool shaderDrawParameters = false; if (vkGetPhysicalDeviceFeatures2Fn != nullptr && properties.apiVersion >= VK_API_VERSION_1_1) {