mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[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:
@@ -1057,6 +1057,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
Bool ProgramLinkTask::ResolveTransformFeedbackVaryings() {
|
||||
artifacts.xfbVaryings.clear();
|
||||
// The GL_TRANSFORM_FEEDBACK_VARYING interface enumerates the request verbatim -
|
||||
// pseudo-varyings included - while xfbVaryings below keeps only what is actually
|
||||
// captured. Snapshot it before the loop consumes gl_NextBuffer/gl_SkipComponentsN.
|
||||
artifacts.xfbInterfaceNames = in.requestedXfbVaryings;
|
||||
artifacts.xfbStrides.clear();
|
||||
artifacts.xfbBufferMode = in.requestedXfbBufferMode;
|
||||
artifacts.xfbVaryingNameMaxLength = 0;
|
||||
@@ -1127,15 +1131,45 @@ namespace MobileGL::MG_State::GLState {
|
||||
bytesPerElement = 4;
|
||||
resolved = true;
|
||||
} else if (linkerObjects != nullptr) {
|
||||
// GL lets a capture name a single element of an output array ("b[0]"), which
|
||||
// captures one element of the element type - not the whole array. Strip a
|
||||
// trailing strict-decimal subscript and look the base declaration up.
|
||||
String declaredName = name;
|
||||
Bool singleElement = false;
|
||||
Uint element = 0;
|
||||
if (name.size() > 3 && name.back() == ']') {
|
||||
const SizeT bracket = name.rfind('[');
|
||||
if (bracket != String::npos && bracket + 1 < name.size() - 1) {
|
||||
Bool digitsOnly = true;
|
||||
for (SizeT c = bracket + 1; c + 1 < name.size(); ++c) {
|
||||
if (name[c] < '0' || name[c] > '9') {
|
||||
digitsOnly = false;
|
||||
break;
|
||||
}
|
||||
element = element * 10 + static_cast<Uint>(name[c] - '0');
|
||||
}
|
||||
if (digitsOnly) {
|
||||
declaredName = name.substr(0, bracket);
|
||||
singleElement = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto* node : linkerObjects->getSequence()) {
|
||||
const glslang::TIntermSymbol* symbol = node->getAsSymbolNode();
|
||||
if (symbol == nullptr || symbol->getType().getQualifier().storage != glslang::EvqVaryingOut) {
|
||||
continue;
|
||||
}
|
||||
if (symbol->getName() != name.c_str()) {
|
||||
if (symbol->getName() != declaredName.c_str()) {
|
||||
continue;
|
||||
}
|
||||
resolved = ResolveXfbSymbolType(symbol->getType(), varying.type, varying.size, bytesPerElement);
|
||||
if (resolved && singleElement) {
|
||||
if (static_cast<Int>(element) >= varying.size) {
|
||||
resolved = false;
|
||||
break;
|
||||
}
|
||||
varying.size = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
artifacts.explicitOpaqueUniformBindings.clear();
|
||||
artifacts.uniformBlockIndexByName.clear();
|
||||
artifacts.uniformBlockBinding.clear();
|
||||
artifacts.shaderStorageBlockBinding.clear();
|
||||
artifacts.uniformOffsets.clear();
|
||||
artifacts.uniformSizesInBytes.clear();
|
||||
artifacts.globalUboScratch.clear();
|
||||
@@ -127,6 +128,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
artifacts.attribInNameMaxLength = 0;
|
||||
artifacts.uniformBlockNameMaxLength = 0;
|
||||
artifacts.xfbVaryings.clear();
|
||||
artifacts.xfbInterfaceNames.clear();
|
||||
artifacts.xfbStrides.clear();
|
||||
artifacts.xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
artifacts.xfbVaryingNameMaxLength = 0;
|
||||
|
||||
@@ -82,6 +82,14 @@ namespace MobileGL::MG_State::GLState {
|
||||
return -1;
|
||||
}
|
||||
if (name.length() < 4) return -1;
|
||||
// An array of arrays is keyed by its full "[0]"-terminated spelling
|
||||
// ("a[2][1][0]"), so a query that already ends in a subscript may still be the
|
||||
// NAME of an array rather than an element of one. Try that first; only then
|
||||
// treat the trailing subscript as an element index.
|
||||
{
|
||||
const auto arrayOfArraysIt = Artifacts().uniformLocations.find(name + "[0]");
|
||||
if (arrayOfArraysIt != Artifacts().uniformLocations.end()) return (Int)arrayOfArraysIt->second;
|
||||
}
|
||||
const SizeT bracket = name.rfind('[');
|
||||
// Require at least one digit between the brackets.
|
||||
if (bracket == String::npos || bracket + 1 >= name.length() - 1) return -1;
|
||||
@@ -130,6 +138,14 @@ namespace MobileGL::MG_State::GLState {
|
||||
if (tIndex < 0 || tIndex >= static_cast<Int>(Artifacts().tProgramUniformIndexToGl.size())) return -1;
|
||||
return Artifacts().tProgramUniformIndexToGl[tIndex];
|
||||
}
|
||||
// GL uniform-block index -> glslang TProgram block index (the inverse of
|
||||
// GlBlockIndexFromTProgram). The interface-query layer needs it to reach block
|
||||
// properties glslang exposes but no typed getter here does.
|
||||
Int TProgramBlockIndex(Uint glBlockIndex) const {
|
||||
return glBlockIndex < Artifacts().glBlockIndexToTProgram.size()
|
||||
? Artifacts().glBlockIndexToTProgram[glBlockIndex]
|
||||
: -1;
|
||||
}
|
||||
Int GlBlockIndexFromTProgram(Int tBlockIndex) const {
|
||||
if (tBlockIndex < 0 || tBlockIndex >= static_cast<Int>(Artifacts().tProgramBlockIndexToGl.size())) return -1;
|
||||
return Artifacts().tProgramBlockIndexToGl[tBlockIndex];
|
||||
@@ -529,9 +545,42 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
Uint GetUniformBlockBinding(Uint index) const { return Artifacts().uniformBlockBinding[index]; }
|
||||
|
||||
// Set by glShaderStorageBlockBinding, keyed by the block's GL name rather than by any
|
||||
// index. A shader storage block has THREE index spaces - the frontend interface-query
|
||||
// enumeration, DirectVulkan's SPIR-V descriptor order and DirectGLES's real-driver
|
||||
// order - and the name is the only coordinate all three agree on. Absent from the map
|
||||
// means "never rebound", and the shader's declared binding still stands.
|
||||
void SetShaderStorageBlockBinding(const String& blockName, Uint binding) {
|
||||
Artifacts().shaderStorageBlockBinding[blockName] = static_cast<Int>(binding);
|
||||
}
|
||||
// -1 when the block has never been rebound. `blockName` is the interface-query
|
||||
// spelling; an arrayed block's elements ("B[0]", "B[1]") are separate GL resources
|
||||
// with separate bindings, so they are separate keys.
|
||||
Int GetShaderStorageBlockBindingOverride(const String& blockName) const {
|
||||
const auto it = Artifacts().shaderStorageBlockBinding.find(blockName);
|
||||
if (it != Artifacts().shaderStorageBlockBinding.end()) return it->second;
|
||||
// A backend that collapses an arrayed block down to one resource knows it only by
|
||||
// the bare block name; answer that with element zero's binding.
|
||||
const auto zeroth = Artifacts().shaderStorageBlockBinding.find(blockName + "[0]");
|
||||
return zeroth != Artifacts().shaderStorageBlockBinding.end() ? zeroth->second : -1;
|
||||
}
|
||||
// Every rebinding recorded so far, for a backend that has to REPLAY them onto a
|
||||
// driver program it just (re)built. Empty for the overwhelming majority of programs -
|
||||
// check .empty() before doing any per-block work.
|
||||
const UnorderedMap<String, Int>& GetShaderStorageBlockBindingOverrides() const {
|
||||
return Artifacts().shaderStorageBlockBinding;
|
||||
}
|
||||
|
||||
Vector<Vector<unsigned>>& GetGeneratedSpirv() { return Artifacts().generatedSpirv; }
|
||||
const Vector<Vector<unsigned>>& GetGeneratedSpirv() const { return Artifacts().generatedSpirv; }
|
||||
|
||||
// The linked glslang reflection itself, for the ONE consumer that needs resource
|
||||
// lists no typed getter above exposes: the GL program-interface query layer
|
||||
// (MG_Impl/GLImpl/Program/ProgramInterface.cpp), which has to enumerate buffer
|
||||
// blocks, buffer variables, atomic counters and per-stage reference masks. Null
|
||||
// until a link has succeeded. Read through the join gate like everything else.
|
||||
const glslang::TProgram* GetReflection() const { return Artifacts().program.get(); }
|
||||
|
||||
Int GetShaderIndexByStage(ShaderStage stage) const {
|
||||
auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [stage](const SharedPtr<ShaderObject>& shader) {
|
||||
return shader->GetShaderStage() == stage;
|
||||
@@ -611,6 +660,9 @@ namespace MobileGL::MG_State::GLState {
|
||||
// These may change after-link (because GL spec decided to have `glUniformBlockBinding`)
|
||||
UnorderedMap<String, Uint> uniformBlockIndexByName;
|
||||
Vector<Int> uniformBlockBinding;
|
||||
// glShaderStorageBlockBinding overrides, keyed by GL block name. See
|
||||
// SetShaderStorageBlockBinding for why this one is by name and not by index.
|
||||
UnorderedMap<String, Int> shaderStorageBlockBinding;
|
||||
|
||||
// Need to be reflected after linking of SPIR-V binary
|
||||
Vector<Uint> uniformOffsets;
|
||||
@@ -629,6 +681,12 @@ namespace MobileGL::MG_State::GLState {
|
||||
// Transform feedback: the linked snapshot (the request lives outside, on the
|
||||
// GL-thread-owned side).
|
||||
Vector<XfbVarying> xfbVaryings;
|
||||
// The glTransformFeedbackVaryings request list exactly as this link consumed it,
|
||||
// INCLUDING the gl_NextBuffer / gl_SkipComponentsN pseudo-varyings that
|
||||
// xfbVaryings deliberately drops (they steer the capture layout and must never
|
||||
// reach a backend's varying list). GL_TRANSFORM_FEEDBACK_VARYING enumerates the
|
||||
// full request, pseudo-varyings and all, so the interface query needs its own copy.
|
||||
Vector<String> xfbInterfaceNames;
|
||||
Vector<Uint32> xfbStrides;
|
||||
Vector<Uint32> gsStripTriangles;
|
||||
Bool gsStripCaptureFixup = false;
|
||||
@@ -711,6 +769,9 @@ namespace MobileGL::MG_State::GLState {
|
||||
return index < Artifacts().xfbVaryings.size() ? &Artifacts().xfbVaryings[index] : nullptr;
|
||||
}
|
||||
const Vector<XfbVarying>& GetTransformFeedbackVaryings() const { return Artifacts().xfbVaryings; }
|
||||
// The GL_TRANSFORM_FEEDBACK_VARYING resource list: every name the last successful
|
||||
// link was asked to capture, in request order, pseudo-varyings included.
|
||||
const Vector<String>& GetTransformFeedbackInterfaceNames() const { return Artifacts().xfbInterfaceNames; }
|
||||
// Stride of one captured vertex in the given capture buffer slot.
|
||||
Uint32 GetTransformFeedbackStride(Uint32 bufferIndex) const {
|
||||
return bufferIndex < Artifacts().xfbStrides.size() ? Artifacts().xfbStrides[bufferIndex] : 0;
|
||||
|
||||
Reference in New Issue
Block a user