[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
+13
View File
@@ -389,6 +389,19 @@ namespace MobileGL {
// where there is no device to be honest about and BuildTBuiltInResource still has to
// hand glslang a workable gl_MaxClipDistances.
Int MaxClipDistances = 8;
// GL_MAX_CULL_DISTANCES and GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES, under exactly
// the contract stated for MaxClipDistances above: ZERO IS A LEGAL ANSWER and a
// backend that cannot host a cull distance MUST report it. The failure this prevents
// is worse than the clip one, because cull distance discards the whole primitive:
// glslang bounds gl_CullDistance[i] against maxCullDistances and expands
// gl_MaxCullDistances from it, SPIRV-Cross then emits
// `#extension GL_EXT_clip_cull_distance : require` into the ESSL, and a host driver
// without that extension rejects the program in an info log nobody surfaces. These
// used to be bare 8s inside BuildTBuiltInResource with no backend consulted at all.
// The DEFAULTS are the GL 4.5 core minimums for the same reason MaxClipDistances'
// is: they describe the no-backend case (standalone compiles, unit tests).
Int MaxCullDistances = 8;
Int MaxCombinedClipAndCullDistances = 8;
Int MaxViewports = 16;
// GL_LAYER_PROVOKING_VERTEX / GL_VIEWPORT_INDEX_PROVOKING_VERTEX: which vertex of a
// primitive supplies gl_Layer and gl_ViewportIndex. GL 4.6 table 23.65 makes
@@ -1465,6 +1465,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_dynamicParameters.MaxDrawBuffers = m_GLESCapabilities.MaxDrawBuffers;
m_dynamicParameters.MaxColorAttachments = m_GLESCapabilities.MaxColorAttachments;
m_dynamicParameters.MaxClipDistances = m_GLESCapabilities.MaxClipDistances;
// The loader already gated both on GL_EXT_clip_cull_distance and left 0 without it, which
// is the answer that keeps glslang from accepting a gl_CullDistance the ESSL compiler
// would reject.
m_dynamicParameters.MaxCullDistances = m_GLESCapabilities.MaxCullDistances;
m_dynamicParameters.MaxCombinedClipAndCullDistances = m_GLESCapabilities.MaxCombinedClipAndCullDistances;
m_dynamicParameters.MaxViewports = m_GLESCapabilities.MaxViewports;
// Whatever the driver said about which vertex supplies gl_Layer, and GL_UNDEFINED_VERTEX
// for gl_ViewportIndex on every driver without GL_OES_viewport_array - which is both test
+12 -1
View File
@@ -351,9 +351,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
// Only sync up to the high-water mark of app-touched points; the fixed array is 36
// Only sync up to the high-water mark of app-touched points; the fixed array is 84
// deep but apps bind a handful, so the never-touched tail is already at GL default 0.
auto bindingPointCnt = MG_State::pGLContext->GetTouchedBufferBindingPointCount(target);
// ...and never past what the ES driver itself can hold. MobileGL advertises the GL 4.5
// minimum of 84 uniform binding points while the ES 3.2 minimum is 72, so a frontend
// index in that gap would reach glBindBufferBase as GL_INVALID_VALUE. Nothing is lost
// by stopping: this frontend-indexed pass exists for the compute path, and the
// per-program rebind in BindCurrentProgramWithResources - which is what actually feeds
// a shader - remaps every block a program declares onto a compacted ES point, so a
// block bound at GL point 83 still reaches its shader.
if (target == BufferTarget::Uniform && g_GLESCapabilities.MaxUniformBufferBindings > 0) {
bindingPointCnt = std::min(bindingPointCnt,
static_cast<SizeT>(g_GLESCapabilities.MaxUniformBufferBindings));
}
for (SizeT i = 0; i < bindingPointCnt; ++i) {
auto& point = MG_State::pGLContext->GetBufferBindingPoint(target, i);
auto& obj = point.GetBoundObject();
@@ -1006,6 +1006,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// 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;
// The cull pair, gated on its own feature. shaderCullDistance is separate from
// shaderClipDistance and VulkanRenderer enables it independently, so it gets its own
// gate rather than riding on the clip one.
m_dynamicParameters.MaxCullDistances =
m_vulkanCaps.SupportsShaderCullDistance ? std::max(m_vulkanCaps.MaxCullDistances, 0) : 0;
// GL 4.6 core 11.1.3.10: the combined limit is at least as large as either half. A device
// with only one of the two features must not report a combined capacity that implies the
// other, so the gate is "either feature" and the value never drops below what is enabled.
m_dynamicParameters.MaxCombinedClipAndCullDistances =
(m_vulkanCaps.SupportsShaderClipDistance || m_vulkanCaps.SupportsShaderCullDistance)
? std::max({m_vulkanCaps.MaxCombinedClipAndCullDistances, m_dynamicParameters.MaxClipDistances,
m_dynamicParameters.MaxCullDistances})
: 0;
m_dynamicParameters.MaxViewports = m_vulkanCaps.MaxViewports;
// Assigned explicitly rather than left to the struct's defaults, like every other
// parameter here, so a second fill cannot inherit a stale value. GL_UNDEFINED_VERTEX is
+10 -3
View File
@@ -1577,8 +1577,13 @@ namespace MobileGL::MG_Impl::GLImpl {
std::to_string(id) + " is not a transform feedback object name."));
return;
}
// GL_MAX_VERTEX_STREAMS is 1, so stream 0 is the only one that exists.
if (stream != 0) {
// GL 4.6 core 10.3.7 bounds `stream` by GL_MAX_VERTEX_STREAMS, which is 4. Only stream 0
// can ever have been written - nothing in the shader pipeline supports
// layout(stream = N) - so a higher stream captured zero vertices and the draw is a legal
// no-op rather than an error. The bound is read from the getter so the two cannot drift.
GLint maxVertexStreams = 1;
GetIntegerv(GL_MAX_VERTEX_STREAMS, &maxVertexStreams);
if (stream >= static_cast<GLuint>(std::max(maxVertexStreams, 1))) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
@@ -1596,7 +1601,9 @@ namespace MobileGL::MG_Impl::GLImpl {
return;
}
const Uint64 vertices = MG_State::pGLContext->GetTransformFeedbackRecordedVertices(id);
// Only stream 0 ever records anything (see the stream bound above), so a higher stream
// replays nothing.
const Uint64 vertices = stream == 0 ? MG_State::pGLContext->GetTransformFeedbackRecordedVertices(id) : 0;
if (vertices == 0) return;
const auto count = static_cast<GLsizei>(vertices);
AccountTransformFeedbackPrimitives(mode, count);
@@ -688,16 +688,15 @@ namespace MobileGL::MG_Impl::GLImpl {
// GL_MAX_SAMPLES is the ceiling over all formats; an integer format has its own
// (GL_MAX_INTEGER_SAMPLES) and GL 4.6 core 9.2.4 makes exceeding it INVALID_OPERATION.
// The multisample TEXTURE path resolves the limit per format the same way
// (GL_Texture.cpp, GetMaxSupportedTextureSamples). Both are floored to the value MobileGL
// advertises: on a driver where the two differ - Adreno reports GL_MAX_SAMPLES 4 and
// GL_MAX_INTEGER_SAMPLES 1 - rejecting the advertised count here only moves the failure
// from the driver into MobileGL, so the frontend accepts it and the backend clamps the
// count it actually hands the driver.
// (GL_Texture.cpp, GetMaxSupportedTextureSamples), and both now enforce exactly what their
// pname advertises. The integer ceiling used to be floored at GL_MAX_SAMPLES so that the
// frontend would accept a count it had advertised globally - but on Adreno and Mali the
// integer path is genuinely one sample, and accepting four only moved the failure from an
// honest INVALID_OPERATION here to a silently under-allocated renderbuffer.
Int GetMaxRenderbufferSamplesForFormat_State(TextureInternalFormat format) {
if (MG_Backend::pActiveBackendObject == nullptr) {
return std::numeric_limits<Int>::max();
}
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
GLenum normalizedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(format);
GLenum normalizedFormat = GL_RGBA;
@@ -711,10 +710,8 @@ namespace MobileGL::MG_Impl::GLImpl {
if (!isIntegerFormat) {
return GetMaxRenderbufferSamples_State();
}
// Per-format still, but never below the ceiling glGetIntegerv(GL_MAX_SAMPLES) promised:
// the driver's raw GL_MAX_INTEGER_SAMPLES stays the *backend* limit and the backend
// clamps to it, while the frontend honours what it advertised.
return std::max(dynamicParameters.MaxIntegerSamples, GetAdvertisedMaxSamples());
// Exactly what glGetIntegerv(GL_MAX_INTEGER_SAMPLES) reports.
return GetAdvertisedIntegerMaxSamples();
}
Bool ValidateRenderbufferStorageSize_State(GLsizei width, GLsizei height, const char* caller) {
+209 -21
View File
@@ -93,8 +93,15 @@ namespace MobileGL::MG_Impl::GLImpl {
// limits they advertise still have to be legal.
constexpr GLint kFrontendMaxDebugGroupStackDepth = 64;
constexpr GLint kFrontendMaxDebugLoggedMessages = 1;
constexpr GLint kFrontendMaxVertexUniformComponents = 4096;
constexpr GLint kFrontendMaxVertexUniformVectors = 128;
// The *_VECTORS answers are the *_COMPONENTS ones divided by four, never a second
// literal: they used to be independent (4096 components against 128 vectors, 64 varying
// components against 8 varying vectors) and could not both be describing the same
// capacity. Both are shared with BuildTBuiltInResource through Types.h, because
// gl_MaxVertexUniformVectors and gl_MaxVaryingVectors expand from the same numbers.
constexpr GLint kFrontendMaxVertexUniformComponents =
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_VERTEX_UNIFORM_COMPONENTS);
constexpr GLint kFrontendMaxVertexUniformVectors =
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_VERTEX_UNIFORM_VECTORS);
constexpr GLint kFrontendMaxVertexUniformBlocks = 14;
constexpr GLint kFrontendMaxVertexOutputComponents = 64;
constexpr GLint kFrontendMaxFragmentInputComponents = 128;
@@ -106,21 +113,50 @@ namespace MobileGL::MG_Impl::GLImpl {
constexpr GLint kFrontendMaxGeometryTextureImageUnits = 16;
constexpr GLint kFrontendMaxGeometryUniformComponents = 1024;
constexpr GLint kFrontendMaxGeometryUniformBlocks = 14;
constexpr GLint kFrontendMaxCombinedUniformBlocks = kFrontendMaxVertexUniformBlocks +
kFrontendMaxGeometryUniformBlocks +
kFrontendMaxFragmentUniformBlocks;
constexpr GLint kFrontendMaxVaryingComponents = 64;
constexpr GLint kFrontendMaxVaryingVectors = 8;
// ARB_geometry_shader4's per-invocation count. No TBuiltInResource field and no
// gl_MaxGeometryShaderInvocations built-in exists to keep in step, so this is a getter
// answer only; 32 is the GL 4.6 core minimum (table 23.57).
constexpr GLint kFrontendMaxGeometryShaderInvocations = 32;
constexpr GLint kFrontendMaxTessControlUniformBlocks = 14;
constexpr GLint kFrontendMaxTessEvaluationUniformBlocks = 14;
// GL 4.6's minimum is 14 uniform blocks on each of the FIVE graphics stages (70), not
// three: the two tessellation stages were simply missing from this sum, so even a
// frontend with enough binding points advertised 42.
constexpr GLint kFrontendMaxCombinedUniformBlocks =
kFrontendMaxVertexUniformBlocks + kFrontendMaxTessControlUniformBlocks +
kFrontendMaxTessEvaluationUniformBlocks + kFrontendMaxGeometryUniformBlocks +
kFrontendMaxFragmentUniformBlocks;
constexpr GLint kFrontendMaxVaryingComponents =
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_VARYING_COMPONENTS);
constexpr GLint kFrontendMaxVaryingVectors =
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_VARYING_VECTORS);
constexpr GLint kFrontendMaxProgramTexelOffset = 7;
constexpr GLint kFrontendMinProgramTexelOffset = -8;
constexpr GLint kFrontendMaxTransformFeedbackInterleavedComponents = 64;
constexpr GLint kFrontendMaxTransformFeedbackSeparateAttribs = 4;
constexpr GLint kFrontendMaxTransformFeedbackSeparateComponents = 4;
// ARB_transform_feedback3's vertex-stream count; see the GL_MAX_VERTEX_STREAMS case for
// what streams 1..3 mean in an implementation that can only emit to stream 0.
constexpr GLint kFrontendMaxVertexStreams = 4;
constexpr GLint kFrontendMaxGeometryOutputVertices = 256;
constexpr GLint kFrontendMaxGeometryTotalOutputComponents = 1024;
constexpr GLint kFrontendMinUniformBufferBindings = 36;
// GL 4.5 core table 23.64 requires 84 indexed uniform binding points, and that is exactly
// how wide the state layer's array is (BufferState::BufferBindingPointCount) - see the
// GL_MAX_UNIFORM_BUFFER_BINDINGS case for why the ES driver's own, smaller count is not
// the ceiling here.
constexpr GLint kFrontendMinUniformBufferBindings = 84;
constexpr GLint kFrontendSubpixelBits = 4;
constexpr GLint kFrontendMaxSamples = 4;
constexpr GLint kFrontendMaxSamples =
static_cast<GLint>(MG_Util::ShaderTranspiler::MIN_ADVERTISED_MAX_SAMPLES);
// ARB_shader_subroutine's two limits. NOTHING IMPLEMENTS SUBROUTINES: there is no
// glGetSubroutineIndex / glUniformSubroutinesuiv, only the program-interface enum
// plumbing. These are answered - with the GL 4.5 core minimums - because the conformance
// suite queries them before it checks for the feature and an INVALID_ENUM both leaves the
// caller reading its own uninitialised stack slot and strands an error for the next
// unrelated call to trip over. The extension is deliberately NOT advertised, so the
// numbers are a table entry, not a capability claim.
constexpr GLint kFrontendMaxSubroutines = 256;
constexpr GLint kFrontendMaxSubroutineUniformLocations = 1024;
// The floors under GL_MAX_COMPUTE_WORK_GROUP_COUNT / _SIZE. Shared with the compile
// pipeline (CaptureCompileEnv floors the same driver answers at them, and
@@ -455,10 +491,18 @@ namespace MobileGL::MG_Impl::GLImpl {
} // namespace
// GL 4.6 core table 23.53 requires GL_MAX_SAMPLES >= 4, so the driver's value is floored
// before it is advertised. Every other multisample ceiling MobileGL advertises has to be
// floored the same way: promising 4 samples globally while answering GL_MAX_INTEGER_SAMPLES
// 1 - which is exactly what Adreno reports - makes the frontend reject the very count it
// just told the application to use. The backends clamp the realised count instead.
// before it is advertised. gl_MaxSamples expands from the same floored number
// (BuildTBuiltInResource), which is also what sizes gl_SampleMask[].
//
// THE FLOOR STOPS HERE, and that is the point. It used to be applied to
// GL_MAX_INTEGER_SAMPLES, GL_MAX_COLOR_TEXTURE_SAMPLES and GL_MAX_DEPTH_TEXTURE_SAMPLES too,
// on the reasoning that an application reads GL_MAX_SAMPLES once and hands that count to
// every glTexStorage*Multisample. Table 23.53 gives those three a minimum of ONE, and the
// reasoning had it backwards: Adreno and Mali back an integer multisample texture with a
// single sample, so flooring the query at 4 did not make four samples exist - it made the
// backend silently under-allocate (ClampSamplesToBackendSupport) while the application wrote
// per-sample data it could never read back. Reporting what was probed turns that into an
// honest "unsupported" the application can branch on.
GLint GetAdvertisedMaxSamples() {
if (MG_Backend::pActiveBackendObject == nullptr) {
return kFrontendMaxSamples;
@@ -466,6 +510,30 @@ namespace MobileGL::MG_Impl::GLImpl {
return std::max(MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxSamples, kFrontendMaxSamples);
}
// GL 4.6 core table 23.53 minimum for the per-category multisample ceilings. One, not four:
// see the note on GetAdvertisedMaxSamples. A zero would be a probe that never ran, so it is
// floored rather than trusted.
namespace {
GLint AdvertisedCategoryMaxSamples(Int MG_Backend::DynamicBackendParameters::*categoryLimit) {
if (MG_Backend::pActiveBackendObject == nullptr) {
return 1;
}
return std::max(MG_Backend::pActiveBackendObject->GetDynamicParameters().*categoryLimit, 1);
}
} // namespace
GLint GetAdvertisedColorTextureMaxSamples() {
return AdvertisedCategoryMaxSamples(&MG_Backend::DynamicBackendParameters::MaxColorTextureSamples);
}
GLint GetAdvertisedDepthTextureMaxSamples() {
return AdvertisedCategoryMaxSamples(&MG_Backend::DynamicBackendParameters::MaxDepthTextureSamples);
}
GLint GetAdvertisedIntegerMaxSamples() {
return AdvertisedCategoryMaxSamples(&MG_Backend::DynamicBackendParameters::MaxIntegerSamples);
}
// Declared in GL_Getter.h, so that the draw path can feed the same number to the reserved
// gl_NumSamples stand-in that glGetIntegerv(GL_SAMPLES) reports.
GLint ResolveDrawFramebufferSampleCount() {
@@ -1227,6 +1295,13 @@ namespace MobileGL::MG_Impl::GLImpl {
}
switch (pname) {
case GL_MAX_ELEMENT_INDEX:
// The largest value a GL_UNSIGNED_INT index may take. It has to be answered HERE and
// not left to the 32-bit fallback below: the conformance suite reads it with
// glGetInteger64v, and widening the saturated GLint would report INT32_MAX where the
// spec requires 2^32-1.
params[0] = 0xFFFFFFFFLL;
return;
case GL_MAX_SHADER_STORAGE_BLOCK_SIZE:
if (MG_Backend::pActiveBackendObject) {
params[0] = static_cast<GLint64>(
@@ -1706,6 +1781,9 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_MAX_GEOMETRY_UNIFORM_COMPONENTS:
*params = kFrontendMaxGeometryUniformComponents;
return;
case GL_MAX_GEOMETRY_SHADER_INVOCATIONS:
*params = kFrontendMaxGeometryShaderInvocations;
return;
case GL_MAX_IMAGE_SAMPLES:
*params = 0; // multisampled image load/store is not exposed by the DirectGLES frontend
return;
@@ -1759,6 +1837,59 @@ namespace MobileGL::MG_Impl::GLImpl {
*params =
StageStorageBlockCount(&MG_Backend::DynamicBackendParameters::MaxTessEvaluationShaderStorageBlocks);
return;
// The tessellation per-stage resource limits. Every one of these is ALSO a GLSL built-in
// constant that BuildTBuiltInResource expands, and the two must report the same number
// (KHR-GL45.limits.max_tess_* compares them directly) - which is why the values come from
// the shared block in MG_Util/ShaderTranspiler/Types.h rather than from literals here.
// They were the whole per-stage tess family: the table had been filled in only where the
// honest answer was zero (the atomic counters, the image uniforms) or where a driver
// query existed (GL_MAX_PATCH_VERTICES, GL_MAX_TESS_GEN_LEVEL), so every pname whose
// answer is a real resource count fell through to GL_INVALID_ENUM.
case GL_MAX_TESS_CONTROL_INPUT_COMPONENTS:
*params = static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_TESS_CONTROL_INPUT_COMPONENTS);
return;
case GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS:
*params = static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_TESS_CONTROL_OUTPUT_COMPONENTS);
return;
case GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS:
*params = static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS);
return;
case GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS:
*params = static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS);
return;
case GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS:
*params = static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_TESS_CONTROL_UNIFORM_COMPONENTS);
return;
case GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS:
*params = static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_TESS_EVALUATION_INPUT_COMPONENTS);
return;
case GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS:
*params = static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_TESS_EVALUATION_OUTPUT_COMPONENTS);
return;
case GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS:
*params = static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS);
return;
case GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS:
*params = static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_TESS_EVALUATION_UNIFORM_COMPONENTS);
return;
case GL_MAX_TESS_PATCH_COMPONENTS:
*params = static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_TESS_PATCH_COMPONENTS);
return;
// Routed through the same clamp as every other per-stage block count so the
// MAX_UNIFORM_BUFFER_BINDINGS >= MAX_COMBINED_UNIFORM_BLOCKS >= per-stage ordering of
// GL 4.6 table 23.64 cannot be broken by the two families moving independently.
case GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS:
*params = ClampUniformBlockCount(kFrontendMaxTessControlUniformBlocks);
return;
case GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS:
*params = ClampUniformBlockCount(kFrontendMaxTessEvaluationUniformBlocks);
return;
case GL_MAX_SUBROUTINES:
*params = kFrontendMaxSubroutines;
return;
case GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS:
*params = kFrontendMaxSubroutineUniformLocations;
return;
case GL_MAX_TEXTURE_LOD_BIAS:
*params = 15; // TODO
return;
@@ -2174,7 +2305,12 @@ namespace MobileGL::MG_Impl::GLImpl {
return;
}
case GL_MAX_ELEMENT_INDEX:
*params = 1024 * 1024; // TODO
// 64-bit state (see GetInteger64v); the 32-bit query saturates, per the GL
// state-query conversion rules - the same shape GL_MAX_SHADER_STORAGE_BLOCK_SIZE
// uses. The real answer is 2^32-1 because both backends draw with GL_UNSIGNED_INT
// indices and neither bounds an index value; the old `1024 * 1024` was a placeholder
// that no draw path ever consulted.
*params = INT32_MAX;
return;
case GL_CONTEXT_PROFILE_MASK:
// Reports the requested context profile (EGL defaults 3.x contexts to core);
@@ -2275,7 +2411,7 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = static_cast<GLint>(dynamicParameters.ViewportIndexProvokingVertex);
break;
case GL_MAX_COLOR_TEXTURE_SAMPLES:
*params = std::max(dynamicParameters.MaxColorTextureSamples, GetAdvertisedMaxSamples());
*params = GetAdvertisedColorTextureMaxSamples();
break;
case GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS:
*params = GetMaxCombinedUniformComponents(kFrontendMaxFragmentUniformComponents,
@@ -2305,7 +2441,7 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = dynamicParameters.MaxCubeMapTextureSize;
break;
case GL_MAX_DEPTH_TEXTURE_SAMPLES:
*params = std::max(dynamicParameters.MaxDepthTextureSamples, GetAdvertisedMaxSamples());
*params = GetAdvertisedDepthTextureMaxSamples();
break;
case GL_MAX_FRAMEBUFFER_WIDTH:
*params = dynamicParameters.MaxFramebufferWidth;
@@ -2332,7 +2468,7 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = dynamicParameters.MaxComputeImageUniforms;
break;
case GL_MAX_INTEGER_SAMPLES:
*params = std::max(dynamicParameters.MaxIntegerSamples, GetAdvertisedMaxSamples());
*params = GetAdvertisedIntegerMaxSamples();
break;
case GL_MAX_RENDERBUFFER_SIZE:
*params = dynamicParameters.MaxRenderbufferSize;
@@ -2356,12 +2492,43 @@ namespace MobileGL::MG_Impl::GLImpl {
for (Uint i = 0; i < 2; ++i) params[i] = static_cast<GLint>(std::lround(inner[i]));
break;
}
// GL 4.6 core table 23.66: whether the primitive-restart index terminates a patch.
// GL_FALSE is a legal answer and the true one - neither backend cuts a patch short, and
// the DirectVulkan draw path relies on this staying false (it resolves primitive restart
// to "never" for a PATCH_LIST topology on the strength of it).
case GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED:
*params = GL_FALSE;
break;
case GL_MAX_PATCH_VERTICES:
*params = dynamicParameters.MaxPatchVertices;
break;
case GL_MAX_TESS_GEN_LEVEL:
*params = dynamicParameters.MaxTessGenLevel;
break;
// Same helper, and so the same arithmetic, as every other GL_MAX_COMBINED_*_UNIFORM_
// COMPONENTS: default-block components + blocks * (block size / 4). It reproduces the
// conformance suite's own formula exactly, so the two cannot drift.
case GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS:
*params = GetMaxCombinedUniformComponents(
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_TESS_CONTROL_UNIFORM_COMPONENTS),
kFrontendMaxTessControlUniformBlocks, dynamicParameters.MaxUniformBlockSize);
break;
case GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS:
*params = GetMaxCombinedUniformComponents(
static_cast<GLint>(MG_Util::ShaderTranspiler::MAX_TESS_EVALUATION_UNIFORM_COMPONENTS),
kFrontendMaxTessEvaluationUniformBlocks, dynamicParameters.MaxUniformBlockSize);
break;
// ARB_cull_distance. Backend-derived exactly like GL_MAX_CLIP_DISTANCES beside it, and
// for a stronger reason: a cull distance discards the whole primitive, so advertising
// eight the rasterizer cannot serve turns every culling draw into a silent no-op. Zero is
// the honest answer on a host with no cull-distance route, and the conformance suite then
// skips the functional cases instead of failing them deep inside a pixel comparison.
case GL_MAX_CULL_DISTANCES:
*params = dynamicParameters.MaxCullDistances;
break;
case GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES:
*params = dynamicParameters.MaxCombinedClipAndCullDistances;
break;
case GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET:
*params = dynamicParameters.MinProgramTextureGatherOffset;
break;
@@ -2412,7 +2579,15 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = kFrontendMaxTransformFeedbackSeparateAttribs;
break;
case GL_MAX_VERTEX_STREAMS:
*params = 1;
// GL 4.5 core table 23.62 requires four. MobileGL can only ever EMIT to stream 0 -
// nothing in the shader pipeline supports layout(stream = N), EmitStreamVertex or a
// per-stream capture layout - but that is a statement about what a geometry shader
// may produce, not about which stream indices exist. Streams 1..3 exist and are
// permanently empty, and the two entry points that address a stream say so: an
// indexed primitive query on one answers zero (GL_Query's emptyVertexStream) and
// glDrawTransformFeedbackStream on one draws nothing. Answering 1 instead used to
// make both of them GL_INVALID_VALUE.
*params = kFrontendMaxVertexStreams;
break;
case GL_TRANSFORM_FEEDBACK_ACTIVE:
*params = MG_State::pGLContext->IsTransformFeedbackActive() ? 1 : 0;
@@ -2429,15 +2604,28 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_MAX_TEXTURE_SIZE:
*params = dynamicParameters.MaxTextureSize;
break;
case GL_MAX_UNIFORM_BUFFER_BINDINGS:
case GL_MAX_UNIFORM_BUFFER_BINDINGS: {
// Never advertise more bindings than the state layer's indexed-binding array can track
// (BufferState::BufferBindingPointCount): glBindBufferBase rejects indices past that
// capacity, and the GL CTS per-case state reset calls glBindBufferBase on every
// advertised index and expects no error. The floor equals the GL 3.3 core minimum
// (36), so the clamp never under-advertises.
// advertised index and expects no error. The floor is the GL 4.5 core minimum, and
// the array was widened to exactly it, so the two coincide by construction.
//
// WHY THE BACKEND'S OWN COUNT IS NOT THE CEILING HERE, unlike the shader-storage
// family. A GL uniform binding point is where an APPLICATION parks a buffer; it is
// not a driver binding point. Neither backend forwards it as one on the draw path:
// DirectGLES rebinds the blocks a program declares onto COMPACTED ES points
// (BindCurrentProgramWithResources maps block i to ES point i+1) and DirectVulkan
// resolves each block to a descriptor. So what the host driver's count bounds is how
// many blocks ONE PROGRAM may use, which is GL_MAX_COMBINED_UNIFORM_BLOCKS (70) -
// inside the ES 3.2 minimum of 72 - and not how many points an application may bind.
static_assert(static_cast<GLint>(MG_State::GLState::BufferBindingPointCount) >=
kFrontendMinUniformBufferBindings,
"the indexed-binding array must be able to hold every advertised uniform binding point");
*params = std::clamp(dynamicParameters.MaxUniformBufferBindings, kFrontendMinUniformBufferBindings,
static_cast<GLint>(MG_State::GLState::BufferBindingPointCount));
break;
}
case GL_MAX_UNIFORM_BLOCK_SIZE:
*params = dynamicParameters.MaxUniformBlockSize;
break;
+10 -2
View File
@@ -25,9 +25,17 @@ namespace MobileGL::MG_Impl::GLImpl {
GLenum GetError();
GLenum GetGraphicsResetStatus();
// The GL_MAX_SAMPLES value MobileGL advertises, i.e. the driver's value floored to the GL
// core minimum. Frontend multisample validators have to honour this ceiling for every
// format, otherwise MobileGL rejects a sample count it advertised itself.
// core minimum of 4. This is the RENDERBUFFER ceiling; the three per-category texture
// ceilings below have a minimum of one and are reported as probed.
GLint GetAdvertisedMaxSamples();
// Exactly what GL_MAX_COLOR_TEXTURE_SAMPLES / GL_MAX_DEPTH_TEXTURE_SAMPLES /
// GL_MAX_INTEGER_SAMPLES report: the probed backend limit floored at the GL 4.6 core minimum
// of ONE (table 23.53). Exported so the frontend's storage validation enforces exactly what
// the query promised - it used to floor both at 4 and then let the backend quietly
// under-allocate whatever the driver could not actually provide.
GLint GetAdvertisedColorTextureMaxSamples();
GLint GetAdvertisedDepthTextureMaxSamples();
GLint GetAdvertisedIntegerMaxSamples();
// What glGetIntegerv(GL_SAMPLES) answers for the CURRENT draw framebuffer: the largest sample
// count over its attachments, and 0 for a single-sample or default framebuffer (GL 4.6 core
// 9.2.3 / 22.2 - GL_SAMPLE_BUFFERS is 1 exactly when this is non-zero).
+48 -5
View File
@@ -40,6 +40,12 @@ namespace MobileGL::MG_Impl::GLImpl {
// stand in for the backend's.
Uint64 accountedCaptureDrawSnapshot = 0;
Uint64 geometryCaptureDrawSnapshot = 0;
// Set when glBeginQueryIndexed named a vertex stream above 0. MobileGL advertises
// GL_MAX_VERTEX_STREAMS = 4 because GL 4.5 requires it, and nothing in the shader
// pipeline can emit to a stream other than 0 - so the primitive count on any other
// stream is provably zero, and this makes the object report that instead of
// aliasing stream 0's backend counter.
Bool emptyVertexStream = false;
};
// Query calls may arrive from any thread (launchers migrate the context
@@ -116,6 +122,7 @@ namespace MobileGL::MG_Impl::GLImpl {
queryObject->ended = false;
queryObject->resultCached = false;
queryObject->cachedResult = 0;
queryObject->emptyVertexStream = false;
}
// Callers must hold g_queryObjectsMutex.
@@ -188,6 +195,23 @@ namespace MobileGL::MG_Impl::GLImpl {
return false;
}
// A span begun on a vertex stream this implementation can never emit to. The answer
// is zero, and it is available immediately - the backend query that ran alongside it
// counted stream 0 and must not be reported here.
if (queryObject->emptyVertexStream) {
switch (pname) {
case GL_QUERY_RESULT:
case GL_QUERY_RESULT_NO_WAIT:
outValue = 0;
return true;
case GL_QUERY_RESULT_AVAILABLE:
outValue = GL_TRUE;
return true;
default:
break;
}
}
switch (pname) {
case GL_QUERY_TARGET:
// The target a query was begun with (or created with, for glCreateQueries) - state
@@ -741,14 +765,15 @@ namespace MobileGL::MG_Impl::GLImpl {
}
namespace {
Bool IsPerVertexStreamQueryTarget(GLenum target) {
return target == GL_PRIMITIVES_GENERATED || target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN;
}
// The indexed query entry points differ from the plain ones only in the vertex
// stream they address (GL 4.6 core 4.2.1): index must be below GL_MAX_VERTEX_STREAMS
// for the two transform feedback targets and zero for every other target. With a
// single vertex stream both bounds are 1, so a valid call is always index 0 and
// forwards to the unindexed implementation.
// for the two transform feedback targets and zero for every other target.
Bool ValidateQueryStreamIndex(const char* function, GLenum target, GLuint index) {
const Bool perStreamTarget =
target == GL_PRIMITIVES_GENERATED || target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN;
const Bool perStreamTarget = IsPerVertexStreamQueryTarget(target);
GLint maxVertexStreams = 1;
if (perStreamTarget) {
GetIntegerv(GL_MAX_VERTEX_STREAMS, &maxVertexStreams);
@@ -761,11 +786,29 @@ namespace MobileGL::MG_Impl::GLImpl {
: "index must be zero for this query target.");
return false;
}
// Streams 1..GL_MAX_VERTEX_STREAMS-1 exist but nothing can emit to them, so a span begun
// on one counts zero primitives. Flagging the object is what keeps that answer honest:
// BeginQuery below still opens a real backend query (it is the only way to reuse the
// whole target/object state machine), and that query counts STREAM 0.
//
// Known simplification, spelled out rather than hidden: because the backend query is
// shared, only ONE query may be active per target here, while GL allows one per
// (target, stream) pair. A program running a stream-0 and a stream-2
// GL_PRIMITIVES_GENERATED query at the same time gets GL_INVALID_OPERATION on the
// second. Nothing can produce a non-zero stream-2 result to be worth more than that
// until the shader pipeline grows layout(stream = N).
void MarkQueryEmptyVertexStream(GLuint id) {
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
auto* queryObject = FindQueryObjectLocked(id);
if (queryObject && queryObject->active) queryObject->emptyVertexStream = true;
}
} // namespace
void BeginQueryIndexed(GLenum target, GLuint index, GLuint id) {
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
BeginQuery(target, id);
if (index != 0 && IsPerVertexStreamQueryTarget(target)) MarkQueryEmptyVertexStream(id);
}
void EndQueryIndexed(GLenum target, GLuint index) {
+30 -28
View File
@@ -522,44 +522,46 @@ namespace MobileGL::MG_Impl::GLImpl {
return sampleCounts.empty() ? 0 : sampleCounts.front();
}
// The ceiling the frontend enforces, which must never be lower than the one MobileGL
// advertises: the CTS - and real applications - read GL_MAX_SAMPLES once and hand that
// exact count to glTexImage*Multisample for every format. Answering 4 there and then
// rejecting 4 here because the ES driver reports GL_MAX_INTEGER_SAMPLES 1 (Adreno) is a
// self-inconsistency, not a spec-mandated error. The backends clamp the count they hand
// the driver; the shadow state keeps reporting what the application asked for.
// The ceiling the frontend enforces, which is EXACTLY the one MobileGL advertises for
// this format's category - GL_MAX_DEPTH_TEXTURE_SAMPLES, GL_MAX_INTEGER_SAMPLES or
// GL_MAX_COLOR_TEXTURE_SAMPLES, all three of which have a GL 4.6 minimum of one and are
// reported as probed. It used to floor all three at GL_MAX_SAMPLES (4) on the reasoning
// that an application reads GL_MAX_SAMPLES once and hands that count to every
// glTexStorage*Multisample. That reasoning had it backwards: on Adreno and on Mali an
// integer multisample texture is backed by ONE sample, so accepting four here did not
// make four samples exist - ClampSamplesToBackendSupport quietly allocated one and the
// application wrote per-sample data it could never read back. Raising INVALID_OPERATION
// is what a real driver does, and it is what makes that silent squeeze unreachable for
// application-visible storage.
Int GetMaxSupportedTextureSamples(TextureTarget textureTarget,
TextureInternalFormat textureInternalFormat) {
if (MG_Backend::pActiveBackendObject == nullptr) {
return std::numeric_limits<Int>::max();
}
const Int advertisedMaxSamples = GetAdvertisedMaxSamples();
const Bool isDepthOrStencil = MG_Util::IsDepthFormatInternalFormat(textureInternalFormat) ||
MG_Util::IsStencilFormatInternalFormat(textureInternalFormat);
Bool isIntegerFormat = false;
if (!isDepthOrStencil) {
GLenum normalizedInternalFormat =
MG_Util::ConvertTextureInternalFormatToGLEnum(textureInternalFormat);
GLenum normalizedFormat = GL_RGBA;
GLenum normalizedType = GL_UNSIGNED_BYTE;
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
normalizedInternalFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat,
&normalizedFormat, &normalizedType);
isIntegerFormat = normalizedFormat == GL_RED_INTEGER || normalizedFormat == GL_RG_INTEGER ||
normalizedFormat == GL_RGB_INTEGER || normalizedFormat == GL_RGBA_INTEGER;
}
const Int categoryMaxSamples = isDepthOrStencil ? GetAdvertisedDepthTextureMaxSamples()
: isIntegerFormat ? GetAdvertisedIntegerMaxSamples()
: GetAdvertisedColorTextureMaxSamples();
// glGetInternalformativ(GL_SAMPLES) is answered from this very list (GetInternalformativ
// below), and GL 4.6 core 8.8 makes that query the definition of the per-format
// maximum - validating against anything else is how the two answers drifted apart.
const Int probedMaxSamples = GetProbedMaxTextureSamples(textureTarget, textureInternalFormat);
if (probedMaxSamples > 0) {
return std::max(probedMaxSamples, advertisedMaxSamples);
}
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
if (MG_Util::IsDepthFormatInternalFormat(textureInternalFormat) ||
MG_Util::IsStencilFormatInternalFormat(textureInternalFormat)) {
return std::max(dynamicParameters.MaxDepthTextureSamples, advertisedMaxSamples);
}
GLenum normalizedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(textureInternalFormat);
GLenum normalizedFormat = GL_RGBA;
GLenum normalizedType = GL_UNSIGNED_BYTE;
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
normalizedInternalFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat,
&normalizedFormat, &normalizedType);
const Bool isIntegerFormat = normalizedFormat == GL_RED_INTEGER || normalizedFormat == GL_RG_INTEGER ||
normalizedFormat == GL_RGB_INTEGER || normalizedFormat == GL_RGBA_INTEGER;
return std::max(isIntegerFormat ? dynamicParameters.MaxIntegerSamples
: dynamicParameters.MaxColorTextureSamples,
advertisedMaxSamples);
return probedMaxSamples > 0 ? std::max(probedMaxSamples, categoryMaxSamples) : categoryMaxSamples;
}
Bool ValidateTextureMultisampleStorage(TextureTarget textureTarget, GLsizei samples, GLsizei width,
@@ -12,15 +12,15 @@
#include <MG_State/GLState/ErrorState/Error.h>
#include <MG_Util/Converters/MGToGL/DataTypeConverter.h>
#include <MG_Util/Converters/MGToStr/DataTypeConverter.h>
#include <MG_Util/ShaderTranspiler/CompileEnv.h>
namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
Uint GetMaxVertexAttribs() {
constexpr Uint capacity = static_cast<Uint>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS);
if (!MG_Backend::pActiveBackendObject) return capacity;
const Int backendLimit = MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxVertexAttribs;
if (backendLimit <= 0) return capacity;
return std::min(static_cast<Uint>(backendLimit), capacity);
// Shared with reflection's limit and with gl_MaxVertexAttribs; see ResolveMaxVertexAttribs.
const Bool hasBackend = MG_Backend::pActiveBackendObject != nullptr;
const Int backendLimit =
hasBackend ? MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxVertexAttribs : 0;
return static_cast<Uint>(MG_Util::ShaderTranspiler::ResolveMaxVertexAttribs(hasBackend, backendLimit));
}
Uint GetMaxVertexAttribBindings() {
@@ -56,7 +56,13 @@ namespace MGITest {
const std::vector<LimitBound>& BufferLimitTable() {
static const std::vector<LimitBound> table = {
{GL_MAX_UNIFORM_BUFFER_BINDINGS, "GL_MAX_UNIFORM_BUFFER_BINDINGS", 36, 256},
// 84 is the GL 4.5 core table 23.64 minimum, and also the width of the state
// layer's indexed-binding array - the two were made to coincide when the array
// was widened from 36, which had made the clamp in GL_Getter degenerate.
{GL_MAX_UNIFORM_BUFFER_BINDINGS, "GL_MAX_UNIFORM_BUFFER_BINDINGS", 84, 256},
// 14 uniform blocks on each of the FIVE graphics stages. The sum used to count
// three, and the two tessellation stages were simply missing from it.
{GL_MAX_COMBINED_UNIFORM_BLOCKS, "GL_MAX_COMBINED_UNIFORM_BLOCKS", 70, 256},
{GL_MAX_COMPUTE_UNIFORM_BLOCKS, "GL_MAX_COMPUTE_UNIFORM_BLOCKS", 12, 256},
{GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, "GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS", 8, 256},
{GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, "GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS", 8, 256},
@@ -199,6 +205,75 @@ namespace MGITest {
"derived component limits are computed in";
}
// The GL 4.5 core minimums that had no case in the getter at all, or that were still
// carrying an ES/GL3.3-tier number. Every one of these answered GL_INVALID_ENUM or a
// too-small value against a context advertising 4.6, and each is the FIRST call its
// conformance case makes - so the case died before it could measure anything.
//
// The cull pair is deliberately absent: zero is a legal answer there (a backend with no
// cull-distance route MUST report it), so it is checked for answerability only, below.
TEST_F(AdvertisedLimitsScenario, EveryGL45CoreMinimumIsMet) {
const std::vector<LimitBound> table = {
{GL_MAX_VARYING_VECTORS, "GL_MAX_VARYING_VECTORS", 15, 256},
{GL_MAX_VERTEX_UNIFORM_VECTORS, "GL_MAX_VERTEX_UNIFORM_VECTORS", 256, 1 << 20},
{GL_MAX_VARYING_COMPONENTS, "GL_MAX_VARYING_COMPONENTS", 60, 1 << 20},
{GL_MAX_VERTEX_STREAMS, "GL_MAX_VERTEX_STREAMS", 4, 64},
{GL_MAX_GEOMETRY_SHADER_INVOCATIONS, "GL_MAX_GEOMETRY_SHADER_INVOCATIONS", 32, 256},
{GL_MAX_SUBROUTINES, "GL_MAX_SUBROUTINES", 256, 1 << 20},
{GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS, "GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS", 1024, 1 << 20},
{GL_MAX_TESS_CONTROL_INPUT_COMPONENTS, "GL_MAX_TESS_CONTROL_INPUT_COMPONENTS", 128, 1 << 16},
{GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS, "GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS", 128, 1 << 16},
{GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS, "GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS", 4096,
1 << 20},
{GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS, "GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS", 16, 256},
{GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS, "GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS", 1024, 1 << 20},
{GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS, "GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS", 14, 256},
{GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS, "GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS", 128, 1 << 16},
{GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS, "GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS", 128, 1 << 16},
{GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS, "GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS", 16, 256},
{GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS, "GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS", 1024,
1 << 20},
{GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, "GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS", 14, 256},
{GL_MAX_TESS_PATCH_COMPONENTS, "GL_MAX_TESS_PATCH_COMPONENTS", 120, 1 << 16},
{GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS, "GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS",
58368, 1 << 30},
{GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS,
"GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS", 58368, 1 << 30},
};
for (const LimitBound& bound : table) {
GLint value = -424242;
glGetIntegerv(bound.pname, &value);
const unsigned int error = FirstGLError();
EXPECT_EQ(error, GLenum(GL_NO_ERROR)) << bound.name << " is not answerable: " << GLErrorName(error);
if (error != GL_NO_ERROR) continue;
EXPECT_GE(value, bound.minimum) << bound.name << " = " << value << " is below the GL 4.5 minimum "
<< bound.minimum;
EXPECT_LE(value, bound.ceiling) << bound.name << " = " << value << " exceeds the ceiling "
<< bound.ceiling;
}
// ARB_cull_distance's pair. Zero is honest on a backend with no cull-distance route,
// so only answerability and the combined-limit ordering are checked here.
GLint cull = -1;
GLint clip = -1;
GLint combined = -1;
glGetIntegerv(GL_MAX_CULL_DISTANCES, &cull);
glGetIntegerv(GL_MAX_CLIP_DISTANCES, &clip);
glGetIntegerv(GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES, &combined);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the ARB_cull_distance queries must not error";
EXPECT_GE(cull, 0);
EXPECT_GE(combined, cull) << "GL 4.6 core 11.1.3.10: the combined limit is at least the cull one";
EXPECT_GE(combined, clip) << "GL 4.6 core 11.1.3.10: the combined limit is at least the clip one";
// GL_MAX_ELEMENT_INDEX is 64-bit state: the required 2^32-1 does not fit a GLint, so
// the wide query must answer it and the narrow one must saturate rather than wrap.
GLint64 elementIndex = -1;
glGetInteger64v(GL_MAX_ELEMENT_INDEX, &elementIndex);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
EXPECT_GE(elementIndex, static_cast<GLint64>(4294967295LL))
<< "GL 4.5 core table 23.55 sets the GL_MAX_ELEMENT_INDEX minimum at 2^32-1";
}
// ARB_viewport_array's own limits. They are advertised from three different places -
// GL_MAX_VIEWPORTS from the frontend's indexed state width, the bounds range and the
// subpixel bits from the backend caps table - and each backend fills that table from a
@@ -19,7 +19,14 @@ namespace MobileGL::MG_State::GLState {
BufferTarget::DrawIndirect, BufferTarget::Parameter, BufferTarget::ShaderStorage);
constexpr const auto BufferBindPointTargets = ToArray(BufferTarget::Uniform, BufferTarget::TransformFeedback,
BufferTarget::AtomicCounter, BufferTarget::ShaderStorage);
constexpr SizeT BufferBindingPointCount = 36;
// How many indexed binding points each of BufferBindPointTargets gets. 84 is the GL 4.5 core
// minimum for GL_MAX_UNIFORM_BUFFER_BINDINGS (table 23.64) and this array is the capacity
// that limit is clamped against - at 36 the clamp in GL_Getter was degenerate (lo == hi) and
// no application could ever be told about, or bind to, a binding point past the 36th. The
// other three targets advertise their own, smaller ceilings out of
// GetIndexedBufferQueryPointCount, so widening this does not widen what they promise; it only
// costs the unused tail of three arrays.
constexpr SizeT BufferBindingPointCount = 84;
class BufferState {
public:
@@ -29,13 +29,11 @@ namespace {
// capacity, which is also the width of the Uint32 masks backends build from it.
static MobileGL::Int GetReflectionVertexAttribLimit(
const MobileGL::MG_Util::ShaderTranspiler::CompileEnv& env) {
constexpr MobileGL::Int capacity =
static_cast<MobileGL::Int>(MobileGL::MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS);
if (!env.HasBackend()) return capacity;
const MobileGL::Int backendLimit = env.params.MaxVertexAttribs;
if (backendLimit <= 0) return capacity;
return std::min(backendLimit, capacity);
// One shared definition with glGetIntegerv(GL_MAX_VERTEX_ATTRIBS) and with
// BuildTBuiltInResource's gl_MaxVertexAttribs - the three used to carry three copies of
// this formula and glslang's copy was a hardcoded 64.
return MobileGL::MG_Util::ShaderTranspiler::ResolveMaxVertexAttribs(env.HasBackend(),
env.params.MaxVertexAttribs);
}
// Everything the post-link query surface ever asks a glslang::TType, flattened into a
+237 -3
View File
@@ -41,8 +41,15 @@
namespace {
class DynamicParameterBackend final : public MobileGL::MG_Backend::BackendObject {
public:
explicit DynamicParameterBackend(MobileGL::MG_Backend::DynamicBackendParameters params):
m_params(params) {}
// `type` defaults to Unknown, which is what every existing case wanted: a limits-only
// double with no backend identity. A case that captures a CompileEnv from it and then
// compares the result against glGetIntegerv has to pass a REAL type, because
// CompileEnv::HasBackend() is what BuildTBuiltInResource bounds gl_MaxVertexAttribs by
// while the getter bounds it by "a backend object exists" - two spellings of the same
// thing in production, and only in production.
explicit DynamicParameterBackend(MobileGL::MG_Backend::DynamicBackendParameters params,
MobileGL::BackendType type = MobileGL::BackendType::Unknown):
m_params(params), m_type(type) {}
void Initialize() override {}
MobileGL::Bool InitCapabilities() override { return true; }
@@ -55,10 +62,11 @@ namespace {
const MobileGL::MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override {
return m_params;
}
MobileGL::BackendType GetBackendType() const override { return MobileGL::BackendType::Unknown; }
MobileGL::BackendType GetBackendType() const override { return m_type; }
private:
MobileGL::MG_Backend::DynamicBackendParameters m_params;
MobileGL::BackendType m_type = MobileGL::BackendType::Unknown;
MobileGL::MG_Backend::GlobalBackendFunctionsTable m_functions{};
MobileGL::RendererInfo m_info{
.RendererName = "Test",
@@ -722,6 +730,62 @@ TEST(DirectVulkanSanity, GatesClipDistancesOnTheShaderClipDistanceFeature) {
EXPECT_EQ(backend.GetDynamicParameters().MaxClipDistances, 8);
}
// The cull half of the same contract. shaderCullDistance is a SEPARATE feature from
// shaderClipDistance - VulkanRenderer enables each independently - so it gets its own gate, and
// the combined limit is gated on either being present because GL 4.6 core 11.1.3.10 makes it at
// least as large as both halves. These three used to be literal 8s inside BuildTBuiltInResource
// with no device consulted at all, which let glslang accept a gl_CullDistance write that then
// discarded every primitive it touched.
TEST(DirectVulkanSanity, GatesCullDistancesOnTheShaderCullDistanceFeature) {
using namespace MobileGL;
MG_Backend::DirectVulkan::BackendObject_DirectVulkan backend;
MG_External::VulkanCapabilities caps;
caps.MaxClipDistances = 8;
caps.MaxCullDistances = 8;
caps.MaxCombinedClipAndCullDistances = 8;
caps.SupportsShaderClipDistance = false;
caps.SupportsShaderCullDistance = false;
backend.ApplyVulkanCapabilitiesForTesting(caps);
EXPECT_EQ(backend.GetDynamicParameters().MaxCullDistances, 0);
EXPECT_EQ(backend.GetDynamicParameters().MaxCombinedClipAndCullDistances, 0);
// Clip only: cull stays zero, and the combined limit still describes the clip capacity.
caps.SupportsShaderClipDistance = true;
backend.ApplyVulkanCapabilitiesForTesting(caps);
EXPECT_EQ(backend.GetDynamicParameters().MaxCullDistances, 0);
EXPECT_EQ(backend.GetDynamicParameters().MaxCombinedClipAndCullDistances, 8);
caps.SupportsShaderCullDistance = true;
backend.ApplyVulkanCapabilitiesForTesting(caps);
EXPECT_EQ(backend.GetDynamicParameters().MaxCullDistances, 8);
EXPECT_EQ(backend.GetDynamicParameters().MaxCombinedClipAndCullDistances, 8);
}
// DirectGLES reaches clip AND cull distances only through GL_EXT_clip_cull_distance, so the
// loader leaves all three at zero without it and the backend forwards that verbatim. Zero is the
// answer that stops a gl_CullDistance shader from reaching an ESSL compiler that would reject it.
TEST(DirectGLESSanity, ForwardsTheProbedClipAndCullDistanceLimits) {
using namespace MobileGL;
MG_Backend::DirectGLES::BackendObject_DirectGLES backend;
MG_External::GLESCapabilities caps;
backend.ApplyGLESCapabilitiesForTesting(caps);
EXPECT_EQ(backend.GetDynamicParameters().MaxClipDistances, 0);
EXPECT_EQ(backend.GetDynamicParameters().MaxCullDistances, 0);
EXPECT_EQ(backend.GetDynamicParameters().MaxCombinedClipAndCullDistances, 0);
caps.SupportsClipDistance = true;
caps.MaxClipDistances = 8;
caps.MaxCullDistances = 8;
caps.MaxCombinedClipAndCullDistances = 8;
backend.ApplyGLESCapabilitiesForTesting(caps);
EXPECT_EQ(backend.GetDynamicParameters().MaxClipDistances, 8);
EXPECT_EQ(backend.GetDynamicParameters().MaxCullDistances, 8);
EXPECT_EQ(backend.GetDynamicParameters().MaxCombinedClipAndCullDistances, 8);
}
// GL_LAYER_PROVOKING_VERTEX / GL_VIEWPORT_INDEX_PROVOKING_VERTEX were a hard-coded
// GL_LAST_VERTEX_CONVENTION for both backends, derived from nothing, and wrong on both test
// devices in opposite directions. DirectGLES now forwards what its loader resolved; DirectVulkan
@@ -1234,6 +1298,176 @@ void main() {
MG_State::pGLContext = Move(previousContext);
}
// THE invariant every KHR-GL45.limits.* case checks, in one place. When the conformance table
// gives a limit both a glGetIntegerv pname and a GLSL built-in constant, it reads the query and
// then compiles a shader that writes the built-in into an SSBO and demands EXACT equality - so a
// limit answered from two unreconciled tables fails the SECOND half of the case, with a message
// about a number rather than about the two tables. Seven of them did: gl_MaxVertexAttribs said 64
// against a query of 32, gl_MaxDrawBuffers 32 against 8, gl_MaxCombinedTextureImageUnits 80
// against 96, gl_MaxVaryingComponents 60 against 64, gl_MaxCombinedShaderOutputResources 8
// against 29.
//
// KEEP THIS TABLE GROWING. Every pname added to GL_Getter that also has a gl_Max* built-in
// belongs here; that is what stops the next one from drifting.
TEST(GetterSanity, EveryLimitWithABuiltinAgreesWithItsQuery) {
using namespace MobileGL;
auto previousContext = Move(MG_State::pGLContext);
auto previousBackend = Move(MG_Backend::pActiveBackendObject);
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
MG_Backend::pActiveBackendObject =
MakeUnique<DynamicParameterBackend>(MG_Backend::DynamicBackendParameters{}, BackendType::DirectGLES);
struct LimitPair {
GLenum pname;
const char* builtin;
};
const LimitPair pairs[] = {
{GL_MAX_VERTEX_ATTRIBS, "gl_MaxVertexAttribs"},
{GL_MAX_VERTEX_UNIFORM_COMPONENTS, "gl_MaxVertexUniformComponents"},
{GL_MAX_VERTEX_UNIFORM_VECTORS, "gl_MaxVertexUniformVectors"},
{GL_MAX_VERTEX_OUTPUT_COMPONENTS, "gl_MaxVertexOutputComponents"},
{GL_MAX_VARYING_COMPONENTS, "gl_MaxVaryingComponents"},
{GL_MAX_VARYING_VECTORS, "gl_MaxVaryingVectors"},
{GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, "gl_MaxVertexTextureImageUnits"},
{GL_MAX_TEXTURE_IMAGE_UNITS, "gl_MaxTextureImageUnits"},
{GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, "gl_MaxCombinedTextureImageUnits"},
{GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, "gl_MaxFragmentUniformComponents"},
{GL_MAX_FRAGMENT_UNIFORM_VECTORS, "gl_MaxFragmentUniformVectors"},
{GL_MAX_FRAGMENT_INPUT_COMPONENTS, "gl_MaxFragmentInputComponents"},
{GL_MAX_DRAW_BUFFERS, "gl_MaxDrawBuffers"},
{GL_MAX_IMAGE_UNITS, "gl_MaxImageUnits"},
// The SAME token (0x8F39) under two spellings, and the two glslang fields behind them
// must therefore carry the same value.
{GL_MAX_COMBINED_IMAGE_UNITS_AND_FRAGMENT_OUTPUTS, "gl_MaxCombinedImageUnitsAndFragmentOutputs"},
{GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES, "gl_MaxCombinedShaderOutputResources"},
{GL_MAX_CLIP_DISTANCES, "gl_MaxClipDistances"},
{GL_MAX_CULL_DISTANCES, "gl_MaxCullDistances"},
{GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES, "gl_MaxCombinedClipAndCullDistances"},
{GL_MAX_SAMPLES, "gl_MaxSamples"},
{GL_MIN_PROGRAM_TEXEL_OFFSET, "gl_MinProgramTexelOffset"},
{GL_MAX_PROGRAM_TEXEL_OFFSET, "gl_MaxProgramTexelOffset"},
{GL_MAX_GEOMETRY_INPUT_COMPONENTS, "gl_MaxGeometryInputComponents"},
{GL_MAX_GEOMETRY_OUTPUT_COMPONENTS, "gl_MaxGeometryOutputComponents"},
{GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS, "gl_MaxGeometryTextureImageUnits"},
{GL_MAX_GEOMETRY_OUTPUT_VERTICES, "gl_MaxGeometryOutputVertices"},
{GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, "gl_MaxGeometryTotalOutputComponents"},
{GL_MAX_GEOMETRY_UNIFORM_COMPONENTS, "gl_MaxGeometryUniformComponents"},
{GL_MAX_PATCH_VERTICES, "gl_MaxPatchVertices"},
{GL_MAX_TESS_GEN_LEVEL, "gl_MaxTessGenLevel"},
{GL_MAX_TESS_CONTROL_INPUT_COMPONENTS, "gl_MaxTessControlInputComponents"},
{GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS, "gl_MaxTessControlOutputComponents"},
{GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS, "gl_MaxTessControlTextureImageUnits"},
{GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS, "gl_MaxTessControlUniformComponents"},
{GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS, "gl_MaxTessControlTotalOutputComponents"},
{GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS, "gl_MaxTessEvaluationInputComponents"},
{GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS, "gl_MaxTessEvaluationOutputComponents"},
{GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS, "gl_MaxTessEvaluationTextureImageUnits"},
{GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS, "gl_MaxTessEvaluationUniformComponents"},
{GL_MAX_TESS_PATCH_COMPONENTS, "gl_MaxTessPatchComponents"},
{GL_MAX_TRANSFORM_FEEDBACK_BUFFERS, "gl_MaxTransformFeedbackBuffers"},
{GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS, "gl_MaxTransformFeedbackInterleavedComponents"},
// gl_MaxAtomicCounterBindings is glslang's name for the binding count; the GL spelling is
// GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS.
{GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS, "gl_MaxAtomicCounterBindings"},
{GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE, "gl_MaxAtomicCounterBufferSize"},
};
// The compile runs against a captured env, exactly as the pipeline's does - that is what
// makes "the resource table" mean the same thing here as it does in production.
const auto env = MG_Util::ShaderTranspiler::CaptureCompileEnv();
for (const LimitPair& pair : pairs) {
GLint reported = -424242;
MG_Impl::GLImpl::GetIntegerv(pair.pname, &reported);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR)
<< pair.builtin << "'s pname is not answerable at all";
// A negative array size is a compile error, so the stage only compiles when the built-in
// equals what the query just reported. Two-sided by construction: a resource table that
// is too permissive fails it exactly like one that is too tight. One shader per pair, so
// a failure names the limit instead of reporting "something disagreed".
const String source = String("#version 460 core\nout vec4 mgColor;\nconst int mgAgree = (") +
pair.builtin + " == " + std::to_string(reported) +
") ? 1 : -1;\nint mgProbe[mgAgree];\nvoid main() { mgProbe[0] = 0; mgColor = "
"vec4(float(mgProbe[0])); }\n";
auto compiled = MG_Util::ShaderTranspiler::ShaderCompiler::CompileShader({
.shaderType = GL_FRAGMENT_SHADER,
.sourceStr = source,
.env = env.get(),
});
EXPECT_TRUE(compiled) << pair.builtin << " does not equal glGetIntegerv's " << reported << ":\n"
<< (compiled ? String() : compiled.error().log);
}
MG_Backend::pActiveBackendObject = Move(previousBackend);
MG_State::pGLContext = Move(previousContext);
}
// GL_MAX_ELEMENT_INDEX is 64-bit state whose required value (2^32-1) does not fit a GLint, so it
// needs its own case in BOTH widths: the 64-bit query has to answer 4294967295 and the 32-bit one
// has to saturate, per the GL state-query conversion rules. It used to be a single `1024 * 1024;
// // TODO` in the 32-bit table, and glGetInteger64v - which is how the conformance suite reads it
// - widened that.
TEST(GetterSanity, MaxElementIndexIsTheFull32BitIndexCeiling) {
using namespace MobileGL;
auto previousContext = Move(MG_State::pGLContext);
auto previousBackend = Move(MG_Backend::pActiveBackendObject);
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
MG_Backend::pActiveBackendObject = MakeUnique<DynamicParameterBackend>(MG_Backend::DynamicBackendParameters{});
GLint64 wide = -1;
MG_Impl::GLImpl::GetInteger64v(GL_MAX_ELEMENT_INDEX, &wide);
EXPECT_EQ(wide, static_cast<GLint64>(0xFFFFFFFFLL));
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
GLint narrow = -1;
MG_Impl::GLImpl::GetIntegerv(GL_MAX_ELEMENT_INDEX, &narrow);
EXPECT_EQ(narrow, INT32_MAX) << "the 32-bit query must saturate, not truncate or wrap";
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Backend::pActiveBackendObject = Move(previousBackend);
MG_State::pGLContext = Move(previousContext);
}
// GL 4.6 core table 23.53 gives GL_MAX_SAMPLES a minimum of four and the three per-category
// ceilings a minimum of ONE. Flooring the latter at four is the advertised-caps lie that made
// KHR-GL46.sample_variables.mask.rgba8i run at all: the frontend promised four integer samples,
// the backend clamped the realised allocation to the one the driver can back, and the application
// wrote per-sample data it could never read.
TEST(GetterSanity, PerCategoryMultisampleCeilingsAreProbedRatherThanFlooredAtFour) {
using namespace MobileGL;
auto previousContext = Move(MG_State::pGLContext);
auto previousBackend = Move(MG_Backend::pActiveBackendObject);
MG_State::pGLContext = MakeUnique<MG_State::GLState::GLContext>();
MG_Backend::DynamicBackendParameters params;
params.MaxSamples = 4;
params.MaxColorTextureSamples = 4;
params.MaxDepthTextureSamples = 2;
params.MaxIntegerSamples = 1;
MG_Backend::pActiveBackendObject = MakeUnique<DynamicParameterBackend>(params);
GLint reported = -1;
MG_Impl::GLImpl::GetIntegerv(GL_MAX_INTEGER_SAMPLES, &reported);
EXPECT_EQ(reported, 1) << "an integer multisample texture is backed by one sample here, and "
"saying otherwise is what the application allocates against";
MG_Impl::GLImpl::GetIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &reported);
EXPECT_EQ(reported, 2);
MG_Impl::GLImpl::GetIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &reported);
EXPECT_EQ(reported, 4);
// ...while GL_MAX_SAMPLES keeps its floor of four, which is the one the spec really requires.
params.MaxSamples = 1;
MG_Backend::pActiveBackendObject = MakeUnique<DynamicParameterBackend>(params);
MG_Impl::GLImpl::GetIntegerv(GL_MAX_SAMPLES, &reported);
EXPECT_EQ(reported, 4);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Backend::pActiveBackendObject = Move(previousBackend);
MG_State::pGLContext = Move(previousContext);
}
TEST(GetterSanity, ReportsKhrSubgroupDynamicParameters) {
using namespace MobileGL;
@@ -563,7 +563,12 @@ namespace {
// BuildTBuiltInResource does not read it.
a.maxComputeWorkGroupInvocations = 128;
b.maxComputeWorkGroupInvocations = 2048;
// (4) a spread of DynamicBackendParameters fields the front end never reads
// (4) a spread of DynamicBackendParameters fields the front end never reads.
// MaxTextureImageUnits used to be here and is NOT any more: the GL 4.6 API-surface wave
// made BuildTBuiltInResource read it (gl_MaxTextureImageUnits expands from it), so it
// moved to TheFrontendFingerprintMovesWithEveryFrontendLimit. That migration is the
// third one this helper has survived; check BuildTBuiltInResource before adding a field
// here.
a.params.MaxColorTextureSamples = 1;
b.params.MaxColorTextureSamples = 8;
a.params.MaxTextureSize = 4096;
@@ -572,8 +577,8 @@ namespace {
b.params.MaxViewports = 16;
a.params.MaxUniformBufferBindings = 24;
b.params.MaxUniformBufferBindings = 84;
a.params.MaxTextureImageUnits = 16;
b.params.MaxTextureImageUnits = 32;
a.params.MaxRenderbufferSize = 4096;
b.params.MaxRenderbufferSize = 16384;
return {a, b};
}
} // namespace
@@ -677,6 +682,20 @@ TEST_F(TranslationCacheTest, TheFrontendFingerprintMovesWithEveryFrontendLimit)
// wave4's 4fc3531d: glslang rejects gl_ClipDistance[i] past this at parse AND expands
// gl_MaxClipDistances from it, so it is both a compile gate and a baked constant.
{"params.MaxClipDistances", [](CompileEnv& e) { e.params.MaxClipDistances += 1; }},
// The GL 4.6 API-surface wave: six more TBuiltInResource fields that used to be stock
// glslang literals. The cull pair is the MaxClipDistances story exactly (parse gate plus
// gl_MaxCullDistances / gl_MaxCombinedClipAndCullDistances); the texture-image-unit three
// and MaxSamples are baked constants (gl_MaxTextureImageUnits,
// gl_MaxVertexTextureImageUnits, gl_MaxCombinedTextureImageUnits, gl_MaxSamples - the
// last of which also sizes gl_SampleMask[]).
{"params.MaxCullDistances", [](CompileEnv& e) { e.params.MaxCullDistances += 1; }},
{"params.MaxCombinedClipAndCullDistances",
[](CompileEnv& e) { e.params.MaxCombinedClipAndCullDistances += 1; }},
{"params.MaxTextureImageUnits", [](CompileEnv& e) { e.params.MaxTextureImageUnits += 1; }},
{"params.MaxVertexTextureImageUnits", [](CompileEnv& e) { e.params.MaxVertexTextureImageUnits += 1; }},
{"params.MaxCombinedTextureImageUnits",
[](CompileEnv& e) { e.params.MaxCombinedTextureImageUnits += 1; }},
{"params.MaxSamples", [](CompileEnv& e) { e.params.MaxSamples += 1; }},
{"maxComputeWorkGroupSize[0]", [](CompileEnv& e) { e.maxComputeWorkGroupSize[0] += 1; }},
{"maxComputeWorkGroupSize[1]", [](CompileEnv& e) { e.maxComputeWorkGroupSize[1] += 1; }},
{"maxComputeWorkGroupSize[2]", [](CompileEnv& e) { e.maxComputeWorkGroupSize[2] += 1; }},
@@ -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 {