Commit Graph
4 Commits
Author SHA1 Message Date
swung0x48 8566a288f8 [Refactor] (Pipe, State): extract MGPipeValueTypes.h - move the render-state, sampler and vertex value types and their enums out of MG_State/GLState so MG_Pipe no longer reaches RenderState.h; pure move, member order and namespaces unchanged
- ROADMAP P0.5 / ARCHITECTURE.md value-header manifest: MGPipeTypes.h embedded
  RenderStateParameters and PixelStoreParameters through RenderState.h, which drags
  FramebufferObject.h and the whole texture/renderbuffer/sampler chain into MG_Pipe;
  purity gate A (no MG_State/MG_Impl/MG_Backend/MG_Remote in the closure) could not
  be armed for anything in MG_Pipe while that include existed.
- MGPipeValueTypes.h is a verbatim cut, comments included: the eleven RenderState.h
  enums (all of them - a split would be the maintenance trap), PixelStoreParameters,
  PerBufferBlendState, StencilFaceState, RenderStateParameters (member order
  untouched: DirectGLES' offsetof spans and PipeSpanTable.inc name the members),
  the six SamplerObject.h enums and SamplerParameters (BorderColorForm stays Uint8,
  it sets the tail padding), and VertexAttribute / VertexBufferBindingPoint /
  VertexAttributeVersion, which keep namespace MobileGL::MG_State::GLState with a
  forward-declared BufferObject so no mangled name changes.
- MAX_DRAW_BUFFERS becomes inline constexpr kMGMaxDrawBuffers in namespace MobileGL
  and FramebufferObject::MAX_DRAW_BUFFERS is defined from it, so the eighty existing
  spellings keep working and the two cannot drift. No other constant is added.
- The four MG_State headers become forwarders (include the value header, keep their
  class definitions); RenderState.cpp gains a direct FramebufferObject.h include
  because it spells FramebufferObject::MAX_DRAW_BUFFERS and only ever got that
  header transitively. No other TU lost a transitive include: the full build
  (Release, clang, tests + integration tests) passed without touching anything
  under MG_Backend, MG_Impl or MG_Util.
- DynamicBackendParameters does NOT move (SizeT members and TextureTarget-taking
  member functions make that a type change, not a move); MGPipeTypes.h keeps its
  BackendObject.h include and the debt comment now says so, which is why gate A
  asserts MGPipeValueTypes.h rather than MGPipeTypes.h.
- New trip wires in the header: trivially-copyable + exact sizeof for
  PixelStoreParameters/PerBufferBlendState/StencilFaceState (28), SamplerParameters
  (100), VertexAttributeVersion (6), RenderStateParameters (1168, standard layout,
  BlendStates before LogicOp, BlendStates sized by kMGMaxDrawBuffers). Their runtime
  twins ValueTypeLayoutsArePinned and the carrier check
  ResidualBlockIsExactlyItsTwoValueStructsPlusPatchTail (Pack at 1168,
  CapabilityBits at 1200) are added to PipeCatalogueTest without a new include.
- gen_pipe.py's "field lists of their own in P0.5" comment now says P1 (the
  comparator needs std::array<struct> support first); PipeVerify.inc regenerated.
- Verified: ctest -L unit 1460/1460 and -L integration-gpu green; ctest -N names
  a superset of feat/disaggregated@6672778b (two added, none lost); one definition
  per moved type; the -H closure of the new header contains no MG_State/MG_Backend/
  MG_Impl/MG_Remote header and the header compiles alone; nm --defined-only -S
  against the base libMobileGL.so: 0 added / 0 removed / 0 resized, .text
  byte-identical, the only differing bytes are the build-id and two stamp strings.
