[Fix] (Pipe): carry the class a vertex-attribute default was written through, and stop the applier's lossy write from being observable

- set_vertex_attrib_defaults hard-coded MGPAttribValue::ValueClass to 0 for every attribute
  and always sent the FLOAT view's four words. A CurrentVertexAttributeValue is one value in
  three views and GLContext converts numerically between them, so those bytes cannot
  reproduce the frontend value: glVertexAttrib4f(loc, 1.5f, ..) leaves 1 in intValue and
  0x3FC00000 in floatValue, and every glVertexAttribI4i/ui default was wrong too
- GLContext now records which view each glVertexAttrib* write filled directly
  (GetCurrentVertexAttributeClass, push-only) and the payload carries that class and THAT
  class's own words. It is kept beside the value rather than inside it because
  CurrentVertexAttributeValue is mirrored into PipeInputs and compared there by a memcmp
  whose size assertion lives in a file this package does not own
- MGPipeFillAttribValue is the flattening, in one named place, so TrackerAttribPayload can
  pin it: the old defect turns three of its four cases red
- the applier (package A's) still memcpys the four words into all three views regardless of
  ValueClass, so the emitter now CHECKS: it compares the mirror the applier wrote against the
  frontend's value and, when they differ, copies the field itself and says so once. That
  closes the window the old code left wrong - a glVertexAttrib* write followed by a verb
  whose class does not read the field, where the residual fill does not run for it - and it
  stops repairing by itself the day A's applier honours the class
- MGPipeVertexAttribDefaultRepairCount() makes that repair observable to a test without
  reading storage the fill table forbids that verb to read
- the emission gate now goes through MGPipeSubsystemForDirty, the one bit-to-subsystem map,
  instead of a second copy of it written out by hand at the validate point; five
  static_asserts tie that map to the field-emitter map it has to agree with
- the staging mirror is advanced only by the branch that sent dynamic bytes, with the
  invariant it used to rely on (BumpVersions moves both counters, RenderState.h) asserted
  here rather than assumed of another package's file
- TrackerShippedEmitter drives MGPipeValidateForVerb itself and reads the real singletons
  back, so the blend-toggle and viewport shapes are pinned on the shipped emitter and not
  only on the unit tests' local re-implementation; the fixtures reset the applier, the cache
  and the tracker together, which is the only consistent state of the three
- comments: the derivation probe is a one-field sample, the residual block's trip wire is
  half a tautology until package A's c1 lands, and the widened counter cannot see a change of
  exactly 65536 - all three recorded where the code is, not only in a review
This commit is contained in:
2026-09-07 23:18:09 -04:00
parent bb781df527
commit 5d4d91fe7e
6 changed files with 437 additions and 28 deletions
+11
View File
@@ -214,6 +214,11 @@ namespace MobileGL::MG_State {
current.intValue[component] = static_cast<Int32>(value[component]);
current.uintValue[component] = static_cast<Uint32>(value[component]);
}
#if MOBILEGL_PIPE_PUSH
// The two views above are CONVERSIONS, not bit copies, so which one was written
// is part of the value; set_vertex_attrib_defaults carries it.
m_currentVertexAttributeClasses[index] = kVertexAttribValueClassFloat;
#endif
MGP_NOTE_AGGREGATE(VertexAttribDefault);
}
@@ -229,6 +234,9 @@ namespace MobileGL::MG_State {
current.floatValue[component] = static_cast<Float>(value[component]);
current.uintValue[component] = static_cast<Uint32>(value[component]);
}
#if MOBILEGL_PIPE_PUSH
m_currentVertexAttributeClasses[index] = kVertexAttribValueClassInt;
#endif
MGP_NOTE_AGGREGATE(VertexAttribDefault);
}
@@ -244,6 +252,9 @@ namespace MobileGL::MG_State {
current.floatValue[component] = static_cast<Float>(value[component]);
current.intValue[component] = static_cast<Int32>(value[component]);
}
#if MOBILEGL_PIPE_PUSH
m_currentVertexAttributeClasses[index] = kVertexAttribValueClassUint;
#endif
MGP_NOTE_AGGREGATE(VertexAttribDefault);
}
+39
View File
@@ -30,10 +30,26 @@ namespace MobileGL {
void Init();
namespace GLState {
#if MOBILEGL_PIPE_PUSH
// MGPAttribValue::ValueClass' encoding (MG_Pipe/MGPipeTypes.h documents the order
// "Float | Int | Uint | Double"). It lives here rather than in MG_Pipe because the
// FRONTEND is the only thing that knows which of the three views below a value was
// written through - the other two are numeric conversions of it - and MG_Pipe has
// no enum for the field yet. If package A introduces one, this becomes its alias.
inline constexpr Uint32 kVertexAttribValueClassFloat = 0;
inline constexpr Uint32 kVertexAttribValueClassInt = 1;
inline constexpr Uint32 kVertexAttribValueClassUint = 2;
#endif
struct CurrentVertexAttributeValue {
Array<Float, 4> floatValue{0.f, 0.f, 0.f, 1.f};
Array<Int32, 4> intValue{0, 0, 0, 1};
Array<Uint32, 4> uintValue{0u, 0u, 0u, 1u};
// Three scalar arrays and NOTHING ELSE. MG_Backend/MGPipe/PipeInputs.cpp
// compares this storage with one memcmp and asserts that size, so a fourth
// member here is a build break in a file P2 package B does not own. The
// written-class discriminator set_vertex_attrib_defaults needs therefore
// lives beside the array on GLContext, not inside the value.
};
// Which of the three views above a shader input of a given GLSL type consumes.
@@ -233,6 +249,27 @@ namespace MobileGL {
Uint64 GetAnyVertexAttribDefaultGeneration() const {
return m_anyVertexAttribDefaultGeneration;
}
// Which of the three views of m_currentVertexAttributes[index] the last
// glVertexAttrib* write filled DIRECTLY. The other two are NUMERIC
// conversions of it (SetCurrentVertexAttribute* below), not bit copies, so
// four words on a wire are not the value unless the class travels with them:
// glVertexAttrib4f(loc, 1.5f, ...) leaves 1 in intValue and 0x3FC00000 in
// floatValue. set_vertex_attrib_defaults carries this as MGPAttribValue's
// ValueClass so the applier can redo the conversion instead of memcpying one
// view into all three.
//
// It is kept BESIDE the array rather than inside CurrentVertexAttributeValue
// because that struct is mirrored into PipeInputs and compared there by a
// memcmp whose size assertion (MG_Backend/MGPipe/PipeInputs.cpp) is a file
// this package does not own - and because it need not be mirrored: the class
// only decides how to REBUILD the three views, so two writes that leave the
// three views identical rebuild identically whichever class they carried.
Uint32 GetCurrentVertexAttributeClass(Uint index) const {
return index < m_currentVertexAttributeClasses.size()
? m_currentVertexAttributeClasses[index]
: kVertexAttribValueClassFloat;
}
#endif
// RenderState
@@ -554,6 +591,8 @@ namespace MobileGL {
SharedPtr<ProgramObject> m_transformFeedbackProgram;
#if MOBILEGL_PIPE_PUSH
Uint64 m_anyVertexAttribDefaultGeneration = 0;
// Parallel to m_currentVertexAttributes; see GetCurrentVertexAttributeClass.
Array<Uint32, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributeClasses{};
#endif
Uint64 m_transformFeedbackGeneration = 0;
// Source of the per-span ids above; never rolls back with an object switch.