[Fix, Test] (BackendLoader, DirectVulkan, ShaderTranspiler): report GL_MAX_CLIP_DISTANCES from the backend's real clip-distance capability

This commit is contained in:
2026-08-20 15:25:29 -04:00
parent 9bde0e500f
commit 4fc3531d0d
10 changed files with 194 additions and 7 deletions
@@ -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,
@@ -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;
@@ -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<SizeT>(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<SizeT>(properties.limits.maxStorageBufferRange);
caps.SupportsShaderSubgroup = false;
caps.SubgroupSize = 0;
@@ -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;
@@ -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