mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Fix, Test] (BackendLoader): drain and gate every capability probe whose pname is not ES core
This commit is contained in:
@@ -1082,11 +1082,39 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
GLint maxProgramTextureGatherOffset = 7;
|
||||
GLint maxPatchVertices = 32;
|
||||
GLint maxTessGenLevel = 64;
|
||||
// Function-scope, and used by every probe group below rather than redeclared inside each
|
||||
// one. Returns whether anything was drained, which is what lets a group tell "the driver
|
||||
// answered" from "the driver rejected the pname and left my local alone".
|
||||
const auto drainErrors = [&glesFuncs]() {
|
||||
Bool hadError = false;
|
||||
if (glesFuncs.glGetError) {
|
||||
while (glesFuncs.glGetError() != GL_NO_ERROR) hadError = true;
|
||||
}
|
||||
return hadError;
|
||||
};
|
||||
|
||||
// THE GENERATOR OF THIS WHOLE BUG FAMILY, closed here. A bare glGetIntegerv/glGetFloatv
|
||||
// of a pname the driver does not have does two damaging things at once: it leaves the
|
||||
// local at whatever the declaration initialised it to - an optimistic number the frontend
|
||||
// then advertises as a capability - and it leaves a GL_INVALID_ENUM in the queue where
|
||||
// the next unrelated probe's caller, or the application's first glGetError, gets blamed
|
||||
// for it. The per-stage storage block, fragment interpolation and buffer texture probes
|
||||
// below already drain and fall back; this unconditional run did neither, which is how
|
||||
// GL_MAX_CLIP_DISTANCES came to be advertised as 8 on a driver with no clip distances at
|
||||
// all. Every pname here that is not ES core is now either gated on the capability that
|
||||
// makes it exist or floored at the value a rejected probe would have left, and the whole
|
||||
// run is bracketed by a drain.
|
||||
drainErrors();
|
||||
glesFuncs.glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, aliasedLineWidthRange);
|
||||
// GL_SMOOTH_LINE_WIDTH_RANGE / GL_SMOOTH_LINE_WIDTH_GRANULARITY (0x0B22 / 0x0B23) are
|
||||
// desktop-only - ES has never had an antialiased line width query - so on a real GLES
|
||||
// driver these two raise GL_INVALID_ENUM. Kept as probes rather than dropped because the
|
||||
// ANGLE and desktop-GL hosts MobileGL also runs on do answer them; the initialisers are
|
||||
// the GL 4.6 table 23.55 minimum of [1, 1], which is both the honest answer for a driver
|
||||
// that cannot say and what an untouched out-param already holds.
|
||||
glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, smoothLineWidthRange);
|
||||
glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_GRANULARITY, &smoothLineWidthGranularity);
|
||||
glesFuncs.glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, aliasedPointSizeRange);
|
||||
glesFuncs.glGetFloatv(GL_VIEWPORT_BOUNDS_RANGE, viewportBoundsRange);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE, &max3DTextureSize);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxArrayTextureLayers);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &maxCubeMapTextureSize);
|
||||
@@ -1110,8 +1138,25 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
// single test case. 1 is a spec-legal value (the minimum required), so cap
|
||||
// to what is actually implemented instead of forwarding the raw driver limit.
|
||||
maxSampleMaskWords = std::min(maxSampleMaskWords, 1);
|
||||
// The multisample ceilings above are ES 3.1 state apart from GL_MAX_SAMPLES, which is ES
|
||||
// 3.0, so a 3.0 context rejects five of the six and leaves whatever the out-param held.
|
||||
// One sample is what a rejected probe leaves behind and is also the smallest legal
|
||||
// answer, so clamp rather than trust: a zero reaching GL_Getter would have the frontend
|
||||
// reject the very sample count it just advertised (see GetAdvertisedMaxSamples).
|
||||
maxColorTextureSamples = std::max(maxColorTextureSamples, 1);
|
||||
maxDepthTextureSamples = std::max(maxDepthTextureSamples, 1);
|
||||
maxFramebufferSamples = std::max(maxFramebufferSamples, 1);
|
||||
maxIntegerSamples = std::max(maxIntegerSamples, 1);
|
||||
maxSamples = std::max(maxSamples, 1);
|
||||
maxSampleMaskWords = std::max(maxSampleMaskWords, 1);
|
||||
// ES 3.2 core, or EXT_tessellation_shader on 3.1. Probed rather than version-gated so a
|
||||
// 3.1 driver that HAS the extension still gets to answer; the clamp below is what makes a
|
||||
// rejected query safe, since GL 4.6 table 23.66 and ES 3.2 table 21.45 set the same
|
||||
// minimums the initialisers carry and neither API permits less.
|
||||
glesFuncs.glGetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_TESS_GEN_LEVEL, &maxTessGenLevel);
|
||||
maxPatchVertices = std::max(maxPatchVertices, 32);
|
||||
maxTessGenLevel = std::max(maxTessGenLevel, 64);
|
||||
glesFuncs.glGetIntegerv(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET, &minProgramTextureGatherOffset);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET, &maxProgramTextureGatherOffset);
|
||||
// A driver that leaves the probe untouched (pre-ES 3.1, or an ignored enum) must not
|
||||
@@ -1148,6 +1193,13 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
(caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2)) {
|
||||
glesFuncs.glGetIntegerv(GL_MAX_GEOMETRY_IMAGE_UNIFORMS, &maxGeometryImageUniforms);
|
||||
}
|
||||
// Closes the bracket opened before the run: every local above now holds either the
|
||||
// driver's answer or a floor, and nothing this function asked for is left in the error
|
||||
// queue for a later probe - or the application - to be blamed for.
|
||||
if (drainErrors()) {
|
||||
MGLOG_W("One or more capability queries were rejected by this driver; the affected "
|
||||
"limits keep MobileGL's spec-minimum floors");
|
||||
}
|
||||
// Per-stage storage-block counts. Deliberately NOT batched with the unconditional probes
|
||||
// above, for the reason GL_MAX_TEXTURE_BUFFER_SIZE is not: the vertex and fragment pnames
|
||||
// are ES 3.1, but the tessellation and geometry ones only exist from ES 3.2 on (or under
|
||||
@@ -1160,14 +1212,6 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
// stages is 0. That is the honest answer: DirectGLES emits ESSL 3.10 on an ES 3.1 context,
|
||||
// where those stages do not exist at all.
|
||||
{
|
||||
const auto drainErrors = [&glesFuncs]() {
|
||||
Bool hadError = false;
|
||||
if (glesFuncs.glGetError) {
|
||||
while (glesFuncs.glGetError() != GL_NO_ERROR) hadError = true;
|
||||
}
|
||||
return hadError;
|
||||
};
|
||||
|
||||
// Isolate from errors raised by the preceding probes so the drain below reports on
|
||||
// these queries only.
|
||||
drainErrors();
|
||||
@@ -1210,35 +1254,40 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
// exists; everywhere else the honest 0 stands and no GL_INVALID_ENUM is left behind for an
|
||||
// unrelated query - or the application's first glGetError - to trip over.
|
||||
if (caps.SupportsClipDistance) {
|
||||
if (glesFuncs.glGetError) {
|
||||
while (glesFuncs.glGetError() != GL_NO_ERROR) {
|
||||
}
|
||||
}
|
||||
drainErrors();
|
||||
glesFuncs.glGetIntegerv(GL_MAX_CLIP_DISTANCES, &maxClipDistances);
|
||||
Bool queryFailed = false;
|
||||
if (glesFuncs.glGetError) {
|
||||
while (glesFuncs.glGetError() != GL_NO_ERROR) {
|
||||
queryFailed = true;
|
||||
}
|
||||
}
|
||||
if (queryFailed) {
|
||||
if (drainErrors()) {
|
||||
MGLOG_W("GL_EXT_clip_cull_distance is advertised but GL_MAX_CLIP_DISTANCES was "
|
||||
"rejected; reporting no clip distances");
|
||||
maxClipDistances = 0;
|
||||
}
|
||||
}
|
||||
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORT_DIMS, maxViewportDims);
|
||||
glesFuncs.glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &viewportSubpixelBits);
|
||||
// GL_MAX_VIEWPORTS (0x825B), GL_VIEWPORT_SUBPIXEL_BITS (0x825C) and GL_VIEWPORT_BOUNDS_RANGE
|
||||
// (0x825D) all arrive with GL_OES_viewport_array and exist nowhere in ES core, so on the
|
||||
// drivers DirectGLES actually runs on all three raise GL_INVALID_ENUM. The values MobileGL
|
||||
// advertises do not change by asking: GL_Getter answers GL_MAX_VIEWPORTS from the frontend
|
||||
// state width (indexed viewport entry points validate against RenderStateParameters::
|
||||
// MAX_VIEWPORTS, so a device answer of 1 would reject indices the state can legitimately
|
||||
// hold), floors GL_SUBPIXEL_BITS at its own 4, and the bounds range is clamped to the core
|
||||
// minimum below. What changes is that the errors stop being manufactured.
|
||||
if (caps.SupportsViewportArray) {
|
||||
drainErrors();
|
||||
glesFuncs.glGetIntegerv(GL_MAX_VIEWPORTS, &maxViewports);
|
||||
glesFuncs.glGetIntegerv(GL_VIEWPORT_SUBPIXEL_BITS, &viewportSubpixelBits);
|
||||
if (glesFuncs.glGetFloatv) {
|
||||
glesFuncs.glGetFloatv(GL_VIEWPORT_BOUNDS_RANGE, viewportBoundsRange);
|
||||
}
|
||||
if (drainErrors()) {
|
||||
MGLOG_W("GL_OES_viewport_array is advertised but its viewport limit queries were "
|
||||
"rejected; keeping the OpenGL core minimums");
|
||||
maxViewports = 16;
|
||||
viewportSubpixelBits = 0;
|
||||
viewportBoundsRange[0] = -32768.0f;
|
||||
viewportBoundsRange[1] = 32767.0f;
|
||||
}
|
||||
}
|
||||
if (caps.SupportsShaderMultisampleInterpolation && glesFuncs.glGetFloatv) {
|
||||
const auto drainErrors = [&glesFuncs]() {
|
||||
Bool hadError = false;
|
||||
if (glesFuncs.glGetError) {
|
||||
while (glesFuncs.glGetError() != GL_NO_ERROR) hadError = true;
|
||||
}
|
||||
return hadError;
|
||||
};
|
||||
|
||||
// Isolate these optional queries from errors raised by preceding capability
|
||||
// probes, then consume any query error so initialization never leaks it into
|
||||
// the application's first glGetError call.
|
||||
@@ -1517,6 +1566,14 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
caps.AvoidSamplerMipmapMinFilter ? "true" : "false");
|
||||
MGLOG_I(" Avoid explicit LOD bias: %s", caps.AvoidExplicitLodBias ? "true" : "false");
|
||||
|
||||
// Last line of defence. Capability init is the very first thing that touches the driver,
|
||||
// so anything it leaves in the error queue surfaces at the APPLICATION's first
|
||||
// glGetError and gets attributed to whatever call the app happened to make. Every group
|
||||
// above drains its own, but a probe added later must not be able to reintroduce the leak.
|
||||
if (drainErrors()) {
|
||||
MGLOG_W("Capability initialization left a GL error behind; it has been consumed so it "
|
||||
"cannot surface at the application's first glGetError");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace MobileGL::MG_Util::BackendLoader
|
||||
|
||||
Reference in New Issue
Block a user