[Feat, Fix] (MG_Impl, MG_State, MG_Backend): program interface queries from frontend reflection; Espryt state-shadow reset

Wave 2 of the advertised-extension conformance campaign.

PROGRAM INTERFACE QUERIES (the load-bearing piece). glGetProgramInterfaceiv
and the five glGetProgramResource* entry points were answered by the
BACKENDS - Espryt asked the real driver about SPIRV-Cross-generated ESSL
whose namespace is not the GL one (default-block uniforms live in
MGL_GLOBAL_UBO there), and Magma kept a second, partial reflection that
hardcoded types and diverged from the frontend. Both are now deleted; a new
frontend resource-model layer (ProgramInterface.{h,cpp}) answers every
interface - uniforms, uniform blocks, atomic-counter buffers (recovered
from glslang's synthesized gl_AtomicCounterBlock_<binding> lowering),
buffer variables, shader-storage blocks (classified by TType storage
qualifier since glslang reflects them as uniform blocks), program inputs/
outputs (built-ins' layoutLocationEnd sentinel mapped to -1), and
transform-feedback varyings including the gl_NextBuffer/gl_SkipComponentsN
pseudo-varyings - from the glslang reflection the frontend already trusts
for glGetActiveUniform. Name/index round-tripping, the "[0]" array
spelling, and the GL 4.6 table 7.2 prop/error matrix live in the new layer
only; GetActiveUniform*/GetActiveAttrib* are untouched.

glShaderStorageBlockBinding now takes the interface-layer index (the one
GetProgramResourceIndex returns, with a range check it never had), records
the binding on the program keyed by block NAME - the one coordinate all
three index spaces agree on - and delegates by name across the backend
boundary. Both backends reseed the recorded bindings on their own program
rebuilds, so an unrelated resync can no longer silently revert a rebound
block, and GL_BUFFER_BINDING reports the live binding, not the declared
one. The Espryt delegate applies only to an already-synced twin and can no
longer trigger SyncToBackend from a getter; the sync path's GL query
out-params are initialized and clamped (a load-dependent stack-garbage
Vector size crash caught by the gate, reproduced 3/50 pre-fix, 100/100
post-fix under saturating load).

ESPRYT RENDER-STATE SHADOW RESET (rides along because it shares
DirectGLES.cpp): the render-state shadow is file-static and survives
MobileGL context switches, so GL_FRAMEBUFFER_SRGB (and the whole synced-cap
class) leaked between contexts - the cross-test leakage class the CTS maps
have carried for a week. MakeCurrent now invalidates the shadow like it
already invalidates the program/FBO/buffer caches, and the resync resolves
a never-set scissor box to the current surface instead of pushing the
(0,0,0,0) sentinel verbatim (which scissored everything away - caught by
the retrace gate, bisected to the exact field via a bitmask probe, and
fixed by resolving like the viewport path rather than reverting).

Frontend riders exposed by the layer: glGetUniformLocation resolves
arrays-of-arrays element addressing ("a[2][1]"); transform-feedback capture
accepts element-addressed varying names ("b[1]") and snapshots the request
verbatim for the interface (Magma's decorate pass logs loudly that element
capture is unimplemented there - follow-up).

KNOWN GAPS, documented in code and tests: the 6 subroutines-* cases
(glslang refuses subroutine for SPIR-V; wave 3), the 5 separate-programs-*
cases (glslang's pipe-I/O reflection cannot see a separable non-vertex
stage's own inputs; needs stage-aware output validation first), and
uniform-block-types' per-instance stage masks (not derivable from the
reflection).

Gate: 593/593 unit at default and kill-switch, x10 each, plus the SSB race
case 100/100 under 20-way CPU load; ext caselist Espryt 77.87% -> 80.42%,
Magma 77.58% -> 79.39% (+212 fixed, 0 newly broken); program_interface_query
2/43 -> 31/43 unique on Espryt, 9/43 -> 31/43 on Magma, backends now
byte-identical; KHR-GL45.direct_state_access 370/371 + 371/371 with the 4
sRGB leak victims recovered in cross-test ordering; KHR-GL33 held at
9884/9886; full 39x2 CI retrace with zero wave-attributable failures (the
3 failing newly-added fixtures are bit-identical on the pristine baseline).
This commit is contained in:
BZLZHH
2026-08-09 08:12:30 -04:00
parent 33ff177bb2
commit 3e0460e472
18 changed files with 2515 additions and 790 deletions
@@ -968,12 +968,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
funcsTable.GL.GetIntegeri_v = GetIntegeri_v;
funcsTable.GL.GetInteger64i_v = GetInteger64i_v;
funcsTable.GL.GetProgramiv = GetProgramiv;
funcsTable.GL.GetProgramInterfaceiv = GetProgramInterfaceiv;
funcsTable.GL.GetProgramResourceIndex = GetProgramResourceIndex;
funcsTable.GL.GetProgramResourceName = GetProgramResourceName;
funcsTable.GL.GetProgramResourceiv = GetProgramResourceiv;
funcsTable.GL.GetProgramResourceLocation = GetProgramResourceLocation;
funcsTable.GL.GetProgramResourceLocationIndex = GetProgramResourceLocationIndex;
funcsTable.GL.ShaderStorageBlockBinding = ShaderStorageBlockBinding;
funcsTable.GL.Clear = Clear;
funcsTable.GL.ClearBufferfi = ClearBufferfi;
+134 -80
View File
@@ -1422,15 +1422,38 @@ namespace MobileGL::MG_Backend::DirectGLES {
static Bool g_hasSyncedRenderState = false;
static RenderStateParameters g_syncedRenderStateParameters;
static IntVec4 g_syncedBackendViewport = IntVec4(-1, -1, -1, -1);
// The RESOLVED scissor rectangle last pushed (see the scissor block in SyncRenderState);
// an impossible value so the first sync always pushes.
static IntVec4 g_syncedBackendScissorBox = IntVec4(-1, -1, -1, -1);
// GLES starts with sRGB framebuffer encoding on, so the first sync always has to push the
// frontend's (desktop-GL default) disabled state down.
static Bool g_syncedSrgbFramebufferWrites = true;
// Set when the shadow below stops describing the real ES context. The ES context
// OUTLIVES every MobileGL context, so a MobileGL context switch leaves it holding the
// previous context's enable state while the frontend's parameter block AND its
// version counter both restart from defaults. Every "differs from what I last pushed"
// test in SyncRenderState would then agree that nothing needs pushing, and the
// leftover state silently applies to the new context - the class of bug the CTS
// caught as GL_FRAMEBUFFER_SRGB surviving from vertex_attrib_binding into
// direct_state_access.renderbuffers_storage. One unconditional push settles the whole
// block rather than the one cap that happened to be noticed.
static Bool g_forceFullRenderStateResync = true;
void InvalidateSyncedRenderState() {
g_forceFullRenderStateResync = true;
g_hasSyncedRenderState = false;
g_syncedBackendViewport = IntVec4(-1, -1, -1, -1);
g_syncedBackendScissorBox = IntVec4(-1, -1, -1, -1);
}
void SyncRenderState() {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
Uint16 currentRenderStateVersion = MG_State::pGLContext->GetRenderStateParametersVersion();
if (g_hasSyncedRenderState && currentRenderStateVersion == g_syncedRenderStateVersion) return;
const Bool forceFullPush = g_forceFullRenderStateResync;
g_forceFullRenderStateResync = false;
if (!forceFullPush && g_hasSyncedRenderState && currentRenderStateVersion == g_syncedRenderStateVersion) {
return;
}
const auto& parameters = MG_State::pGLContext->GetRenderStateParameters();
@@ -1478,7 +1501,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// All 12 capability bools live after LogicOp in the struct, i.e. in the tail span.
if (tailSpanDirty) {
#define SYNC_CAPABILITY(cap_mg, cap_gl) \
if (parameters.cap_mg##Enabled != g_syncedRenderStateParameters.cap_mg##Enabled) { \
if (forceFullPush || parameters.cap_mg##Enabled != g_syncedRenderStateParameters.cap_mg##Enabled) { \
if (parameters.cap_mg##Enabled) { \
g_GLESFuncs.glEnable(cap_gl); \
} else { \
@@ -1507,7 +1530,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// into an sRGB colour buffer comes back encoded once too often (the shader's own
// decode on the next fetch then leaves the value one conversion short).
const Bool srgbWrites = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb);
if (g_GLESCapabilities.SupportsSrgbWriteControl && srgbWrites != g_syncedSrgbFramebufferWrites) {
if (g_GLESCapabilities.SupportsSrgbWriteControl &&
(forceFullPush || srgbWrites != g_syncedSrgbFramebufferWrites)) {
srgbWrites ? g_GLESFuncs.glEnable(GL_FRAMEBUFFER_SRGB)
: g_GLESFuncs.glDisable(GL_FRAMEBUFFER_SRGB);
g_syncedSrgbFramebufferWrites = srgbWrites;
@@ -1520,7 +1544,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Bool restart = parameters.PrimitiveRestartFixedIndexEnabled || parameters.PrimitiveRestartEnabled;
const Bool syncedRestart = g_syncedRenderStateParameters.PrimitiveRestartFixedIndexEnabled ||
g_syncedRenderStateParameters.PrimitiveRestartEnabled;
if (restart != syncedRestart) {
if (forceFullPush || restart != syncedRestart) {
restart ? g_GLESFuncs.glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX)
: g_GLESFuncs.glDisable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
}
@@ -1556,7 +1580,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool allEnabled = true;
Bool allDisabled = true;
Bool anyCapDirty = false;
Bool anyCapDirty = forceFullPush;
for (Uint i = 0; i < FBO::MAX_DRAW_BUFFERS; ++i) {
Bool enabled = targetStates[i].Enabled;
@@ -1581,7 +1605,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
s.Enabled = false;
} else {
for (Uint i = 0; i < FBO::MAX_DRAW_BUFFERS; ++i) {
if (targetStates[i].Enabled != syncedStates[i].Enabled) {
if (forceFullPush || targetStates[i].Enabled != syncedStates[i].Enabled) {
syncedStates[i].Enabled = targetStates[i].Enabled;
syncedStates[i].Enabled ? g_GLESFuncs.glEnablei(GL_BLEND, i)
: g_GLESFuncs.glDisablei(GL_BLEND, i);
@@ -1591,7 +1615,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
Bool allFuncsSame = true;
Bool anyFuncDirty = false;
Bool anyFuncDirty = forceFullPush;
const auto& first = targetStates[0];
for (Uint i = 0; i < FBO::MAX_DRAW_BUFFERS; ++i) {
@@ -1630,8 +1654,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto& cur = targetStates[i];
auto& syn = syncedStates[i];
if (cur.SrcFactorRGB != syn.SrcFactorRGB || cur.DstFactorRGB != syn.DstFactorRGB ||
cur.SrcFactorAlpha != syn.SrcFactorAlpha || cur.DstFactorAlpha != syn.DstFactorAlpha) {
if (forceFullPush || cur.SrcFactorRGB != syn.SrcFactorRGB ||
cur.DstFactorRGB != syn.DstFactorRGB ||
cur.SrcFactorAlpha != syn.SrcFactorAlpha ||
cur.DstFactorAlpha != syn.DstFactorAlpha) {
syn.SrcFactorRGB = cur.SrcFactorRGB;
syn.DstFactorRGB = cur.DstFactorRGB;
syn.SrcFactorAlpha = cur.SrcFactorAlpha;
@@ -1648,7 +1674,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
Bool allEquationsSame = true;
Bool anyEquationDirty = false;
Bool anyEquationDirty = forceFullPush;
for (Uint i = 0; i < FBO::MAX_DRAW_BUFFERS; ++i) {
const auto& cur = targetStates[i];
@@ -1679,7 +1705,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto& cur = targetStates[i];
auto& syn = syncedStates[i];
if (cur.ColorEquation != syn.ColorEquation || cur.AlphaEquation != syn.AlphaEquation) {
if (forceFullPush || cur.ColorEquation != syn.ColorEquation ||
cur.AlphaEquation != syn.AlphaEquation) {
syn.ColorEquation = cur.ColorEquation;
syn.AlphaEquation = cur.AlphaEquation;
@@ -1693,13 +1720,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (tailSpanDirty) { // Depth state
if (parameters.DepthFunc != g_syncedRenderStateParameters.DepthFunc) {
if (forceFullPush || parameters.DepthFunc != g_syncedRenderStateParameters.DepthFunc) {
g_GLESFuncs.glDepthFunc(MG_Util::ConvertDepthTestFuncToGLEnum(parameters.DepthFunc));
}
if (parameters.DepthMask != g_syncedRenderStateParameters.DepthMask) {
if (forceFullPush || parameters.DepthMask != g_syncedRenderStateParameters.DepthMask) {
g_GLESFuncs.glDepthMask(parameters.DepthMask ? GL_TRUE : GL_FALSE);
}
if (parameters.DepthRange != g_syncedRenderStateParameters.DepthRange) {
if (forceFullPush || parameters.DepthRange != g_syncedRenderStateParameters.DepthRange) {
g_GLESFuncs.glDepthRangef(parameters.DepthRange.x(), parameters.DepthRange.y());
}
}
@@ -1710,16 +1737,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
const StencilFaceState& synced = g_syncedRenderStateParameters.StencilStates[faceIndex];
const GLenum glFace = faceIndex == 0 ? GL_FRONT : GL_BACK;
if (current.Func != synced.Func || current.Ref != synced.Ref ||
if (forceFullPush || current.Func != synced.Func || current.Ref != synced.Ref ||
current.ValueMask != synced.ValueMask) {
g_GLESFuncs.glStencilFuncSeparate(
glFace, MG_Util::ConvertDepthTestFuncToGLEnum(current.Func), current.Ref,
current.ValueMask);
}
if (current.WriteMask != synced.WriteMask) {
if (forceFullPush || current.WriteMask != synced.WriteMask) {
g_GLESFuncs.glStencilMaskSeparate(glFace, current.WriteMask);
}
if (current.FailOp != synced.FailOp || current.PassDepthFailOp != synced.PassDepthFailOp ||
if (forceFullPush || current.FailOp != synced.FailOp ||
current.PassDepthFailOp != synced.PassDepthFailOp ||
current.PassDepthPassOp != synced.PassDepthPassOp) {
g_GLESFuncs.glStencilOpSeparate(
glFace, MG_Util::ConvertStencilOperationToGLEnum(current.FailOp),
@@ -1736,7 +1764,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto& targetMasks = parameters.ColorMasks;
const auto& syncedMasks = g_syncedRenderStateParameters.ColorMasks;
Bool anyDirty = false;
Bool anyDirty = forceFullPush;
Bool allSame = true;
for (Uint i = 0; i < FBO::MAX_DRAW_BUFFERS; ++i) {
if (targetMasks[i] != syncedMasks[i]) anyDirty = true;
@@ -1753,7 +1781,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
: g_GLESFuncs.glColorMaskiEXT ? g_GLESFuncs.glColorMaskiEXT
: g_GLESFuncs.glColorMaskiOES;
for (Uint i = 0; i < FBO::MAX_DRAW_BUFFERS; ++i) {
if (targetMasks[i] != syncedMasks[i]) {
if (forceFullPush || targetMasks[i] != syncedMasks[i]) {
const BoolVec4& m = targetMasks[i];
colorMaskiFn(i, ToGLBoolean(m.x()), ToGLBoolean(m.y()), ToGLBoolean(m.z()),
ToGLBoolean(m.w()));
@@ -1765,7 +1793,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (tailSpanDirty) { // 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 &&
if ((forceFullPush || parameters.PolygonModeFront != g_syncedRenderStateParameters.PolygonModeFront) &&
g_GLESCapabilities.SupportsPolygonMode) {
const auto polygonModeFn =
g_GLESFuncs.glPolygonModeNV ? g_GLESFuncs.glPolygonModeNV : g_GLESFuncs.glPolygonModeANGLE;
@@ -1774,67 +1802,97 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (tailSpanDirty) { // Clear values
if (parameters.ClearColor != g_syncedRenderStateParameters.ClearColor) {
if (forceFullPush || parameters.ClearColor != g_syncedRenderStateParameters.ClearColor) {
const FloatVec4& clearCol = parameters.ClearColor;
g_GLESFuncs.glClearColor(clearCol.x(), clearCol.y(), clearCol.z(), clearCol.w());
}
if (parameters.ClearDepth != g_syncedRenderStateParameters.ClearDepth) {
if (forceFullPush || parameters.ClearDepth != g_syncedRenderStateParameters.ClearDepth) {
g_GLESFuncs.glClearDepthf(parameters.ClearDepth);
}
if (parameters.ClearStencil != g_syncedRenderStateParameters.ClearStencil) {
if (forceFullPush || parameters.ClearStencil != g_syncedRenderStateParameters.ClearStencil) {
g_GLESFuncs.glClearStencil(static_cast<GLint>(parameters.ClearStencil));
}
if (parameters.BlendColor != g_syncedRenderStateParameters.BlendColor) {
if (forceFullPush || parameters.BlendColor != g_syncedRenderStateParameters.BlendColor) {
const FloatVec4& blendColor = parameters.BlendColor;
g_GLESFuncs.glBlendColor(blendColor.x(), blendColor.y(), blendColor.z(), blendColor.w());
}
}
if (tailSpanDirty) { // Cull face mode
if (parameters.CullFaceModeSetting != g_syncedRenderStateParameters.CullFaceModeSetting) {
if (forceFullPush || parameters.CullFaceModeSetting != g_syncedRenderStateParameters.CullFaceModeSetting) {
const CullFaceMode& cfm = parameters.CullFaceModeSetting;
g_GLESFuncs.glCullFace(MG_Util::ConvertCullFaceModeToGLEnum(cfm));
}
if (parameters.FrontFaceModeSetting != g_syncedRenderStateParameters.FrontFaceModeSetting) {
if (forceFullPush || parameters.FrontFaceModeSetting != g_syncedRenderStateParameters.FrontFaceModeSetting) {
const FrontFaceMode& ffm = parameters.FrontFaceModeSetting;
g_GLESFuncs.glFrontFace(MG_Util::ConvertFrontFaceModeToGLEnum(ffm));
}
}
if (tailSpanDirty) { // Scissor box
if (parameters.ScissorBox != g_syncedRenderStateParameters.ScissorBox) {
const IntVec4& scissorBox = parameters.ScissorBox;
g_GLESFuncs.glScissor(scissorBox.x(), scissorBox.y(), scissorBox.z(), scissorBox.w());
if (tailSpanDirty) { // Scissor box. Resolved and shadowed like the viewport above, and
// for the same reason: what has to reach the driver is NOT simply the parameter
// field. (0,0,0,0) is where RenderStateParameters::ScissorBox starts and the only
// thing that ever writes it is glScissor, so that value means "the application has
// never called glScissor" - it is not a GL scissor box. GL's initial box is the
// whole window, which the frontend has no way to spell before a surface exists.
// The pre-resync code got away with pushing the field verbatim only by accident:
// the shadow held the same default, the field never compared unequal, and the ES
// context kept its own correct default. Under the forced full push that accident
// is gone, glScissor(0,0,0,0) shrinks the scissor to an EMPTY rectangle, and
// everything drawn with GL_SCISSOR_TEST enabled before the app's first glScissor
// is clipped away - Minecraft 26.2 keeps only its unscissored sky and hand and
// loses the terrain and the whole GUI.
IntVec4 backendScissorBox = parameters.ScissorBox;
if (backendScissorBox.z() <= 0 || backendScissorBox.w() <= 0) {
Int surfaceWidth = 0;
Int surfaceHeight = 0;
if (QueryCurrentSurfaceSize(surfaceWidth, surfaceHeight)) {
backendScissorBox = IntVec4(0, 0, surfaceWidth, surfaceHeight);
}
}
// Compared against what was actually PUSHED, not against the parameter field, so
// the resolved value and the diff can never disagree.
if (backendScissorBox != g_syncedBackendScissorBox) {
g_GLESFuncs.glScissor(backendScissorBox.x(), backendScissorBox.y(), backendScissorBox.z(),
backendScissorBox.w());
g_syncedBackendScissorBox = backendScissorBox;
}
}
if (tailSpanDirty) { // Logic op (first field of the tail span)
if (parameters.LogicOp != g_syncedRenderStateParameters.LogicOp) {
// glLogicOp is GLES 1.x / EXT only - eglGetProcAddress returns null for it on a
// plain ES 3.x driver. Before the forced resync it was reached only when an app
// actually set a logic op; now every MakeCurrent would call it, so the null check
// is mandatory rather than defensive.
if (g_GLESFuncs.glLogicOp &&
(forceFullPush || parameters.LogicOp != g_syncedRenderStateParameters.LogicOp)) {
g_GLESFuncs.glLogicOp(MG_Util::ConvertLogicOperationToGLEnum(parameters.LogicOp));
}
}
if (headSpanDirty) { // Polygon offset (head-span scalars, like line width / point size below)
if (parameters.PolygonOffsetFactor != g_syncedRenderStateParameters.PolygonOffsetFactor ||
if (forceFullPush || parameters.PolygonOffsetFactor != g_syncedRenderStateParameters.PolygonOffsetFactor ||
parameters.PolygonOffsetUnits != g_syncedRenderStateParameters.PolygonOffsetUnits) {
g_GLESFuncs.glPolygonOffset(parameters.PolygonOffsetFactor, parameters.PolygonOffsetUnits);
}
}
if (headSpanDirty) { // Line width
if (parameters.LineWidth != g_syncedRenderStateParameters.LineWidth) {
if (forceFullPush || parameters.LineWidth != g_syncedRenderStateParameters.LineWidth) {
g_GLESFuncs.glLineWidth(parameters.LineWidth);
}
}
if (headSpanDirty) { // Point size
if (parameters.PointSize != g_syncedRenderStateParameters.PointSize) {
if (headSpanDirty) { // Point size (GLES 1.x only - ES 2+ sets it from gl_PointSize,
// so the entry point is absent on most drivers; see the glLogicOp note above)
if (g_GLESFuncs.glPointSize &&
(forceFullPush || parameters.PointSize != g_syncedRenderStateParameters.PointSize)) {
g_GLESFuncs.glPointSize(parameters.PointSize);
}
}
if (tailSpanDirty) { // Sample coverage
if (parameters.SampleCoverageValue != g_syncedRenderStateParameters.SampleCoverageValue ||
if (forceFullPush || parameters.SampleCoverageValue != g_syncedRenderStateParameters.SampleCoverageValue ||
parameters.SampleCoverageInvert != g_syncedRenderStateParameters.SampleCoverageInvert) {
g_GLESFuncs.glSampleCoverage(parameters.SampleCoverageValue,
ToGLBoolean(parameters.SampleCoverageInvert));
@@ -1842,7 +1900,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (tailSpanDirty) { // Sample mask
if (g_GLESFuncs.glSampleMaski && parameters.SampleMaskValue != g_syncedRenderStateParameters.SampleMaskValue) {
if (g_GLESFuncs.glSampleMaski &&
(forceFullPush || parameters.SampleMaskValue != g_syncedRenderStateParameters.SampleMaskValue)) {
g_GLESFuncs.glSampleMaski(0, parameters.SampleMaskValue);
}
}
@@ -5189,50 +5248,41 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glGetProgramiv(backendProgramId, pname, params);
}
void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params) {
GLuint backendProgramId = GetBackendProgramId(program);
if (!backendProgramId) return;
g_GLESFuncs.glGetProgramInterfaceiv(backendProgramId, programInterface, pname, params);
}
// NOTE the shape here, and do not "simplify" it back to GetBackendProgramId(): this entry
// point must never be the thing that BUILDS a backend program.
//
// The frontend has already recorded the rebinding on the program object
// (SetShaderStorageBlockBinding) - that record is what GL_BUFFER_BINDING reports and what
// BackendProgramObjectImpl::SyncToBackend replays onto every driver program it builds. So
// the only work left here is an optimisation: push the change straight onto a driver
// program that is ALREADY built and already current with this link, so the next draw does
// not have to be preceded by a rebuild.
//
// Calling GetBackendProgramId() instead would sync-on-demand from a non-draw entry point,
// i.e. transpile and compile the whole program while the draw-path globals that the ESSL
// is generated against (PrgramImpl::g_fragColorBroadcastCount, the snorm/unorm clamp
// masks - established by SyncCurrentProgram) still hold another program's values. That
// bakes a program against the wrong state, and under CPU load it was also observed to
// fail the driver compile outright. Deferring is spec-fine: a binding only has to take
// effect by the block's next use.
void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding) {
if (!storageBlockName) return;
if (!MG_State::pGLContext->ValidateProgramName(program)) return;
auto& programObject = MG_State::pGLContext->GetProgramObject(program);
if (!programObject) return;
GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name) {
GLuint backendProgramId = GetBackendProgramId(program);
if (!backendProgramId) return GL_INVALID_INDEX;
return g_GLESFuncs.glGetProgramResourceIndex(backendProgramId, programInterface, name);
}
void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei* length,
GLchar* name) {
GLuint backendProgramId = GetBackendProgramId(program);
if (!backendProgramId) return;
g_GLESFuncs.glGetProgramResourceName(backendProgramId, programInterface, index, bufSize, length, name);
}
void GetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount,
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params) {
GLuint backendProgramId = GetBackendProgramId(program);
if (!backendProgramId) return;
g_GLESFuncs.glGetProgramResourceiv(backendProgramId, programInterface, index, propCount, props, bufSize, length,
params);
}
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name) {
GLuint backendProgramId = GetBackendProgramId(program);
if (!backendProgramId) return -1;
return g_GLESFuncs.glGetProgramResourceLocation(backendProgramId, programInterface, name);
}
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name) {
(void)program;
(void)programInterface;
(void)name;
return -1;
}
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding) {
GLuint backendProgramId = GetBackendProgramId(program);
if (!backendProgramId) return;
g_GLESFuncs.glShaderStorageBlockBinding(backendProgramId, storageBlockIndex, storageBlockBinding);
auto* backendProgramSlot = PrgramImpl::g_backendProgramObjects.Find(programObject.get());
if (!backendProgramSlot || !*backendProgramSlot) return;
auto& backendObj = *backendProgramSlot;
// Not merely "a program id exists": a backend object whose synced link version has
// fallen behind is about to be rebuilt anyway, and its current driver interface is
// the PREVIOUS link's - applying to it could land the binding on an unrelated block.
if (!backendObj->GetBackendProgramId() ||
backendObj->GetSyncedLinkVersion() != programObject->GetLinkVersion()) {
return; // SyncToBackend's reseed will carry it
}
PrgramImpl::ApplyShaderStorageBlockBinding(backendObj->GetBackendProgramId(), storageBlockName,
storageBlockBinding);
}
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {
@@ -6778,6 +6828,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
BufferImpl::InvalidatePixelBufferBindingCaches();
FramebufferImpl::InvalidateFramebufferBindingCache();
PixelStoreImpl::InvalidatePackStateCache();
// The render-state shadow belongs in this list for the same reason as the ones above:
// it describes the real ES context, which outlives the MobileGL context that is
// becoming current. See InvalidateSyncedRenderState.
RenderStateImpl::InvalidateSyncedRenderState();
// eglSwapInterval requires a current context; a request made while none was
// current (and dropped by the driver) is retried here.
ApplyRequestedSwapInterval();
+1 -9
View File
@@ -92,15 +92,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
void GetIntegeri_v(GLenum target, GLuint index, GLint* data);
void GetInteger64i_v(GLenum target, GLuint index, GLint64* data);
void GetProgramiv(GLuint program, GLenum pname, GLint* params);
void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name);
void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei* length,
GLchar* name);
void GetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount,
const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params);
GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name);
GLint GetProgramResourceLocationIndex(GLuint program, GLenum programInterface, const GLchar* name);
void ShaderStorageBlockBinding(GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding);
void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding);
Bool InitWindowSurface(NativeWindowType window);
Bool InitPbufferSurface(EGLint width, EGLint height);
Bool MakeCurrent();
+58 -7
View File
@@ -3812,6 +3812,34 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
}
Bool ApplyShaderStorageBlockBinding(Uint backendProgramId, const String& blockName, Uint binding) {
if (backendProgramId == 0 || blockName.empty()) return false;
if (!g_GLESFuncs.glGetProgramResourceIndex || !g_GLESFuncs.glShaderStorageBlockBinding) return false;
GLuint driverIndex =
g_GLESFuncs.glGetProgramResourceIndex(backendProgramId, GL_SHADER_STORAGE_BLOCK, blockName.c_str());
if (driverIndex == GL_INVALID_INDEX) {
// An arrayed block is enumerated per element by GL but declared once; the
// generated ESSL carries the bare block name.
const auto bracket = blockName.rfind('[');
if (bracket == String::npos || blockName.back() != ']') return false;
driverIndex = g_GLESFuncs.glGetProgramResourceIndex(backendProgramId, GL_SHADER_STORAGE_BLOCK,
blockName.substr(0, bracket).c_str());
if (driverIndex == GL_INVALID_INDEX) return false;
}
g_GLESFuncs.glShaderStorageBlockBinding(backendProgramId, driverIndex, binding);
return true;
}
void ReseedShaderStorageBlockBindings(Uint backendProgramId,
const MG_State::GLState::ProgramObject& stateProgramObject) {
const auto& overrides = stateProgramObject.GetShaderStorageBlockBindingOverrides();
if (overrides.empty()) return; // the overwhelming majority of programs
for (const auto& [blockName, binding] : overrides) {
if (binding < 0) continue;
ApplyShaderStorageBlockBinding(backendProgramId, blockName, static_cast<Uint>(binding));
}
}
void BackendProgramObjectImpl::SyncToBackend(
const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject) {
#ifdef TRACY_ENABLE
@@ -3846,9 +3874,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (attachedCount > 0) {
Vector<GLuint> attachedShaders(attachedCount);
GLsizei actualCount;
// Every GL out-param in this function is pre-initialized and every count is
// re-clamped after the query. A driver that returns without writing the
// out-param (no current context, a lost context, a stubbed entry point) would
// otherwise leak an uninitialized stack value straight into a container size
// or a loop bound - which is exactly how this path used to throw
// length_error out of a Vector fill-ctor.
GLsizei actualCount = 0;
g_GLESFuncs.glGetAttachedShaders(m_backendProgramId, attachedCount, &actualCount,
attachedShaders.data());
actualCount = std::clamp<GLsizei>(actualCount, 0, static_cast<GLsizei>(attachedShaders.size()));
MGLOG_D("Detaching %d existing shaders from program %u", actualCount, m_backendProgramId);
for (GLsizei i = 0; i < actualCount; ++i) {
@@ -3986,13 +4021,21 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glShaderSource(backendShaderId, 1, &sourceCStr, nullptr);
g_GLESFuncs.glCompileShader(backendShaderId);
GLint compileStatus;
// GL_FALSE, not GL_TRUE: an unwritten out-param must read as "compile failed"
// and take the diagnostic path, never as a silent success that attaches an
// uncompiled shader.
GLint compileStatus = GL_FALSE;
g_GLESFuncs.glGetShaderiv(backendShaderId, GL_COMPILE_STATUS, &compileStatus);
if (compileStatus == GL_FALSE) {
GLint logLength;
GLint logLength = 0;
g_GLESFuncs.glGetShaderiv(backendShaderId, GL_INFO_LOG_LENGTH, &logLength);
Vector<GLchar> log(logLength);
if (logLength < 0) logLength = 0;
// +1 and zero-filled: GL_INFO_LOG_LENGTH already counts the terminator,
// but a driver that reports 0 (or fails the query) must still leave
// log.data() a readable empty C string for the %s below.
Vector<GLchar> log(static_cast<SizeT>(logLength) + 1, '\0');
g_GLESFuncs.glGetShaderInfoLog(backendShaderId, logLength, nullptr, log.data());
log.back() = '\0';
MGLOG_E("Shader compilation failed for backend ID %u: %s", backendShaderId, log.data());
m_backendProgramUsable = false;
continue;
@@ -4028,14 +4071,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_D("Linking program %u", m_backendProgramId);
g_GLESFuncs.glLinkProgram(m_backendProgramId);
GLint linkStatus;
GLint linkStatus = GL_FALSE;
g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_LINK_STATUS, &linkStatus);
m_backendProgramUsable = m_backendProgramUsable && linkStatus == GL_TRUE;
if (linkStatus != GL_TRUE) {
GLint logLength;
GLint logLength = 0;
g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_INFO_LOG_LENGTH, &logLength);
Vector<GLchar> log(logLength);
if (logLength < 0) logLength = 0;
Vector<GLchar> log(static_cast<SizeT>(logLength) + 1, '\0');
g_GLESFuncs.glGetProgramInfoLog(m_backendProgramId, logLength, nullptr, log.data());
log.back() = '\0';
MGLOG_E("Program %u linking failed for %u: %s", stateProgramObject->GetExternalIndex(),
m_backendProgramId, log.data());
} else {
@@ -4068,6 +4113,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
CacheResourceLocations(stateProgramObject);
// AFTER the link, because glShaderStorageBlockBinding needs the driver's linked
// interface. This is the only place Espryt applies a rebinding: the frontend
// record is authoritative and the glShaderStorageBlockBinding entry point itself
// deliberately never forces a program build (see DirectGLES.cpp), so a rebinding
// requested while no backend program existed yet arrives here instead.
ReseedShaderStorageBlockBindings(m_backendProgramId, *stateProgramObject);
m_syncedLinkVersion = stateProgramObject->GetLinkVersion();
m_isInitialized = true;
+16
View File
@@ -981,6 +981,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
extern Uint g_lastUsedBackendProgramId;
extern StateBackendObjectRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl>
g_backendProgramObjects;
// Points one shader storage block of an ALREADY-LINKED backend program at
// `binding`. `blockName` is the frontend interface-query spelling; the real
// driver's own index for it is looked up here, because the transpiled ESSL's
// block order is not the frontend's. Returns false when the block does not exist
// on the backend program (eliminated as unused, or the driver lacks the entry
// points), which is not an error - GL_BUFFER_BINDING is served from the frontend
// record either way.
Bool ApplyShaderStorageBlockBinding(Uint backendProgramId, const String& blockName, Uint binding);
// Replays every glShaderStorageBlockBinding recorded on the program onto a backend
// program that was just built. The frontend record is authoritative (only the
// shader's DECLARED binding survives in the SPIR-V), so without this replay any
// rebuild would silently revert rebound blocks. Mirrors DirectVulkan's
// reseed-on-rebuild in BuildProgramResourceCache.
void ReseedShaderStorageBlockBindings(Uint backendProgramId,
const MG_State::GLState::ProgramObject& stateProgramObject);
} // namespace PrgramImpl
namespace SamplerImpl {