[Feat] (MG_Backend/DirectGLES, MG_Util/Loader): wire glPolygonMode and glColorMaski into GLES sync

Neither entry point exists in unextended OpenGL ES core, so both are
gated on optional extensions detected and cached at init, with a runtime
fallback when absent.

Loader:
- Add glPolygonModeNV/glPolygonModeANGLE and glColorMaskiEXT/glColorMaskiOES
  to the GLES function table, loaded via a new INIT_GLES_FUNC_OPTIONAL
  macro that does not log an error when the driver lacks them.
- Cache GLESCapabilities.SupportsPolygonMode and SupportsIndexedColorMask
  from whether the entry points loaded (glColorMaski is GLES 3.2 core with
  no extension string, so pointer presence is the reliable signal).

Sync (SyncRenderState):
- Color mask: uniform masks keep using the non-indexed glColorMask (works
  everywhere); divergent per-draw-buffer masks use glColorMaski (core /
  EXT / OES, whichever loaded) when SupportsIndexedColorMask, else fall
  back to broadcasting draw buffer 0. Mirrors the existing indexed-blend
  block's all-same-vs-per-buffer structure.
- Polygon mode: new sync block calls glPolygonModeNV/ANGLE(GL_FRONT_AND_BACK,
  mode) when SupportsPolygonMode; without the extension the mode stays FILL
  and non-FILL requests are dropped.

