[Feat] (State): give the frontend six aggregate generations so a tracker can answer "did any bound texture, buffer, attachment or attribute move" with one Uint64 compare

- MGP_NOTE_AGGREGATE(Aggregate) next to MGP_NOTE_MUTATION in MG_Pipe/PipeMutation.h, ((void)0)
  in the pull build for the same reason and with the same shape. It answers a DIFFERENT
  question from MGP_NOTE_MUTATION - "did any object of this class move since the tracker last
  looked", not "did a backend move a frontend value inside its own verb" - which is why it is a
  second macro rather than an overload.
- The counters are members of the owning MG_State container (VertexArrayState,
  FramebufferState, TextureState x2, BufferState) and are reached through a push-only GLContext
  facade, because the bump points sit on OBJECTS and an object has no back-pointer to the state
  that owns it. That is the free-function form P2 brief D4 allows, and it costs a global load on
  a path that has just written object state.
- A SIXTH aggregate, VertexAttribDefault on GLContext, which D4 does not list. Its bit
  (NEW_VERTEX_ATTRIB_DEFAULTS) is specified there with a ContentHash over all 32
  CurrentVertexAttributeValues, and hashing 768 bytes on every draw does not fit inside the T1
  ceiling the same brief pins. The hash still decides whether to EMIT (D11's set-hash
  suppressor); the generation decides whether to hash at all.
- 30 bump points: 3 VertexArrayObject config-version sites, 3 FramebufferObject object-version
  sites (one of them inside MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER, so the statement
  carries its own line continuation), 5 texture content-version sites, 12 texture
  params-version sites, SamplerObject::BumpVersion as the sampler choke point, 7 BufferObject
  change-serial sites and the 3 glVertexAttrib* defaults.
- Every counter is deliberately COARSER than the state it guards: over-firing costs one extra
  push, under-firing renders stale, and under-firing is the direction ARCHITECTURE.md 13.2
  names as the dangerous one and the P1 verify comparator cannot see for object-class state.
- TrackerTest: each bump point moves ITS aggregate and no other, plus a null-context note.
- G1: the pull build is 0 added / 0 removed / 0 renamed and the four resized symbols are the
  contract commit's own, unchanged by this commit.
This commit is contained in:
2026-09-07 23:18:09 -04:00
parent 3d1a866e82
commit de532f55a9
16 changed files with 366 additions and 11 deletions
+45
View File
@@ -33,10 +33,55 @@ namespace MobileGL::MG_Pipe {
// A no-op unless a context is live, a verb has been filled, and `field` is in that verb
// class's may-read mask; a forwarded (sticky) field has no storage and is never copied.
void MGPipeNoteFrontendMutation(MGPipeInputField field);
// ---- the aggregate generations (P2 brief D4, ARCHITECTURE.md 5.2) ----
//
// MGP_NOTE_MUTATION answers "a backend moved a frontend value INSIDE its own verb".
// MGP_NOTE_AGGREGATE answers a different question, which is why it is a second macro
// and not an overload: "did ANY object of this class move since the last time the
// tracker looked", collapsed onto one monotonic Uint64 per class so a per-verb dirty
// walk is a handful of compares rather than a scan over 32 attributes, 16 attachments,
// 32 texture units and 84 binding points.
//
// The counters are members of the owning MG_State container, all guarded by
// MOBILEGL_PIPE_PUSH so the pull build's state objects do not change size (G1). The
// bump points sit on OBJECTS, which have no back-pointer to their state, so the macro
// goes through a free function that finds the live GLContext - the same shape, and for
// the same reason, as MGP_NOTE_MUTATION (MG_Impl/Pipe/PipeFill.cpp). It costs a global
// load on a path that has just written object state.
//
// Monotonic and never reset: the tracker widens and compares, it never subtracts.
// Over-firing is free (one extra re-push); under-firing renders stale, which is why
// every counter here is deliberately COARSER than the state it guards.
enum class MGPipeAggregate : Uint32 {
// VertexArrayState: any VAO attribute format / buffer / enable moved.
VaoAttribute = 0,
// FramebufferState: any FBO attachment or default-geometry write, or a bind.
FramebufferAttachment,
// TextureState: any texture object CONTENT moved (an upload, a dirty region).
TextureContent,
// TextureState: any texture object or sampler object PARAMETER moved.
TextureParams,
// BufferState: any buffer object contents moved.
BufferChange,
// GLContext: a glVertexAttrib* default value moved. Not one of D4 five: the bit it
// shutters (NEW_VERTEX_ATTRIB_DEFAULTS) is specified there as a ContentHash over
// all 32 CurrentVertexAttributeValues, and hashing 768 bytes on EVERY draw does not
// fit inside the T1 ceiling. The hash still decides whether to EMIT (D11 set-hash
// suppressor); this decides whether to hash at all.
VertexAttribDefault,
Count,
};
// MG_Impl/Pipe/PipeFill.cpp. A no-op unless a context is live.
void MGPipeNoteAggregate(MGPipeAggregate aggregate);
} // namespace MobileGL::MG_Pipe
#define MGP_NOTE_MUTATION(Field) \
::MobileGL::MG_Pipe::MGPipeNoteFrontendMutation(::MobileGL::MG_Pipe::MGPipeInputField::Field)
#define MGP_NOTE_AGGREGATE(Aggregate) \
::MobileGL::MG_Pipe::MGPipeNoteAggregate(::MobileGL::MG_Pipe::MGPipeAggregate::Aggregate)
#else
#define MGP_NOTE_MUTATION(Field) ((void)0)
#define MGP_NOTE_AGGREGATE(Aggregate) ((void)0)
#endif
#endif