mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Fix] (MG_Util, MG_Test): never take a non-null eglGetProcAddress result as support
On GLVND Linux eglGetProcAddress returns a non-NULL trampoline for ANY name - including a fabricated one - so pointer-nullness can never signal driver support. The three EXT multi-draw entry points were registered as required (spurious error logs on drivers without them) and their pointers were trusted; the NVIDIA ES driver hands back a stub for glMultiDrawElementsBaseVertexEXT that SILENTLY DROPS draws, which once made a "77% faster" multi-draw batch that rendered nothing. The three entries are optional now, and two extension-derived capability flags follow the established Supports* pattern - each is an extension-string check AND a resolved pointer, so a flag alone is sufficient at a call site: SupportsMultiDrawIndirect: GL_EXT_multi_draw_indirect + both entry points resolved. SupportsMultiDrawElementsBaseVertex: (GL_EXT or GL_OES_draw_elements_base_vertex) + GL_EXT_multi_draw_arrays + the entry point resolved. The multi_draw_arrays conjunct is the registry fact the stub exploited: glMultiDrawElementsBaseVertexEXT exists only in interaction with GL_EXT_multi_draw_arrays, and this NVIDIA driver advertises everything else EXCEPT that one - so the entry point is genuinely unsupported while eglGetProcAddress still "resolves" it. Two DriverPost rows report both capabilities (INFO when absent - a fallback always exists). Unit tests pin the stub shape, the exact NVIDIA shape, the supported shape and extension-without-pointer. Proven load-bearing: forcing the old pointer-only condition on the NVIDIA ES driver reproduces the silent drop exactly (sodium retrace SSIM 1.000000 -> 0.329522, no crash, no GL error); with the gate the same run is a literal 1.000000. Unit suite 423/423 (two new tests), retrace subset 10/10, integration suite 52/52.
This commit is contained in:
@@ -524,9 +524,15 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
INIT_GLES_FUNC_OPTIONAL(glPolygonModeANGLE)
|
||||
INIT_GLES_FUNC_OPTIONAL(glColorMaskiEXT)
|
||||
INIT_GLES_FUNC_OPTIONAL(glColorMaskiOES)
|
||||
INIT_GLES_FUNC(glMultiDrawArraysIndirectEXT)
|
||||
INIT_GLES_FUNC(glMultiDrawElementsIndirectEXT)
|
||||
INIT_GLES_FUNC(glMultiDrawElementsBaseVertexEXT)
|
||||
// Extension-only multi-draw entry points. eglGetProcAddress may legally return a
|
||||
// non-NULL stub for these on drivers that do not implement them (NVIDIA's ES driver
|
||||
// returns one for glMultiDrawElementsBaseVertexEXT that silently drops every draw),
|
||||
// so pointer presence proves nothing: callers must gate on the extension-derived
|
||||
// SupportsMultiDrawIndirect / SupportsMultiDrawElementsBaseVertex capability flags,
|
||||
// never on these pointers alone.
|
||||
INIT_GLES_FUNC_OPTIONAL(glMultiDrawArraysIndirectEXT)
|
||||
INIT_GLES_FUNC_OPTIONAL(glMultiDrawElementsIndirectEXT)
|
||||
INIT_GLES_FUNC_OPTIONAL(glMultiDrawElementsBaseVertexEXT)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,6 +820,11 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
GLint extCount = 0;
|
||||
glesFuncs.glGetIntegerv(GL_NUM_EXTENSIONS, &extCount);
|
||||
MGLOG_I("Detected %d OpenGL ES extensions:", extCount);
|
||||
// Combined below: glMultiDrawElementsBaseVertexEXT exists only where EXT/OES
|
||||
// draw_elements_base_vertex interacts with GL_EXT_multi_draw_arrays.
|
||||
Bool hasMultiDrawIndirectExtension = false;
|
||||
Bool hasDrawElementsBaseVertexExtension = false;
|
||||
Bool hasMultiDrawArraysExtension = false;
|
||||
for (GLint i = 0; i < extCount; ++i) {
|
||||
const char* extension = (const char*)glesFuncs.glGetStringi(GL_EXTENSIONS, i);
|
||||
if (extension) {
|
||||
@@ -856,8 +867,27 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
if (std::strcmp(extension, "GL_OES_shader_multisample_interpolation") == 0) {
|
||||
caps.SupportsShaderMultisampleInterpolation = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_multi_draw_indirect") == 0) {
|
||||
hasMultiDrawIndirectExtension = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_draw_elements_base_vertex") == 0 ||
|
||||
std::strcmp(extension, "GL_OES_draw_elements_base_vertex") == 0) {
|
||||
hasDrawElementsBaseVertexExtension = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_multi_draw_arrays") == 0) {
|
||||
hasMultiDrawArraysExtension = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// The pointer check on top of the extension check makes each flag sufficient on its own
|
||||
// at a call site; the extension check on top of the pointer keeps a stub returned by
|
||||
// eglGetProcAddress (see AcquireGLESFunctions) from ever counting as support.
|
||||
caps.SupportsMultiDrawIndirect = hasMultiDrawIndirectExtension &&
|
||||
glesFuncs.glMultiDrawArraysIndirectEXT != nullptr &&
|
||||
glesFuncs.glMultiDrawElementsIndirectEXT != nullptr;
|
||||
caps.SupportsMultiDrawElementsBaseVertex = hasDrawElementsBaseVertexExtension &&
|
||||
hasMultiDrawArraysExtension &&
|
||||
glesFuncs.glMultiDrawElementsBaseVertexEXT != nullptr;
|
||||
caps.SupportsShaderMultisampleInterpolation =
|
||||
caps.SupportsShaderMultisampleInterpolation || caps.GLESVersion.Major > 3 ||
|
||||
(caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2);
|
||||
@@ -873,6 +903,10 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
MGLOG_I(" indexed glColorMaski: %s", caps.SupportsIndexedColorMask ? "yes" : "no");
|
||||
MGLOG_I(" dual-source blend (EXT_blend_func_extended): %s",
|
||||
caps.SupportsDualSourceBlend ? "yes" : "no");
|
||||
MGLOG_I(" multi-draw indirect (EXT_multi_draw_indirect): %s",
|
||||
caps.SupportsMultiDrawIndirect ? "yes" : "no");
|
||||
MGLOG_I(" multi-draw base vertex (EXT/OES_draw_elements_base_vertex + EXT_multi_draw_arrays): %s",
|
||||
caps.SupportsMultiDrawElementsBaseVertex ? "yes" : "no");
|
||||
|
||||
MGLOG_I("OpenGL ES capabilities:");
|
||||
glesFuncs.glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &caps.UniformBufferOffsetAlignment);
|
||||
|
||||
@@ -1071,6 +1071,19 @@ namespace MobileGL {
|
||||
// GLES 3.2 core or GL_OES_shader_multisample_interpolation exposes
|
||||
// interpolateAtOffset and the three fragment-offset limit queries.
|
||||
Bool SupportsShaderMultisampleInterpolation = false;
|
||||
// GL_EXT_multi_draw_indirect is present AND glMultiDrawArraysIndirectEXT /
|
||||
// glMultiDrawElementsIndirectEXT both resolved. Multi-draw is not core in any ES
|
||||
// version, and eglGetProcAddress may return a live-looking stub on drivers without
|
||||
// the extension, so the pointers alone must never be used as the support signal.
|
||||
Bool SupportsMultiDrawIndirect = false;
|
||||
// glMultiDrawElementsBaseVertexEXT is callable. Per the Khronos registry the entry
|
||||
// point is added by GL_EXT/OES_draw_elements_base_vertex ONLY in interaction with
|
||||
// GL_EXT_multi_draw_arrays; NVIDIA's ES driver advertises both base_vertex
|
||||
// extensions but not GL_EXT_multi_draw_arrays, and its eglGetProcAddress still
|
||||
// hands back a non-NULL stub that silently drops every draw. Hence this flag
|
||||
// requires (EXT or OES draw_elements_base_vertex) AND GL_EXT_multi_draw_arrays
|
||||
// AND a resolved pointer; callers must gate on it, never on the pointer.
|
||||
Bool SupportsMultiDrawElementsBaseVertex = false;
|
||||
// GL_RENDERER contains "ANGLE".
|
||||
Bool IsAngleRenderer = false;
|
||||
// GL_RENDERER contains both "ANGLE" and "llvmpipe".
|
||||
|
||||
@@ -298,6 +298,31 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
"not supported; no impact: the native indirect path deliberately does not "
|
||||
"rely on it (shader-side emulation handles baseInstance semantics)");
|
||||
}
|
||||
// Both multi-draw rows gate on the capability flags, not the entry-point pointers:
|
||||
// eglGetProcAddress may hand back a non-NULL stub for these on drivers without the
|
||||
// extension (NVIDIA ES does, and its glMultiDrawElementsBaseVertexEXT stub silently
|
||||
// drops every draw), so the pointers prove nothing. Absence is INFO in both cases
|
||||
// because MobileGL falls back to an equivalent per-draw loop.
|
||||
if (caps.SupportsMultiDrawIndirect) {
|
||||
builder.Pass("Multi-draw indirect",
|
||||
"glMultiDrawArrays/ElementsIndirectEXT available via GL_EXT_multi_draw_indirect");
|
||||
} else {
|
||||
builder.Info("Multi-draw indirect",
|
||||
"GL_EXT_multi_draw_indirect not supported; no impact today: multi-draw "
|
||||
"indirect is decomposed into per-command indirect draws regardless");
|
||||
}
|
||||
if (caps.SupportsMultiDrawElementsBaseVertex) {
|
||||
builder.Pass("Multi-draw base vertex",
|
||||
"glMultiDrawElementsBaseVertexEXT available (EXT/OES_draw_elements_base_vertex "
|
||||
"with GL_EXT_multi_draw_arrays); glMultiDrawElementsBaseVertex batches into one "
|
||||
"driver call");
|
||||
} else {
|
||||
builder.Info("Multi-draw base vertex",
|
||||
"glMultiDrawElementsBaseVertexEXT not supported (needs EXT/OES_"
|
||||
"draw_elements_base_vertex plus GL_EXT_multi_draw_arrays); "
|
||||
"glMultiDrawElementsBaseVertex falls back to a per-draw loop with "
|
||||
"identical output");
|
||||
}
|
||||
if (caps.SupportsTextureBorderClamp) {
|
||||
builder.Pass("Texture border clamp",
|
||||
"supported (GL_TEXTURE_BORDER_COLOR reaches the driver, so "
|
||||
|
||||
Reference in New Issue
Block a user