mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 20:58:31 +09:00
[Refactor] (Espryt): give the buffer resource its own {slot, gen} table instead of hanging it off the frontend object
This commit is contained in:
@@ -1501,6 +1501,48 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
g_bufferMutationEpoch.fetch_add(1, std::memory_order_release);
|
||||
}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// The seventh slot table. A process-lifetime global, like the other six, so it links
|
||||
// itself into its own holder list from its own constructor with no initialisation
|
||||
// order question to answer (SlotTables.h).
|
||||
BackendBufferResourceTable g_backendBufferResources;
|
||||
|
||||
Bool ResolveResourceSubsystemArm() {
|
||||
const Bool enabled = (MG_Config::Features.PipePush & MG_Pipe::kMGPipeSubsystemResources) != 0;
|
||||
MGLOG_D("MGPipe: Espryt resource family runs the %s arm", enabled ? "handle" : "legacy");
|
||||
return enabled;
|
||||
}
|
||||
|
||||
Bool ResolveVertexInputSubsystemArm() {
|
||||
const Bool enabled = (MG_Config::Features.PipePush & MG_Pipe::kMGPipeSubsystemVertexInput) != 0;
|
||||
MGLOG_D("MGPipe: Espryt vertex-input family runs the %s arm", enabled ? "handle" : "legacy");
|
||||
return enabled;
|
||||
}
|
||||
|
||||
GLESBufferResource* GetOrCreateBufferResourceForHandle(MG_Pipe::MGPipeHandle res) {
|
||||
if (MG_Pipe::MGPipeHandleIsNull(res)) return nullptr;
|
||||
auto& twin = g_backendBufferResources.GetOrCreate(res);
|
||||
if (!twin) {
|
||||
twin = MakeShared<GLESBufferResource>();
|
||||
// Same seed the lazy legacy path gives a resource it has just minted: nothing
|
||||
// has defined storage yet, so the first sync owes a full (re)specification.
|
||||
twin->pendingRespecify = true;
|
||||
}
|
||||
return twin.get();
|
||||
}
|
||||
|
||||
GLESBufferResource* FindBufferResourceForHandle(MG_Pipe::MGPipeHandle res) {
|
||||
auto* twin = g_backendBufferResources.FindByHandle(res);
|
||||
return twin ? twin->get() : nullptr;
|
||||
}
|
||||
|
||||
MG_Pipe::MGPipeHandle HandleOfBuffer(const MG_State::GLState::BufferObject* bufferObject) {
|
||||
if (bufferObject == nullptr) return MG_Pipe::kMGPipeNullHandle;
|
||||
return MG_Pipe::MGPipeSlots().FindByLifetimeId(MG_Pipe::MGPipeKind::Buffer,
|
||||
bufferObject->GetLifetimeId());
|
||||
}
|
||||
#endif
|
||||
|
||||
// See the declaration: re-mints of a live resource's driver id. Written only on
|
||||
// the context thread (all re-mint sites run there), read only by the VAO sync.
|
||||
Uint64 g_bufferBackendIdGeneration = 0;
|
||||
|
||||
@@ -644,8 +644,76 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// no map, and a respecification then has to retire the id rather than hand it
|
||||
// to glBufferData, which the driver would silently refuse.
|
||||
Bool immutableStorage = false;
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P3a: the client's shadow base as the last content-carrying resource call left
|
||||
// it. The handle-shaped ops carry `shadow + offset` beside their record, so the
|
||||
// base is recovered by subtracting the record's own offset once, here, instead of
|
||||
// asking a frontend object for MappedData() at every later drain. Everything the
|
||||
// legacy arm reads through bufferObject.MappedData() - the three-tier range
|
||||
// flush, the pool reseed, the full re-upload - reads this on the handle arm.
|
||||
//
|
||||
// RAW, and it may not be dereferenced except while the client's shadow is known
|
||||
// to be live: the client owns the allocation and hands the base over per call.
|
||||
// Null until the first respecify/subdata/flush that carries content, which is
|
||||
// exactly when nothing has bytes to move yet.
|
||||
const Uint8* hostBytes = nullptr;
|
||||
#endif
|
||||
};
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P3a (D-A4): the SEVENTH Espryt slot table, and the first one keyed by a handle the
|
||||
// CALL carried rather than one this backend minted off a frontend object's lifetime
|
||||
// id. That is what discharges, for this kind, the debt SlotTables.h records against
|
||||
// itself: GLESBufferResource stops hanging off PipeResource::m_backend and lives here
|
||||
// instead, so the resource table is the server's own and a frontend heap reference is
|
||||
// no longer part of resolving it.
|
||||
//
|
||||
// The StateObject parameter is BufferObject only because the template names one; not
|
||||
// one member that touches it is instantiated on this table (no Find(StateObject*), no
|
||||
// HandleOf, no ForEachLive), and the handle overloads never look at it. Death is
|
||||
// announced by the family's own ResourceDestroy call, not by the shared death notice
|
||||
// (D-L), and the slot is freed by the CLIENT after that call returns.
|
||||
using BackendBufferResourceTable =
|
||||
BackendSlotTable<MG_State::GLState::BufferObject, GLESBufferResource, MG_Pipe::MGPipeKind::Buffer>;
|
||||
extern BackendBufferResourceTable g_backendBufferResources;
|
||||
|
||||
// Resolved once per process and latched, exactly like EsprytSlotTablesEnabled() and
|
||||
// for the same reason: the two arms hold GLESBufferResource in DIFFERENT containers -
|
||||
// the legacy arm in the frontend object's PipeResource::m_backend, the handle arm in
|
||||
// the table above - so an answer that changed mid-run would strand every resource
|
||||
// already built and leak the driver ids they own.
|
||||
Bool ResolveResourceSubsystemArm();
|
||||
// Same shape for the vertex-input family (bit 8). Kept separate because the two bits
|
||||
// are separately clearable and the A/B has to be able to run either one alone.
|
||||
Bool ResolveVertexInputSubsystemArm();
|
||||
|
||||
// INLINE for the reason SlotTables.h spells out at EsprytSlotTablesEnabled: both are
|
||||
// consulted on the per-draw path (the VAO sync's gate, EnsureBufferResource, every
|
||||
// buffer op), and out-of-line they would be a call through the PLT per consult.
|
||||
inline Bool ResourceSubsystemEnabled() {
|
||||
static const Bool enabled = ResolveResourceSubsystemArm();
|
||||
return enabled;
|
||||
}
|
||||
inline Bool VertexInputSubsystemEnabled() {
|
||||
static const Bool enabled = ResolveVertexInputSubsystemArm();
|
||||
return enabled;
|
||||
}
|
||||
|
||||
// Resolve-or-create / resolve-only, by the handle the call carried. Neither touches
|
||||
// MGPipeSlots(): the handle ARRIVED already minted by the side that owns minting.
|
||||
GLESBufferResource* GetOrCreateBufferResourceForHandle(MG_Pipe::MGPipeHandle res);
|
||||
GLESBufferResource* FindBufferResourceForHandle(MG_Pipe::MGPipeHandle res);
|
||||
|
||||
// MONOLITH GLUE, and named as such: the handle of a resource this backend is looking
|
||||
// at through a frontend object, resolved through the client allocator's lifetime-id
|
||||
// index. Every caller is a site P3a deliberately does NOT migrate - the SSBO / UBO /
|
||||
// indirect / pack-PBO binding walks are dirty bits 15-17 and P4b's, and the index
|
||||
// host mirror is P8's - so they still arrive holding the object. Under a real split
|
||||
// neither the object nor its lifetime id exists on this side and every one of them
|
||||
// has to receive the handle in a payload instead.
|
||||
MG_Pipe::MGPipeHandle HandleOfBuffer(const MG_State::GLState::BufferObject* bufferObject);
|
||||
#endif
|
||||
|
||||
// Registered as the frontend's BufferBackendOps at backend init and on
|
||||
// every MakeCurrent (the ES context can be destroyed and recreated, e.g.
|
||||
// by the trace replayer's probe context).
|
||||
|
||||
@@ -285,6 +285,32 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return entry.backend;
|
||||
}
|
||||
|
||||
// P3a: the death half of the overload above, for a kind whose announcement is its own
|
||||
// destroy CALL rather than the shared death notice (D-L). Hands the twin OUT rather
|
||||
// than destroying it in place, because the caller may still have to decide what
|
||||
// happens to the driver id it owns - Espryt pools it, deletes it, or parks it on the
|
||||
// deferred-release list when no context is current on this thread - and every one of
|
||||
// those outcomes has to be reached with the entry already retired, so a re-entrant
|
||||
// GetOrCreate from a twin destructor cannot resurrect it.
|
||||
//
|
||||
// The slot itself is NOT freed here: it belongs to the kind, and for a handle-keyed
|
||||
// kind the CLIENT frees it after the destroy call returns (SlotAllocator.h:60 - the
|
||||
// Gen bump rides the next handout, so a double free cannot skip a generation). An
|
||||
// entry whose Gen no longer matches is a twin of the slot's previous owner and is
|
||||
// left alone: the successor's own GetOrCreate resets it.
|
||||
BackendPtr ReleaseByHandle(MG_Pipe::MGPipeHandle handle) {
|
||||
if (MG_Pipe::MGPipeHandleIsNull(handle)) return BackendPtr{};
|
||||
if (m_memoHandle.Slot == handle.Slot) ForgetHandle();
|
||||
if (handle.Slot >= m_slots.size()) return BackendPtr{};
|
||||
Entry& entry = m_slots[handle.Slot];
|
||||
if (!entry.Live || entry.Gen != handle.Gen) return BackendPtr{};
|
||||
BackendPtr dead = std::move(entry.backend);
|
||||
entry.backend.reset();
|
||||
entry.stateRef.reset();
|
||||
entry.Live = false;
|
||||
return dead;
|
||||
}
|
||||
|
||||
// Null when no live twin of this object exists. Unlike the registry's Find this NEVER
|
||||
// mutates the table, so the returned pointer survives any later Find on it; only a
|
||||
// GetOrCreate that grows the vector can move it, and callers that hold one across a
|
||||
|
||||
Reference in New Issue
Block a user