[Fix] (Getter): answer the GL 4.6 limit surface honestly - tess/cull/subroutine pnames, TBuiltInResource drift, 84 UBO binding points, 64-bit GL_MAX_ELEMENT_INDEX, per-category sample truth

This commit is contained in:
2026-08-27 03:47:16 -04:00
parent 07669aacd4
commit 7168f2ef77
24 changed files with 905 additions and 120 deletions
@@ -1139,6 +1139,11 @@ namespace MobileGL::MG_Util::BackendLoader {
// 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;
// The cull half of the same extension, and the same "zero is a legal answer" rule: a cull
// distance discards the whole primitive, so promising eight on a driver that has none does
// not fail loudly, it drops every draw of a culling program.
GLint maxCullDistances = 0;
GLint maxCombinedClipAndCullDistances = 0;
GLint maxViewports = 16;
// GL_UNDEFINED_VERTEX is what stands when the probes below cannot run, and it is a legal
// answer rather than a placeholder: with neither geometry shaders nor a viewport array
@@ -1333,6 +1338,23 @@ namespace MobileGL::MG_Util::BackendLoader {
"rejected; reporting no clip distances");
maxClipDistances = 0;
}
// GL_MAX_CULL_DISTANCES_EXT (0x82F9) and GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES_EXT
// (0x82FA) are the same tokens as their desktop spellings and arrive with the same
// extension, so they are probed under the same guard and the same drain sandwich.
drainErrors();
glesFuncs.glGetIntegerv(GL_MAX_CULL_DISTANCES, &maxCullDistances);
if (drainErrors()) {
MGLOG_W("GL_EXT_clip_cull_distance is advertised but GL_MAX_CULL_DISTANCES was "
"rejected; reporting no cull distances");
maxCullDistances = 0;
}
drainErrors();
glesFuncs.glGetIntegerv(GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES, &maxCombinedClipAndCullDistances);
if (drainErrors()) {
MGLOG_W("GL_EXT_clip_cull_distance is advertised but "
"GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES was rejected; deriving it from the pair");
maxCombinedClipAndCullDistances = 0;
}
}
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims);
// GL_LAYER_PROVOKING_VERTEX is ES 3.2 core (it arrives with geometry shaders, which is
@@ -1562,6 +1584,14 @@ namespace MobileGL::MG_Util::BackendLoader {
// 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.MaxCullDistances = caps.SupportsClipDistance ? std::max(maxCullDistances, 0) : 0;
// The combined limit can never be smaller than either half (GL 4.6 core 11.1.3.10 / the
// EXT spec say so), so a driver that rejected the combined query but answered the other
// two still gets a usable - and never over-stated - number.
caps.MaxCombinedClipAndCullDistances =
caps.SupportsClipDistance
? std::max({maxCombinedClipAndCullDistances, caps.MaxClipDistances, caps.MaxCullDistances})
: 0;
caps.MaxViewports = maxViewports;
caps.LayerProvokingVertex = layerProvokingVertex;
caps.ViewportIndexProvokingVertex = viewportIndexProvokingVertex;
@@ -1651,6 +1681,8 @@ namespace MobileGL::MG_Util::BackendLoader {
// 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_CULL_DISTANCES: %d", caps.MaxCullDistances);
MGLOG_I(" GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES: %d", caps.MaxCombinedClipAndCullDistances);
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,
@@ -1299,6 +1299,11 @@ namespace MobileGL {
// GL_EXT_clip_cull_distance, so a driver without it has none. See the guarded probe
// in FillInGLESCapabilities.
Int MaxClipDistances = 0;
// Same contract, same reason, same extension: GL_MAX_CULL_DISTANCES_EXT and
// GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES_EXT exist in ES only under
// GL_EXT_clip_cull_distance, so zero is the honest answer without it.
Int MaxCullDistances = 0;
Int MaxCombinedClipAndCullDistances = 0;
Int MaxViewports = 16;
// GL_LAYER_PROVOKING_VERTEX (ES 3.2 core) and GL_VIEWPORT_INDEX_PROVOKING_VERTEX
// (GL_OES_viewport_array). GL_UNDEFINED_VERTEX is a legal answer for both and is what
@@ -199,6 +199,8 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.MaxDrawBuffers = SaturateToInt(p.limits.maxFragmentOutputAttachments);
caps.MaxColorAttachments = SaturateToInt(p.limits.maxColorAttachments);
caps.MaxClipDistances = SaturateToInt(p.limits.maxClipDistances);
caps.MaxCullDistances = SaturateToInt(p.limits.maxCullDistances);
caps.MaxCombinedClipAndCullDistances = SaturateToInt(p.limits.maxCombinedClipAndCullDistances);
caps.MaxViewports = SaturateToInt(p.limits.maxViewports);
caps.MaxViewportWidth = SaturateToInt(p.limits.maxViewportDimensions[0]);
caps.MaxViewportHeight = SaturateToInt(p.limits.maxViewportDimensions[1]);
@@ -239,6 +241,7 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.SupportsFragmentStoresAndAtomics = supportedFeatures.fragmentStoresAndAtomics == VK_TRUE;
caps.SupportsGeometryShader = supportedFeatures.geometryShader == VK_TRUE;
caps.SupportsShaderClipDistance = supportedFeatures.shaderClipDistance == VK_TRUE;
caps.SupportsShaderCullDistance = supportedFeatures.shaderCullDistance == VK_TRUE;
caps.MaxShaderStorageBlockSize = static_cast<SizeT>(p.limits.maxStorageBufferRange);
const Bool supportsShaderSubgroup = vk.vkGetPhysicalDeviceProperties2 &&
HasUsableShaderSubgroupSupport(subgroupProps);
@@ -319,6 +322,8 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.MaxDrawBuffers = SaturateToInt(properties.limits.maxFragmentOutputAttachments);
caps.MaxColorAttachments = SaturateToInt(properties.limits.maxColorAttachments);
caps.MaxClipDistances = SaturateToInt(properties.limits.maxClipDistances);
caps.MaxCullDistances = SaturateToInt(properties.limits.maxCullDistances);
caps.MaxCombinedClipAndCullDistances = SaturateToInt(properties.limits.maxCombinedClipAndCullDistances);
caps.MaxViewports = SaturateToInt(properties.limits.maxViewports);
caps.MaxViewportWidth = SaturateToInt(properties.limits.maxViewportDimensions[0]);
caps.MaxViewportHeight = SaturateToInt(properties.limits.maxViewportDimensions[1]);
@@ -336,6 +341,7 @@ namespace MobileGL::MG_Util::BackendLoader {
caps.SupportsFragmentStoresAndAtomics = false;
caps.SupportsGeometryShader = false;
caps.SupportsShaderClipDistance = false;
caps.SupportsShaderCullDistance = false;
caps.MaxShaderStorageBlockSize = static_cast<SizeT>(properties.limits.maxStorageBufferRange);
caps.SupportsShaderSubgroup = false;
caps.SubgroupSize = 0;
@@ -68,6 +68,11 @@ namespace MobileGL {
Int MaxDrawBuffers = 8;
Int MaxColorAttachments = 8;
Int MaxClipDistances = 8;
// VkPhysicalDeviceLimits::maxCullDistances / maxCombinedClipAndCullDistances, gated
// by SupportsShaderCullDistance exactly as the clip pair is gated by
// SupportsShaderClipDistance.
Int MaxCullDistances = 8;
Int MaxCombinedClipAndCullDistances = 8;
Int MaxViewports = 16;
Int MaxViewportWidth = 16384;
Int MaxViewportHeight = 16384;
@@ -106,6 +111,11 @@ namespace MobileGL {
// 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;
// VkPhysicalDeviceFeatures::shaderCullDistance, the same story one field down:
// VulkanRenderer already ENABLES this feature where the device has it, but nobody
// ever read the limits it unlocks, so the frontend advertised eight cull distances
// from a literal instead of from the device.
Bool SupportsShaderCullDistance = false;
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
Bool SupportsShaderSubgroup = false;
Uint32 SubgroupSize = 0;
@@ -23,6 +23,13 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
}
} // namespace
Int ResolveMaxVertexAttribs(const Bool hasBackend, const Int backendMaxVertexAttribs) {
constexpr Int capacity = static_cast<Int>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS);
if (!hasBackend) return capacity;
if (backendMaxVertexAttribs <= 0) return capacity;
return std::min(backendMaxVertexAttribs, capacity);
}
Uint64 ComputeCompileEnvFingerprint(const CompileEnv& env) {
Uint64 state = 0x9e3779b97f4a7c15ull;
HashValue(state, env.maxComputeWorkGroupSize[0]);
@@ -67,6 +74,24 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
// and expands gl_MaxClipDistances from the same number, so it decides both whether a
// shader compiles at all and what a module that reads the constant generates.
HashValue(state, env.params.MaxClipDistances);
// The cull-distance pair, added when the GL 4.6 API-surface wave made them env-derived:
// they were bare literals (8/8) in BuildTBuiltInResource while no backend had ever been
// asked whether it can host a cull distance. Exactly the MaxClipDistances class - glslang
// bounds gl_CullDistance[i] against maxCullDistances at parse and expands
// gl_MaxCullDistances / gl_MaxCombinedClipAndCullDistances from the same numbers.
HashValue(state, env.params.MaxCullDistances);
HashValue(state, env.params.MaxCombinedClipAndCullDistances);
// The texture-image-unit family, made env-derived in the same wave. They were stock
// glslang defaults (32/32/80) that disagreed with what glGetIntegerv answered, and
// gl_MaxTextureImageUnits / gl_MaxVertexTextureImageUnits / gl_MaxCombinedTextureImageUnits
// expand from them.
HashValue(state, env.params.MaxTextureImageUnits);
HashValue(state, env.params.MaxVertexTextureImageUnits);
HashValue(state, env.params.MaxCombinedTextureImageUnits);
// gl_MaxSamples, which also sizes gl_SampleMask[] / gl_SampleMaskIn[] and bounds a
// constant index into them, so a module that touches either generates different SPIR-V
// on two backends that report different sample counts.
HashValue(state, env.params.MaxSamples);
// The compute work-group limits, likewise added by wave3 (cb155c5b). They used to be
// hardcoded maxima in BuildTBuiltInResource, and the L1 key comment said in so many
// words that the day they became backend-derived they would have to move in here -
+19 -4
View File
@@ -84,11 +84,14 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
// * the DynamicBackendParameters fields BuildTBuiltInResource copies into
// TBuiltInResource - MaxImageUnits, MaxDrawBuffers, MaxVertexImageUniforms,
// MaxGeometryImageUniforms, MaxFragmentImageUniforms, MaxComputeImageUniforms,
// MaxCombinedImageUniforms, MaxComputeTextureImageUnits, MaxClipDistances. glslang
// MaxCombinedImageUniforms, MaxComputeTextureImageUnits, MaxClipDistances,
// MaxCullDistances, MaxCombinedClipAndCullDistances, MaxTextureImageUnits,
// MaxVertexTextureImageUnits, MaxCombinedTextureImageUnits, MaxSamples. glslang
// enforces those at parse, so they decide whether a shader compiles at all and can
// change the link result. MaxClipDistances moved in at the wave4 merge (4fc3531d),
// the third time in three waves that a hardcoded TBuiltInResource field became
// env-derived - assume the next wave does it again and re-audit.
// change the link result. MaxClipDistances moved in at the wave4 merge (4fc3531d)
// and the six after it at the GL 4.6 API-surface wave - the fourth time in four
// waves that a hardcoded TBuiltInResource field became env-derived. Assume the next
// wave does it again and re-audit.
// * maxComputeWorkGroupSize and maxComputeWorkGroupCount, all three components each.
// These moved IN at the dev merge that brought wave3's cb155c5b, which made
// BuildTBuiltInResource read them from the env instead of hardcoding a permissive
@@ -160,6 +163,18 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
}
};
// How many vertex input locations exist, from the frontend's point of view: the backend's
// advertised count bounded by the state layer's current-value storage capacity
// (VertexArrayObject::MAX_VERTEX_ATTRIBS). ONE definition, because three places have to
// agree on it and used to carry three copies of the formula - glGetIntegerv
// (VertexArrayImpl::GetMaxVertexAttribs), the limit reflection records vertex inputs
// against (ProgramLinkTask), and gl_MaxVertexAttribs (BuildTBuiltInResource, which had a
// hardcoded 64 the other two never saw). A disagreement there is not cosmetic: glslang
// ACCEPTS a vertex input at a location the runtime cannot bind, and the draw then silently
// reads nothing. `hasBackend` false means "no backend to be bounded by" and yields the
// storage capacity, matching what all three did before.
Int ResolveMaxVertexAttribs(Bool hasBackend, Int backendMaxVertexAttribs);
// Hashes every semantically relevant member. Public so a test can assert that two
// different envs really do produce different P0b cache keys.
Uint64 ComputeCompileEnvFingerprint(const CompileEnv& env);
@@ -101,16 +101,11 @@ namespace MobileGL {
Resources.maxClipPlanes = 6;
Resources.maxTextureUnits = 32;
Resources.maxTextureCoords = 32;
Resources.maxVertexAttribs = 64;
Resources.maxVertexUniformComponents = 4096;
Resources.maxVaryingFloats = 64;
Resources.maxVertexTextureImageUnits = 32;
Resources.maxCombinedTextureImageUnits = 80;
Resources.maxTextureImageUnits = 32;
Resources.maxVertexUniformComponents = MAX_VERTEX_UNIFORM_COMPONENTS;
Resources.maxVaryingFloats = MAX_VARYING_COMPONENTS;
Resources.maxFragmentUniformComponents = 4096;
Resources.maxDrawBuffers = 32;
Resources.maxVertexUniformVectors = 128;
Resources.maxVaryingVectors = 8;
Resources.maxVertexUniformVectors = MAX_VERTEX_UNIFORM_VECTORS;
Resources.maxVaryingVectors = MAX_VARYING_VECTORS;
Resources.maxFragmentUniformVectors = 256;
Resources.maxVertexOutputVectors = 16;
Resources.maxFragmentInputVectors = 15;
@@ -121,14 +116,12 @@ namespace MobileGL {
Resources.maxComputeImageUniforms = 8;
Resources.maxComputeAtomicCounters = MAX_ATOMIC_COUNTERS_PER_STAGE;
Resources.maxComputeAtomicCounterBuffers = MAX_ATOMIC_COUNTER_BUFFERS_PER_STAGE;
Resources.maxVaryingComponents = 60;
Resources.maxVaryingComponents = MAX_VARYING_COMPONENTS;
Resources.maxVertexOutputComponents = 64;
Resources.maxGeometryInputComponents = 64;
Resources.maxGeometryOutputComponents = 128;
Resources.maxFragmentInputComponents = 128;
Resources.maxImageUnits = 8;
Resources.maxCombinedImageUnitsAndFragmentOutputs = 8;
Resources.maxCombinedShaderOutputResources = 8;
Resources.maxImageSamples = 0;
Resources.maxVertexImageUniforms = 0;
Resources.maxTessControlImageUniforms = 0;
@@ -141,16 +134,18 @@ namespace MobileGL {
Resources.maxGeometryTotalOutputComponents = 1024;
Resources.maxGeometryUniformComponents = 1024;
Resources.maxGeometryVaryingComponents = 64;
Resources.maxTessControlInputComponents = 128;
Resources.maxTessControlOutputComponents = 128;
Resources.maxTessControlTextureImageUnits = 16;
Resources.maxTessControlUniformComponents = 1024;
Resources.maxTessControlTotalOutputComponents = 4096;
Resources.maxTessEvaluationInputComponents = 128;
Resources.maxTessEvaluationOutputComponents = 128;
Resources.maxTessEvaluationTextureImageUnits = 16;
Resources.maxTessEvaluationUniformComponents = 1024;
Resources.maxTessPatchComponents = 120;
// The tessellation block is shared with glGetIntegerv through Types.h; see the
// "Never move one of these without the other" note there.
Resources.maxTessControlInputComponents = MAX_TESS_CONTROL_INPUT_COMPONENTS;
Resources.maxTessControlOutputComponents = MAX_TESS_CONTROL_OUTPUT_COMPONENTS;
Resources.maxTessControlTextureImageUnits = MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS;
Resources.maxTessControlUniformComponents = MAX_TESS_CONTROL_UNIFORM_COMPONENTS;
Resources.maxTessControlTotalOutputComponents = MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS;
Resources.maxTessEvaluationInputComponents = MAX_TESS_EVALUATION_INPUT_COMPONENTS;
Resources.maxTessEvaluationOutputComponents = MAX_TESS_EVALUATION_OUTPUT_COMPONENTS;
Resources.maxTessEvaluationTextureImageUnits = MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS;
Resources.maxTessEvaluationUniformComponents = MAX_TESS_EVALUATION_UNIFORM_COMPONENTS;
Resources.maxTessPatchComponents = MAX_TESS_PATCH_COMPONENTS;
Resources.maxPatchVertices = 32;
Resources.maxTessGenLevel = 64;
Resources.maxViewports = 16;
@@ -176,9 +171,6 @@ namespace MobileGL {
Resources.maxAtomicCounterBufferSize = MAX_ATOMIC_COUNTER_BUFFER_SIZE;
Resources.maxTransformFeedbackBuffers = 4;
Resources.maxTransformFeedbackInterleavedComponents = 64;
Resources.maxCullDistances = 8;
Resources.maxCombinedClipAndCullDistances = 8;
Resources.maxSamples = 4;
Resources.maxMeshOutputVerticesNV = 256;
Resources.maxMeshOutputPrimitivesNV = 512;
Resources.maxMeshWorkGroupSizeX_NV = 32;
@@ -208,12 +200,37 @@ namespace MobileGL {
Resources.maxImageUnits = dynamicParameters.MaxImageUnits;
Resources.maxCombinedImageUnitsAndFragmentOutputs =
dynamicParameters.MaxImageUnits + dynamicParameters.MaxDrawBuffers;
// GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES and
// GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS are the SAME token (0x8F39), so
// the two glslang fields have to carry the same value: glGetIntegerv answers this
// expression while gl_MaxCombinedShaderOutputResources expanded from a stale
// literal 8, and the CTS compares the two directly.
Resources.maxCombinedShaderOutputResources =
Resources.maxCombinedImageUnitsAndFragmentOutputs;
Resources.maxVertexImageUniforms = dynamicParameters.MaxVertexImageUniforms;
Resources.maxGeometryImageUniforms = dynamicParameters.MaxGeometryImageUniforms;
Resources.maxFragmentImageUniforms = dynamicParameters.MaxFragmentImageUniforms;
Resources.maxComputeImageUniforms = dynamicParameters.MaxComputeImageUniforms;
Resources.maxCombinedImageUniforms = dynamicParameters.MaxCombinedImageUniforms;
Resources.maxComputeTextureImageUnits = dynamicParameters.MaxComputeTextureImageUnits;
// The texture-image-unit family and the draw-buffer count. These were stock
// glslang defaults (32 / 32 / 80 / 32) that had nothing to do with what
// glGetIntegerv answers off the same backend, and the divergence is a live
// correctness bug rather than a reporting one: gl_MaxDrawBuffers = 32 makes
// glslang ACCEPT a fragment output at location 8..31 that the runtime cannot
// bind, and gl_MaxCombinedTextureImageUnits = 80 under-reports a device that
// really has 96.
Resources.maxTextureImageUnits = dynamicParameters.MaxTextureImageUnits;
Resources.maxVertexTextureImageUnits = dynamicParameters.MaxVertexTextureImageUnits;
Resources.maxCombinedTextureImageUnits = dynamicParameters.MaxCombinedTextureImageUnits;
Resources.maxDrawBuffers = dynamicParameters.MaxDrawBuffers;
// The same number glGetIntegerv(GL_MAX_VERTEX_ATTRIBS) reports and the same one
// reflection records vertex inputs against - see ResolveMaxVertexAttribs.
Resources.maxVertexAttribs = ResolveMaxVertexAttribs(
env ? env->HasBackend() : (activeBackend != nullptr), dynamicParameters.MaxVertexAttribs);
// gl_MaxSamples, floored exactly as GL_Getter::GetAdvertisedMaxSamples floors
// GL_MAX_SAMPLES. It also sizes gl_SampleMask[] / gl_SampleMaskIn[].
Resources.maxSamples = std::max(dynamicParameters.MaxSamples, MIN_ADVERTISED_MAX_SAMPLES);
// 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,
@@ -222,6 +239,14 @@ namespace MobileGL {
// 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 cull pair, for the same reason and with a sharper edge: cull distance
// discards the WHOLE primitive, so a shader that gets to declare gl_CullDistance
// on a backend that cannot host one does not render subtly wrong pixels, it
// renders nothing at all. These were literal 8s that no backend was ever asked
// about; a backend without cull distances now reports 0 and glslang rejects the
// declaration with a diagnostic the application can read.
Resources.maxCullDistances = dynamicParameters.MaxCullDistances;
Resources.maxCombinedClipAndCullDistances = dynamicParameters.MaxCombinedClipAndCullDistances;
// 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
+47
View File
@@ -80,6 +80,53 @@ namespace MobileGL {
inline constexpr Int MAX_ATOMIC_COUNTER_BUFFERS_PER_STAGE = 8;
inline constexpr Int MAX_ATOMIC_COUNTERS_PER_STAGE = 8;
// ---- Tessellation per-stage resource limits ----
//
// Here for exactly the reason the atomic-counter block above is here. GL 4.6 requires
// glGetIntegerv and the matching gl_MaxTess* built-in constant to report the same
// number (KHR-GL45.limits.max_tess_* reads the query and then compiles a shader that
// writes the built-in into an SSBO and demands equality), and these numbers used to
// exist ONLY inside BuildTBuiltInResource - so gl_MaxTessControlInputComponents
// compiled fine while glGetIntegerv of the same limit had no case at all and answered
// GL_INVALID_ENUM. Never move one of these without the other.
//
// The values are the GL 4.6 core minimums (table 23.55), which is what a frontend that
// synthesizes the tessellation stages onto ES/Vulkan can honestly promise.
inline constexpr Int MAX_TESS_CONTROL_INPUT_COMPONENTS = 128;
inline constexpr Int MAX_TESS_CONTROL_OUTPUT_COMPONENTS = 128;
inline constexpr Int MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS = 16;
inline constexpr Int MAX_TESS_CONTROL_UNIFORM_COMPONENTS = 1024;
inline constexpr Int MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS = 4096;
inline constexpr Int MAX_TESS_EVALUATION_INPUT_COMPONENTS = 128;
inline constexpr Int MAX_TESS_EVALUATION_OUTPUT_COMPONENTS = 128;
inline constexpr Int MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS = 16;
inline constexpr Int MAX_TESS_EVALUATION_UNIFORM_COMPONENTS = 1024;
inline constexpr Int MAX_TESS_PATCH_COMPONENTS = 120;
// ---- Varying and default-block uniform capacities ----
//
// The *_VECTORS limits are the *_COMPONENTS ones counted in vec4s, so they are DERIVED
// rather than typed independently: GL_MAX_VARYING_COMPONENTS said 64 while
// GL_MAX_VARYING_VECTORS said 8, and GL_MAX_VERTEX_UNIFORM_COMPONENTS said 4096 while
// GL_MAX_VERTEX_UNIFORM_VECTORS said 128 - two pairs that cannot both describe the
// same capacity, and both *_VECTORS answers were below the GL 4.5 core minimum
// (15 and 256 respectively). Shared with BuildTBuiltInResource because
// gl_MaxVaryingVectors and gl_MaxVertexUniformVectors expand from the same numbers.
inline constexpr Int MAX_VARYING_COMPONENTS = 64;
inline constexpr Int MAX_VARYING_VECTORS = MAX_VARYING_COMPONENTS / 4;
inline constexpr Int MAX_VERTEX_UNIFORM_COMPONENTS = 4096;
inline constexpr Int MAX_VERTEX_UNIFORM_VECTORS = MAX_VERTEX_UNIFORM_COMPONENTS / 4;
// GL 4.6 core table 23.53 sets the GL_MAX_SAMPLES minimum at 4, and MobileGL floors
// the backend's answer at it (GL_Getter's GetAdvertisedMaxSamples). gl_MaxSamples has
// to expand to the SAME number - it is also what sizes gl_SampleMask[] /
// gl_SampleMaskIn[] and what bounds a constant index into them - so the floor lives
// here and both sides apply it. NOTE the deliberate asymmetry: only MAX_SAMPLES has a
// floor of 4. MAX_INTEGER_SAMPLES, MAX_COLOR_TEXTURE_SAMPLES and
// MAX_DEPTH_TEXTURE_SAMPLES have a minimum of ONE in the same table and are reported
// as the backend probed them.
inline constexpr Int MIN_ADVERTISED_MAX_SAMPLES = 4;
struct EmptyType {};
enum class ShaderCompileBits : Uint {