diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index 9e1f1a56..8ae52826 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -114,6 +115,17 @@ namespace MobileGL::MG_Backend::DirectGLES { return source; } + namespace { + Bool g_processTeardown = false; + std::once_flag g_teardownSentinelOnce; + } // namespace + + Bool InProcessTeardown() { return g_processTeardown; } + void EnsureProcessTeardownSentinel() { + std::call_once(g_teardownSentinelOnce, + [] { std::atexit(+[] { g_processTeardown = true; }); }); + } + String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType) { if (shaderType != GL_VERTEX_SHADER || source.find("gl_BaseInstance") == String::npos) { return source; @@ -1388,6 +1400,9 @@ namespace MobileGL::MG_Backend::DirectGLES { } BackendVertexArrayObject::~BackendVertexArrayObject() { + if (InProcessTeardown()) { + return; // see InProcessTeardown(): the driver may be unloaded already + } if (m_backendVAOId != 0) { NoteVAOIdDeleted(m_backendVAOId); g_GLESFuncs.glDeleteVertexArrays(1, &m_backendVAOId); @@ -1641,6 +1656,9 @@ namespace MobileGL::MG_Backend::DirectGLES { } BackendTextureObject::~BackendTextureObject() { + if (InProcessTeardown()) { + return; // see InProcessTeardown(): the driver may be unloaded already + } if (m_backendTextureId == 0) { return; } @@ -3780,6 +3798,9 @@ namespace MobileGL::MG_Backend::DirectGLES { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif + if (InProcessTeardown()) { + return; // see InProcessTeardown(): the driver may be unloaded already + } if (m_backendProgramId != 0) { MGLOG_D("Deleting backend program object with ID: %u", m_backendProgramId); g_GLESFuncs.glDeleteProgram(m_backendProgramId); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 2900c726..d3a27ef2 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -21,9 +21,25 @@ namespace MobileGL::MG_Backend::DirectGLES { String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType); String PromoteDrawParameterGlobalsToUniforms(String source, GLenum shaderType); + // True once the process has entered exit(): past that point the EGL library and + // the driver may already be unloaded, so a backend twin's destructor must not + // call into g_GLESFuncs (the observed crash is a jump through an unmapped driver + // pointer from __run_exit_handlers) nor touch statics in other TUs (cross-TU + // destruction order is unspecified). Deliberate leak: the process is exiting and + // the driver reclaims GPU objects. The flag is set by a std::atexit handler that + // EnsureProcessTeardownSentinel() registers lazily on first registry use - by + // then every static everywhere has finished constructing, so this handler is + // guaranteed to run BEFORE any static destructor (atexit is LIFO). A destructor + // hook on the registry itself was tried first and is WRONG: tests and cache + // resets destroy temporary registry instances mid-run, which would latch the + // flag while the process is very much alive. + Bool InProcessTeardown(); + void EnsureProcessTeardownSentinel(); + template class StateBackendObjectRegistry { public: + using StatePtr = SharedPtr; using StateWeakPtr = std::weak_ptr; using BackendPtr = SharedPtr; @@ -43,6 +59,9 @@ namespace MobileGL::MG_Backend::DirectGLES { BackendPtr& GetOrCreate(const StatePtr& stateObj) { MOBILEGL_ASSERT(stateObj != nullptr, "State object must not be null"); + // Twin creation is the moment a driver-owned id starts needing a guarded + // destructor; cold path, so the once-guard costs nothing per draw. + EnsureProcessTeardownSentinel(); auto& entry = m_entries[stateObj.get()]; if (entry.stateRef.expired()) { // The previous owner of this address is gone and the allocator handed it