Library builds clean; full SanityTest sweep green (30/30).
This commit is contained in:
2026-07-10 22:27:21 -04:00
parent aa5c33a42d
commit 041de6cba3
3 changed files with 77 additions and 5 deletions
+41 -5
View File
@@ -702,11 +702,47 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
}
{ // Color mask. GLES core has only the non-indexed glColorMask, so sync draw buffer 0.
if (parameters.ColorMasks[0] != g_syncedRenderStateParameters.ColorMasks[0]) {
const BoolVec4& colorMask = parameters.ColorMasks[0];
g_GLESFuncs.glColorMask(ToGLBoolean(colorMask.x()), ToGLBoolean(colorMask.y()),
ToGLBoolean(colorMask.z()), ToGLBoolean(colorMask.w()));
{ // Color mask. Uniform masks use the non-indexed glColorMask (works everywhere); divergent
// per-draw-buffer masks use the indexed glColorMaski when draw_buffers_indexed is
// available, otherwise fall back to broadcasting draw buffer 0. Mirrors the blend block.
using FBO = MG_State::GLState::FramebufferObject;
const auto& targetMasks = parameters.ColorMasks;
const auto& syncedMasks = g_syncedRenderStateParameters.ColorMasks;
Bool anyDirty = false;
Bool allSame = true;
for (Uint i = 0; i < FBO::MAX_DRAW_BUFFERS; ++i) {
if (targetMasks[i] != syncedMasks[i]) anyDirty = true;
if (i > 0 && targetMasks[i] != targetMasks[0]) allSame = false;
}
if (anyDirty) {
if (allSame || !g_GLESCapabilities.SupportsIndexedColorMask) {
const BoolVec4& m = targetMasks[0];
g_GLESFuncs.glColorMask(ToGLBoolean(m.x()), ToGLBoolean(m.y()), ToGLBoolean(m.z()),
ToGLBoolean(m.w()));
} else {
const auto colorMaskiFn = g_GLESFuncs.glColorMaski ? g_GLESFuncs.glColorMaski
: g_GLESFuncs.glColorMaskiEXT ? g_GLESFuncs.glColorMaskiEXT
: g_GLESFuncs.glColorMaskiOES;
for (Uint i = 0; i < FBO::MAX_DRAW_BUFFERS; ++i) {
if (targetMasks[i] != syncedMasks[i]) {
const BoolVec4& m = targetMasks[i];
colorMaskiFn(i, ToGLBoolean(m.x()), ToGLBoolean(m.y()), ToGLBoolean(m.z()),
ToGLBoolean(m.w()));
}
}
}
}
}
{ // Polygon mode. GLES core has no glPolygonMode; use NV/ANGLE_polygon_mode when present.
// Without the extension the mode stays FILL and non-FILL requests are dropped.
if (parameters.PolygonModeFront != g_syncedRenderStateParameters.PolygonModeFront &&
g_GLESCapabilities.SupportsPolygonMode) {
const auto polygonModeFn =
g_GLESFuncs.glPolygonModeNV ? g_GLESFuncs.glPolygonModeNV : g_GLESFuncs.glPolygonModeANGLE;
polygonModeFn(GL_FRONT_AND_BACK, parameters.PolygonModeFront);
}
}
@@ -82,6 +82,13 @@ namespace MobileGL::MG_Util::BackendLoader {
} \
} while (0);
// Optional (extension-provided) entry points: a null pointer is expected on drivers that lack the
// extension, so absence is not an error. The call site null-checks before use.
#define INIT_GLES_FUNC_OPTIONAL(name) \
do { \
funcs.name = (MG_External::GLES::name##_PTR)procAddress(#name); \
} while (0);
{
INIT_GLES_FUNC(glActiveTexture)
INIT_GLES_FUNC(glAttachShader)
@@ -454,6 +461,10 @@ namespace MobileGL::MG_Util::BackendLoader {
INIT_GLES_FUNC(glGetQueryObjectui64vEXT)
INIT_GLES_FUNC(glBindFragDataLocationEXT)
INIT_GLES_FUNC(glMapBufferOES)
INIT_GLES_FUNC_OPTIONAL(glPolygonModeNV)
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)
@@ -734,6 +745,16 @@ namespace MobileGL::MG_Util::BackendLoader {
}
}
// Detect optional raster/color-mask entry points by whether they loaded. glColorMaski is GLES
// 3.2 core (no extension string), so pointer presence is the reliable signal for all of these.
caps.SupportsPolygonMode =
glesFuncs.glPolygonModeNV != nullptr || glesFuncs.glPolygonModeANGLE != nullptr;
caps.SupportsIndexedColorMask = glesFuncs.glColorMaski != nullptr ||
glesFuncs.glColorMaskiEXT != nullptr ||
glesFuncs.glColorMaskiOES != nullptr;
MGLOG_I(" glPolygonMode (NV/ANGLE): %s", caps.SupportsPolygonMode ? "yes" : "no");
MGLOG_I(" indexed glColorMaski: %s", caps.SupportsIndexedColorMask ? "yes" : "no");
MGLOG_I("OpenGL ES capabilities:");
glesFuncs.glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &caps.UniformBufferOffsetAlignment);
MGLOG_I(" GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT: %d", caps.UniformBufferOffsetAlignment);
@@ -615,6 +615,11 @@ namespace MobileGL {
GL_FUNC_TYPEDEF(void, glGetQueryObjectui64vEXT, GLuint id, GLenum pname, GLuint64* params)
GL_FUNC_TYPEDEF(void, glBindFragDataLocationEXT, GLuint program, GLuint colorNumber, const GLchar* name)
GL_FUNC_TYPEDEF(void*, glMapBufferOES, GLenum target, GLenum access)
// Extension aliases for glPolygonMode (GLES core has none) and indexed glColorMaski.
GL_FUNC_TYPEDEF(void, glPolygonModeNV, GLenum face, GLenum mode)
GL_FUNC_TYPEDEF(void, glPolygonModeANGLE, GLenum face, GLenum mode)
GL_FUNC_TYPEDEF(void, glColorMaskiEXT, GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a)
GL_FUNC_TYPEDEF(void, glColorMaskiOES, GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a)
GL_FUNC_TYPEDEF(void, glMultiDrawArraysIndirectEXT, GLenum mode, const void* indirect, GLsizei drawcount,
GLsizei stride)
@@ -1006,6 +1011,10 @@ namespace MobileGL {
GL_FUNC_DECL(glGetQueryObjectui64vEXT)
GL_FUNC_DECL(glBindFragDataLocationEXT)
GL_FUNC_DECL(glMapBufferOES)
GL_FUNC_DECL(glPolygonModeNV)
GL_FUNC_DECL(glPolygonModeANGLE)
GL_FUNC_DECL(glColorMaskiEXT)
GL_FUNC_DECL(glColorMaskiOES)
GL_FUNC_DECL(glMultiDrawArraysIndirectEXT)
GL_FUNC_DECL(glMultiDrawElementsIndirectEXT)
@@ -1025,6 +1034,12 @@ namespace MobileGL {
Bool SupportsBaseInstance = false;
// GL_EXT_disjoint_timer_query is present in the extension string.
Bool SupportsDisjointTimerQuery = false;
// glPolygonModeNV/ANGLE loaded (GL_NV_polygon_mode / GL_ANGLE_polygon_mode). GLES core
// has no glPolygonMode, so without this the mode stays FILL.
Bool SupportsPolygonMode = false;
// An indexed glColorMaski entry point loaded (GLES 3.2 core, or GL_OES/EXT_draw_buffers_indexed).
// Without it, only the non-indexed glColorMask (draw buffer 0 broadcast) is available.
Bool SupportsIndexedColorMask = false;
// GL_RENDERER contains "ANGLE".
Bool IsAngleRenderer = false;
// GL_RENDERER contains both "ANGLE" and "llvmpipe".