2026-09-05 23:11:00 -04:00
swung0x48 901d48a678 [Fix] (MGPipe, Metrics, Config, CI): exchange the per-frame stats instead of racing a store, carry the three uncarried table entries, drop the inline host span from a buffer range, spell the buffer subdata range, and close the small gate holes
- S-1 PipeStats::OnPresent read each frame accumulator and then store(0)'d it; a Bump from a staging thread landing in between was lost from the Tracy plot and from every frame. Each accumulator is now exchange(0, relaxed) and the exchanged value is what is plotted, so every add lands in exactly one frame.
- T-3 FdPassing without MSG_CMSG_CLOEXEC (macOS, BSD) handed back descriptors that survived exec; every received fd now gets FD_CLOEXEC by hand under !MSG_CMSG_CLOEXEC. MSG_NOSIGNAL is defined to 0 where the platform lacks it (FdPassing.cpp, Doorbell.cpp) and SO_NOSIGPIPE is set on the socketpair and on a SocketDoorbell's descriptor where it exists, so a write to a hung-up peer is EPIPE rather than a fatal signal.
- T-4 the missing-flatbuffers fallback wrote OFF into the cache with FORCE, so a plain re-configure after `git submodule update` stayed OFF silently. It is a normal-variable set now, shadowing the cache for that configure only; verified by hiding flatbuffers.h, configuring with ON (warning, transport off, cache still ON) and re-configuring plainly with the header back (transport ON).
- P-2 three LIVE GLFunctionsTable entries had no carrier: GetGpuTimestampNs (glGetInteger64v(GL_TIMESTAMP), a synchronous server answer), QueryCounterTimestamp (glQueryCounter, a one-shot stamp, not a begin/end pair) and WaitSync (the GPU-side wait FenceWait's client wait does not express). QueryTimestamp (MGPTimestampRequest, kCtxQuery, kReplySlot), QueryCounter (MGPQueryDesc with Kind = GL_TIMESTAMP, kCtxQuery) and FenceWaitServer (MGPFenceWait, kScreen) are APPENDED at the end of PipeCalls.def because the opcode is the position: SetSwapInterval stays 68, the three take 69-71, and PipeCatalogue.LateArrivalsAreAppendedWithoutRenumbering pins that. Header counts 71 (screen 11, query 8); the seven generators regenerated.
- P-3 MGPBufferRange inlined a 32-byte MGHostSpan into every range of every class - dead space on every SSBO, atomic-counter and XFB range, and D-B8 says not to freeze the named-UBO payload before the stage-ubo-named numbers exist. The range is 24 bytes now; the host spans are an optional second var-tail behind the ranges, announced by MGPShaderBuffers::HostSpanCount (0 or Count), with set_shader_buffers keeping its kVarTail|kHostSpan flags. PipeCatalogue.BufferRangeCarriesNoInlineHostSpan pins the sizes, the flags and the comparator's view of the count.
- P-4 QueryEnvUint64 parsed with base 0 (a leading zero meant octal: MOBILEGL_PIPE_PUSH=010 read as 8) and accepted -1 as every bit set; it is decimal or explicit 0x now and a '-' anywhere is refused with the warning (smoke through the integration binary: -1 and 12abc warn, 010 and 0x10 parse). The CI stdio gate's alternation now also catches fprintf(stdout, puts( and std::cout/cerr; it is green over MG_Backend and MG_State. MGPSubData states how the buffer half expresses [offset, size): UnionBox.X / UnionBox.W with Target == Buffer, Y = Z = 0, H = D = 1, one record bounded at a 2^31-1 offset and 2^32-1 size beyond which the emitter splits (the same rule the ring's half-capacity bound already imposes); MGPipeSetSubDataBufferRange / MGPipeSubDataBufferOffset / Size are the only spelling and PipeCatalogue.SubDataBufferRangeRidesInTheUnionBox pins the encoding and its bounds. gen_pipe.py now refuses, in both modes, a call payload named in PipeCalls.def with no field list in PipeFields.def (the four memcmp-fallback member types are the documented exception); shown by dropping P(MGPSwapInterval), which exits 1 naming the payload.
- The MGPPixelPackState size assertion compared sizeof against itself; it asserts the literal 28 PixelStoreParameters measures.
- Verified: ctest -L unit green in both the default and the split configuration, gen_pipe.py --check clean with the generated files committed, nm --defined-only of the default libMobileGL.so has no MG_Remote symbol, and the full integration-gpu suite passes (the *IsActuallyArmedWhenTheEnvironmentPinsItOn family trips under -j 8 as documented and passes serially).
2026-09-05 21:19:00 -04:00
swung0x48 1154f9a00d [Fix] (MG_Remote, Transport): give the inproc doorbell a death state so Shutdown can join a parked waiter, and bound a ring record at half the capacity so a refusal can never look like backpressure
- T-1: InProcessChannel::Close rang each CondVarDoorbell once and claimed that unparks a peer mid-frame. It does not. Doorbell::Wait consumes the one ring, re-tests a condition nothing published, finds the bell alive (CondVarDoorbell never overrode Dead(); Doorbell.cpp had no death state at all) and with kWaitForever parks again for good - so Shutdown could never join a server thread sitting in the design's own steady state (spun, set consumerParked, blocked; plan section 8.1 inheriting the earlier plan's 6.2a). CondVarDoorbell now carries an atomic death latch: Kill() sets it under the mutex and notify_all's, Dead() reports it, Park returns false at once on a dead bell (and the wait predicate includes it, so a Kill cannot slip between the test and the wait), and Close kills both bells instead of ringing them. Same shape as SocketDoorbell's EOF latch; Notify stays the ordinary wakeup.
- T-2: RingProducer::Reserve refused only total > capacity, but a record with capacity/2 < total <= capacity is unplaceable at every head offset where neither the space to the wrap boundary nor the space before it holds it - even in an EMPTY ring, because a wrap pad costs spaceToEnd bytes on top of the record. Concretely: head offset 16 of an empty 256-byte ring, a 248-byte record; FreeBytes() says 256, Reserve says nullptr, forever, and a producer waiting for FreeBytes() >= 248 stalls with nothing logged. The bound is now capacity/2, which is exact rather than conservative (worst case 2*total-8 <= capacity-8), exposed as MaxRecordBytes() for the emitter to chunk against; the minimum ring is two headers so the smallest record still fits the bound. Ring.h states Capacity()/2 as the chunking bound and the G3 header comment in gen_pipe.py now states the chunking rule plan section 8.2 asks G3 to define (PipeWire.inc regenerated).
- Tests, each shown red with only the fix site reverted and green with it: InProcessTransportTest.ShutdownUnparksAWaiterWithNoDeadline (bounded join through a shared_ptr-owned waiter: 5 s red instead of a hung job; reverted it hangs and fails at 5051 ms), RingTest.RecordLargerThanHalfTheRingIsRefused (reverted, the 248-byte record is accepted), RingTest.RecordPlaceabilityDoesNotDependOnTheHeadOffset (the offset-0 vs offset-16 negative control), RingTest.HalfCapacityRecordFitsAtEveryHeadOffset (the positive half: the maximal record at all 32 head offsets of a 256-byte ring) and RingTest.RejectsARingTooSmallForTheSmallestRecord.
2026-09-05 21:19:00 -04:00
swung0x48 9c773182bb [Feat] (MGPipe): land the interface skeleton - the complete call catalogue, the payload PODs and the seven generators
- Plan B section 4 makes the frontend/backend boundary explicit and gives the backend its
  own state machine. This is the P0 deliverable of section 11: the whole catalogue exists
  from day one, placeholders included, because the wire opcode is a call's position in
  PipeCalls.def and record numbering must never churn.
