[Feature] (P14): state ownership - multi-session, multi-context, share groups

Breaks the one process = one server = one session = one context equation
that the whole stack's correctness used to rest on. Design and per-slice
shape: docs/Disaggregated/design/11-state-ownership.md.

S1 wire: bind_context ring record (opcode 84) announces the current
context at the make-current edge; CreateContext/DestroyContext join the
SurfaceOp control frames; control revision 5 -> 6; the server keeps a
per-session token table and refuses unknown tokens by name.

S2 server: SessionRuntime per session with thread-scoped resolution
(singletons become fallbacks), per-thread gPipeInputs, per-session latch
domains, per-thread segment resolvers; the in-process unix supervisor
serves up to MOBILEGL_IPC_INPROC_MAX_SESSIONS (16) concurrent sessions;
surface mode is declared per session and the display lease refuses a
second on-screen session by name.

S3 frontend: ShareGroupState owns the shared object stores and name
generators (buffer/texture/renderbuffer/sampler/program), GLContext keeps
per-context bindings/errors/container objects; pGLContext becomes
thread-local with a process default context preserving the single-context
world verbatim; eglCreateContext builds a real GLContext and joins the
sharing context's group.

S4 backend: native EGL tuples keyed by (session, context token);
InitDisplayAndContext's unconditional teardown is gone - creating a
second context or surface no longer destroys the first; eglCreateContext
sharing is wired to the native driver; bind_context is the native switch
point.

S5 applier: MGPipeApplierState splits into MGPipeObjectRecords (per
share group) and MGPipeWorkingState (per context), resolved through a
{session, token} registry with a thread-local cached fast path.

Tests: MultiSessionTest (incl. two sessions x two contexts interleaved
with real clear/readback witnesses), ApplierOwnershipTest (with a
null-resolver negative control), ContextStateOwnershipTest,
ServerLoopEglTest native-context liveness; 99/99 of the acceptance set
green on the host build.

