mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[Fix] (Espryt): tell the backend when a frontend object dies instead of discovering it in a garbage sweep
- 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.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include <MG_Util/Async/ShaderCompilePool.h>
|
||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||
#include <MG_Util/ShaderTranspiler/CompileEnv.h>
|
||||
#include <MG_State/GLState/StateObjectDeathNotice.h>
|
||||
|
||||
const char* kDefaultFragmentShaderSource = R"(#version 460 core
|
||||
layout(location = 0) out vec4 FragColor;
|
||||
@@ -28,7 +29,20 @@ namespace MobileGL::MG_State::GLState {
|
||||
return s_nextProgramLifetimeId.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
ProgramObject::~ProgramObject() { CancelLink(); }
|
||||
ProgramObject::~ProgramObject() {
|
||||
CancelLink();
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P2 step e2: ANNOUNCE the death instead of leaving the backend to discover it in a
|
||||
// garbage sweep. This is the last SharedPtr to this object dropping - not
|
||||
// glDeleteProgram, which only marks the name and leaves a still-bound object very much
|
||||
// alive - so it is the exact moment the backend's twin, and the driver storage that
|
||||
// twin owns, stop being reachable. The notice carries the lifetime id because the
|
||||
// object no longer exists to be passed, and because the lifetime id is what the client
|
||||
// slot allocator resolves the handle from. No-op unless a backend registered the ops
|
||||
// (a pull build declares none at all).
|
||||
NotifyStateObjectDestroyed(MG_Pipe::MGPipeKind::ShaderCso, m_lifetimeId);
|
||||
#endif
|
||||
}
|
||||
|
||||
// EnsureLinkJoined() is defined inline in ProgramObject.h (see the comment there for
|
||||
// why: ~1200 call sites, no LTO). Only its blocking half lives here.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "RenderbufferObject.h"
|
||||
#include <MG_Util/Metrics/TextureMetrics.h>
|
||||
#include <MG_State/GLState/StateObjectDeathNotice.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
@@ -26,6 +27,20 @@ namespace MobileGL {
|
||||
|
||||
RenderbufferObject::RenderbufferObject(Uint externalIndex) : m_externalIndex(externalIndex) {}
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
RenderbufferObject::~RenderbufferObject() {
|
||||
// P2 step e2: ANNOUNCE the death instead of leaving the backend to discover it in a
|
||||
// garbage sweep. This is the last SharedPtr to this object dropping - not
|
||||
// glDeleteRenderbuffers, which only marks the name and leaves a still-bound object very much
|
||||
// alive - so it is the exact moment the backend's twin, and the driver storage that
|
||||
// twin owns, stop being reachable. The notice carries the lifetime id because the
|
||||
// object no longer exists to be passed, and because the lifetime id is what the client
|
||||
// slot allocator resolves the handle from. No-op unless a backend registered the ops
|
||||
// (a pull build declares none at all).
|
||||
NotifyStateObjectDestroyed(MG_Pipe::MGPipeKind::Renderbuffer, m_lifetimeId);
|
||||
}
|
||||
#endif
|
||||
|
||||
Uint RenderbufferObject::GetExternalIndex() const {
|
||||
return m_externalIndex;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,12 @@ namespace MobileGL {
|
||||
using TargetEnum = RenderbufferTarget;
|
||||
|
||||
RenderbufferObject(Uint externalIndex);
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
// P2 step e2. Out of line, and declared only where there is a notice to raise:
|
||||
// in a pull build this class stays trivially destructible, which is what keeps
|
||||
// the pull build's symbol set byte-for-byte the pre-P2 one (G1).
|
||||
~RenderbufferObject();
|
||||
#endif
|
||||
|
||||
Uint GetExternalIndex() const;
|
||||
void SetInternalFormat(TextureInternalFormat format);
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// MobileGL - MobileGL/MG_State/GLState/StateObjectDeathNotice.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
|
||||
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
|
||||
#if MOBILEGL_PIPE_PUSH
|
||||
#include <MG_Pipe/MGPipeHandles.h>
|
||||
|
||||
// P2 step e2, the frontend half: TELL the backend that a state object died, instead of
|
||||
// leaving it to discover the death in a garbage sweep.
|
||||
//
|
||||
// Today the only kind that announces its own death is Buffer, through BufferBackendOps
|
||||
// (BufferState/BufferObject.h) - a frontend-declared ops table that the backend fills in at
|
||||
// context bring-up. This is the same shape for the other six kinds, with two differences that
|
||||
// follow from what the notice is for:
|
||||
//
|
||||
// * it carries {kind, lifetimeId} and NOT the object, because by the time the last
|
||||
// SharedPtr has dropped there is no object left to pass, and the lifetime id is exactly
|
||||
// the key the client slot allocator resolves a handle from (ARCHITECTURE.md 4.2);
|
||||
// * it is one entry point for every kind rather than one ops table per kind, because the
|
||||
// backend's answer is the same for all six: free the slot, drop the twin.
|
||||
//
|
||||
// It exists only under MOBILEGL_PIPE_PUSH. A pull build has no slot allocator, no handle and
|
||||
// nothing that could consume the notice, and G1 requires its symbol set to be byte-for-byte
|
||||
// the pre-P2 one - so in that build this header declares nothing at all and the call sites
|
||||
// compile to nothing.
|
||||
//
|
||||
// The pointer is written once, at backend bring-up, and read from state-object destructors.
|
||||
// It is deliberately a plain pointer and not an atomic: the destructors and the bring-up run
|
||||
// on the context thread, exactly as BufferBackendOps' g_bufferBackendOps does.
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
|
||||
struct StateObjectDeathOps {
|
||||
// The last SharedPtr to the frontend object with this lifetime id has dropped.
|
||||
// Called from the object's destructor, so the object must NOT be touched.
|
||||
void (*OnDestroyed)(MG_Pipe::MGPipeKind kind, Uint64 lifetimeId) = nullptr;
|
||||
};
|
||||
|
||||
inline const StateObjectDeathOps* g_stateObjectDeathOps = nullptr;
|
||||
|
||||
inline void SetStateObjectDeathOps(const StateObjectDeathOps* ops) {
|
||||
g_stateObjectDeathOps = ops;
|
||||
}
|
||||
|
||||
inline const StateObjectDeathOps* GetStateObjectDeathOps() {
|
||||
return g_stateObjectDeathOps;
|
||||
}
|
||||
|
||||
inline void NotifyStateObjectDestroyed(MG_Pipe::MGPipeKind kind, Uint64 lifetimeId) {
|
||||
const StateObjectDeathOps* ops = g_stateObjectDeathOps;
|
||||
if (ops == nullptr || ops->OnDestroyed == nullptr) return;
|
||||
ops->OnDestroyed(kind, lifetimeId);
|
||||
}
|
||||
|
||||
} // namespace MobileGL::MG_State::GLState
|
||||
#endif // MOBILEGL_PIPE_PUSH
|
||||
Reference in New Issue
Block a user