- P2 step e2, as far as the file-ownership table lets one package take it. New frontend
header MG_State/GLState/StateObjectDeathNotice.h carries BufferBackendOps' shape for the
other six kinds: an ops table the backend fills in, and one entry point that takes
{kind, lifetimeId} rather than the object, because by the time the last SharedPtr has
dropped there is no object left to pass and the lifetime id is exactly what the client
slot allocator resolves a handle from. Declared only under MOBILEGL_PIPE_PUSH, so the
pull build's symbol set is untouched.
- BackendSlotTable::DestroyByLifetimeId drops the twin and returns the slot at the moment
the object goes, instead of at the next sweep - which for a renderbuffer or a texture
atlas is the difference between freeing the driver allocation now and freeing it 64
creations from now. It returns the slot only when THIS table holds it: two holders of one
kind already exist (the ScopedDirectGLESTextureBindings fixture; Magma's subsystem-4
table shares the VertexElementsCso kind), and a table that never twinned the object must
not free a slot the other one still names. The legacy arm keys on the frontend heap
address, cannot answer a notice at all, and keeps the sweep - which is the announced-
versus-discovered half of the A/B the compile-time arm exists for.
- Managers.cpp registers one dispatcher for all six kinds from ResolveEsprytSlotTablesArm(),
i.e. exactly when the arm that can answer a notice is the arm that runs, and drops a
notice that arrives after exit() has begun.
- FIRING it needs a destructor per class, and the P2 ownership table gives
{Texture,Framebuffer,Sampler,VertexArray}State/* to other packages, so only ProgramObject
and RenderbufferObject raise it here. The other four still rely on the sweep; their four
one-line calls retire it entirely.
- Three cases pin the three halves: the slot comes back with no sweep and the notice is
idempotent and does not free another holder's slot; a program and a renderbuffer announce
their own death when the last SharedPtr drops and not before; and the handle arm actually
installs a consumer, rather than the two halves each being fine on their own.
- RenderbufferObject was the last state object a backend twin registry keys on
that could only be identified by its heap address or its GL name - both of
which recycle. BufferObject, VertexArrayObject and ProgramObject all carry a
process-wide, never-reused id for exactly this; the renderbuffer's absence is
named in plan B §10.4-5 as one of the two latent problems P0 closes.
- Mirrors BufferObject.h:202-208 / BufferObject.cpp:19-24 verbatim in shape: a
private static AllocateLifetimeId() over a namespace-scope
std::atomic<Uint64> starting at 1 (so a zero-initialised memo slot can never
carry a live object's id), a const member initialised from it at construction,
and an inline const getter. The doc comment is the buffer one restated for the
renderbuffer's own recycling sources.
- Deliberately NO GetVersion(): plan B §11 P0 says the id only. A mutation
counter would be a second, independent invalidation surface to keep correct,
and nothing needs one yet - the renderbuffer's mutable content already reaches
the backends through AllocateStorage / SetInternalFormat / SetSamples.
- No caller yet, by design: the id exists so §4.7.3's D1 rekey (and the
DirectGLES renderbuffer twin registry at Managers.h:1858) has something to key
on. It is a pure addition - no existing field, signature or answer changes.
- ObjectLifetimeIdTest gains the two cases the other two object types already
have, so the renderbuffer is covered by the same allocator-reuse probe: an
object rebuilt at a freed address must not answer to the dead one's id, and
two live ones must differ.
- Tested: cmake --build build-linux -j 24 (clean); ctest -R ObjectLifetimeId ->
6/6 passed (4 pre-existing + 2 new).