mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 14:18:31 +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
|
// where there is no device to be honest about and BuildTBuiltInResource still has to
|
||||||
// hand glslang a workable gl_MaxClipDistances.
|
// hand glslang a workable gl_MaxClipDistances.
|
||||||
Int MaxClipDistances = 8;
|
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;
|
Int MaxViewports = 16;
|
||||||
// GL_LAYER_PROVOKING_VERTEX / GL_VIEWPORT_INDEX_PROVOKING_VERTEX: which vertex of a
|
// 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
|
// 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.MaxDrawBuffers = m_GLESCapabilities.MaxDrawBuffers;
|
||||||
m_dynamicParameters.MaxColorAttachments = m_GLESCapabilities.MaxColorAttachments;
|
m_dynamicParameters.MaxColorAttachments = m_GLESCapabilities.MaxColorAttachments;
|
||||||
m_dynamicParameters.MaxClipDistances = m_GLESCapabilities.MaxClipDistances;
|
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;
|
m_dynamicParameters.MaxViewports = m_GLESCapabilities.MaxViewports;
|
||||||
// Whatever the driver said about which vertex supplies gl_Layer, and GL_UNDEFINED_VERTEX
|
// 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
|
// 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
|
#ifdef TRACY_ENABLE
|
||||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||||
#endif
|
#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.
|
// deep but apps bind a handful, so the never-touched tail is already at GL default 0.
|
||||||
auto bindingPointCnt = MG_State::pGLContext->GetTouchedBufferBindingPointCount(target);
|
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) {
|
for (SizeT i = 0; i < bindingPointCnt; ++i) {
|
||||||
auto& point = MG_State::pGLContext->GetBufferBindingPoint(target, i);
|
auto& point = MG_State::pGLContext->GetBufferBindingPoint(target, i);
|
||||||
auto& obj = point.GetBoundObject();
|
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.
|
// it the limit describes a capacity no shader may use, so report none.
|
||||||
m_dynamicParameters.MaxClipDistances =
|
m_dynamicParameters.MaxClipDistances =
|
||||||
m_vulkanCaps.SupportsShaderClipDistance ? std::max(m_vulkanCaps.MaxClipDistances, 0) : 0;
|
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;
|
m_dynamicParameters.MaxViewports = m_vulkanCaps.MaxViewports;
|
||||||
// Assigned explicitly rather than left to the struct's defaults, like every other
|
// 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
|
// 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."));
|
std::to_string(id) + " is not a transform feedback object name."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// GL_MAX_VERTEX_STREAMS is 1, so stream 0 is the only one that exists.
|
// GL 4.6 core 10.3.7 bounds `stream` by GL_MAX_VERTEX_STREAMS, which is 4. Only stream 0
|
||||||
if (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(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidValue,
|
ErrorCode::InvalidValue,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
@@ -1596,7 +1601,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return;
|
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;
|
if (vertices == 0) return;
|
||||||
const auto count = static_cast<GLsizei>(vertices);
|
const auto count = static_cast<GLsizei>(vertices);
|
||||||
AccountTransformFeedbackPrimitives(mode, count);
|
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_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.
|
// (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
|
// The multisample TEXTURE path resolves the limit per format the same way
|
||||||
// (GL_Texture.cpp, GetMaxSupportedTextureSamples). Both are floored to the value MobileGL
|
// (GL_Texture.cpp, GetMaxSupportedTextureSamples), and both now enforce exactly what their
|
||||||
// advertises: on a driver where the two differ - Adreno reports GL_MAX_SAMPLES 4 and
|
// pname advertises. The integer ceiling used to be floored at GL_MAX_SAMPLES so that the
|
||||||
// GL_MAX_INTEGER_SAMPLES 1 - rejecting the advertised count here only moves the failure
|
// frontend would accept a count it had advertised globally - but on Adreno and Mali the
|
||||||
// from the driver into MobileGL, so the frontend accepts it and the backend clamps the
|
// integer path is genuinely one sample, and accepting four only moved the failure from an
|
||||||
// count it actually hands the driver.
|
// honest INVALID_OPERATION here to a silently under-allocated renderbuffer.
|
||||||
Int GetMaxRenderbufferSamplesForFormat_State(TextureInternalFormat format) {
|
Int GetMaxRenderbufferSamplesForFormat_State(TextureInternalFormat format) {
|
||||||
if (MG_Backend::pActiveBackendObject == nullptr) {
|
if (MG_Backend::pActiveBackendObject == nullptr) {
|
||||||
return std::numeric_limits<Int>::max();
|
return std::numeric_limits<Int>::max();
|
||||||
}
|
}
|
||||||
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
|
|
||||||
|
|
||||||
GLenum normalizedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(format);
|
GLenum normalizedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(format);
|
||||||
GLenum normalizedFormat = GL_RGBA;
|
GLenum normalizedFormat = GL_RGBA;
|
||||||
@@ -711,10 +710,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
if (!isIntegerFormat) {
|
if (!isIntegerFormat) {
|
||||||
return GetMaxRenderbufferSamples_State();
|
return GetMaxRenderbufferSamples_State();
|
||||||
}
|
}
|
||||||
// Per-format still, but never below the ceiling glGetIntegerv(GL_MAX_SAMPLES) promised:
|
// Exactly what glGetIntegerv(GL_MAX_INTEGER_SAMPLES) reports.
|
||||||
// the driver's raw GL_MAX_INTEGER_SAMPLES stays the *backend* limit and the backend
|
return GetAdvertisedIntegerMaxSamples();
|
||||||
// clamps to it, while the frontend honours what it advertised.
|
|
||||||
return std::max(dynamicParameters.MaxIntegerSamples, GetAdvertisedMaxSamples());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateRenderbufferStorageSize_State(GLsizei width, GLsizei height, const char* caller) {
|
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.
|
// limits they advertise still have to be legal.
|
||||||
constexpr GLint kFrontendMaxDebugGroupStackDepth = 64;
|
constexpr GLint kFrontendMaxDebugGroupStackDepth = 64;
|
||||||
constexpr GLint kFrontendMaxDebugLoggedMessages = 1;
|
constexpr GLint kFrontendMaxDebugLoggedMessages = 1;
|
||||||
constexpr GLint kFrontendMaxVertexUniformComponents = 4096;
|
// The *_VECTORS answers are the *_COMPONENTS ones divided by four, never a second
|
||||||
constexpr GLint kFrontendMaxVertexUniformVectors = 128;
|
// 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 kFrontendMaxVertexUniformBlocks = 14;
|
||||||
constexpr GLint kFrontendMaxVertexOutputComponents = 64;
|
constexpr GLint kFrontendMaxVertexOutputComponents = 64;
|
||||||
constexpr GLint kFrontendMaxFragmentInputComponents = 128;
|
constexpr GLint kFrontendMaxFragmentInputComponents = 128;
|
||||||
@@ -106,21 +113,50 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
constexpr GLint kFrontendMaxGeometryTextureImageUnits = 16;
|
constexpr GLint kFrontendMaxGeometryTextureImageUnits = 16;
|
||||||
constexpr GLint kFrontendMaxGeometryUniformComponents = 1024;
|
constexpr GLint kFrontendMaxGeometryUniformComponents = 1024;
|
||||||
constexpr GLint kFrontendMaxGeometryUniformBlocks = 14;
|
constexpr GLint kFrontendMaxGeometryUniformBlocks = 14;
|
||||||
constexpr GLint kFrontendMaxCombinedUniformBlocks = kFrontendMaxVertexUniformBlocks +
|
// ARB_geometry_shader4's per-invocation count. No TBuiltInResource field and no
|
||||||
kFrontendMaxGeometryUniformBlocks +
|
// 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;
|
kFrontendMaxFragmentUniformBlocks;
|
||||||
constexpr GLint kFrontendMaxVaryingComponents = 64;
|
constexpr GLint kFrontendMaxVaryingComponents =
|
||||||
constexpr GLint kFrontendMaxVaryingVectors = 8;
|
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 kFrontendMaxProgramTexelOffset = 7;
|
||||||
constexpr GLint kFrontendMinProgramTexelOffset = -8;
|
constexpr GLint kFrontendMinProgramTexelOffset = -8;
|
||||||
constexpr GLint kFrontendMaxTransformFeedbackInterleavedComponents = 64;
|
constexpr GLint kFrontendMaxTransformFeedbackInterleavedComponents = 64;
|
||||||
constexpr GLint kFrontendMaxTransformFeedbackSeparateAttribs = 4;
|
constexpr GLint kFrontendMaxTransformFeedbackSeparateAttribs = 4;
|
||||||
constexpr GLint kFrontendMaxTransformFeedbackSeparateComponents = 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 kFrontendMaxGeometryOutputVertices = 256;
|
||||||
constexpr GLint kFrontendMaxGeometryTotalOutputComponents = 1024;
|
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 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
|
// The floors under GL_MAX_COMPUTE_WORK_GROUP_COUNT / _SIZE. Shared with the compile
|
||||||
// pipeline (CaptureCompileEnv floors the same driver answers at them, and
|
// pipeline (CaptureCompileEnv floors the same driver answers at them, and
|
||||||
@@ -455,10 +491,18 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// GL 4.6 core table 23.53 requires GL_MAX_SAMPLES >= 4, so the driver's value is floored
|
// 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
|
// before it is advertised. gl_MaxSamples expands from the same floored number
|
||||||
// floored the same way: promising 4 samples globally while answering GL_MAX_INTEGER_SAMPLES
|
// (BuildTBuiltInResource), which is also what sizes gl_SampleMask[].
|
||||||
// 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.
|
// 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() {
|
GLint GetAdvertisedMaxSamples() {
|
||||||
if (MG_Backend::pActiveBackendObject == nullptr) {
|
if (MG_Backend::pActiveBackendObject == nullptr) {
|
||||||
return kFrontendMaxSamples;
|
return kFrontendMaxSamples;
|
||||||
@@ -466,6 +510,30 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return std::max(MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxSamples, kFrontendMaxSamples);
|
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
|
// 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.
|
// gl_NumSamples stand-in that glGetIntegerv(GL_SAMPLES) reports.
|
||||||
GLint ResolveDrawFramebufferSampleCount() {
|
GLint ResolveDrawFramebufferSampleCount() {
|
||||||
@@ -1227,6 +1295,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (pname) {
|
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:
|
case GL_MAX_SHADER_STORAGE_BLOCK_SIZE:
|
||||||
if (MG_Backend::pActiveBackendObject) {
|
if (MG_Backend::pActiveBackendObject) {
|
||||||
params[0] = static_cast<GLint64>(
|
params[0] = static_cast<GLint64>(
|
||||||
@@ -1706,6 +1781,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_MAX_GEOMETRY_UNIFORM_COMPONENTS:
|
case GL_MAX_GEOMETRY_UNIFORM_COMPONENTS:
|
||||||
*params = kFrontendMaxGeometryUniformComponents;
|
*params = kFrontendMaxGeometryUniformComponents;
|
||||||
return;
|
return;
|
||||||
|
case GL_MAX_GEOMETRY_SHADER_INVOCATIONS:
|
||||||
|
*params = kFrontendMaxGeometryShaderInvocations;
|
||||||
|
return;
|
||||||
case GL_MAX_IMAGE_SAMPLES:
|
case GL_MAX_IMAGE_SAMPLES:
|
||||||
*params = 0; // multisampled image load/store is not exposed by the DirectGLES frontend
|
*params = 0; // multisampled image load/store is not exposed by the DirectGLES frontend
|
||||||
return;
|
return;
|
||||||
@@ -1759,6 +1837,59 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params =
|
*params =
|
||||||
StageStorageBlockCount(&MG_Backend::DynamicBackendParameters::MaxTessEvaluationShaderStorageBlocks);
|
StageStorageBlockCount(&MG_Backend::DynamicBackendParameters::MaxTessEvaluationShaderStorageBlocks);
|
||||||
return;
|
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:
|
case GL_MAX_TEXTURE_LOD_BIAS:
|
||||||
*params = 15; // TODO
|
*params = 15; // TODO
|
||||||
return;
|
return;
|
||||||
@@ -2174,7 +2305,12 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case GL_MAX_ELEMENT_INDEX:
|
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;
|
return;
|
||||||
case GL_CONTEXT_PROFILE_MASK:
|
case GL_CONTEXT_PROFILE_MASK:
|
||||||
// Reports the requested context profile (EGL defaults 3.x contexts to core);
|
// 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);
|
*params = static_cast<GLint>(dynamicParameters.ViewportIndexProvokingVertex);
|
||||||
break;
|
break;
|
||||||
case GL_MAX_COLOR_TEXTURE_SAMPLES:
|
case GL_MAX_COLOR_TEXTURE_SAMPLES:
|
||||||
*params = std::max(dynamicParameters.MaxColorTextureSamples, GetAdvertisedMaxSamples());
|
*params = GetAdvertisedColorTextureMaxSamples();
|
||||||
break;
|
break;
|
||||||
case GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS:
|
case GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS:
|
||||||
*params = GetMaxCombinedUniformComponents(kFrontendMaxFragmentUniformComponents,
|
*params = GetMaxCombinedUniformComponents(kFrontendMaxFragmentUniformComponents,
|
||||||
@@ -2305,7 +2441,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params = dynamicParameters.MaxCubeMapTextureSize;
|
*params = dynamicParameters.MaxCubeMapTextureSize;
|
||||||
break;
|
break;
|
||||||
case GL_MAX_DEPTH_TEXTURE_SAMPLES:
|
case GL_MAX_DEPTH_TEXTURE_SAMPLES:
|
||||||
*params = std::max(dynamicParameters.MaxDepthTextureSamples, GetAdvertisedMaxSamples());
|
*params = GetAdvertisedDepthTextureMaxSamples();
|
||||||
break;
|
break;
|
||||||
case GL_MAX_FRAMEBUFFER_WIDTH:
|
case GL_MAX_FRAMEBUFFER_WIDTH:
|
||||||
*params = dynamicParameters.MaxFramebufferWidth;
|
*params = dynamicParameters.MaxFramebufferWidth;
|
||||||
@@ -2332,7 +2468,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params = dynamicParameters.MaxComputeImageUniforms;
|
*params = dynamicParameters.MaxComputeImageUniforms;
|
||||||
break;
|
break;
|
||||||
case GL_MAX_INTEGER_SAMPLES:
|
case GL_MAX_INTEGER_SAMPLES:
|
||||||
*params = std::max(dynamicParameters.MaxIntegerSamples, GetAdvertisedMaxSamples());
|
*params = GetAdvertisedIntegerMaxSamples();
|
||||||
break;
|
break;
|
||||||
case GL_MAX_RENDERBUFFER_SIZE:
|
case GL_MAX_RENDERBUFFER_SIZE:
|
||||||
*params = dynamicParameters.MaxRenderbufferSize;
|
*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]));
|
for (Uint i = 0; i < 2; ++i) params[i] = static_cast<GLint>(std::lround(inner[i]));
|
||||||
break;
|
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:
|
case GL_MAX_PATCH_VERTICES:
|
||||||
*params = dynamicParameters.MaxPatchVertices;
|
*params = dynamicParameters.MaxPatchVertices;
|
||||||
break;
|
break;
|
||||||
case GL_MAX_TESS_GEN_LEVEL:
|
case GL_MAX_TESS_GEN_LEVEL:
|
||||||
*params = dynamicParameters.MaxTessGenLevel;
|
*params = dynamicParameters.MaxTessGenLevel;
|
||||||
break;
|
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:
|
case GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET:
|
||||||
*params = dynamicParameters.MinProgramTextureGatherOffset;
|
*params = dynamicParameters.MinProgramTextureGatherOffset;
|
||||||
break;
|
break;
|
||||||
@@ -2412,7 +2579,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params = kFrontendMaxTransformFeedbackSeparateAttribs;
|
*params = kFrontendMaxTransformFeedbackSeparateAttribs;
|
||||||
break;
|
break;
|
||||||
case GL_MAX_VERTEX_STREAMS:
|
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;
|
break;
|
||||||
case GL_TRANSFORM_FEEDBACK_ACTIVE:
|
case GL_TRANSFORM_FEEDBACK_ACTIVE:
|
||||||
*params = MG_State::pGLContext->IsTransformFeedbackActive() ? 1 : 0;
|
*params = MG_State::pGLContext->IsTransformFeedbackActive() ? 1 : 0;
|
||||||
@@ -2429,15 +2604,28 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
case GL_MAX_TEXTURE_SIZE:
|
case GL_MAX_TEXTURE_SIZE:
|
||||||
*params = dynamicParameters.MaxTextureSize;
|
*params = dynamicParameters.MaxTextureSize;
|
||||||
break;
|
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
|
// Never advertise more bindings than the state layer's indexed-binding array can track
|
||||||
// (BufferState::BufferBindingPointCount): glBindBufferBase rejects indices past that
|
// (BufferState::BufferBindingPointCount): glBindBufferBase rejects indices past that
|
||||||
// capacity, and the GL CTS per-case state reset calls glBindBufferBase on every
|
// 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
|
// advertised index and expects no error. The floor is the GL 4.5 core minimum, and
|
||||||
// (36), so the clamp never under-advertises.
|
// 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,
|
*params = std::clamp(dynamicParameters.MaxUniformBufferBindings, kFrontendMinUniformBufferBindings,
|
||||||
static_cast<GLint>(MG_State::GLState::BufferBindingPointCount));
|
static_cast<GLint>(MG_State::GLState::BufferBindingPointCount));
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case GL_MAX_UNIFORM_BLOCK_SIZE:
|
case GL_MAX_UNIFORM_BLOCK_SIZE:
|
||||||
*params = dynamicParameters.MaxUniformBlockSize;
|
*params = dynamicParameters.MaxUniformBlockSize;
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -25,9 +25,17 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLenum GetError();
|
GLenum GetError();
|
||||||
GLenum GetGraphicsResetStatus();
|
GLenum GetGraphicsResetStatus();
|
||||||
// The GL_MAX_SAMPLES value MobileGL advertises, i.e. the driver's value floored to the GL
|
// 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
|
// core minimum of 4. This is the RENDERBUFFER ceiling; the three per-category texture
|
||||||
// format, otherwise MobileGL rejects a sample count it advertised itself.
|
// ceilings below have a minimum of one and are reported as probed.
|
||||||
GLint GetAdvertisedMaxSamples();
|
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
|
// 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
|
// 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).
|
// 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.
|
// stand in for the backend's.
|
||||||
Uint64 accountedCaptureDrawSnapshot = 0;
|
Uint64 accountedCaptureDrawSnapshot = 0;
|
||||||
Uint64 geometryCaptureDrawSnapshot = 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
|
// Query calls may arrive from any thread (launchers migrate the context
|
||||||
@@ -116,6 +122,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
queryObject->ended = false;
|
queryObject->ended = false;
|
||||||
queryObject->resultCached = false;
|
queryObject->resultCached = false;
|
||||||
queryObject->cachedResult = 0;
|
queryObject->cachedResult = 0;
|
||||||
|
queryObject->emptyVertexStream = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callers must hold g_queryObjectsMutex.
|
// Callers must hold g_queryObjectsMutex.
|
||||||
@@ -188,6 +195,23 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return false;
|
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) {
|
switch (pname) {
|
||||||
case GL_QUERY_TARGET:
|
case GL_QUERY_TARGET:
|
||||||
// The target a query was begun with (or created with, for glCreateQueries) - state
|
// The target a query was begun with (or created with, for glCreateQueries) - state
|
||||||
@@ -741,14 +765,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
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
|
// 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
|
// 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
|
// for the two transform feedback targets and zero for every other target.
|
||||||
// single vertex stream both bounds are 1, so a valid call is always index 0 and
|
|
||||||
// forwards to the unindexed implementation.
|
|
||||||
Bool ValidateQueryStreamIndex(const char* function, GLenum target, GLuint index) {
|
Bool ValidateQueryStreamIndex(const char* function, GLenum target, GLuint index) {
|
||||||
const Bool perStreamTarget =
|
const Bool perStreamTarget = IsPerVertexStreamQueryTarget(target);
|
||||||
target == GL_PRIMITIVES_GENERATED || target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN;
|
|
||||||
GLint maxVertexStreams = 1;
|
GLint maxVertexStreams = 1;
|
||||||
if (perStreamTarget) {
|
if (perStreamTarget) {
|
||||||
GetIntegerv(GL_MAX_VERTEX_STREAMS, &maxVertexStreams);
|
GetIntegerv(GL_MAX_VERTEX_STREAMS, &maxVertexStreams);
|
||||||
@@ -761,11 +786,29 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
: "index must be zero for this query target.");
|
: "index must be zero for this query target.");
|
||||||
return false;
|
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
|
} // namespace
|
||||||
|
|
||||||
void BeginQueryIndexed(GLenum target, GLuint index, GLuint id) {
|
void BeginQueryIndexed(GLenum target, GLuint index, GLuint id) {
|
||||||
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
if (!ValidateQueryStreamIndex(__FUNCTION__, target, index)) return;
|
||||||
BeginQuery(target, id);
|
BeginQuery(target, id);
|
||||||
|
if (index != 0 && IsPerVertexStreamQueryTarget(target)) MarkQueryEmptyVertexStream(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EndQueryIndexed(GLenum target, GLuint index) {
|
void EndQueryIndexed(GLenum target, GLuint index) {
|
||||||
|
|||||||
@@ -522,44 +522,46 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return sampleCounts.empty() ? 0 : sampleCounts.front();
|
return sampleCounts.empty() ? 0 : sampleCounts.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The ceiling the frontend enforces, which must never be lower than the one MobileGL
|
// The ceiling the frontend enforces, which is EXACTLY the one MobileGL advertises for
|
||||||
// advertises: the CTS - and real applications - read GL_MAX_SAMPLES once and hand that
|
// this format's category - GL_MAX_DEPTH_TEXTURE_SAMPLES, GL_MAX_INTEGER_SAMPLES or
|
||||||
// exact count to glTexImage*Multisample for every format. Answering 4 there and then
|
// GL_MAX_COLOR_TEXTURE_SAMPLES, all three of which have a GL 4.6 minimum of one and are
|
||||||
// rejecting 4 here because the ES driver reports GL_MAX_INTEGER_SAMPLES 1 (Adreno) is a
|
// reported as probed. It used to floor all three at GL_MAX_SAMPLES (4) on the reasoning
|
||||||
// self-inconsistency, not a spec-mandated error. The backends clamp the count they hand
|
// that an application reads GL_MAX_SAMPLES once and hands that count to every
|
||||||
// the driver; the shadow state keeps reporting what the application asked for.
|
// 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,
|
Int GetMaxSupportedTextureSamples(TextureTarget textureTarget,
|
||||||
TextureInternalFormat textureInternalFormat) {
|
TextureInternalFormat textureInternalFormat) {
|
||||||
if (MG_Backend::pActiveBackendObject == nullptr) {
|
if (MG_Backend::pActiveBackendObject == nullptr) {
|
||||||
return std::numeric_limits<Int>::max();
|
return std::numeric_limits<Int>::max();
|
||||||
}
|
}
|
||||||
|
|
||||||
const Int advertisedMaxSamples = GetAdvertisedMaxSamples();
|
const Bool isDepthOrStencil = MG_Util::IsDepthFormatInternalFormat(textureInternalFormat) ||
|
||||||
// glGetInternalformativ(GL_SAMPLES) is answered from this very list (GetInternalformativ
|
MG_Util::IsStencilFormatInternalFormat(textureInternalFormat);
|
||||||
// below), and GL 4.6 core 8.8 makes that query the definition of the per-format
|
Bool isIntegerFormat = false;
|
||||||
// maximum - validating against anything else is how the two answers drifted apart.
|
if (!isDepthOrStencil) {
|
||||||
const Int probedMaxSamples = GetProbedMaxTextureSamples(textureTarget, textureInternalFormat);
|
GLenum normalizedInternalFormat =
|
||||||
if (probedMaxSamples > 0) {
|
MG_Util::ConvertTextureInternalFormatToGLEnum(textureInternalFormat);
|
||||||
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 normalizedFormat = GL_RGBA;
|
||||||
GLenum normalizedType = GL_UNSIGNED_BYTE;
|
GLenum normalizedType = GL_UNSIGNED_BYTE;
|
||||||
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
|
||||||
normalizedInternalFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat,
|
normalizedInternalFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat,
|
||||||
&normalizedFormat, &normalizedType);
|
&normalizedFormat, &normalizedType);
|
||||||
const Bool isIntegerFormat = normalizedFormat == GL_RED_INTEGER || normalizedFormat == GL_RG_INTEGER ||
|
isIntegerFormat = normalizedFormat == GL_RED_INTEGER || normalizedFormat == GL_RG_INTEGER ||
|
||||||
normalizedFormat == GL_RGB_INTEGER || normalizedFormat == GL_RGBA_INTEGER;
|
normalizedFormat == GL_RGB_INTEGER || normalizedFormat == GL_RGBA_INTEGER;
|
||||||
return std::max(isIntegerFormat ? dynamicParameters.MaxIntegerSamples
|
}
|
||||||
: dynamicParameters.MaxColorTextureSamples,
|
const Int categoryMaxSamples = isDepthOrStencil ? GetAdvertisedDepthTextureMaxSamples()
|
||||||
advertisedMaxSamples);
|
: 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);
|
||||||
|
return probedMaxSamples > 0 ? std::max(probedMaxSamples, categoryMaxSamples) : categoryMaxSamples;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateTextureMultisampleStorage(TextureTarget textureTarget, GLsizei samples, GLsizei width,
|
Bool ValidateTextureMultisampleStorage(TextureTarget textureTarget, GLsizei samples, GLsizei width,
|
||||||
|
|||||||
@@ -12,15 +12,15 @@
|
|||||||
#include <MG_State/GLState/ErrorState/Error.h>
|
#include <MG_State/GLState/ErrorState/Error.h>
|
||||||
#include <MG_Util/Converters/MGToGL/DataTypeConverter.h>
|
#include <MG_Util/Converters/MGToGL/DataTypeConverter.h>
|
||||||
#include <MG_Util/Converters/MGToStr/DataTypeConverter.h>
|
#include <MG_Util/Converters/MGToStr/DataTypeConverter.h>
|
||||||
|
#include <MG_Util/ShaderTranspiler/CompileEnv.h>
|
||||||
|
|
||||||
namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
||||||
Uint GetMaxVertexAttribs() {
|
Uint GetMaxVertexAttribs() {
|
||||||
constexpr Uint capacity = static_cast<Uint>(MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS);
|
// Shared with reflection's limit and with gl_MaxVertexAttribs; see ResolveMaxVertexAttribs.
|
||||||
if (!MG_Backend::pActiveBackendObject) return capacity;
|
const Bool hasBackend = MG_Backend::pActiveBackendObject != nullptr;
|
||||||
|
const Int backendLimit =
|
||||||
const Int backendLimit = MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxVertexAttribs;
|
hasBackend ? MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxVertexAttribs : 0;
|
||||||
if (backendLimit <= 0) return capacity;
|
return static_cast<Uint>(MG_Util::ShaderTranspiler::ResolveMaxVertexAttribs(hasBackend, backendLimit));
|
||||||
return std::min(static_cast<Uint>(backendLimit), capacity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint GetMaxVertexAttribBindings() {
|
Uint GetMaxVertexAttribBindings() {
|
||||||
|
|||||||
@@ -56,7 +56,13 @@ namespace MGITest {
|
|||||||
|
|
||||||
const std::vector<LimitBound>& BufferLimitTable() {
|
const std::vector<LimitBound>& BufferLimitTable() {
|
||||||
static const std::vector<LimitBound> table = {
|
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_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_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},
|
{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";
|
"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 -
|
// 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
|
// 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
|
// 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);
|
BufferTarget::DrawIndirect, BufferTarget::Parameter, BufferTarget::ShaderStorage);
|
||||||
constexpr const auto BufferBindPointTargets = ToArray(BufferTarget::Uniform, BufferTarget::TransformFeedback,
|
constexpr const auto BufferBindPointTargets = ToArray(BufferTarget::Uniform, BufferTarget::TransformFeedback,
|
||||||
BufferTarget::AtomicCounter, BufferTarget::ShaderStorage);
|
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 {
|
class BufferState {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -29,13 +29,11 @@ namespace {
|
|||||||
// capacity, which is also the width of the Uint32 masks backends build from it.
|
// capacity, which is also the width of the Uint32 masks backends build from it.
|
||||||
static MobileGL::Int GetReflectionVertexAttribLimit(
|
static MobileGL::Int GetReflectionVertexAttribLimit(
|
||||||
const MobileGL::MG_Util::ShaderTranspiler::CompileEnv& env) {
|
const MobileGL::MG_Util::ShaderTranspiler::CompileEnv& env) {
|
||||||
constexpr MobileGL::Int capacity =
|
// One shared definition with glGetIntegerv(GL_MAX_VERTEX_ATTRIBS) and with
|
||||||
static_cast<MobileGL::Int>(MobileGL::MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS);
|
// BuildTBuiltInResource's gl_MaxVertexAttribs - the three used to carry three copies of
|
||||||
if (!env.HasBackend()) return capacity;
|
// this formula and glslang's copy was a hardcoded 64.
|
||||||
|
return MobileGL::MG_Util::ShaderTranspiler::ResolveMaxVertexAttribs(env.HasBackend(),
|
||||||
const MobileGL::Int backendLimit = env.params.MaxVertexAttribs;
|
env.params.MaxVertexAttribs);
|
||||||
if (backendLimit <= 0) return capacity;
|
|
||||||
return std::min(backendLimit, capacity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything the post-link query surface ever asks a glslang::TType, flattened into a
|
// Everything the post-link query surface ever asks a glslang::TType, flattened into a
|
||||||
|
|||||||
@@ -41,8 +41,15 @@
|
|||||||
namespace {
|
namespace {
|
||||||
class DynamicParameterBackend final : public MobileGL::MG_Backend::BackendObject {
|
class DynamicParameterBackend final : public MobileGL::MG_Backend::BackendObject {
|
||||||
public:
|
public:
|
||||||
explicit DynamicParameterBackend(MobileGL::MG_Backend::DynamicBackendParameters params):
|
// `type` defaults to Unknown, which is what every existing case wanted: a limits-only
|
||||||
m_params(params) {}
|
// 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 {}
|
void Initialize() override {}
|
||||||
MobileGL::Bool InitCapabilities() override { return true; }
|
MobileGL::Bool InitCapabilities() override { return true; }
|
||||||
@@ -55,10 +62,11 @@ namespace {
|
|||||||
const MobileGL::MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override {
|
const MobileGL::MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override {
|
||||||
return m_params;
|
return m_params;
|
||||||
}
|
}
|
||||||
MobileGL::BackendType GetBackendType() const override { return MobileGL::BackendType::Unknown; }
|
MobileGL::BackendType GetBackendType() const override { return m_type; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MobileGL::MG_Backend::DynamicBackendParameters m_params;
|
MobileGL::MG_Backend::DynamicBackendParameters m_params;
|
||||||
|
MobileGL::BackendType m_type = MobileGL::BackendType::Unknown;
|
||||||
MobileGL::MG_Backend::GlobalBackendFunctionsTable m_functions{};
|
MobileGL::MG_Backend::GlobalBackendFunctionsTable m_functions{};
|
||||||
MobileGL::RendererInfo m_info{
|
MobileGL::RendererInfo m_info{
|
||||||
.RendererName = "Test",
|
.RendererName = "Test",
|
||||||
@@ -722,6 +730,62 @@ TEST(DirectVulkanSanity, GatesClipDistancesOnTheShaderClipDistanceFeature) {
|
|||||||
EXPECT_EQ(backend.GetDynamicParameters().MaxClipDistances, 8);
|
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_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
|
// 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
|
// devices in opposite directions. DirectGLES now forwards what its loader resolved; DirectVulkan
|
||||||
@@ -1234,6 +1298,176 @@ void main() {
|
|||||||
MG_State::pGLContext = Move(previousContext);
|
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) {
|
TEST(GetterSanity, ReportsKhrSubgroupDynamicParameters) {
|
||||||
using namespace MobileGL;
|
using namespace MobileGL;
|
||||||
|
|
||||||
|
|||||||
@@ -563,7 +563,12 @@ namespace {
|
|||||||
// BuildTBuiltInResource does not read it.
|
// BuildTBuiltInResource does not read it.
|
||||||
a.maxComputeWorkGroupInvocations = 128;
|
a.maxComputeWorkGroupInvocations = 128;
|
||||||
b.maxComputeWorkGroupInvocations = 2048;
|
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;
|
a.params.MaxColorTextureSamples = 1;
|
||||||
b.params.MaxColorTextureSamples = 8;
|
b.params.MaxColorTextureSamples = 8;
|
||||||
a.params.MaxTextureSize = 4096;
|
a.params.MaxTextureSize = 4096;
|
||||||
@@ -572,8 +577,8 @@ namespace {
|
|||||||
b.params.MaxViewports = 16;
|
b.params.MaxViewports = 16;
|
||||||
a.params.MaxUniformBufferBindings = 24;
|
a.params.MaxUniformBufferBindings = 24;
|
||||||
b.params.MaxUniformBufferBindings = 84;
|
b.params.MaxUniformBufferBindings = 84;
|
||||||
a.params.MaxTextureImageUnits = 16;
|
a.params.MaxRenderbufferSize = 4096;
|
||||||
b.params.MaxTextureImageUnits = 32;
|
b.params.MaxRenderbufferSize = 16384;
|
||||||
return {a, b};
|
return {a, b};
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
@@ -677,6 +682,20 @@ TEST_F(TranslationCacheTest, TheFrontendFingerprintMovesWithEveryFrontendLimit)
|
|||||||
// wave4's 4fc3531d: glslang rejects gl_ClipDistance[i] past this at parse AND expands
|
// 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.
|
// gl_MaxClipDistances from it, so it is both a compile gate and a baked constant.
|
||||||
{"params.MaxClipDistances", [](CompileEnv& e) { e.params.MaxClipDistances += 1; }},
|
{"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[0]", [](CompileEnv& e) { e.maxComputeWorkGroupSize[0] += 1; }},
|
||||||
{"maxComputeWorkGroupSize[1]", [](CompileEnv& e) { e.maxComputeWorkGroupSize[1] += 1; }},
|
{"maxComputeWorkGroupSize[1]", [](CompileEnv& e) { e.maxComputeWorkGroupSize[1] += 1; }},
|
||||||
{"maxComputeWorkGroupSize[2]", [](CompileEnv& e) { e.maxComputeWorkGroupSize[2] += 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
|
// 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.
|
// clipping program silently rendered nothing. The guarded probe below only ever widens it.
|
||||||
GLint maxClipDistances = 0;
|
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;
|
GLint maxViewports = 16;
|
||||||
// GL_UNDEFINED_VERTEX is what stands when the probes below cannot run, and it is a legal
|
// 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
|
// 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");
|
"rejected; reporting no clip distances");
|
||||||
maxClipDistances = 0;
|
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);
|
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims);
|
||||||
// GL_LAYER_PROVOKING_VERTEX is ES 3.2 core (it arrives with geometry shaders, which is
|
// 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
|
// 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.
|
// 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.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.MaxViewports = maxViewports;
|
||||||
caps.LayerProvokingVertex = layerProvokingVertex;
|
caps.LayerProvokingVertex = layerProvokingVertex;
|
||||||
caps.ViewportIndexProvokingVertex = viewportIndexProvokingVertex;
|
caps.ViewportIndexProvokingVertex = viewportIndexProvokingVertex;
|
||||||
@@ -1651,6 +1681,8 @@ namespace MobileGL::MG_Util::BackendLoader {
|
|||||||
// and "this driver has no clip distances".
|
// and "this driver has no clip distances".
|
||||||
MGLOG_I(" GL_MAX_CLIP_DISTANCES: %d%s", caps.MaxClipDistances,
|
MGLOG_I(" GL_MAX_CLIP_DISTANCES: %d%s", caps.MaxClipDistances,
|
||||||
caps.SupportsClipDistance ? "" : " (no GL_EXT_clip_cull_distance on this driver)");
|
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_VIEWPORTS: %d", caps.MaxViewports);
|
||||||
MGLOG_I(" GL_MAX_VIEWPORT_DIMS: [%d, %d]", caps.MaxViewportWidth, caps.MaxViewportHeight);
|
MGLOG_I(" GL_MAX_VIEWPORT_DIMS: [%d, %d]", caps.MaxViewportWidth, caps.MaxViewportHeight);
|
||||||
MGLOG_I(" GL_VIEWPORT_BOUNDS_RANGE: [%.3f, %.3f]", caps.ViewportBoundsRangeMin,
|
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
|
// GL_EXT_clip_cull_distance, so a driver without it has none. See the guarded probe
|
||||||
// in FillInGLESCapabilities.
|
// in FillInGLESCapabilities.
|
||||||
Int MaxClipDistances = 0;
|
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;
|
Int MaxViewports = 16;
|
||||||
// GL_LAYER_PROVOKING_VERTEX (ES 3.2 core) and GL_VIEWPORT_INDEX_PROVOKING_VERTEX
|
// 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
|
// (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.MaxDrawBuffers = SaturateToInt(p.limits.maxFragmentOutputAttachments);
|
||||||
caps.MaxColorAttachments = SaturateToInt(p.limits.maxColorAttachments);
|
caps.MaxColorAttachments = SaturateToInt(p.limits.maxColorAttachments);
|
||||||
caps.MaxClipDistances = SaturateToInt(p.limits.maxClipDistances);
|
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.MaxViewports = SaturateToInt(p.limits.maxViewports);
|
||||||
caps.MaxViewportWidth = SaturateToInt(p.limits.maxViewportDimensions[0]);
|
caps.MaxViewportWidth = SaturateToInt(p.limits.maxViewportDimensions[0]);
|
||||||
caps.MaxViewportHeight = SaturateToInt(p.limits.maxViewportDimensions[1]);
|
caps.MaxViewportHeight = SaturateToInt(p.limits.maxViewportDimensions[1]);
|
||||||
@@ -239,6 +241,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
|||||||
caps.SupportsFragmentStoresAndAtomics = supportedFeatures.fragmentStoresAndAtomics == VK_TRUE;
|
caps.SupportsFragmentStoresAndAtomics = supportedFeatures.fragmentStoresAndAtomics == VK_TRUE;
|
||||||
caps.SupportsGeometryShader = supportedFeatures.geometryShader == VK_TRUE;
|
caps.SupportsGeometryShader = supportedFeatures.geometryShader == VK_TRUE;
|
||||||
caps.SupportsShaderClipDistance = supportedFeatures.shaderClipDistance == VK_TRUE;
|
caps.SupportsShaderClipDistance = supportedFeatures.shaderClipDistance == VK_TRUE;
|
||||||
|
caps.SupportsShaderCullDistance = supportedFeatures.shaderCullDistance == VK_TRUE;
|
||||||
caps.MaxShaderStorageBlockSize = static_cast<SizeT>(p.limits.maxStorageBufferRange);
|
caps.MaxShaderStorageBlockSize = static_cast<SizeT>(p.limits.maxStorageBufferRange);
|
||||||
const Bool supportsShaderSubgroup = vk.vkGetPhysicalDeviceProperties2 &&
|
const Bool supportsShaderSubgroup = vk.vkGetPhysicalDeviceProperties2 &&
|
||||||
HasUsableShaderSubgroupSupport(subgroupProps);
|
HasUsableShaderSubgroupSupport(subgroupProps);
|
||||||
@@ -319,6 +322,8 @@ namespace MobileGL::MG_Util::BackendLoader {
|
|||||||
caps.MaxDrawBuffers = SaturateToInt(properties.limits.maxFragmentOutputAttachments);
|
caps.MaxDrawBuffers = SaturateToInt(properties.limits.maxFragmentOutputAttachments);
|
||||||
caps.MaxColorAttachments = SaturateToInt(properties.limits.maxColorAttachments);
|
caps.MaxColorAttachments = SaturateToInt(properties.limits.maxColorAttachments);
|
||||||
caps.MaxClipDistances = SaturateToInt(properties.limits.maxClipDistances);
|
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.MaxViewports = SaturateToInt(properties.limits.maxViewports);
|
||||||
caps.MaxViewportWidth = SaturateToInt(properties.limits.maxViewportDimensions[0]);
|
caps.MaxViewportWidth = SaturateToInt(properties.limits.maxViewportDimensions[0]);
|
||||||
caps.MaxViewportHeight = SaturateToInt(properties.limits.maxViewportDimensions[1]);
|
caps.MaxViewportHeight = SaturateToInt(properties.limits.maxViewportDimensions[1]);
|
||||||
@@ -336,6 +341,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
|||||||
caps.SupportsFragmentStoresAndAtomics = false;
|
caps.SupportsFragmentStoresAndAtomics = false;
|
||||||
caps.SupportsGeometryShader = false;
|
caps.SupportsGeometryShader = false;
|
||||||
caps.SupportsShaderClipDistance = false;
|
caps.SupportsShaderClipDistance = false;
|
||||||
|
caps.SupportsShaderCullDistance = false;
|
||||||
caps.MaxShaderStorageBlockSize = static_cast<SizeT>(properties.limits.maxStorageBufferRange);
|
caps.MaxShaderStorageBlockSize = static_cast<SizeT>(properties.limits.maxStorageBufferRange);
|
||||||
caps.SupportsShaderSubgroup = false;
|
caps.SupportsShaderSubgroup = false;
|
||||||
caps.SubgroupSize = 0;
|
caps.SubgroupSize = 0;
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ namespace MobileGL {
|
|||||||
Int MaxDrawBuffers = 8;
|
Int MaxDrawBuffers = 8;
|
||||||
Int MaxColorAttachments = 8;
|
Int MaxColorAttachments = 8;
|
||||||
Int MaxClipDistances = 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 MaxViewports = 16;
|
||||||
Int MaxViewportWidth = 16384;
|
Int MaxViewportWidth = 16384;
|
||||||
Int MaxViewportHeight = 16384;
|
Int MaxViewportHeight = 16384;
|
||||||
@@ -106,6 +111,11 @@ namespace MobileGL {
|
|||||||
// device has it, and without it a shader writing gl_ClipDistance is invalid. Very
|
// 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.
|
// widely supported, hence read from the device features and never assumed false.
|
||||||
Bool SupportsShaderClipDistance = 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;
|
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
||||||
Bool SupportsShaderSubgroup = false;
|
Bool SupportsShaderSubgroup = false;
|
||||||
Uint32 SubgroupSize = 0;
|
Uint32 SubgroupSize = 0;
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ namespace MobileGL::MG_Util::ShaderTranspiler {
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // 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 ComputeCompileEnvFingerprint(const CompileEnv& env) {
|
||||||
Uint64 state = 0x9e3779b97f4a7c15ull;
|
Uint64 state = 0x9e3779b97f4a7c15ull;
|
||||||
HashValue(state, env.maxComputeWorkGroupSize[0]);
|
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
|
// 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.
|
// shader compiles at all and what a module that reads the constant generates.
|
||||||
HashValue(state, env.params.MaxClipDistances);
|
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
|
// 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
|
// 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 -
|
// 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
|
// * the DynamicBackendParameters fields BuildTBuiltInResource copies into
|
||||||
// TBuiltInResource - MaxImageUnits, MaxDrawBuffers, MaxVertexImageUniforms,
|
// TBuiltInResource - MaxImageUnits, MaxDrawBuffers, MaxVertexImageUniforms,
|
||||||
// MaxGeometryImageUniforms, MaxFragmentImageUniforms, MaxComputeImageUniforms,
|
// 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
|
// 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),
|
// change the link result. MaxClipDistances moved in at the wave4 merge (4fc3531d)
|
||||||
// the third time in three waves that a hardcoded TBuiltInResource field became
|
// and the six after it at the GL 4.6 API-surface wave - the fourth time in four
|
||||||
// env-derived - assume the next wave does it again and re-audit.
|
// 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.
|
// * maxComputeWorkGroupSize and maxComputeWorkGroupCount, all three components each.
|
||||||
// These moved IN at the dev merge that brought wave3's cb155c5b, which made
|
// 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
|
// 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
|
// Hashes every semantically relevant member. Public so a test can assert that two
|
||||||
// different envs really do produce different P0b cache keys.
|
// different envs really do produce different P0b cache keys.
|
||||||
Uint64 ComputeCompileEnvFingerprint(const CompileEnv& env);
|
Uint64 ComputeCompileEnvFingerprint(const CompileEnv& env);
|
||||||
|
|||||||
@@ -101,16 +101,11 @@ namespace MobileGL {
|
|||||||
Resources.maxClipPlanes = 6;
|
Resources.maxClipPlanes = 6;
|
||||||
Resources.maxTextureUnits = 32;
|
Resources.maxTextureUnits = 32;
|
||||||
Resources.maxTextureCoords = 32;
|
Resources.maxTextureCoords = 32;
|
||||||
Resources.maxVertexAttribs = 64;
|
Resources.maxVertexUniformComponents = MAX_VERTEX_UNIFORM_COMPONENTS;
|
||||||
Resources.maxVertexUniformComponents = 4096;
|
Resources.maxVaryingFloats = MAX_VARYING_COMPONENTS;
|
||||||
Resources.maxVaryingFloats = 64;
|
|
||||||
Resources.maxVertexTextureImageUnits = 32;
|
|
||||||
Resources.maxCombinedTextureImageUnits = 80;
|
|
||||||
Resources.maxTextureImageUnits = 32;
|
|
||||||
Resources.maxFragmentUniformComponents = 4096;
|
Resources.maxFragmentUniformComponents = 4096;
|
||||||
Resources.maxDrawBuffers = 32;
|
Resources.maxVertexUniformVectors = MAX_VERTEX_UNIFORM_VECTORS;
|
||||||
Resources.maxVertexUniformVectors = 128;
|
Resources.maxVaryingVectors = MAX_VARYING_VECTORS;
|
||||||
Resources.maxVaryingVectors = 8;
|
|
||||||
Resources.maxFragmentUniformVectors = 256;
|
Resources.maxFragmentUniformVectors = 256;
|
||||||
Resources.maxVertexOutputVectors = 16;
|
Resources.maxVertexOutputVectors = 16;
|
||||||
Resources.maxFragmentInputVectors = 15;
|
Resources.maxFragmentInputVectors = 15;
|
||||||
@@ -121,14 +116,12 @@ namespace MobileGL {
|
|||||||
Resources.maxComputeImageUniforms = 8;
|
Resources.maxComputeImageUniforms = 8;
|
||||||
Resources.maxComputeAtomicCounters = MAX_ATOMIC_COUNTERS_PER_STAGE;
|
Resources.maxComputeAtomicCounters = MAX_ATOMIC_COUNTERS_PER_STAGE;
|
||||||
Resources.maxComputeAtomicCounterBuffers = MAX_ATOMIC_COUNTER_BUFFERS_PER_STAGE;
|
Resources.maxComputeAtomicCounterBuffers = MAX_ATOMIC_COUNTER_BUFFERS_PER_STAGE;
|
||||||
Resources.maxVaryingComponents = 60;
|
Resources.maxVaryingComponents = MAX_VARYING_COMPONENTS;
|
||||||
Resources.maxVertexOutputComponents = 64;
|
Resources.maxVertexOutputComponents = 64;
|
||||||
Resources.maxGeometryInputComponents = 64;
|
Resources.maxGeometryInputComponents = 64;
|
||||||
Resources.maxGeometryOutputComponents = 128;
|
Resources.maxGeometryOutputComponents = 128;
|
||||||
Resources.maxFragmentInputComponents = 128;
|
Resources.maxFragmentInputComponents = 128;
|
||||||
Resources.maxImageUnits = 8;
|
Resources.maxImageUnits = 8;
|
||||||
Resources.maxCombinedImageUnitsAndFragmentOutputs = 8;
|
|
||||||
Resources.maxCombinedShaderOutputResources = 8;
|
|
||||||
Resources.maxImageSamples = 0;
|
Resources.maxImageSamples = 0;
|
||||||
Resources.maxVertexImageUniforms = 0;
|
Resources.maxVertexImageUniforms = 0;
|
||||||
Resources.maxTessControlImageUniforms = 0;
|
Resources.maxTessControlImageUniforms = 0;
|
||||||
@@ -141,16 +134,18 @@ namespace MobileGL {
|
|||||||
Resources.maxGeometryTotalOutputComponents = 1024;
|
Resources.maxGeometryTotalOutputComponents = 1024;
|
||||||
Resources.maxGeometryUniformComponents = 1024;
|
Resources.maxGeometryUniformComponents = 1024;
|
||||||
Resources.maxGeometryVaryingComponents = 64;
|
Resources.maxGeometryVaryingComponents = 64;
|
||||||
Resources.maxTessControlInputComponents = 128;
|
// The tessellation block is shared with glGetIntegerv through Types.h; see the
|
||||||
Resources.maxTessControlOutputComponents = 128;
|
// "Never move one of these without the other" note there.
|
||||||
Resources.maxTessControlTextureImageUnits = 16;
|
Resources.maxTessControlInputComponents = MAX_TESS_CONTROL_INPUT_COMPONENTS;
|
||||||
Resources.maxTessControlUniformComponents = 1024;
|
Resources.maxTessControlOutputComponents = MAX_TESS_CONTROL_OUTPUT_COMPONENTS;
|
||||||
Resources.maxTessControlTotalOutputComponents = 4096;
|
Resources.maxTessControlTextureImageUnits = MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS;
|
||||||
Resources.maxTessEvaluationInputComponents = 128;
|
Resources.maxTessControlUniformComponents = MAX_TESS_CONTROL_UNIFORM_COMPONENTS;
|
||||||
Resources.maxTessEvaluationOutputComponents = 128;
|
Resources.maxTessControlTotalOutputComponents = MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS;
|
||||||
Resources.maxTessEvaluationTextureImageUnits = 16;
|
Resources.maxTessEvaluationInputComponents = MAX_TESS_EVALUATION_INPUT_COMPONENTS;
|
||||||
Resources.maxTessEvaluationUniformComponents = 1024;
|
Resources.maxTessEvaluationOutputComponents = MAX_TESS_EVALUATION_OUTPUT_COMPONENTS;
|
||||||
Resources.maxTessPatchComponents = 120;
|
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.maxPatchVertices = 32;
|
||||||
Resources.maxTessGenLevel = 64;
|
Resources.maxTessGenLevel = 64;
|
||||||
Resources.maxViewports = 16;
|
Resources.maxViewports = 16;
|
||||||
@@ -176,9 +171,6 @@ namespace MobileGL {
|
|||||||
Resources.maxAtomicCounterBufferSize = MAX_ATOMIC_COUNTER_BUFFER_SIZE;
|
Resources.maxAtomicCounterBufferSize = MAX_ATOMIC_COUNTER_BUFFER_SIZE;
|
||||||
Resources.maxTransformFeedbackBuffers = 4;
|
Resources.maxTransformFeedbackBuffers = 4;
|
||||||
Resources.maxTransformFeedbackInterleavedComponents = 64;
|
Resources.maxTransformFeedbackInterleavedComponents = 64;
|
||||||
Resources.maxCullDistances = 8;
|
|
||||||
Resources.maxCombinedClipAndCullDistances = 8;
|
|
||||||
Resources.maxSamples = 4;
|
|
||||||
Resources.maxMeshOutputVerticesNV = 256;
|
Resources.maxMeshOutputVerticesNV = 256;
|
||||||
Resources.maxMeshOutputPrimitivesNV = 512;
|
Resources.maxMeshOutputPrimitivesNV = 512;
|
||||||
Resources.maxMeshWorkGroupSizeX_NV = 32;
|
Resources.maxMeshWorkGroupSizeX_NV = 32;
|
||||||
@@ -208,12 +200,37 @@ namespace MobileGL {
|
|||||||
Resources.maxImageUnits = dynamicParameters.MaxImageUnits;
|
Resources.maxImageUnits = dynamicParameters.MaxImageUnits;
|
||||||
Resources.maxCombinedImageUnitsAndFragmentOutputs =
|
Resources.maxCombinedImageUnitsAndFragmentOutputs =
|
||||||
dynamicParameters.MaxImageUnits + dynamicParameters.MaxDrawBuffers;
|
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.maxVertexImageUniforms = dynamicParameters.MaxVertexImageUniforms;
|
||||||
Resources.maxGeometryImageUniforms = dynamicParameters.MaxGeometryImageUniforms;
|
Resources.maxGeometryImageUniforms = dynamicParameters.MaxGeometryImageUniforms;
|
||||||
Resources.maxFragmentImageUniforms = dynamicParameters.MaxFragmentImageUniforms;
|
Resources.maxFragmentImageUniforms = dynamicParameters.MaxFragmentImageUniforms;
|
||||||
Resources.maxComputeImageUniforms = dynamicParameters.MaxComputeImageUniforms;
|
Resources.maxComputeImageUniforms = dynamicParameters.MaxComputeImageUniforms;
|
||||||
Resources.maxCombinedImageUniforms = dynamicParameters.MaxCombinedImageUniforms;
|
Resources.maxCombinedImageUniforms = dynamicParameters.MaxCombinedImageUniforms;
|
||||||
Resources.maxComputeTextureImageUnits = dynamicParameters.MaxComputeTextureImageUnits;
|
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
|
// Load-bearing, not cosmetic. glslang rejects gl_ClipDistance[i] for
|
||||||
// i >= maxClipDistances (ParseHelper.cpp) and expands gl_MaxClipDistances from the
|
// i >= maxClipDistances (ParseHelper.cpp) and expands gl_MaxClipDistances from the
|
||||||
// same number, so tracking the backend limit is what turns "the program links,
|
// 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
|
// also what makes glGetIntegerv(GL_MAX_CLIP_DISTANCES) and gl_MaxClipDistances
|
||||||
// agree, which KHR-GLxx.clip_distance.coverage compares directly.
|
// agree, which KHR-GLxx.clip_distance.coverage compares directly.
|
||||||
Resources.maxClipDistances = dynamicParameters.MaxClipDistances;
|
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
|
// 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
|
// 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_COUNTER_BUFFERS_PER_STAGE = 8;
|
||||||
inline constexpr Int MAX_ATOMIC_COUNTERS_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 {};
|
struct EmptyType {};
|
||||||
|
|
||||||
enum class ShaderCompileBits : Uint {
|
enum class ShaderCompileBits : Uint {
|
||||||
|
|||||||
Reference in New Issue
Block a user