From 2d938971b9e1b51940d4ece2c05ab689914e4eb3 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 27 Aug 2026 04:24:39 -0400 Subject: [PATCH] [Fix] (Getter): saturate combined uniform components in 64-bit and answer the missing GL4 state tokens --- MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp | 51 +++++++++++++++++- MobileGL/MG_Test/SanityTest.cpp | 56 ++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index a907ed6f..69a1dd49 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -7,7 +7,9 @@ // End of Source File Header #include "GL_Getter.h" +#include #include +#include #include #include #include @@ -181,9 +183,19 @@ namespace MobileGL::MG_Impl::GLImpl { return index < 3 ? static_cast(MG_Util::ShaderTranspiler::MIN_COMPUTE_WORK_GROUP_SIZE[index]) : 0; } + // GL 4.6 core table 23.64: components + blocks * (blockSize / 4). The product has to be + // formed in 64 bits and saturated on the way out - it overflowed a signed 32-bit int on + // every Vulkan host that reports a large maxUniformBufferRange. A Mali driver answering + // 0xFFFFFFFF saturates to INT32_MAX in the loader, and 14 * (2147483647 / 4) + 4096 wraps + // to -1073737742, which the conformance suite read back as a limit "smaller than 58368". + // Saturating instead of wrapping is also the only honest answer: an implementation that + // can serve more components than a GLint holds still has to report a GLint. GLint GetMaxCombinedUniformComponents(GLint maxDefaultUniformComponents, GLint maxUniformBlocks, GLint maxUniformBlockSizeBytes) { - return maxDefaultUniformComponents + maxUniformBlocks * (maxUniformBlockSizeBytes / 4); + const Int64 blocks = std::max(static_cast(maxUniformBlocks), 0); + const Int64 componentsPerBlock = std::max(static_cast(maxUniformBlockSizeBytes), 0) / 4; + const Int64 total = static_cast(maxDefaultUniformComponents) + blocks * componentsPerBlock; + return static_cast(std::min(total, std::numeric_limits::max())); } bool TryDecodeIndexedBufferQuery(GLenum pname, BufferTarget& bufferTarget, IndexedBufferQueryKind& queryKind) { @@ -915,6 +927,11 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_POLYGON_OFFSET_UNITS: params[0] = MG_State::pGLContext->GetPolygonOffsetUnits(); return; + case GL_POLYGON_OFFSET_CLAMP: + // Float-native state, so it is answered here rather than through the integer + // fallback: glPolygonOffsetClamp(1, 1, 0.5) must read back as 0.5, not as 0. + params[0] = MG_State::pGLContext->GetPolygonOffsetClamp(); + return; case GL_SMOOTH_LINE_WIDTH_RANGE: { const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters(); params[0] = dynamicParameters.SmoothLineWidthRangeMin; @@ -1449,6 +1466,15 @@ namespace MobileGL::MG_Impl::GLImpl { *params = 0; return; } + // GL_TEXTURE_BUFFER_BINDING and GL_TEXTURE_BUFFER are the same token (0x8C2A): as a + // glGetIntegerv pname it asks which BUFFER object is bound to the buffer-texture target, + // not which texture is (that one is GL_TEXTURE_BINDING_BUFFER, handled by the texture-unit + // decoder above). + case GL_TEXTURE_BUFFER_BINDING: { + auto& obj = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Texture).GetBoundObject(); + *params = obj ? static_cast(obj->GetExternalIndex()) : 0; + return; + } case GL_BLEND: *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::Blend) ? GL_TRUE : GL_FALSE; return; @@ -1504,6 +1530,16 @@ namespace MobileGL::MG_Impl::GLImpl { // this single case serves every getter flavor. *params = static_cast(MG_State::pGLContext->GetClampReadColor()); return; + // glClipControl's two state variables (GL 4.5 core table 23.7). They answer from the + // state the entry point records, which is what the conformance suite's initial-value and + // set-then-get cases read - the RASTERIZATION half of clip control is a separate, + // backend-side question and does not gate the query. + case GL_CLIP_ORIGIN: + *params = static_cast(MG_State::pGLContext->GetClipOrigin()); + return; + case GL_CLIP_DEPTH_MODE: + *params = static_cast(MG_State::pGLContext->GetClipDepthMode()); + return; case GL_COLOR_CLEAR_VALUE: { const FloatVec4& clearColor = MG_State::pGLContext->GetClearColor(); params[0] = static_cast(clearColor.x()); @@ -1946,6 +1982,14 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_NUM_PROGRAM_BINARY_FORMATS: *params = 0; return; + // GL_ARB_spirv_extensions / GL 4.6 core 22.2. An implementation that advertises no + // SPIR-V extension answers zero here, and glGetStringi(GL_SPIR_V_EXTENSIONS, i) is then + // never legally called - MobileGL runs the module through its own translation pipeline + // and relies on no SPIR-V extension to do it, so zero is the true answer rather than a + // placeholder. + case GL_NUM_SPIR_V_EXTENSIONS: + *params = 0; + return; case GL_NUM_SHADER_BINARY_FORMATS: *params = 0; // ShaderBinary entrypoints are stubbed return; @@ -2006,6 +2050,11 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_PRIMITIVE_RESTART_INDEX: *params = static_cast(MG_State::pGLContext->GetPrimitiveRestartIndex()); return; + case GL_POLYGON_OFFSET_CLAMP: + // Float state (see GetFloatv); rounded to nearest for the integer query per GL 4.6 + // core 22.1's float-to-integer rule. + *params = static_cast(std::lround(MG_State::pGLContext->GetPolygonOffsetClamp())); + return; case GL_PROGRAM_BINARY_FORMATS: *params = 0; // program-binary entrypoints are stubbed return; diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index 8f93287b..ba00e611 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include namespace { @@ -3059,3 +3060,58 @@ TEST(DirectVulkanSanity, GraphicsSamplerFeedbackOnlyAliasesWritableOverlappingMi EXPECT_FALSE(UniformManager::SamplerOverlapsWritableImageSubresource(1, 3, 0, GL_WRITE_ONLY)); EXPECT_FALSE(UniformManager::SamplerOverlapsWritableImageSubresource(1, 3, 4, GL_WRITE_ONLY)); } + +// GL_MAX_COMBINED_*_UNIFORM_COMPONENTS is components + blocks * (blockSize / 4). The product was +// formed in signed 32-bit, and a Vulkan host that reports a large VkPhysicalDeviceLimits:: +// maxUniformBufferRange (a Mali driver answers 0xFFFFFFFF, which the loader saturates to +// INT32_MAX) made 14 * (2147483647 / 4) + 4096 wrap to -1073737742 - which is byte for byte what +// the conformance suite read back as "Limit value is: -1073737742 when it should not be smaller +// than 58368". GLES escaped it only because the ES driver answers 65536 for the block size. +TEST(GetterSanity, CombinedUniformComponentsSaturateInsteadOfOverflowing) { + using namespace MobileGL; + + MG_State::pGLContext = MakeUnique(); + + static constexpr GLenum kCombinedPnames[] = { + GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS, GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS, + GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS, GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS, + GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS, GL_MAX_COMPUTE_UNIFORM_COMPONENTS, + }; + // The GL 4.6 core table 23.64 floors for the five combined pnames above; compute's per-stage + // GL_MAX_COMPUTE_UNIFORM_COMPONENTS is not a combined limit and carries its own, much smaller + // floor, so only the sign of its answer is asserted. + static constexpr GLint kCombinedFloor = 58368; + + { + MG_Backend::DynamicBackendParameters params; + params.MaxUniformBlockSize = std::numeric_limits::max(); + MG_Backend::pActiveBackendObject = MakeUnique(params); + + for (const GLenum pname: kCombinedPnames) { + GLint reported = 0; + MG_Impl::GLImpl::GetIntegerv(pname, &reported); + EXPECT_GT(reported, 0) << "pname 0x" << pname << " wrapped to a negative combined component count"; + if (pname != GL_MAX_COMPUTE_UNIFORM_COMPONENTS) { + EXPECT_GE(reported, kCombinedFloor) << "pname 0x" << pname << " fell under the GL 4.6 floor"; + } + } + MG_Backend::pActiveBackendObject.reset(); + } + + // An ordinary 64 KiB block size still produces the plain arithmetic, not a saturated value: + // saturation must be the ceiling, never the answer. + { + MG_Backend::DynamicBackendParameters params; + params.MaxUniformBlockSize = 65536; + MG_Backend::pActiveBackendObject = MakeUnique(params); + + GLint reported = 0; + MG_Impl::GLImpl::GetIntegerv(GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS, &reported); + // 4096 default-block components + 14 blocks x (65536 / 4) components each. + EXPECT_EQ(reported, 4096 + 14 * (65536 / 4)); + EXPECT_LT(reported, std::numeric_limits::max()); + MG_Backend::pActiveBackendObject.reset(); + } + + MG_State::pGLContext.reset(); +}