From 4fc3531d0d40b87c6e26c87f042bc085b2e0e9a6 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 20 Aug 2026 15:25:29 -0400 Subject: [PATCH] [Fix, Test] (BackendLoader, DirectVulkan, ShaderTranspiler): report GL_MAX_CLIP_DISTANCES from the backend's real clip-distance capability --- MobileGL/MG_Backend/BackendObject.h | 10 +++ .../BackendObject_DirectVulkan.cpp | 7 +- .../Scenarios/ClipDistanceScenario.cpp | 21 ++++++ .../BackendLoader/BackendLoaderTest.cpp | 73 +++++++++++++++++++ MobileGL/MG_Test/SanityTest.cpp | 22 ++++++ .../MG_Util/BackendLoaders/OpenGL/Loader.cpp | 46 +++++++++++- .../MG_Util/BackendLoaders/OpenGL/Loader.h | 5 +- .../MG_Util/BackendLoaders/Vulkan/Loader.cpp | 2 + .../MG_Util/BackendLoaders/Vulkan/Loader.h | 6 ++ .../ShaderTranspiler/ShaderCompiler.cpp | 9 ++- 10 files changed, 194 insertions(+), 7 deletions(-) diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index d3808546..59642841 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -372,6 +372,16 @@ namespace MobileGL { Int MaxComputeImageUniforms = 8; Int MaxDrawBuffers = 8; Int MaxColorAttachments = 8; + // GL_MAX_CLIP_DISTANCES. Zero is a legal answer here, not a placeholder, and a + // backend that cannot host a clip distance MUST report it: advertising eight the + // backend will refuse does not make gl_ClipDistance work, it only moves the failure + // from an honest "unsupported" at query time to a backend shader-compile error the + // frontend never surfaces, after which every draw with that program silently renders + // nothing. DirectGLES fills it from GL_EXT_clip_cull_distance, DirectVulkan from the + // shaderClipDistance device feature. The DEFAULT stays at the GL 4.3 core minimum + // because it describes the no-backend case (standalone shader compiles, unit tests), + // where there is no device to be honest about and BuildTBuiltInResource still has to + // hand glslang a workable gl_MaxClipDistances. Int MaxClipDistances = 8; Int MaxViewports = 16; Int MaxViewportWidth = 16384; diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 05d1a543..7c32172b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -905,7 +905,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Int maxSupportedDrawBuffers = static_cast(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS); m_dynamicParameters.MaxDrawBuffers = std::min(m_vulkanCaps.MaxDrawBuffers, maxSupportedDrawBuffers); m_dynamicParameters.MaxColorAttachments = std::min(m_vulkanCaps.MaxColorAttachments, maxSupportedDrawBuffers); - m_dynamicParameters.MaxClipDistances = m_vulkanCaps.MaxClipDistances; + // Same shape as the image-uniform limits three lines above: maxClipDistances is reported + // by every device, but declaring ClipDistance in a module needs the shaderClipDistance + // FEATURE, which VulkanRenderer enables exactly where the physical device has it. Without + // it the limit describes a capacity no shader may use, so report none. + m_dynamicParameters.MaxClipDistances = + m_vulkanCaps.SupportsShaderClipDistance ? std::max(m_vulkanCaps.MaxClipDistances, 0) : 0; m_dynamicParameters.MaxViewports = m_vulkanCaps.MaxViewports; m_dynamicParameters.MaxViewportWidth = m_vulkanCaps.MaxViewportWidth; m_dynamicParameters.MaxViewportHeight = m_vulkanCaps.MaxViewportHeight; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ClipDistanceScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ClipDistanceScenario.cpp index 15ccbfc1..15d9a9d9 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ClipDistanceScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ClipDistanceScenario.cpp @@ -151,6 +151,18 @@ void main() { fragColor = vec4(0.0, 1.0, 0.0, 1.0); } glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, out); } + // GL_MAX_CLIP_DISTANCES is a real backend answer, not a constant: DirectGLES reports + // 0 on a driver without GL_EXT_clip_cull_distance, and DirectVulkan reports 0 without + // the shaderClipDistance device feature. On such a stack the shader above cannot + // compile - and MUST not, because declaring a clip distance the backend cannot host + // is exactly what used to link cleanly and then render nothing. Skip rather than + // fail: there is no clipping to assert about. + static bool BackendHostsTwoClipDistances() { + GLint maxClipDistances = 0; + glGetIntegerv(GL_MAX_CLIP_DISTANCES, &maxClipDistances); + return maxClipDistances >= 2; + } + // Never assume the eight start disabled - see the header note about // XfbAfterClipDistanceScenario leaving one on for the rest of the process. static void DisableEveryClipDistance() { @@ -229,6 +241,9 @@ void main() { fragColor = vec4(0.0, 1.0, 0.0, 1.0); } // The claim: an enabled clip distance removes the fragments where it is negative. TEST_F(ClipDistanceScenario, AnEnabledClipDistanceRemovesTheNegativeHalf) { if (!Ready()) return; + if (!BackendHostsTwoClipDistances()) { + GTEST_SKIP() << "this backend advertises no clip distances, so there is nothing to clip with"; + } HeadlessGL& gl = Gl(); const int width = gl.Width(); const int height = gl.Height(); @@ -280,6 +295,9 @@ void main() { fragColor = vec4(0.0, 1.0, 0.0, 1.0); } // draw simply failed - would pass the case above. TEST_F(ClipDistanceScenario, ADisabledClipDistanceRemovesNothing) { if (!Ready()) return; + if (!BackendHostsTwoClipDistances()) { + GTEST_SKIP() << "this backend advertises no clip distances, so there is nothing to clip with"; + } HeadlessGL& gl = Gl(); const int width = gl.Width(); const int height = gl.Height(); @@ -329,6 +347,9 @@ void main() { fragColor = vec4(0.0, 1.0, 0.0, 1.0); } // passes both cases above and fails this one. TEST_F(ClipDistanceScenario, TheEnablesAreIndependentPerDistance) { if (!Ready()) return; + if (!BackendHostsTwoClipDistances()) { + GTEST_SKIP() << "this backend advertises no clip distances, so there is nothing to clip with"; + } HeadlessGL& gl = Gl(); const int width = gl.Width(); const int height = gl.Height(); diff --git a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp index c69b8088..77c981af 100644 --- a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp +++ b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp @@ -45,6 +45,13 @@ namespace { GLint maxFragmentSsboBlocks = 9; bool tessAndGeometrySsboBlocksQueried = false; bool perStageSsboBlockQueryRaisesError = false; + // GL_MAX_CLIP_DISTANCES. Not ES core in any version - it exists only as + // GL_MAX_CLIP_DISTANCES_EXT under GL_EXT_clip_cull_distance - so asking a driver without + // the extension raises GL_INVALID_ENUM and leaves the out-param untouched. The "queried" + // flag is what pins the gating; the "raises error" knob is what pins the drain. + GLint maxClipDistances = 8; + bool maxClipDistancesQueried = false; + bool clipDistanceQueryRaisesError = false; GLfloat minFragmentInterpolationOffset = -0.75f; GLfloat maxFragmentInterpolationOffset = 0.625f; GLint fragmentInterpolationOffsetBits = 6; @@ -160,6 +167,14 @@ namespace { case GL_MAX_COMPUTE_IMAGE_UNIFORMS: *data = g_fake.maxComputeImageUniforms; break; + case GL_MAX_CLIP_DISTANCES: + g_fake.maxClipDistancesQueried = true; + if (g_fake.clipDistanceQueryRaisesError) { + g_fake.pendingError = GL_INVALID_ENUM; + } else { + *data = g_fake.maxClipDistances; + } + break; case GL_FRAGMENT_INTERPOLATION_OFFSET_BITS: g_fake.fragmentInterpolationLimitsQueried = true; if (g_fake.fragmentInterpolationQueryRaisesError) { @@ -642,6 +657,64 @@ TEST(PerStageStorageBlockCapabilities, ARejectedQueryIsDrainedAndFallsBackToTheS EXPECT_EQ(g_fake.pendingError, static_cast(GL_NO_ERROR)); } +// GL_MAX_CLIP_DISTANCES is the same defect as the per-stage storage blocks above, one pname +// over: the query does not exist without GL_EXT_clip_cull_distance, so an unguarded probe left +// an optimistic 8 behind on every ARM driver. Advertising eight clip planes a driver cannot host +// does not make gl_ClipDistance work - SPIRV-Cross emits it behind an `#extension ... : require` +// the ESSL compiler rejects, DirectGLES has nowhere to put the per-distance enables, and the +// draw renders nothing while LINK_STATUS says everything is fine. +TEST(ClipDistanceCapabilities, NoExtensionMeansNoClipDistancesAndNoQuery) { + const auto funcs = MakeFakeGLESFunctions(); + + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + + MobileGL::MG_External::GLESCapabilities caps; + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(caps, funcs)); + + EXPECT_FALSE(caps.SupportsClipDistance); + EXPECT_EQ(caps.MaxClipDistances, 0); + EXPECT_FALSE(g_fake.maxClipDistancesQueried) + << "GL_MAX_CLIP_DISTANCES is not ES core; asking for it without the extension only leaks " + "a GL_INVALID_ENUM"; +} + +// The other half of the same claim, and the one that keeps this from being a blanket zero: a +// driver that HAS the extension must have its real limit come through untouched. Adreno does, +// and it passes the clip-distance conformance cases on the strength of it. +TEST(ClipDistanceCapabilities, TheExtensionIsQueriedAndItsLimitIsReportedVerbatim) { + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + g_fake.extensions.emplace_back("GL_EXT_clip_cull_distance"); + g_fake.maxClipDistances = 6; + const auto funcs = MakeFakeGLESFunctions(); + + MobileGL::MG_External::GLESCapabilities caps; + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(caps, funcs)); + + EXPECT_TRUE(caps.SupportsClipDistance); + EXPECT_TRUE(g_fake.maxClipDistancesQueried); + EXPECT_EQ(caps.MaxClipDistances, 6); +} + +// A driver that advertises the extension and then refuses the query is a driver fault, not a +// missing feature - but the answer has to be the honest zero either way, and the error must not +// be left for the application's first glGetError to find. +TEST(ClipDistanceCapabilities, ARejectedQueryIsDrainedAndReportsZero) { + ResetFakeDriver(); + g_fake.maxVertexSsboBlocks = 0; + g_fake.extensions.emplace_back("GL_EXT_clip_cull_distance"); + g_fake.clipDistanceQueryRaisesError = true; + const auto funcs = MakeFakeGLESFunctions(); + + MobileGL::MG_External::GLESCapabilities caps; + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(caps, funcs)); + + EXPECT_TRUE(g_fake.maxClipDistancesQueried); + EXPECT_EQ(caps.MaxClipDistances, 0); + EXPECT_EQ(funcs.glGetError(), GL_NO_ERROR) << "the failed query must not leave an error behind"; +} + TEST(FragmentInterpolationCapabilities, QueriesOnlyWhenSupportedAndPreservesDriverLimits) { const auto funcs = MakeFakeGLESFunctions(); diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index a2193fce..beea5f01 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -643,6 +643,28 @@ TEST(DirectGLESSanity, PreservesHostPerStageImageUniformLimits) { EXPECT_EQ(params.MaxComputeImageUniforms, 5); } +// maxClipDistances is a LIMIT every Vulkan device reports; declaring ClipDistance in a module +// needs the shaderClipDistance FEATURE, which is separate and which VulkanRenderer enables only +// where the physical device has it. Forwarding the limit without the feature advertises eight +// clip planes no shader may use - the same shape as the image-uniform limits above, and the same +// shape as the GL_EXT_clip_cull_distance lie on DirectGLES. Not a blanket zero: a device WITH the +// feature keeps its real number. +TEST(DirectVulkanSanity, GatesClipDistancesOnTheShaderClipDistanceFeature) { + using namespace MobileGL; + + MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend; + MG_External::VulkanCapabilities caps; + caps.MaxClipDistances = 8; + + caps.SupportsShaderClipDistance = false; + backend.ApplyVulkanCapabilitiesForTesting(caps); + EXPECT_EQ(backend.GetDynamicParameters().MaxClipDistances, 0); + + caps.SupportsShaderClipDistance = true; + backend.ApplyVulkanCapabilitiesForTesting(caps); + EXPECT_EQ(backend.GetDynamicParameters().MaxClipDistances, 8); +} + TEST(FragmentInterpolationCapabilities, PlumbsGLESAndBothVulkanPropertyPaths) { using namespace MobileGL; diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index b22d522f..f2da5d89 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -1063,7 +1063,15 @@ namespace MobileGL::MG_Util::BackendLoader { GLint maxComputeImageUniforms = 8; GLint maxDrawBuffers = 8; GLint maxColorAttachments = 8; - GLint maxClipDistances = 8; + // Zero is a legal answer, not a placeholder. GL_MAX_CLIP_DISTANCES exists in ES only as + // GL_MAX_CLIP_DISTANCES_EXT under GL_EXT_clip_cull_distance, so on a driver without that + // extension there is nowhere to put a clip distance at all: SPIRV-Cross emits + // gl_ClipDistance behind an `#extension ... : require` the ESSL compiler rejects, and + // DirectGLES has no state to forward the per-distance enables into (see the gate in + // DirectGLES::SyncRenderState). Starting at 8 meant a probe that could never run left an + // optimistic 8 behind, so the frontend promised eight clip planes and every draw with a + // clipping program silently rendered nothing. The guarded probe below only ever widens it. + GLint maxClipDistances = 0; GLint maxViewports = 16; GLfloat minFragmentInterpolationOffset = -0.5f; GLfloat maxFragmentInterpolationOffset = 0.4375f; @@ -1195,7 +1203,30 @@ namespace MobileGL::MG_Util::BackendLoader { } glesFuncs.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); glesFuncs.glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments); - glesFuncs.glGetIntegerv(GL_MAX_CLIP_DISTANCES, &maxClipDistances); + // GL_MAX_CLIP_DISTANCES is 0x0D32, which ES only ever spells GL_MAX_CLIP_DISTANCES_EXT and + // only ever has under GL_EXT_clip_cull_distance. The extension was already resolved into + // caps.SupportsClipDistance a few hundred lines above and is the same flag DirectGLES + // gates the CLIP_DISTANCEi enable forwarding on, so ask the driver only where the pname + // exists; everywhere else the honest 0 stands and no GL_INVALID_ENUM is left behind for an + // unrelated query - or the application's first glGetError - to trip over. + if (caps.SupportsClipDistance) { + if (glesFuncs.glGetError) { + while (glesFuncs.glGetError() != GL_NO_ERROR) { + } + } + glesFuncs.glGetIntegerv(GL_MAX_CLIP_DISTANCES, &maxClipDistances); + Bool queryFailed = false; + if (glesFuncs.glGetError) { + while (glesFuncs.glGetError() != GL_NO_ERROR) { + queryFailed = true; + } + } + if (queryFailed) { + MGLOG_W("GL_EXT_clip_cull_distance is advertised but GL_MAX_CLIP_DISTANCES was " + "rejected; reporting no clip distances"); + maxClipDistances = 0; + } + } glesFuncs.glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports); glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims); glesFuncs.glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &viewportSubpixelBits); @@ -1368,7 +1399,9 @@ namespace MobileGL::MG_Util::BackendLoader { caps.MaxComputeImageUniforms = maxComputeImageUniforms; caps.MaxDrawBuffers = maxDrawBuffers; caps.MaxColorAttachments = maxColorAttachments; - caps.MaxClipDistances = maxClipDistances; + // A driver is free to write nonsense into an out-param it then rejects, and without the + // extension the probe above never ran at all - so the flag, not the local, decides. + caps.MaxClipDistances = caps.SupportsClipDistance ? std::max(maxClipDistances, 0) : 0; caps.MaxViewports = maxViewports; caps.MaxViewportWidth = maxViewportDims[0]; caps.MaxViewportHeight = maxViewportDims[1]; @@ -1450,7 +1483,12 @@ namespace MobileGL::MG_Util::BackendLoader { MGLOG_I(" GL_MAX_COMPUTE_IMAGE_UNIFORMS: %d", caps.MaxComputeImageUniforms); MGLOG_I(" GL_MAX_DRAW_BUFFERS: %d", caps.MaxDrawBuffers); MGLOG_I(" GL_MAX_COLOR_ATTACHMENTS: %d", caps.MaxColorAttachments); - MGLOG_I(" GL_MAX_CLIP_DISTANCES: %d", caps.MaxClipDistances); + // Worth spelling the reason out for the same reason the per-stage storage block counts + // are: a zero here is what stops an application's gl_ClipDistance from ever clipping, and + // reading it back from an artifact is the difference between "MobileGL dropped my draw" + // and "this driver has no clip distances". + MGLOG_I(" GL_MAX_CLIP_DISTANCES: %d%s", caps.MaxClipDistances, + caps.SupportsClipDistance ? "" : " (no GL_EXT_clip_cull_distance on this driver)"); MGLOG_I(" GL_MAX_VIEWPORTS: %d", caps.MaxViewports); MGLOG_I(" GL_MAX_VIEWPORT_DIMS: [%d, %d]", caps.MaxViewportWidth, caps.MaxViewportHeight); MGLOG_I(" GL_VIEWPORT_BOUNDS_RANGE: [%.3f, %.3f]", caps.ViewportBoundsRangeMin, diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index 41e5d63a..d495738c 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -1275,7 +1275,10 @@ namespace MobileGL { Int MaxComputeImageUniforms = 8; Int MaxDrawBuffers = 8; Int MaxColorAttachments = 8; - Int MaxClipDistances = 8; + // Zero is a legal answer, not a placeholder: ES reaches clip distances only through + // GL_EXT_clip_cull_distance, so a driver without it has none. See the guarded probe + // in FillInGLESCapabilities. + Int MaxClipDistances = 0; Int MaxViewports = 16; Int MaxViewportWidth = 16384; Int MaxViewportHeight = 16384; diff --git a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp index 872d6775..44511b19 100644 --- a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp @@ -237,6 +237,7 @@ namespace MobileGL::MG_Util::BackendLoader { supportedFeatures.vertexPipelineStoresAndAtomics == VK_TRUE; caps.SupportsFragmentStoresAndAtomics = supportedFeatures.fragmentStoresAndAtomics == VK_TRUE; caps.SupportsGeometryShader = supportedFeatures.geometryShader == VK_TRUE; + caps.SupportsShaderClipDistance = supportedFeatures.shaderClipDistance == VK_TRUE; caps.MaxShaderStorageBlockSize = static_cast(p.limits.maxStorageBufferRange); const Bool supportsShaderSubgroup = vk.vkGetPhysicalDeviceProperties2 && HasUsableShaderSubgroupSupport(subgroupProps); @@ -331,6 +332,7 @@ namespace MobileGL::MG_Util::BackendLoader { caps.SupportsVertexPipelineStoresAndAtomics = false; caps.SupportsFragmentStoresAndAtomics = false; caps.SupportsGeometryShader = false; + caps.SupportsShaderClipDistance = false; caps.MaxShaderStorageBlockSize = static_cast(properties.limits.maxStorageBufferRange); caps.SupportsShaderSubgroup = false; caps.SubgroupSize = 0; diff --git a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.h b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.h index 171a8100..a4d60fdd 100644 --- a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.h @@ -96,6 +96,12 @@ namespace MobileGL { Bool SupportsVertexPipelineStoresAndAtomics = false; Bool SupportsFragmentStoresAndAtomics = false; Bool SupportsGeometryShader = false; + // VkPhysicalDeviceFeatures::shaderClipDistance. maxClipDistances is a LIMIT and is + // reported whatever the feature says, so the limit alone does not mean a module may + // declare ClipDistance - VulkanRenderer enables the feature only where the physical + // device has it, and without it a shader writing gl_ClipDistance is invalid. Very + // widely supported, hence read from the device features and never assumed false. + Bool SupportsShaderClipDistance = false; SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024; Bool SupportsShaderSubgroup = false; Uint32 SubgroupSize = 0; diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index d96e36de..506c0931 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -82,7 +82,6 @@ namespace MobileGL { Resources.maxFragmentInputVectors = 15; Resources.minProgramTexelOffset = -8; Resources.maxProgramTexelOffset = 7; - Resources.maxClipDistances = 8; Resources.maxComputeUniformComponents = MAX_COMPUTE_UNIFORM_COMPONENTS; Resources.maxComputeTextureImageUnits = 16; Resources.maxComputeImageUniforms = 8; @@ -173,6 +172,14 @@ namespace MobileGL { Resources.maxComputeImageUniforms = dynamicParameters.MaxComputeImageUniforms; Resources.maxCombinedImageUniforms = dynamicParameters.MaxCombinedImageUniforms; Resources.maxComputeTextureImageUnits = dynamicParameters.MaxComputeTextureImageUnits; + // Load-bearing, not cosmetic. glslang rejects gl_ClipDistance[i] for + // i >= maxClipDistances (ParseHelper.cpp) and expands gl_MaxClipDistances from the + // same number, so tracking the backend limit is what turns "the program links, + // the backend's shader compile fails somewhere the frontend never surfaces, and + // the draw renders nothing" into an honest glCompileShader error with a log. It is + // also what makes glGetIntegerv(GL_MAX_CLIP_DISTANCES) and gl_MaxClipDistances + // agree, which KHR-GLxx.clip_distance.coverage compares directly. + Resources.maxClipDistances = dynamicParameters.MaxClipDistances; // The compute work-group limits are the env's, not the backend parameters': they // are the only ones that come from a REAL indexed driver query, which