- MG_Pipe/PipeCalls.def is the single source of truth: 68 unique calls as
  X(Name, Payload, Class, Flags). Its header reconciles that number with the plan's
  headline counts (section 4.4, appendix A), which double count - "CSO 15" names
  bind_sampler_states and set_sampler_views that the "set_* 17" list also names, "screen
  14" tabulates the query family that section 4.3 assigns to the context, and the
  "transfer 12" row enumerates 11 calls. Each reconciliation is written down next to the
  count rather than resolved silently.
- MGPipeHandles.h: the 8-byte {slot, gen} pair, dense per-kind slots, the reserved null
  and default-framebuffer handles, and the ShaderCso composite band (sections 4.2, 5.6.3).
  The two generations are documented as strictly separate, with the interface rule that no
  call may require the client to know MGGen.
- MGPipeTypes.h: every payload of section 4.5 as a flat POD with explicit padding, a
  trivial-copyability assertion and an exact sizeof assertion, because the wire records are
  memcpy'd and a field silently changing width is a protocol break no test would see.
  MGPCaps embeds DynamicBackendParameters by inclusion so a caps field added there needs no
  second edit here; its assertion is stated as a composition because that struct still
  carries SizeT. ResidualValueBlock is pinned at MGL_RESIDUAL_BLOCK_SIZE 1248, the ratchet
  that only ever goes down and reaches static_assert(... == 0) in P13 (section 6.3).
- MGPipeHostSpan.h keeps the one shape that changes with the transport isolated behind one
  predictable branch, with the kFromServerIndexMirror sentinel D-B7 needs.
- MGPipeCallbacks.h names the reverse channel as ten callbacks plus the forward terminator
  in the context table, replacing 95 poke sites across 17 methods (section 7.1).
- scripts/gen_pipe.py runs G1-G7 off those .def files. G1 asserts each table is EXACTLY its
  call count of function pointers; G3 pads every wire record to the stream's 8-byte
  granularity and checks size >= sizeof && size <= remaining && size % 8 == 0 before
  dispatch, fatally; G4 compares field by field (padding excluded, floats by bits) because
  a comparator with false positives is one nobody reads - DirectGLES.cpp says the same
  thing about its own memcmp of RenderStateParameters; G5 turns the accessor list into
  per-verb poison generations rather than a written-once bitmap, which is the only version
  that can see a field left over from the previous draw (section 6.2.2); G6 joins the 477
  read points of the vendored backend_read_inventory.md against Coverage.def and reports
  0 UNMAPPED (299 to a call, 167 signatures that become handle parameters, 6 reverse
  channel, 5 client-resolved); G7 pins the pipeline subset BY MEMBER NAME from what
  VulkanRenderer::ComputePipelineStateHash hashes today, computing no offsets in python.
- The generated files are committed so the build never depends on python; CI regenerates
  and diffs them.
2026-09-05 20:16:49 -04:00