Known debts (documented in the design doc): twin registries still keyed
by handle alone, no per-record ContextSlot, process-global backend
generations, MakeCurrent dedup per current binding.
This commit is contained in:
2026-10-01 19:16:41 -04:00
parent ebc552a048
commit 4ed1c9cef2
80 changed files with 4677 additions and 949 deletions
+349 -186
View File
@@ -477,11 +477,173 @@ namespace MobileGL::MG_Pipe {
static_assert(kCapabilityCount <= 64,
"ResidualValueBlock::CapabilityBits is a Uint64; 35 bits fit, 65 would not");
// ============================================================================
// P14 S5: THE APPLIER REGISTRY.
// ============================================================================
//
// A REFERENCE TO A NEVER-DESTROYED BLOCK, for MGPipeSlots()' reason
// (MG_Impl/Pipe/SlotAllocator.cpp): resource_destroy and delete_vertex_elements are
// raised from ~BufferObject / ~VertexArrayObject, and those objects are released by
// exit handlers that run after this translation unit's own globals are gone.
MGPipeApplierState& g_applier = *new MGPipeApplierState{};
//
// THE DEFAULT IS THE OLD SINGLETON, unrenamed in what it means: the applier of a thread
// that belongs to nobody - the client process, the monolith, the inproc one-process
// shape, every unit case. Its object records are the group it owns alone.
MGPipeObjectRecords& g_defaultObjects = *new MGPipeObjectRecords{};
MGPipeApplierState& g_defaultApplier = *new MGPipeApplierState(g_defaultObjects);
// Installed once by the session registry (MG_Remote/Server/SessionRuntime.cpp), never
// replaced. Null in every process that serves no session, which is what keeps the
// monolith's `MGPipeApplier()` a load and a branch.
MGPipeApplierKeyResolver g_applierKeyResolver = nullptr;
// One served session's appliers. Groups own object records; Contexts own working state
// and point at their group. Both are held through UniquePtr so a table's ADDRESS is
// stable across a rehash - MGPipeApplierState binds its group by reference - and the
// maps are std::unordered_map rather than the house UnorderedMap (a flat hash map) for
// that same reason at the map level: a reference into it may not be invalidated by an
// insert somewhere else in the same call.
struct ApplierSession {
std::unordered_map<Uint64, UniquePtr<MGPipeObjectRecords>> Groups;
std::unordered_map<Uint64, UniquePtr<MGPipeApplierState>> Contexts;
std::unordered_map<Uint64, Uint64> ContextGroup;
std::unordered_map<Uint64, Uint32> GroupContexts;
};
std::unordered_map<Uint64, ApplierSession> g_applierSessions;
std::mutex g_applierSessionsMutex;
// Bumped by EVERY registry mutation. A thread's cached applier pointer is only trusted
// while it was bound at this generation, which is what makes "a context was destroyed on
// another thread" unable to leave a dangling cache behind.
std::atomic<Uint64> g_applierRegistryGeneration{1};
// A context that started its own share group is keyed by its own token, in a namespace
// of its own: the frontend mints share-group tokens and context tokens from separate
// counters, so without this a group 7 and a context 7 would be one group.
constexpr Uint64 kMGPipeOwnShareGroupBit = Uint64{1} << 63;
// `shareGroupToken` 0 means "this context started its own group" - the sentinel S1's
// ContextRuntime documents and the one a caller that frames a context by hand (a test,
// the in-process server helpers) leaves behind.
Uint64 ApplierShareGroupKey(Uint64 contextToken, Uint64 shareGroupToken) {
return shareGroupToken != 0 ? shareGroupToken : (contextToken | kMGPipeOwnShareGroupBit);
}
// The three tables of the applier this thread's records are being applied through.
// `generation` is the registry's, read here so a caller can bind a pointer under one
// generation and publish it under the same one.
MGPipeApplierState* FindApplierForKey(const MGPipeApplierKey& key) {
std::lock_guard<std::mutex> guard(g_applierSessionsMutex);
ApplierSession& session = g_applierSessions[key.SessionKey];
const auto found = session.Contexts.find(key.ContextToken);
if (found != session.Contexts.end() && found->second) return found->second.get();
// A context the session never registered - a token that predates the first
// CreateContext, or the 0 of a session that has not bound one yet. It gets the same
// shape as any other: a working state in a group of its own.
const Uint64 groupKey = ApplierShareGroupKey(key.ContextToken, 0);
auto group = session.Groups.find(groupKey);
if (group == session.Groups.end()) {
group = session.Groups.emplace(groupKey, MakeUnique<MGPipeObjectRecords>()).first;
}
auto applier = MakeUnique<MGPipeApplierState>(*group->second);
MGPipeApplierState* const raw = applier.get();
session.Contexts.emplace(key.ContextToken, std::move(applier));
session.ContextGroup.emplace(key.ContextToken, groupKey);
++session.GroupContexts[groupKey];
return raw;
}
thread_local MGPipeApplierState* t_applierCached = nullptr;
thread_local Uint64 t_applierCachedSession = 0;
thread_local Uint64 t_applierCachedContext = 0;
thread_local Uint64 t_applierCachedGeneration = 0;
} // namespace
MGPipeApplierState& MGPipeApplier() {
const MGPipeApplierKeyResolver resolve = g_applierKeyResolver;
if (resolve == nullptr) return g_defaultApplier;
MGPipeApplierKey key;
if (!resolve(&key)) return g_defaultApplier;
// THE FAST PATH, and the reason this is a cache rather than a lookup: this function is
// called from the backend per state read. A hit is three TLS loads, one atomic load and
// two compares; a miss is the registry, which only moves when a context is created,
// destroyed or made current.
const Uint64 generation = g_applierRegistryGeneration.load(std::memory_order_acquire);
if (t_applierCached != nullptr && t_applierCachedGeneration == generation &&
t_applierCachedSession == key.SessionKey && t_applierCachedContext == key.ContextToken) {
return *t_applierCached;
}
MGPipeApplierState* const applier = FindApplierForKey(key);
t_applierCached = applier;
t_applierCachedSession = key.SessionKey;
t_applierCachedContext = key.ContextToken;
t_applierCachedGeneration = generation;
return *applier;
}
void MGPipeSetApplierKeyResolver(MGPipeApplierKeyResolver resolver) { g_applierKeyResolver = resolver; }
void MGPipeApplierRegisterContext(Uint64 sessionKey, Uint64 contextToken, Uint64 shareGroupToken) {
std::lock_guard<std::mutex> guard(g_applierSessionsMutex);
ApplierSession& session = g_applierSessions[sessionKey];
const Uint64 groupKey = ApplierShareGroupKey(contextToken, shareGroupToken);
auto group = session.Groups.find(groupKey);
if (group == session.Groups.end()) {
group = session.Groups.emplace(groupKey, MakeUnique<MGPipeObjectRecords>()).first;
}
const auto existing = session.Contexts.find(contextToken);
if (existing != session.Contexts.end() && existing->second) {
// Idempotent: the same token registered twice keeps the state it already has. The
// session refuses a duplicate CreateContext before reaching here, so this arm is a
// caller that re-registers deliberately.
g_applierRegistryGeneration.fetch_add(1, std::memory_order_acq_rel);
return;
}
session.Contexts.emplace(contextToken, MakeUnique<MGPipeApplierState>(*group->second));
session.ContextGroup[contextToken] = groupKey;
++session.GroupContexts[groupKey];
g_applierRegistryGeneration.fetch_add(1, std::memory_order_acq_rel);
}
void MGPipeApplierUnregisterContext(Uint64 sessionKey, Uint64 contextToken) {
std::lock_guard<std::mutex> guard(g_applierSessionsMutex);
const auto sessionIt = g_applierSessions.find(sessionKey);
if (sessionIt == g_applierSessions.end()) return;
ApplierSession& session = sessionIt->second;
const auto context = session.Contexts.find(contextToken);
if (context == session.Contexts.end()) return;
const auto groupIt = session.ContextGroup.find(contextToken);
const Uint64 groupKey = groupIt != session.ContextGroup.end() ? groupIt->second : 0;
// The OBJECT RECORDS GO WITH THE LAST CONTEXT OF THE GROUP, which is the per-group
// spelling of MGPipeApplierReleaseObjectRecords' scope. A surviving context of the same
// group keeps every record - that is what sharing means.
const auto refs = session.GroupContexts.find(groupKey);
const Bool lastOfGroup = refs != session.GroupContexts.end() && refs->second <= 1;
session.Contexts.erase(context);
if (groupIt != session.ContextGroup.end()) session.ContextGroup.erase(groupIt);
if (lastOfGroup) {
if (refs != session.GroupContexts.end()) session.GroupContexts.erase(refs);
const auto group = session.Groups.find(groupKey);
if (group != session.Groups.end()) session.Groups.erase(group);
} else if (refs != session.GroupContexts.end()) {
--refs->second;
}
if (session.Contexts.empty()) g_applierSessions.erase(sessionIt);
g_applierRegistryGeneration.fetch_add(1, std::memory_order_acq_rel);
}
void MGPipeApplierReleaseSession(Uint64 sessionKey) {
std::lock_guard<std::mutex> guard(g_applierSessionsMutex);
if (g_applierSessions.erase(sessionKey) == 0) return;
g_applierRegistryGeneration.fetch_add(1, std::memory_order_acq_rel);
}
Uint64 MGPipeApplierShareGroupCountForTesting(Uint64 sessionKey) {
std::lock_guard<std::mutex> guard(g_applierSessionsMutex);
const auto sessionIt = g_applierSessions.find(sessionKey);
return sessionIt == g_applierSessions.end() ? 0 : static_cast<Uint64>(sessionIt->second.Groups.size());
}
namespace {
// The installed handle-shaped resource table. Null until a backend registers one,
// which is what makes the client half landable on its own: with nothing here every
@@ -490,8 +652,8 @@ namespace MobileGL::MG_Pipe {
const MGPipeResourceOps* g_resourceOps = nullptr;
MGPipeRenderStateCsoRecord* FindCso(MGPipeHandle handle) {
if (handle.Slot >= g_applier.RenderStateCsos.size()) return nullptr;
MGPipeRenderStateCsoRecord& record = g_applier.RenderStateCsos[handle.Slot];
if (handle.Slot >= MGPipeApplier().RenderStateCsos.size()) return nullptr;
MGPipeRenderStateCsoRecord& record = MGPipeApplier().RenderStateCsos[handle.Slot];
if (!record.Live || record.Gen != handle.Gen) return nullptr;
return &record;
}
@@ -527,15 +689,15 @@ namespace MobileGL::MG_Pipe {
// that is not live, or a handle whose generation has moved on because the slot was
// recycled under it. FindCso above is the same three questions for the CSO store.
MGPipeResourceRecord* FindResource(MGPipeHandle res) {
if (res.Slot >= g_applier.Resources.size()) return nullptr;
MGPipeResourceRecord& record = g_applier.Resources[res.Slot];
if (res.Slot >= MGPipeApplier().Resources.size()) return nullptr;
MGPipeResourceRecord& record = MGPipeApplier().Resources[res.Slot];
if (!record.Live || record.Gen != res.Gen) return nullptr;
return &record;
}
MGPipeVertexElementsRecord* FindVertexElements(MGPipeHandle cso) {
if (cso.Slot >= g_applier.VertexElementsCsos.size()) return nullptr;
MGPipeVertexElementsRecord& record = g_applier.VertexElementsCsos[cso.Slot];
if (cso.Slot >= MGPipeApplier().VertexElementsCsos.size()) return nullptr;
MGPipeVertexElementsRecord& record = MGPipeApplier().VertexElementsCsos[cso.Slot];
if (!record.Live || record.Gen != cso.Gen) return nullptr;
return &record;
}
@@ -562,15 +724,15 @@ namespace MobileGL::MG_Pipe {
// respecify or destroy an unrelated LIVE object that happens to hold the same slot in
// another kind's space, and that is the "act outside its own storage" class.
Vector<MGPipeResourceRecord>* ResourceTableForTarget(Uint8 target) {
if (target == kMGPipeResourceTargetBuffer) return &g_applier.Resources;
if (target == kMGPipeResourceTargetBuffer) return &MGPipeApplier().Resources;
if (target == static_cast<Uint8>(MGPipeResourceTarget::Renderbuffer)) {
return &g_applier.RenderbufferResources;
return &MGPipeApplier().RenderbufferResources;
}
if (target < static_cast<Uint8>(MGPipeResourceTarget::Count)) {
// Every remaining enumerator is a texture target, and they share one table
// because they share one kind: MGPipeKind::Texture. Tex1D..TexCubeArray,
// Tex2DMS/MSArray, TexBuffer and TexRect are all one slot space.
return &g_applier.TextureResources;
return &MGPipeApplier().TextureResources;
}
return nullptr;
}
@@ -579,11 +741,11 @@ namespace MobileGL::MG_Pipe {
Vector<MGPipeResourceRecord>* ResourceTableForKind(Uint32 kind) {
switch (static_cast<MGPipeKind>(kind)) {
case MGPipeKind::Buffer:
return &g_applier.Resources;
return &MGPipeApplier().Resources;
case MGPipeKind::Texture:
return &g_applier.TextureResources;
return &MGPipeApplier().TextureResources;
case MGPipeKind::Renderbuffer:
return &g_applier.RenderbufferResources;
return &MGPipeApplier().RenderbufferResources;
default:
return nullptr;
}
@@ -710,7 +872,7 @@ namespace MobileGL::MG_Pipe {
MGPipeResourceRecord* record = FindResource(res);
MOBILEGL_ASSERT(record != nullptr, "%s named {slot=%u, gen=%u}: %s", call, res.Slot, res.Gen,
kResourceRefusalNote);
if (record == nullptr) ++g_applier.RefusedResourceCalls;
if (record == nullptr) ++MGPipeApplier().RefusedResourceCalls;
return record;
}
@@ -723,7 +885,7 @@ namespace MobileGL::MG_Pipe {
MGPipeResourceRecord* record = FindIn(table, res);
MOBILEGL_ASSERT(record != nullptr, "%s named {slot=%u, gen=%u}: %s", call, res.Slot, res.Gen,
kResourceRefusalNote);
if (record == nullptr) ++g_applier.RefusedResourceCalls;
if (record == nullptr) ++MGPipeApplier().RefusedResourceCalls;
return record;
}
@@ -736,7 +898,7 @@ namespace MobileGL::MG_Pipe {
Record* record = FindIn(table, handle);
MOBILEGL_ASSERT(record != nullptr, "%s named {slot=%u, gen=%u}: %s", call, handle.Slot,
handle.Gen, kResourceRefusalNote);
if (record == nullptr) ++g_applier.RefusedObjectCalls;
if (record == nullptr) ++MGPipeApplier().RefusedObjectCalls;
return record;
}
@@ -744,7 +906,7 @@ namespace MobileGL::MG_Pipe {
MGPipeVertexElementsRecord* record = FindVertexElements(cso);
MOBILEGL_ASSERT(record != nullptr, "%s named {slot=%u, gen=%u}: %s", call, cso.Slot, cso.Gen,
kResourceRefusalNote);
if (record == nullptr) ++g_applier.RefusedVertexInputCalls;
if (record == nullptr) ++MGPipeApplier().RefusedVertexInputCalls;
return record;
}
@@ -1198,7 +1360,7 @@ namespace MobileGL::MG_Pipe {
// hook; the monolith shape of everything above them is unchanged.
Bool ApplyTextureUpload(const MGPSubData& record, const void* bytes, const MGPSubRegion* regions) {
MGPipeResourceRecord* stored =
ResolveResourceIn(g_applier.TextureResources, "resource_subdata", record.Res);
ResolveResourceIn(MGPipeApplier().TextureResources, "resource_subdata", record.Res);
if (stored == nullptr) return false;
const char* fault = SubDataTextureFault(record, regions);
@@ -1319,28 +1481,28 @@ namespace MobileGL::MG_Pipe {
// ----------------------------------------------------------------------------
MGPipeShaderCsoRecord* ShaderCsoRecordAt(Uint32 slot) {
if (MGPipeIsCompositeShaderSlot(slot)) {
return RecordAt(g_applier.CompositeShaderCsos, slot - kMGPipeShaderCsoCompositeSlotBase,
return RecordAt(MGPipeApplier().CompositeShaderCsos, slot - kMGPipeShaderCsoCompositeSlotBase,
kMGPipeShaderCsoSlotLimit - kMGPipeShaderCsoCompositeSlotBase);
}
// The ordinary table is bounded by the BAND'S BASE and not by the slot limit: an
// ordinary program can never be handed a band slot (the allocator refuses it), so a
// slot at or above the base that is not a composite is out of range by definition.
return RecordAt(g_applier.ShaderCsos, slot, kMGPipeShaderCsoCompositeSlotBase);
return RecordAt(MGPipeApplier().ShaderCsos, slot, kMGPipeShaderCsoCompositeSlotBase);
}
MGPipeShaderCsoRecord* FindShaderCso(MGPipeHandle cso) {
if (MGPipeIsCompositeShaderSlot(cso.Slot)) {
return FindIn(g_applier.CompositeShaderCsos,
return FindIn(MGPipeApplier().CompositeShaderCsos,
MGPipeHandle{cso.Slot - kMGPipeShaderCsoCompositeSlotBase, cso.Gen});
}
return FindIn(g_applier.ShaderCsos, cso);
return FindIn(MGPipeApplier().ShaderCsos, cso);
}
MGPipeShaderCsoRecord* ResolveShaderCso(const char* call, MGPipeHandle cso) {
MGPipeShaderCsoRecord* record = FindShaderCso(cso);
MOBILEGL_ASSERT(record != nullptr, "%s named {slot=%u, gen=%u}: %s", call, cso.Slot, cso.Gen,
kResourceRefusalNote);
if (record == nullptr) ++g_applier.RefusedObjectCalls;
if (record == nullptr) ++MGPipeApplier().RefusedObjectCalls;
return record;
}
@@ -1388,13 +1550,14 @@ namespace MobileGL::MG_Pipe {
#endif
} // namespace
MGPipeApplierState& MGPipeApplier() { return g_applier; }
// MGPipeApplier() is defined with the registry at the top of this file: it is the one
// function that has to know whose applier the calling thread is in.
// P5c (rv, CONTRACT-P5C.md §5.3): the three texture shutters' server-side answers. See
// MGPipeApplierState::TextureShutterSerial / ContextSerial for the bump rule.
Uint64 MGPipeApplierTextureShutterSerial() { return g_applier.TextureShutterSerial; }
Uint64 MGPipeApplierContextSerial() { return g_applier.ContextSerial; }
void MGPipeApplierNoteTextureStateMoved() { ++g_applier.TextureShutterSerial; }
Uint64 MGPipeApplierTextureShutterSerial() { return MGPipeApplier().TextureShutterSerial; }
Uint64 MGPipeApplierContextSerial() { return MGPipeApplier().ContextSerial; }
void MGPipeApplierNoteTextureStateMoved() { ++MGPipeApplier().TextureShutterSerial; }
void MGPipeSetResourceOps(const MGPipeResourceOps* ops) { g_resourceOps = ops; }
const MGPipeResourceOps* MGPipeGetResourceOps() { return g_resourceOps; }
@@ -1427,7 +1590,7 @@ namespace MobileGL::MG_Pipe {
}
#endif
if (g_resourceOps != nullptr) return false;
++g_applier.RefusedNoConsumer;
++MGPipeApplier().RefusedNoConsumer;
return true;
}
@@ -1438,11 +1601,11 @@ namespace MobileGL::MG_Pipe {
// zero" rule is written out.
void ClearShaderBufferWindows() {
for (Uint32 cls = 0; cls < kMGPipeShaderBufferClassCount; ++cls) {
g_applier.BoundShaderBuffers[cls] = {};
g_applier.ShaderBufferStart[cls] = 0;
g_applier.ShaderBufferCount[cls] = 0;
MGPipeApplier().BoundShaderBuffers[cls] = {};
MGPipeApplier().ShaderBufferStart[cls] = 0;
MGPipeApplier().ShaderBufferCount[cls] = 0;
for (Uint32 w = 0; w < kMGPipeShaderBufferWritableMaskWords; ++w) {
g_applier.ShaderBufferWritableMask[cls][w] = 0;
MGPipeApplier().ShaderBufferWritableMask[cls][w] = 0;
}
}
}
@@ -1480,15 +1643,15 @@ namespace MobileGL::MG_Pipe {
std::abort();
}
#endif
g_applier.RenderStateCsos.clear();
g_applier.BoundRenderStateCso = kMGPipeNullHandle;
g_applier.Residual = ResidualValueBlock{};
g_applier.HasResidual = false;
g_applier.ScatteredChunkBits = 0;
g_applier.ResidualCapabilitiesCompared = 0;
g_applier.ResidualDivergences = 0;
g_applier.PatchCarrierComparisons = 0;
g_applier.PatchCarrierDivergences = 0;
MGPipeApplier().RenderStateCsos.clear();
MGPipeApplier().BoundRenderStateCso = kMGPipeNullHandle;
MGPipeApplier().Residual = ResidualValueBlock{};
MGPipeApplier().HasResidual = false;
MGPipeApplier().ScatteredChunkBits = 0;
MGPipeApplier().ResidualCapabilitiesCompared = 0;
MGPipeApplier().ResidualDivergences = 0;
MGPipeApplier().PatchCarrierComparisons = 0;
MGPipeApplier().PatchCarrierDivergences = 0;
// P3a. THIS RUNS AT EVERY CHANGE OF THE CURRENT CONTEXT, not once per fresh one:
// MGPipeTracker::Update resets itself whenever the context pointer moves and the
// emitter calls this from the walk that follows, so a make-current BACK to a context
@@ -1505,16 +1668,16 @@ namespace MobileGL::MG_Pipe {
//
// The OP TABLE is deliberately not cleared either - it is installed and uninstalled by
// the backend's own bring-up and teardown, not by a state reset.
g_applier.RefusedResourceCalls = 0;
g_applier.RefusedVertexInputCalls = 0;
g_applier.RefusedObjectCalls = 0;
g_applier.RefusedNoConsumer = 0;
g_applier.BoundVertexElements = kMGPipeNullHandle;
g_applier.VertexBuffers = {};
g_applier.VertexBufferStart = 0;
g_applier.VertexBufferCount = 0;
g_applier.VertexFetchBaseInstance = 0;
g_applier.IndexBuffer = MGPIndexBuffer{};
MGPipeApplier().RefusedResourceCalls = 0;
MGPipeApplier().RefusedVertexInputCalls = 0;
MGPipeApplier().RefusedObjectCalls = 0;
MGPipeApplier().RefusedNoConsumer = 0;
MGPipeApplier().BoundVertexElements = kMGPipeNullHandle;
MGPipeApplier().VertexBuffers = {};
MGPipeApplier().VertexBufferStart = 0;
MGPipeApplier().VertexBufferCount = 0;
MGPipeApplier().VertexFetchBaseInstance = 0;
MGPipeApplier().IndexBuffer = MGPIndexBuffer{};
// m6 / wire n6, written down rather than left to be rediscovered: THIS counter is
// per-applier and is zeroed at every make-current, while MG_Util::PipeStats' `mpr` -
// emitted from the CLIENT at MG_Impl/Pipe/PipeFill.cpp's MGPipeEmitMapPersistent - is
@@ -1529,7 +1692,7 @@ namespace MobileGL::MG_Pipe {
// MG_IntegrationTest/Harness/PipeStatsWindow.h - they cannot link this symbol at all, on
// Android or anywhere else - so a make-current inside a scenario cannot silently reset
// what they measure. Nothing outside MG_Test reads the member below.
g_applier.MapPersistentRoundtrips = 0;
MGPipeApplier().MapPersistentRoundtrips = 0;
// THE TWO SERIALS ADVANCE; THEY ARE NOT ZEROED. They are MGGens, and an MGGen that
// walks backwards is not one. There are exactly three things a reset can do to a
// version whose data it has just cleared:
@@ -1543,15 +1706,15 @@ namespace MobileGL::MG_Pipe {
// count is stable lands on a stamped value every time;
// - advance: the clearing is itself announced, no stamped value can ever recur, and
// the first compare after the switch is a mismatch, which is the safe direction.
++g_applier.VertexBuffersSerial;
++g_applier.IndexBufferSerial;
++MGPipeApplier().VertexBuffersSerial;
++MGPipeApplier().IndexBufferSerial;
// P5c (rv): a make-current is a fresh server, and the two shutter serials are what the
// PipeInputs texture-shutter accessors answer with - so both ADVANCE here, for the
// serials' own reason: a counter that restarts walks back through values already
// stamped into a memo that outlived the switch. ContextSerial's move is also
// GetTextureContextId's whole job (the backends key their per-context memos on it).
++g_applier.TextureShutterSerial;
++g_applier.ContextSerial;
++MGPipeApplier().TextureShutterSerial;
++MGPipeApplier().ContextSerial;
// ---- P4a's working state, cleared for the same reason and with the same serial rule
// (D-J4). The OBJECT records - texture and renderbuffer resources, sampler CSOs,
@@ -1566,21 +1729,21 @@ namespace MobileGL::MG_Pipe {
// a null Fbo. FramebufferRecords stays because a framebuffer that is only ever addressed
// BY NAME - the DSA blit and clear entry points - has no re-emission trigger at all, so
// dropping its record here would lose it for the life of the process.
g_applier.BoundFramebuffer = {};
g_applier.BoundSamplerViews = {};
g_applier.SamplerViewStart = 0;
g_applier.SamplerViewCount = 0;
g_applier.BoundSamplerStates = {};
g_applier.SamplerStateStart = 0;
g_applier.SamplerStateCount = 0;
g_applier.BoundShaderImages = {};
g_applier.ShaderImageStart = 0;
g_applier.ShaderImageCount = 0;
g_applier.DrawProgram = kMGPipeNullHandle;
g_applier.DispatchProgram = kMGPipeNullHandle;
g_applier.BoundShaderCso = kMGPipeNullHandle;
MGPipeApplier().BoundFramebuffer = {};
MGPipeApplier().BoundSamplerViews = {};
MGPipeApplier().SamplerViewStart = 0;
MGPipeApplier().SamplerViewCount = 0;
MGPipeApplier().BoundSamplerStates = {};
MGPipeApplier().SamplerStateStart = 0;
MGPipeApplier().SamplerStateCount = 0;
MGPipeApplier().BoundShaderImages = {};
MGPipeApplier().ShaderImageStart = 0;
MGPipeApplier().ShaderImageCount = 0;
MGPipeApplier().DrawProgram = kMGPipeNullHandle;
MGPipeApplier().DispatchProgram = kMGPipeNullHandle;
MGPipeApplier().BoundShaderCso = kMGPipeNullHandle;
#if MOBILEGL_BUILD_DISAGGREGATED
g_applier.BoundStreamOutputLifetimeId = 0;
MGPipeApplier().BoundStreamOutputLifetimeId = 0;
// Begin is emitted once per span, not on make-current. Its immutable object
// snapshot survives with the resource/program records until End or release;
// set_context_values restores the returning context's bound lifetime id.
@@ -1595,12 +1758,12 @@ namespace MobileGL::MG_Pipe {
// invalidates the three suppressor slots), or the first emission after a make-current
// would be suppressed as unchanged and the server would draw with a cleared window.
ClearShaderBufferWindows();
++g_applier.FramebufferSerial;
++g_applier.SamplerViewsSerial;
++g_applier.SamplerStatesSerial;
++g_applier.ShaderImagesSerial;
++g_applier.ProgramBindingSerial;
++g_applier.ShaderBuffersSerial;
++MGPipeApplier().FramebufferSerial;
++MGPipeApplier().SamplerViewsSerial;
++MGPipeApplier().SamplerStatesSerial;
++MGPipeApplier().ShaderImagesSerial;
++MGPipeApplier().ProgramBindingSerial;
++MGPipeApplier().ShaderBuffersSerial;
}
void MGPipeApplierReleaseObjectRecords() {
@@ -1615,37 +1778,37 @@ namespace MobileGL::MG_Pipe {
// context, so nothing wires this - see PipeApply.h. The two serials advance here for
// MGPipeApplierReset's reason: state was cleared, and a twin that outlives it must not
// be able to match a value it has already seen.
g_applier.Resources.clear();
g_applier.VertexElementsCsos.clear();
g_applier.BoundVertexElements = kMGPipeNullHandle;
++g_applier.VertexBuffersSerial;
++g_applier.IndexBufferSerial;
MGPipeApplier().Resources.clear();
MGPipeApplier().VertexElementsCsos.clear();
MGPipeApplier().BoundVertexElements = kMGPipeNullHandle;
++MGPipeApplier().VertexBuffersSerial;
++MGPipeApplier().IndexBufferSerial;
// P5c (rv): same rule for the shutter serials - the served context is going away, and
// a memo that outlives it must not match a value these have already answered with.
++g_applier.TextureShutterSerial;
++g_applier.ContextSerial;
++MGPipeApplier().TextureShutterSerial;
++MGPipeApplier().ContextSerial;
// P4a's five object tables go with them, and the working handles they could name go
// too - a bound shader CSO whose record has just been dropped must not survive as a
// handle the next call resolves against.
g_applier.TextureResources.clear();
g_applier.RenderbufferResources.clear();
g_applier.SamplerCsos.clear();
g_applier.SamplerViewCsos.clear();
g_applier.ShaderCsos.clear();
g_applier.CompositeShaderCsos.clear();
MGPipeApplier().TextureResources.clear();
MGPipeApplier().RenderbufferResources.clear();
MGPipeApplier().SamplerCsos.clear();
MGPipeApplier().SamplerViewCsos.clear();
MGPipeApplier().ShaderCsos.clear();
MGPipeApplier().CompositeShaderCsos.clear();
// AND THE FRAMEBUFFER RECORDS, which MGPipeApplierReset above deliberately keeps. Here
// there is nothing for them to outlive: the served context is going away and the
// framebuffers it owned are container objects that go with it. A table left standing
// would answer a lookup from the NEXT context on a slot the allocator has re-minted -
// with the same generation, if the next context happens to reach the same one - and
// that is the aliasing every other table is cleared to prevent.
g_applier.FramebufferRecords.clear();
g_applier.DrawProgram = kMGPipeNullHandle;
g_applier.DispatchProgram = kMGPipeNullHandle;
g_applier.BoundShaderCso = kMGPipeNullHandle;
MGPipeApplier().FramebufferRecords.clear();
MGPipeApplier().DrawProgram = kMGPipeNullHandle;
MGPipeApplier().DispatchProgram = kMGPipeNullHandle;
MGPipeApplier().BoundShaderCso = kMGPipeNullHandle;
#if MOBILEGL_BUILD_DISAGGREGATED
g_applier.BoundStreamOutputLifetimeId = 0;
g_applier.StreamOutputSpans.clear();
MGPipeApplier().BoundStreamOutputLifetimeId = 0;
MGPipeApplier().StreamOutputSpans.clear();
#endif
// AND "THE WORKING HANDLES THEY COULD NAME" IS ALL OF THEM, NOT JUST THE THREE ABOVE.
// Every framebuffer record holds eleven MGPSurface::Res naming texture and renderbuffer
@@ -1657,35 +1820,35 @@ namespace MobileGL::MG_Pipe {
// else's record. So this takes the same working state MGPipeApplierReset does - which is
// the honest reading of the rule as well: Reset keeps the object records BECAUSE they
// outlive a make-current, and here they do not outlive anything.
g_applier.BoundFramebuffer = {};
g_applier.BoundSamplerViews = {};
g_applier.SamplerViewStart = 0;
g_applier.SamplerViewCount = 0;
g_applier.BoundSamplerStates = {};
g_applier.SamplerStateStart = 0;
g_applier.SamplerStateCount = 0;
g_applier.BoundShaderImages = {};
g_applier.ShaderImageStart = 0;
g_applier.ShaderImageCount = 0;
MGPipeApplier().BoundFramebuffer = {};
MGPipeApplier().BoundSamplerViews = {};
MGPipeApplier().SamplerViewStart = 0;
MGPipeApplier().SamplerViewCount = 0;
MGPipeApplier().BoundSamplerStates = {};
MGPipeApplier().SamplerStateStart = 0;
MGPipeApplier().SamplerStateCount = 0;
MGPipeApplier().BoundShaderImages = {};
MGPipeApplier().ShaderImageStart = 0;
MGPipeApplier().ShaderImageCount = 0;
// P5e (sb): and the three binding-point windows, for the paragraph above's reason in
// its own words - every MGPBufferRange::Res names a resource record this function has
// just dropped, so a window left populated here is a set of handles into an empty
// table, which the next resolve either refuses or answers with somebody else's record
// on a slot the next context re-mints.
ClearShaderBufferWindows();
++g_applier.FramebufferSerial;
++g_applier.SamplerViewsSerial;
++g_applier.SamplerStatesSerial;
++g_applier.ShaderImagesSerial;
++g_applier.ProgramBindingSerial;
++g_applier.ShaderBuffersSerial;
++MGPipeApplier().FramebufferSerial;
++MGPipeApplier().SamplerViewsSerial;
++MGPipeApplier().SamplerStatesSerial;
++MGPipeApplier().ShaderImagesSerial;
++MGPipeApplier().ProgramBindingSerial;
++MGPipeApplier().ShaderBuffersSerial;
}
void MGPipeApplyCreateRenderState(const MGPRenderStateDesc& desc, const void* chunkBytes) {
MOBILEGL_ASSERT(desc.Cso.Slot >= kMGPipeFirstAllocatableSlot,
"create_render_state named the reserved slot 0");
MGPipeRenderStateCsoRecord* recordAt =
RecordAt(g_applier.RenderStateCsos, desc.Cso.Slot, kMGPipeMaxRenderStateCsoSlots);
RecordAt(MGPipeApplier().RenderStateCsos, desc.Cso.Slot, kMGPipeMaxRenderStateCsoSlots);
if (recordAt == nullptr) {
#if MOBILEGL_BUILD_DISAGGREGATED
MGPipeSessionFail(MGPipeFatalFamily::ProtocolCorruption,
@@ -1753,12 +1916,12 @@ namespace MobileGL::MG_Pipe {
MGPipeScatterPipelineBytes(record->PipelineBytes.data(),
MGPipeApplyAccess::RenderState(inputs));
MGPipeApplyAccess::SetRenderStateVersions(inputs, bind.Version, bind.PipelineVersion);
g_applier.BoundRenderStateCso = bind.Cso;
MGPipeApplier().BoundRenderStateCso = bind.Cso;
// A bind scatters the WHOLE pipeline half - the record is always a complete one,
// whatever mask minted it - so the pipeline chunks are all "moved" here, and all
// enter the applier's ledger of the bytes it owns.
const Uint32 moved = MGPipeGlobalChunkBitsOfPipelineMask(kAllPipelineChunks);
g_applier.ScatteredChunkBits |= moved;
MGPipeApplier().ScatteredChunkBits |= moved;
MGPipeDeriveRenderStateFieldsForChunks(inputs, moved);
}
@@ -1771,8 +1934,8 @@ namespace MobileGL::MG_Pipe {
// The Gen stays: it is the CLIENT allocator that bumps it when the slot is handed
// out again (MGPipeHandles.h: "Gen increments only when a SLOT IS REUSED"), and a
// server-side bump here would put the two identities out of step.
if (g_applier.BoundRenderStateCso == handle.Handle) {
g_applier.BoundRenderStateCso = kMGPipeNullHandle;
if (MGPipeApplier().BoundRenderStateCso == handle.Handle) {
MGPipeApplier().BoundRenderStateCso = kMGPipeNullHandle;
}
}
@@ -1781,7 +1944,7 @@ namespace MobileGL::MG_Pipe {
MGPipeScatterDynamicChunks(chunkBytes, dyn.ChunkMask, MGPipeApplyAccess::RenderState(inputs));
MGPipeApplyAccess::SetRenderStateParametersVersion(inputs, dyn.Version);
const Uint32 moved = MGPipeGlobalChunkBitsOfDynamicMask(dyn.ChunkMask);
g_applier.ScatteredChunkBits |= moved;
MGPipeApplier().ScatteredChunkBits |= moved;
MGPipeDeriveRenderStateFieldsForChunks(inputs, moved);
}
@@ -1805,9 +1968,9 @@ namespace MobileGL::MG_Pipe {
// filling, so a predicate that read the answer out of THERE would be reading the very
// memory the rule exists to stop it reading. One store, inert until the caps bit is
// published, and it is what makes the two roles' third clause the same clause.
g_applier.IsTransformFeedbackActive = values.IsTransformFeedbackActive != 0;
MGPipeApplier().IsTransformFeedbackActive = values.IsTransformFeedbackActive != 0;
#if MOBILEGL_BUILD_DISAGGREGATED
g_applier.BoundStreamOutputLifetimeId = values.BoundTransformFeedbackLifetimeId;
MGPipeApplier().BoundStreamOutputLifetimeId = values.BoundTransformFeedbackLifetimeId;
#endif
}
@@ -1839,13 +2002,13 @@ namespace MobileGL::MG_Pipe {
// It runs in the shipped push build too, because a wire that is compiled out of
// every build a device runs is not a wire. The cost is a 24-byte memcmp on a call
// that is emitted when the tessellation state CHANGES, i.e. about once per program.
if ((g_applier.ScatteredChunkBits & kChunksPatchTrio) == kChunksPatchTrio) {
++g_applier.PatchCarrierComparisons;
if ((MGPipeApplier().ScatteredChunkBits & kChunksPatchTrio) == kChunksPatchTrio) {
++MGPipeApplier().PatchCarrierComparisons;
const Bool agrees = working.PatchVertices == patch.Vertices &&
std::memcmp(&working.PatchDefaultOuterLevel, &outer, sizeof(outer)) == 0 &&
std::memcmp(&working.PatchDefaultInnerLevel, &inner, sizeof(inner)) == 0;
if (!agrees) {
++g_applier.PatchCarrierDivergences;
++MGPipeApplier().PatchCarrierDivergences;
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipePatchCarriersDiffer")
" set_patch_state says vertices=%u outer=(%g,%g,%g,%g) inner=(%g,%g); "
"chunk P0 delivered vertices=%u outer=(%g,%g,%g,%g) inner=(%g,%g)",
@@ -1921,9 +2084,9 @@ namespace MobileGL::MG_Pipe {
}
void MGPipeApplySetResidualValueState(const ResidualValueBlock& block) {
g_applier.Residual = block;
g_applier.HasResidual = true;
g_applier.ResidualCapabilitiesCompared = 0;
MGPipeApplier().Residual = block;
MGPipeApplier().HasResidual = true;
MGPipeApplier().ResidualCapabilitiesCompared = 0;
// THE TRIP WIRE (ARCHITECTURE.md 9.4, P2 brief D9). CapabilityBits is redundant with
// the assembled working block by design: every one of the 35 capabilities is
@@ -1953,16 +2116,16 @@ namespace MobileGL::MG_Pipe {
// tautology. The redundancy this wire exists to check is between the CARRIED bits and
// the ASSEMBLED block.
const RenderStateParameters& working = MGPipeApplyAccess::RenderState(gPipeInputs);
const Uint32 owned = g_applier.ScatteredChunkBits;
const Uint32 owned = MGPipeApplier().ScatteredChunkBits;
for (SizeT i = 0; i < kCapabilityCount; ++i) {
const CapabilityInput cap = static_cast<CapabilityInput>(i);
const Uint32 sources = CapabilitySourceChunks(cap);
if ((owned & sources) != sources) continue;
++g_applier.ResidualCapabilitiesCompared;
++MGPipeApplier().ResidualCapabilitiesCompared;
const Bool carried = ((block.CapabilityBits >> i) & 1ull) != 0;
const Bool assembledBit = MGPipeApplyAccess::DeriveCapability(working, cap);
if (carried == assembledBit) continue;
++g_applier.ResidualDivergences;
++MGPipeApplier().ResidualDivergences;
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipeResidualDiverged, \"%s\"")
" carried=%d assembled=%d",
kCapabilityNames[i], static_cast<int>(carried),
@@ -2279,7 +2442,7 @@ namespace MobileGL::MG_Pipe {
// THE FOUR BUFFER-ONLY CALLS, AND WHAT POLICES THAT. resource_flush_range,
// resource_readback and map_persistent resolve through ResolveResource - i.e. against
// g_applier.Resources and never the texture or renderbuffer table - which is right, because
// MGPipeApplier().Resources and never the texture or renderbuffer table - which is right, because
// a mapped range, a readback and a persistent donation exist for buffers only. They are
// buffer-only BY CATALOGUE and not by check: MGPFlushRange and MGPReadback carry no kind
// at all, and map_persistent's MGPHandleOnly is not asked for one, so there is nothing here
@@ -2428,7 +2591,7 @@ namespace MobileGL::MG_Pipe {
// Defined that way the number is identical in both modes, equals "one per storage
// definition", and is non-zero and assertable today; defined as "round trips actually
// taken" it would be 0 by construction in monolith and could never go red.
++g_applier.MapPersistentRoundtrips;
++MGPipeApplier().MapPersistentRoundtrips;
MGPipeResourceRecord* record = ResolveResource("map_persistent", handle.Handle);
if (record == nullptr) return nullptr;
@@ -2586,7 +2749,7 @@ namespace MobileGL::MG_Pipe {
}
MGPipeVertexElementsRecord* recordAt =
RecordAt(g_applier.VertexElementsCsos, desc.Cso.Slot, kMGPipeMaxVertexElementsSlots);
RecordAt(MGPipeApplier().VertexElementsCsos, desc.Cso.Slot, kMGPipeMaxVertexElementsSlots);
if (recordAt == nullptr) {
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption")
" create_vertex_elements {slot=%u, gen=%u}: the slot is outside the record "
@@ -2634,14 +2797,14 @@ namespace MobileGL::MG_Pipe {
// The null handle is legal and means "no vertex array bound" - GL's unbound state is a
// state, not an error, and the backend has a branch for it.
if (MGPipeHandleIsNull(handle.Handle)) {
g_applier.BoundVertexElements = kMGPipeNullHandle;
MGPipeApplier().BoundVertexElements = kMGPipeNullHandle;
return;
}
// A dead handle leaves the PREVIOUS binding untouched, which is bind_render_state's
// precedent for the same question, and is counted like every other refusal.
const MGPipeVertexElementsRecord* record = ResolveVertexElements("bind_vertex_elements", handle.Handle);
if (record == nullptr) return;
g_applier.BoundVertexElements = handle.Handle;
MGPipeApplier().BoundVertexElements = handle.Handle;
}
void MGPipeApplyDeleteVertexElements(const MGPHandleOnly& handle) {
@@ -2663,8 +2826,8 @@ namespace MobileGL::MG_Pipe {
const Uint32 gen = record->Gen;
*record = MGPipeVertexElementsRecord{};
record->Gen = gen;
if (g_applier.BoundVertexElements == handle.Handle) {
g_applier.BoundVertexElements = kMGPipeNullHandle;
if (MGPipeApplier().BoundVertexElements == handle.Handle) {
MGPipeApplier().BoundVertexElements = kMGPipeNullHandle;
}
}
@@ -2692,10 +2855,10 @@ namespace MobileGL::MG_Pipe {
// are not cleared: this record is "the last set as received", and a set that names
// four entries has said nothing about the rest.
for (Uint32 i = 0; i < hdr.Count; ++i) {
g_applier.VertexBuffers[hdr.Start + i] = tail[i];
MGPipeApplier().VertexBuffers[hdr.Start + i] = tail[i];
}
g_applier.VertexBufferStart = hdr.Start;
g_applier.VertexBufferCount = hdr.Count;
MGPipeApplier().VertexBufferStart = hdr.Start;
MGPipeApplier().VertexBufferCount = hdr.Count;
// THE RAW VALUE IS STORED, NOT A RESOLVED SHIFT, and the resolution is one step
// further out on purpose. Whether the fetch shift has to be emulated at all is a
@@ -2710,8 +2873,8 @@ namespace MobileGL::MG_Pipe {
// input: set_vertex_buffers is suppressed on an unchanged hash, so a base instance
// that moved while the buffer set did not would otherwise never arrive and the server
// would keep the previous shift.
g_applier.VertexFetchBaseInstance = hdr.BaseInstance;
++g_applier.VertexBuffersSerial;
MGPipeApplier().VertexFetchBaseInstance = hdr.BaseInstance;
++MGPipeApplier().VertexBuffersSerial;
}
void MGPipeApplySetIndexBuffer(const MGPIndexBuffer& record) {
@@ -2725,8 +2888,8 @@ namespace MobileGL::MG_Pipe {
// - it is an INDEPENDENT call and NOT a subset of the vertex-elements configuration
// (D5): a rebind of the index slot must move this serial without touching the
// configuration's, which is exactly what the backend's two separate compares need.
g_applier.IndexBuffer = record;
++g_applier.IndexBufferSerial;
MGPipeApplier().IndexBuffer = record;
++MGPipeApplier().IndexBufferSerial;
}
void MGPipeDeriveRenderStateFields(PipeInputs& inputs) {
@@ -2839,7 +3002,7 @@ namespace MobileGL::MG_Pipe {
if (NoP4aConsumer()) return;
MGPipeFramebufferRecord* record =
RecordAt(g_applier.FramebufferRecords, state.Fbo.Slot, kMGPipeMaxFramebufferSlots);
RecordAt(MGPipeApplier().FramebufferRecords, state.Fbo.Slot, kMGPipeMaxFramebufferSlots);
// Unreachable: the bound was checked above, before anything moved. The null check is
// what makes that an argument rather than an assumption.
if (record == nullptr) return;
@@ -2851,11 +3014,11 @@ namespace MobileGL::MG_Pipe {
// target: "this record describes the framebuffer it names; no binding changes".
if (state.Target == static_cast<Uint8>(MGPipeFramebufferTarget::Draw) ||
state.Target == static_cast<Uint8>(MGPipeFramebufferTarget::Both)) {
g_applier.BoundFramebuffer[static_cast<SizeT>(MGPipeFramebufferTarget::Draw)] = state.Fbo;
MGPipeApplier().BoundFramebuffer[static_cast<SizeT>(MGPipeFramebufferTarget::Draw)] = state.Fbo;
}
if (state.Target == static_cast<Uint8>(MGPipeFramebufferTarget::Read) ||
state.Target == static_cast<Uint8>(MGPipeFramebufferTarget::Both)) {
g_applier.BoundFramebuffer[static_cast<SizeT>(MGPipeFramebufferTarget::Read)] = state.Fbo;
MGPipeApplier().BoundFramebuffer[static_cast<SizeT>(MGPipeFramebufferTarget::Read)] = state.Fbo;
}
// ONE SERIAL FOR THE FAMILY, and it moves once per applied record - a Both record is one
// record and a Named record is one record. It is what retires the four g_fboSynced*
@@ -2864,7 +3027,7 @@ namespace MobileGL::MG_Pipe {
// serial still the server's". A Named record moves it for the same reason a bound one
// does - a twin that memoised a framebuffer's attachments has to hear that they moved,
// and whether the framebuffer happens to be bound is not that question.
++g_applier.FramebufferSerial;
++MGPipeApplier().FramebufferSerial;
}
// THE READ SIDE OF THE TABLE (ID-19(b)/(d)). Package D asks it by handle at every DSA entry
@@ -2941,7 +3104,7 @@ namespace MobileGL::MG_Pipe {
}
MGPipeSamplerCsoRecord* recordAt =
RecordAt(g_applier.SamplerCsos, desc.Cso.Slot, kMGPipeMaxSamplerCsoSlots);
RecordAt(MGPipeApplier().SamplerCsos, desc.Cso.Slot, kMGPipeMaxSamplerCsoSlots);
if (recordAt == nullptr) {
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption")
" create_sampler_state {slot=%u, gen=%u}: the slot is outside the record "
@@ -2978,7 +3141,7 @@ namespace MobileGL::MG_Pipe {
MOBILEGL_ASSERT(handle.Kind == static_cast<Uint32>(MGPipeKind::SamplerCso),
"delete_sampler_state on kind %u", handle.Kind);
MGPipeSamplerCsoRecord* record =
ResolveObject(g_applier.SamplerCsos, "delete_sampler_state", handle.Handle);
ResolveObject(MGPipeApplier().SamplerCsos, "delete_sampler_state", handle.Handle);
if (record == nullptr) return;
// Dropped whole, generation kept, for resource_destroy's reason: the client allocator
@@ -3000,7 +3163,7 @@ namespace MobileGL::MG_Pipe {
if (view.Cso.Slot < kMGPipeFirstAllocatableSlot) return;
MGPipeSamplerViewRecord* recordAt =
RecordAt(g_applier.SamplerViewCsos, view.Cso.Slot, kMGPipeMaxSamplerViewSlots);
RecordAt(MGPipeApplier().SamplerViewCsos, view.Cso.Slot, kMGPipeMaxSamplerViewSlots);
if (recordAt == nullptr) {
MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption")
" create_sampler_view {slot=%u, gen=%u}: the slot is outside the record "
@@ -3031,7 +3194,7 @@ namespace MobileGL::MG_Pipe {
// arriving before - or without - the texture record is an ordering fact and not a
// refusal. When the record is there this is the back-pointer that lets a sync reach a
// texture's view without walking every view the applier holds.
MGPipeResourceRecord* texture = FindIn(g_applier.TextureResources, view.Texture);
MGPipeResourceRecord* texture = FindIn(MGPipeApplier().TextureResources, view.Texture);
if (texture != nullptr) texture->ViewCso = view.Cso;
}
@@ -3039,13 +3202,13 @@ namespace MobileGL::MG_Pipe {
MOBILEGL_ASSERT(handle.Kind == static_cast<Uint32>(MGPipeKind::SamplerViewCso),
"delete_sampler_view on kind %u", handle.Kind);
MGPipeSamplerViewRecord* record =
ResolveObject(g_applier.SamplerViewCsos, "delete_sampler_view", handle.Handle);
ResolveObject(MGPipeApplier().SamplerViewCsos, "delete_sampler_view", handle.Handle);
if (record == nullptr) return;
// The back-pointer goes first, while the record that names the texture still exists,
// and only if that texture still names THIS view - a texture whose view has already
// been re-minted must not have the new handle cleared out from under it.
MGPipeResourceRecord* texture = FindIn(g_applier.TextureResources, record->View.Texture);
MGPipeResourceRecord* texture = FindIn(MGPipeApplier().TextureResources, record->View.Texture);
if (texture != nullptr && texture->ViewCso == handle.Handle) {
texture->ViewCso = kMGPipeNullHandle;
}
@@ -3066,7 +3229,7 @@ namespace MobileGL::MG_Pipe {
// READ-attachment case reaches no parameter push at all today. The record exists the
// moment the parameters move, whether or not anything is bound.
MGPipeResourceRecord* record =
ResolveObject(g_applier.TextureResources, "set_texture_params", params.Res);
ResolveObject(MGPipeApplier().TextureResources, "set_texture_params", params.Res);
if (record == nullptr) return false;
// EVERY ITextureObject OWNS A SamplerObject, so the built-in sampler CSO is not
@@ -3121,11 +3284,11 @@ namespace MobileGL::MG_Pipe {
void MGPipeApplySetSamplerViews(const MGPSamplerViews& hdr, const MGPBoundView* tail) {
if (NoP4aConsumer()) return;
if (!ApplyUnitWindow("set_sampler_views", hdr.Start, hdr.Count, hdr.ContentHash, tail,
g_applier.BoundSamplerViews, g_applier.SamplerViewStart,
g_applier.SamplerViewCount)) {
MGPipeApplier().BoundSamplerViews, MGPipeApplier().SamplerViewStart,
MGPipeApplier().SamplerViewCount)) {
return;
}
++g_applier.SamplerViewsSerial;
++MGPipeApplier().SamplerViewsSerial;
// P5c (rv): a texture bind is what the bind-generation shutter guarded.
MGPipeApplierNoteTextureStateMoved();
}
@@ -3133,11 +3296,11 @@ namespace MobileGL::MG_Pipe {
void MGPipeApplyBindSamplerStates(const MGPSamplerStates& hdr, const MGPipeHandle* tail) {
if (NoP4aConsumer()) return;
if (!ApplyUnitWindow("bind_sampler_states", hdr.Start, hdr.Count, hdr.ContentHash, tail,
g_applier.BoundSamplerStates, g_applier.SamplerStateStart,
g_applier.SamplerStateCount)) {
MGPipeApplier().BoundSamplerStates, MGPipeApplier().SamplerStateStart,
MGPipeApplier().SamplerStateCount)) {
return;
}
++g_applier.SamplerStatesSerial;
++MGPipeApplier().SamplerStatesSerial;
// P5c (rv): a sampler bind or parameter change is what the shutters guarded.
MGPipeApplierNoteTextureStateMoved();
}
@@ -3150,11 +3313,11 @@ namespace MobileGL::MG_Pipe {
// formats on Adreno will accept is the backend's, not this applier's.
if (NoP4aConsumer()) return;
if (!ApplyUnitWindow("set_shader_images", hdr.Start, hdr.Count, hdr.ContentHash, tail,
g_applier.BoundShaderImages, g_applier.ShaderImageStart,
g_applier.ShaderImageCount)) {
MGPipeApplier().BoundShaderImages, MGPipeApplier().ShaderImageStart,
MGPipeApplier().ShaderImageCount)) {
return;
}
++g_applier.ShaderImagesSerial;
++MGPipeApplier().ShaderImagesSerial;
// P5c (rv): an image bind moves the texture bind generation's guarded set too.
MGPipeApplierNoteTextureStateMoved();
}
@@ -3211,18 +3374,18 @@ namespace MobileGL::MG_Pipe {
// unit sets and set_vertex_buffers work: the record is "the last set as received", and
// a binding at or above Count means "nothing bound" to every reader (the frontend
// array's default says the same thing, which is what makes the two pictures agree).
auto& destination = g_applier.BoundShaderBuffers[hdr.Class];
auto& destination = MGPipeApplier().BoundShaderBuffers[hdr.Class];
for (Uint32 i = 0; i < hdr.Count; ++i) destination[hdr.Start + i] = tail[i];
g_applier.ShaderBufferStart[hdr.Class] = hdr.Start;
g_applier.ShaderBufferCount[hdr.Class] = hdr.Count;
MGPipeApplier().ShaderBufferStart[hdr.Class] = hdr.Start;
MGPipeApplier().ShaderBufferCount[hdr.Class] = hdr.Count;
for (Uint32 w = 0; w < kMGPipeShaderBufferWritableMaskWords; ++w) {
g_applier.ShaderBufferWritableMask[hdr.Class][w] = hdr.WritableMask[w];
MGPipeApplier().ShaderBufferWritableMask[hdr.Class][w] = hdr.WritableMask[w];
}
// ONE SERIAL FOR ALL THREE CLASSES, and it moves on EVERY applied record - a backend
// memo that asks "have the binding points moved since I last bound them" must hear
// about a uniform set exactly as it hears about a storage set, and splitting the serial
// per class would make each of the four consumers pick which one to read.
++g_applier.ShaderBuffersSerial;
++MGPipeApplier().ShaderBuffersSerial;
}
// P5e (pg) WRITES THE SECOND BODY. The three post-link mutable reflection fields -
@@ -3501,14 +3664,14 @@ namespace MobileGL::MG_Pipe {
// error; a DEAD handle leaves the previous binding untouched and is counted, which is
// bind_render_state's precedent for the same question.
if (MGPipeHandleIsNull(handle.Handle)) {
g_applier.BoundShaderCso = kMGPipeNullHandle;
++g_applier.ProgramBindingSerial;
MGPipeApplier().BoundShaderCso = kMGPipeNullHandle;
++MGPipeApplier().ProgramBindingSerial;
return;
}
const MGPipeShaderCsoRecord* record = ResolveShaderCso("bind_shader_state", handle.Handle);
if (record == nullptr) return;
g_applier.BoundShaderCso = handle.Handle;
++g_applier.ProgramBindingSerial;
MGPipeApplier().BoundShaderCso = handle.Handle;
++MGPipeApplier().ProgramBindingSerial;
}
void MGPipeApplyDeleteShaderState(const MGPHandleOnly& handle) {
@@ -3532,19 +3695,19 @@ namespace MobileGL::MG_Pipe {
// next verb resolving a binding to a record this applier no longer has, which is the
// refusal counter firing for a state the applier itself created.
Bool cleared = false;
if (g_applier.BoundShaderCso == handle.Handle) {
g_applier.BoundShaderCso = kMGPipeNullHandle;
if (MGPipeApplier().BoundShaderCso == handle.Handle) {
MGPipeApplier().BoundShaderCso = kMGPipeNullHandle;
cleared = true;
}
if (g_applier.DrawProgram == handle.Handle) {
g_applier.DrawProgram = kMGPipeNullHandle;
if (MGPipeApplier().DrawProgram == handle.Handle) {
MGPipeApplier().DrawProgram = kMGPipeNullHandle;
cleared = true;
}
if (g_applier.DispatchProgram == handle.Handle) {
g_applier.DispatchProgram = kMGPipeNullHandle;
if (MGPipeApplier().DispatchProgram == handle.Handle) {
MGPipeApplier().DispatchProgram = kMGPipeNullHandle;
cleared = true;
}
if (cleared) ++g_applier.ProgramBindingSerial;
if (cleared) ++MGPipeApplier().ProgramBindingSerial;
}
// TWO CALLS AND NOT ONE, because the frontend has two joins and two PipeInputs slots: a
@@ -3555,14 +3718,14 @@ namespace MobileGL::MG_Pipe {
"set_draw_program on kind %u", handle.Kind);
if (NoP4aConsumer()) return; // P4a's belt, for MGPipeApplyBindShaderState's reason.
if (MGPipeHandleIsNull(handle.Handle)) {
g_applier.DrawProgram = kMGPipeNullHandle;
++g_applier.ProgramBindingSerial;
MGPipeApplier().DrawProgram = kMGPipeNullHandle;
++MGPipeApplier().ProgramBindingSerial;
return;
}
const MGPipeShaderCsoRecord* record = ResolveShaderCso("set_draw_program", handle.Handle);
if (record == nullptr) return;
g_applier.DrawProgram = handle.Handle;
++g_applier.ProgramBindingSerial;
MGPipeApplier().DrawProgram = handle.Handle;
++MGPipeApplier().ProgramBindingSerial;
}
void MGPipeApplySetDispatchProgram(const MGPHandleOnly& handle) {
@@ -3570,14 +3733,14 @@ namespace MobileGL::MG_Pipe {
"set_dispatch_program on kind %u", handle.Kind);
if (NoP4aConsumer()) return; // P4a's belt, for MGPipeApplyBindShaderState's reason.
if (MGPipeHandleIsNull(handle.Handle)) {
g_applier.DispatchProgram = kMGPipeNullHandle;
++g_applier.ProgramBindingSerial;
MGPipeApplier().DispatchProgram = kMGPipeNullHandle;
++MGPipeApplier().ProgramBindingSerial;
return;
}
const MGPipeShaderCsoRecord* record = ResolveShaderCso("set_dispatch_program", handle.Handle);
if (record == nullptr) return;
g_applier.DispatchProgram = handle.Handle;
++g_applier.ProgramBindingSerial;
MGPipeApplier().DispatchProgram = handle.Handle;
++MGPipeApplier().ProgramBindingSerial;
}
void MGPipeApplySetGlobalConstants(const MGPGlobalConstants& record, const void* bytes) {