mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-12 20:43:23 +09:00
CONTRACT-P7 §5.5. DirectVulkan had no StateObjectDeathOps at all, so under a Magma
inproc session NotifyStateObjectDestroyed reached nobody and the server was never told
that a frontend object had died. Package L measured the shape: with the WireTables
install condition made backend-agnostic, CtWireScenario's two death cases PASS on Magma
spawn and tcp - the remote client has no local backend, so the client-side emitter
installed there answers the notice - and FAIL on inproc, where the install condition
deliberately stands aside for "the backend's own dispatcher" and Magma had none.
g_magmaStateObjectDeathOps is that dispatcher. It mirrors g_glesStateObjectDeathOps
(DirectGLES/Managers.cpp:335) with ONE arm rather than two: Espryt's other arm destroys
a backend twin keyed by lifetime id, and Magma keeps no such twin - its two
client-minted kinds are keyed on {slot, gen} by MagmaPipeIdentityTables and the other
six are still reached from their frontend objects - so what is left is exactly the emit
of object_death off the apply thread. Installed from
BackendObject_DirectVulkan::Initialize(), i.e. ServerLoop::CreateBackend, step 1 of
InitServerRoleCommon: the one place BOTH Magma server roles pass through, and before any
client session exists. #if MOBILEGL_BUILD_DISAGGREGATED && MOBILEGL_PIPE_PUSH, so the
pull build compiles nothing new.
The two skip guards in CtWireScenario go with it. Every other "Magma installs none" site
the audit found is comment text whose ARGUMENT survives, and each is reworded rather than
deleted: the table emits the wire record and frees no client slot, so P4a's
backend-neutral NotifyAndFree is still the only thing that returns one, HandleRecycle's
C-1 case still measures the only protection the allocator has, and MagmaPipeArms' age
sweep is still the only thing that retires an identity slot.
The framebuffer death case now reads the client's allocator beside the server's tally.
That correction matters: the plan called this a slot leak provable with PipeSlotPeek, and
it is not - it has been backend-neutral since P4a. What leaked was the SERVER's twin, and
a framebuffer has no wire delete opcode at all, so object_death is its only delivery.
Two installers cannot race although the global is last-writer-wins with no stacking:
Magma's runs only in a process that owns a DirectVulkan backend, and
InstallClientWireTables' arm runs only under Transport == Spawn, which is by construction
the process that owns none.
RED-ONCE: `if (false) InstallStateObjectDeathOps();` reds the two Split cases alone
(`Expected: (afterCounters.deaths) > (deathsBefore), actual: 0 vs 0`, CtWireScenario.cpp:264)
and leaves spawn and tcp green - the exact split of responsibility the comment claims.
Restored: 8/8. Recorded in docs/Disaggregated/notes/p7/magma-c.md.
113 lines
7.2 KiB
C++
113 lines
7.2 KiB
C++
// MobileGL - MobileGL/MG_IntegrationTest/Harness/PipeSlotPeek.h
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
//
|
|
// The CLIENT slot allocator's occupancy, read from a scenario.
|
|
//
|
|
// It exists for one assertion, P3a's C-1: a frontend object that dies must return its
|
|
// MGPipeHandle slot WHATEVER BACKEND IS RUNNING. That question has no answer in the GL API -
|
|
// the leak it rules out is entirely inside the library, and it is invisible in pixels, in GL
|
|
// names and in `glGetError` - so the only honest observable is the allocator's own live count
|
|
// and high-water mark. Reading them is what makes the case fail on the backend it actually
|
|
// failed on (DirectVulkan, which at P3a installed no StateObjectDeathOps) rather than only on
|
|
// the one where a backend-owned free happened to exist.
|
|
//
|
|
// P7 wave 2 package C: DirectVulkan now installs a table of its own (CONTRACT-P7 §5.5), and
|
|
// the sentence above is history rather than the current state - but the reason this header
|
|
// exists is UNCHANGED and is worth spelling out, because the obvious reading of that news is
|
|
// wrong. Magma's table does not free a client slot and never could: the free is P4a's, it
|
|
// runs from the frontend object's own destructor on the client thread whatever backend is
|
|
// live (MG_Impl/Pipe/PipeFill.cpp's NotifyAndFree), and Magma's table is the EMIT arm that
|
|
// tells the SERVER to drop its twin. So the allocator is still the only observable of the
|
|
// client half, and a case that reads `deaths` alone would be blind to a slot that never came
|
|
// back. The two readings belong together, which is what CtWireScenario's framebuffer case now
|
|
// does.
|
|
//
|
|
// A separate translation unit for BackendCapsPeek.h's reason, verbatim: the scenario sources
|
|
// include the GL headers with prototypes and MobileGL's umbrella header is not meant to meet
|
|
// them in one file.
|
|
|
|
#pragma once
|
|
|
|
namespace MGITest {
|
|
|
|
// Which client-side object kind to ask about. Mirrors MG_Pipe::MGPipeKind for exactly the
|
|
// kinds a scenario has a reason to count, so that the enum does not travel through this
|
|
// header and the GL headers together.
|
|
enum class PipeSlotKind {
|
|
Buffer,
|
|
VertexElementsCso,
|
|
// P4a's six (G8b). Every one of them is a kind the CLIENT mints and the client alone
|
|
// frees (BRIEF-P4A.md D-I1: one death helper per kind, called from the frontend
|
|
// object's own destructor, whatever backend is running), so every one of them can leak
|
|
// the P3a C-1 way - and the leak is invisible in pixels, in GL names and in
|
|
// glGetError, exactly as the VertexElementsCso one was.
|
|
Texture,
|
|
Renderbuffer,
|
|
// Framebuffer has a HANDLE but no wire lifetime (D-I2): no create_*, no destroy row in
|
|
// the catalogue, and its death helper does the notice and the free and emits nothing.
|
|
// That makes the allocator the ONLY observable of its lifetime, so this row matters
|
|
// more here than the others rather than less.
|
|
Framebuffer,
|
|
SamplerCso,
|
|
SamplerViewCso,
|
|
// ShaderCso covers BOTH the ordinary program slots and the program-pipeline COMPOSITES
|
|
// minted out of the reserved high band (MGPipeHandles.h:86-107, D-H7). One kind, because
|
|
// that is what the allocator has: the band is a second dense table inside the same kind
|
|
// and LiveCount counts both.
|
|
//
|
|
// THE TWO SPACES' HIGH-WATER MARKS ARE NOT ONE NUMBER, and the correction matters here
|
|
// more than anywhere else. c0b split them (contract-v2.md 4.3): HighWater(ShaderCso) is
|
|
// now the ORDINARY space only and the band's own mark is CompositeHighWater(), because
|
|
// a merged mark is pinned at ~983k from the first composite mint onward and every "the
|
|
// high-water mark did not move over N churn rounds" assertion about ordinary programs
|
|
// would be vacuously true for the rest of the process. The composite's leak case is a
|
|
// separate CASE and reads the BAND'S OWN counters below (PeekPipeCompositeSlot*) - a
|
|
// composite's slot has TWO independent release paths (the pipeline cache's LRU eviction
|
|
// and the composite ProgramObject's destructor), and a slot that never comes back to
|
|
// the band moves neither of the ordinary numbers.
|
|
ShaderCso,
|
|
};
|
|
|
|
// Live slots of this kind right now, and one past the highest slot ever handed out.
|
|
// Both return false, touching nothing, where the allocator is out of reach: in a PULL
|
|
// build there is no allocator at all (it is `#if MOBILEGL_PIPE_PUSH`), and on Android this
|
|
// module links the shipping libMobileGL.so built -fvisibility=hidden, so no internal symbol
|
|
// resolves. A caller that gets false must SKIP rather than pass - "could not look" is not
|
|
// "did not leak".
|
|
bool PeekPipeSlotLiveCount(PipeSlotKind kind, unsigned* outLive);
|
|
bool PeekPipeSlotHighWater(PipeSlotKind kind, unsigned* outHighWater);
|
|
|
|
// The ShaderCso COMPOSITE BAND's own three numbers, the seventh..ninth members
|
|
// contract-v2.md 4.3 asks this header for. There is no `kind` argument because the band is
|
|
// ShaderCso's alone - AllocateComposite is the one door into it and no other kind has one.
|
|
// All three return false on the same terms as the two above, and a caller that gets false
|
|
// must SKIP.
|
|
//
|
|
// PeekPipeCompositeSlotLiveCount = MGPipeSlotAllocator::CompositeLiveCount(), the band's
|
|
// share of LiveCount(ShaderCso).
|
|
// PeekPipeCompositeSlotHighWater = CompositeHighWater() VERBATIM, i.e. one past the
|
|
// highest band slot ever handed out. It is an ABSOLUTE
|
|
// slot number and therefore starts at the band's base,
|
|
// not at zero - "no composite was ever minted" reads as
|
|
// `high water == band base`, which is what the third
|
|
// member is for. It is not returned base-relative
|
|
// because a peek whose name says HighWater and whose
|
|
// value is a delta is exactly the kind of quietly
|
|
// redefined counter this member exists to correct.
|
|
// PeekPipeCompositeSlotBandBase = kMGPipeShaderCsoCompositeSlotBase, the floor the
|
|
// other two are read against. A constant, but it
|
|
// reaches a scenario only through this header: the
|
|
// MG_Pipe headers and the GL headers are not meant to
|
|
// meet in one translation unit, which is why this
|
|
// harness exists at all.
|
|
bool PeekPipeCompositeSlotLiveCount(unsigned* outLive);
|
|
bool PeekPipeCompositeSlotHighWater(unsigned* outHighWater);
|
|
bool PeekPipeCompositeSlotBandBase(unsigned* outBandBase);
|
|
|
|
} // namespace MGITest
|