mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 04:38:30 +09:00
[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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 -
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user