[MG_Pipe, MG_Impl, MG_State, MG_Remote, MG_Backend, MG_Test] (Disaggregated): P5e sb - set_shader_buffers is emitted, routed and applied

- MGPShaderBuffers::WritableMask is Uint32[3] and the POD grows 32 -> 40 (ID-104). One
  Uint32 could describe only the first 32 of an 84-point window, so a storage buffer bound
  at point 32 or above read as unwritable with nothing able to see it; the ruling widens the
  field rather than narrowing the window, which would change what an application may bind.
  MGPipeShaderBufferMaskHas / ...Set are the one spelling of the mask, so the emitter, the
  applier and the four backend sites cannot disagree about which word a point lives in.
- MGPipeApplySetShaderBuffers has its body: per-class window, Start/Count validated against
  the capacity, the mask copied by word, ONE ShaderBuffersSerial advanced on every applied
  record and ADVANCED - never zeroed - by both resets, which also clear the three windows.
- Route rows for opcode 38 on both arms (Mono_ and Wire_) and the server sink in the wire
  codec's decode switch, which is where a kCtxState row's sink lives - PipeApplier.cpp's
  ServerVerbSink has no method for it and the brief's "OnSetShaderBuffers" names no code.
  The codec also bounds Class and Start+Count where Count becomes a byte length.
- New MG_Impl/Pipe/ShaderBufferEmit.h: one record per class, window = the touched
  high-water mark clamped to 84, zero early-out before any hash, a BASE binding travelling
  as kMGPipeWholeBuffer so the server re-resolves the extent at use, three suppressor slots
  (ruling 11), and the writable mask that replaces the backend's GPU-write walk.
- BufferState gains a per-target bind-point generation (push-only, G1) and dirty bits
  15/16/17 shutter on it instead of the buffer CONTENT aggregate. That aggregate answered
  the wrong question in both directions: any glBufferSubData fired all three, and
  glBindBufferBase fired none of them, because it mutates a binding point through a returned
  reference. Same defect class as P4a's glBindSampler hole.
- Init.cpp publishes consumer bit 13 for DirectGLES and not for Magma (ID-106), in the same
  commit as the wired constant, and PipeFill's liveness gate asks R-8 for bit 13 in
  particular plus bit 13-requires-bit-7.
- `Touch` joins the dirty-surface scanner's mutator prefixes; TouchBufferBindingPoint gets
  its row and SetNamedTransformFeedbackBinding's is rewritten now that bit 17 reads a
  generation a binding moves. Negative control 6 is re-pointed: the pair it used to refute
  is a live answer, and a control that has quietly become positive is what the self-test is
  for.
This commit is contained in:
2026-09-18 08:08:04 -04:00
parent f6cfcbd36b
commit 1ebe6abeb2
24 changed files with 1434 additions and 89 deletions
+26 -12
View File
@@ -84,8 +84,17 @@ SCAN_ROOTS = (os.path.join(REPO_ROOT, "MobileGL", "MG_Impl", "GLImpl"),
# which is that they create or destroy objects rather than move a pushed field, and each
# object class's creation and destruction is already answered by its own Mark*ForDeletion row
# plus the constructor-time resource_create.
# P5e WIDENS IT BY ONE MORE WORD, `Touch`, and the hole it closes is the same coverage hole
# `Use` and `Bind` closed at P4a. `GLContext::TouchBufferBindingPoint` is the high-water mark
# every indexed-binding-point walk is bounded by - the backend's, the client's GPU-write sweep's
# and, since P5e, set_shader_buffers' emitted window - so a call that moves it moves the SIZE of
# a pushed record, and none of the three sites that call it was visible to this scan because
# none begins with one of the twelve words the pattern matched. The complete set the widening
# surfaces was enumerated by grep before the change landed: ONE name on two call sites, both in
# GL_Buffer.cpp's BindBufferBase_State / BindBufferRange_State, which already carry rows for
# their other mutators.
MUTATOR_PREFIXES = ("Add", "Set", "Mark", "Bump", "Allocate", "Truncate", "Record", "Notify",
"Begin", "End", "Use", "Bind")
"Begin", "End", "Use", "Bind", "Touch")
MUTATOR_RE = re.compile(r"pGLContext->\s*((?:%s)\w*)\s*\(" % "|".join(MUTATOR_PREFIXES))
# The SECOND publish mechanism (MG_Pipe/PipeMutation.h). It carries the FIELD, not a mutator
@@ -1604,22 +1613,27 @@ def self_test(scanned, bits, publishers, movers, moved, outside=None, undecided_
tripped(any(p.startswith("MISSING publisher") for p in problems),
"5 (a dropped render-state publisher)")
# 6. THE CONTROL FOR THE OBJECT-CLASS HALF, and it is again the shape of a defect that
# was actually in this file: X(SetNamedTransformFeedbackBinding, NEW_SO_TARGETS)
# named a shutter (the buffer-content aggregate mixed with the transform-feedback
# generation) that GLContext::SetNamedTransformFeedbackBinding moves on no path - it
# binds a BufferState binding point or writes a saved-bindings entry, and neither is
# a buffer CONTENT write or a BeginTransformFeedback. The row has to be RED - and
# the analysis reaches (by name, through `Bind(`) a body it cannot fully read, so the
# red it can honestly print is "UNDECIDED and unmarked", never "supported".
# 6. THE CONTROL FOR THE OBJECT-CLASS HALF, on a mutator the analysis CANNOT fully read,
# so the red it can honestly print is "UNDECIDED and unmarked" rather than "supported".
# The pair is dead in the plainest way available: glTransformFeedbackBufferBase writes
# a transform-feedback binding point or a named object's saved-bindings entry, and
# NEW_VERTEX_ELEMENTS' shutter reads the bound VAO's identity and attribute generation.
#
# IT USED TO NAME NEW_SO_TARGETS, because that pairing was a defect this file actually
# carried: bit 17 mixed the buffer-CONTENT aggregate, which no binding has ever moved.
# P5e (sb) gave the binding points a generation of their own and bit 17 now reads it, so
# the old pair is a LIVE answer (kPulledPartialShutter|NEW_SO_TARGETS, marked undecided
# for this same taint) and could no longer be the control - a negative control that has
# quietly become positive is the failure this whole self-test exists to make loud, and
# it was loud: this one stopped tripping in the commit that rewrote the row.
with_dead_shutter = dict(real)
with_dead_shutter["SetNamedTransformFeedbackBinding"] = "NEW_SO_TARGETS"
with_dead_shutter["SetNamedTransformFeedbackBinding"] = "NEW_VERTEX_ELEMENTS"
problems = check_mapping(with_dead_shutter, real_duplicates, scanned, bits, publishers,
movers, moved, outside, real_marks)
verdicts = derive_bit_answers(with_dead_shutter, bits, movers, moved, outside)
tripped(any(("NEW_SO_TARGETS for SetNamedTransformFeedbackBinding" in p
tripped(any(("NEW_VERTEX_ELEMENTS for SetNamedTransformFeedbackBinding" in p
and p.startswith(("UNDER-FIRING", "UNDECIDED"))) for p in problems)
and verdicts[("SetNamedTransformFeedbackBinding", "NEW_SO_TARGETS")][0] != SUPPORTED,
and verdicts[("SetNamedTransformFeedbackBinding", "NEW_VERTEX_ELEMENTS")][0] != SUPPORTED,
"6 (an object-class answer whose shutter the mutator never moves)")
# 6b. the same family, on a mutator the analysis reads completely, so the red is the
# verdict itself: a texture-bind bump moves nothing NEW_GLOBAL_CONSTANTS compares.