diff --git a/MobileGL/GlobalObjects.cpp b/MobileGL/GlobalObjects.cpp index f5c40733..2b3cc0e1 100644 --- a/MobileGL/GlobalObjects.cpp +++ b/MobileGL/GlobalObjects.cpp @@ -14,7 +14,13 @@ namespace MobileGL { } // namespace MG_Config namespace MG_Backend { - UniquePtr pActiveBackendObject; + // Leak-at-exit storage: the UniquePtr itself lives on the heap and is + // never destroyed by the runtime, so process exit runs no backend + // destructors (static destruction order across TUs is undefined). + // Deterministic teardown happens inside the EGL lifecycle instead: + // the last eglTerminate calls MobileGL::Destroy(), which .reset()s + // these singletons while the process is still healthy. + UniquePtr& pActiveBackendObject = *new UniquePtr(); GlobalBackendFunctionsTable gBackendFunctionsTable; } // namespace MG_Backend } // namespace MobileGL diff --git a/MobileGL/Init.cpp b/MobileGL/Init.cpp index 2f711d5d..c59e5d52 100644 --- a/MobileGL/Init.cpp +++ b/MobileGL/Init.cpp @@ -15,9 +15,18 @@ #include #include +#include +#include + namespace MobileGL { namespace { - Bool g_isInitialized = false; + std::atomic g_isInitialized = false; + thread_local Bool tl_initializing = false; + + std::mutex& InitMutex() { + static std::mutex mutex; + return mutex; + } void DestroyImpl(Bool logLifecycle) { if (!g_isInitialized) { @@ -65,59 +74,35 @@ namespace MobileGL { MGLOG_I("MobileGL initialized"); } + void EnsureInitialized() { + if (g_isInitialized.load(std::memory_order_acquire)) { + return; + } + // Re-entrant call while this thread is already inside Initialize() + // (e.g. an init step routing back through a public entry point). + if (tl_initializing) { + return; + } + const std::lock_guard lock(InitMutex()); + if (g_isInitialized.load(std::memory_order_acquire)) { + return; + } + tl_initializing = true; + Initialize(); + tl_initializing = false; + } + void Destroy() { DestroyImpl(true); } -#if defined(__linux__) || defined(__APPLE__) - __attribute__((constructor)) static void AutoInit() { - Initialize(); - } - - __attribute__((destructor)) static void AutoDestroy() { - if (MG_Config::Features.TraceSkipAutodestroy) { - return; - } -#if defined(__APPLE__) - // macOS injected dylibs can run destructors after logging/backend static state is already torn down. - return; -#else - DestroyImpl(false); -#endif - } -#endif - -#ifdef _WIN32 - namespace { - // Called on DLL_PROCESS_DETACH, which runs BEFORE the CRT executes this - // DLL's static destructors. Releasing (deliberately leaking) the global - // singletons here keeps those destructors from tearing down the backend - // inside a dying process - other threads have already been terminated, - // so Vulkan/GLES cleanup crashes with the process half-dead. The process - // is exiting; the OS reclaims everything. - // No logging here: this runs under the loader lock after every other - // thread was terminated, where the log mutex/heap/stdio may be in any - // state. Plain pointer stores only. - void AbandonAtProcessExit() { - MG_Backend::DirectVulkan::pVulkanRenderer.release(); - MG_Backend::pActiveBackendObject.release(); - MG_State::pGLContext.release(); - MG_State::pEGLContext.release(); - MG_Impl::GLImpl::TextureImpl::pProxyTextureManager.release(); - MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo.release(); - } - } // namespace -#endif + // MobileGL's lifecycle is owned entirely by the host-API layers + // (EGL/WGL/CGL): initialization happens lazily on the first entry point + // via EnsureInitialized(), and full teardown happens deterministically + // when the last EGL display is terminated with nothing current (EGLImpl + // calls Destroy()). There is intentionally no static constructor, no + // static destructor, and no DllMain: the global singletons use + // leak-at-exit storage (see GlobalObjects.cpp), so a process that exits + // without eglTerminate simply leaks them to the OS instead of running + // backend destructors during static teardown. } // namespace MobileGL - -#ifdef _WIN32 -// Initialization is NOT done here: MobileGL::Initialize() loads libraries -// (Vulkan/ANGLE) and spins up glslang, none of which is safe under the loader -// lock. The WGL host layer calls Initialize() lazily on its first entry point. -extern "C" BOOL WINAPI DllMain(HINSTANCE, DWORD reason, LPVOID) { - if (reason == DLL_PROCESS_DETACH) { - MobileGL::AbandonAtProcessExit(); - } - return TRUE; -} -#endif diff --git a/MobileGL/Init.h b/MobileGL/Init.h index 349f6c9b..724f1a8e 100644 --- a/MobileGL/Init.h +++ b/MobileGL/Init.h @@ -11,6 +11,12 @@ namespace MobileGL { void Initialize(); + // Thread-safe, idempotent, and re-entrant wrapper around Initialize(). + // Host layers (EGL/WGL/CGL entry points) call this lazily on first use so + // MobileGL's lifecycle never depends on ELF/DLL static constructors, and + // so a fresh init can follow a full Destroy() (e.g. after the last + // eglTerminate). + void EnsureInitialized(); void Destroy(); namespace MG_Util::Debug { diff --git a/MobileGL/MG_Backend/BackendObjects.h b/MobileGL/MG_Backend/BackendObjects.h index d95f12dc..1a148824 100644 --- a/MobileGL/MG_Backend/BackendObjects.h +++ b/MobileGL/MG_Backend/BackendObjects.h @@ -13,6 +13,6 @@ #include "DirectVulkan/BackendObject_DirectVulkan.h" namespace MobileGL::MG_Backend { - extern UniquePtr pActiveBackendObject; + extern UniquePtr& pActiveBackendObject; extern GlobalBackendFunctionsTable gBackendFunctionsTable; } // namespace MobileGL::MG_Backend diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index cf9ca0ab..f9abfddb 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -20,7 +20,8 @@ #include namespace MobileGL::MG_Backend::DirectVulkan { - UniquePtr pVulkanRenderer = nullptr; + // Leak-at-exit storage; see GlobalObjects.cpp. + UniquePtr& pVulkanRenderer = *new UniquePtr(); namespace { // Generation of the live VulkanRenderer instance, mirroring diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h index 27bb4f96..8e268e0d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h @@ -12,7 +12,7 @@ #include "Renderer/VulkanRenderer.h" namespace MobileGL::MG_Backend::DirectVulkan { - extern UniquePtr pVulkanRenderer; + extern UniquePtr& pVulkanRenderer; // Generation of the live VulkanRenderer instance, mirroring DirectGLES's // g_syncContextGeneration. BackendObject_DirectVulkan bumps it wherever diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index 42590ad7..cb3af392 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -8,6 +8,7 @@ #include "EGLImpl.h" #include "../GetProcAddress.h" +#include #include #include #include @@ -25,6 +26,17 @@ namespace MobileGL::MG_Impl::EGLImpl { return MG_State::pEGLContext.get(); } + // Entry points that can legitimately be an application's FIRST EGL + // call (display/proc-address/string queries) lazily bring MobileGL + // up here, so the library needs no static constructor and can + // re-initialize after the last eglTerminate tore everything down. + // Teardown-ish entry points keep using GetState() and fail benignly + // when MobileGL is not initialized. + EGLStateContext* GetStateEnsureInitialized() { + MobileGL::EnsureInitialized(); + return GetState(); + } + MG_Backend::BackendObject* GetBackendObject(EGLStateContext* state) { auto* backendObject = MG_Backend::pActiveBackendObject.get(); if (!backendObject && state) { @@ -189,7 +201,7 @@ namespace MobileGL::MG_Impl::EGLImpl { } EGLBoolean Initialize(EGLDisplay dpy, EGLint* major, EGLint* minor) { - auto* state = GetState(); + auto* state = GetStateEnsureInitialized(); if (!state) { return EGL_FALSE; } @@ -210,7 +222,7 @@ namespace MobileGL::MG_Impl::EGLImpl { } EGLDisplay GetDisplay(NativeDisplayType display) { - auto* state = GetState(); + auto* state = GetStateEnsureInitialized(); if (!state) { return EGL_NO_DISPLAY; } @@ -315,6 +327,14 @@ namespace MobileGL::MG_Impl::EGLImpl { if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) { backendObject->ReleaseEGLResources(); } + // The last initialized display is gone and nothing is current on any + // thread: tear the whole library down deterministically inside the + // EGL lifecycle (backend, GL/EGL state, glslang). A later EGL call + // re-initializes lazily via GetStateEnsureInitialized(); process exit + // then has nothing left to destroy. + if (!state->HasAnyInitializedDisplay() && !state->HasAnyCurrentContext()) { + MobileGL::Destroy(); + } return EGL_TRUE; } @@ -347,7 +367,7 @@ namespace MobileGL::MG_Impl::EGLImpl { } EGLBoolean BindAPI(EGLenum api) { - auto* state = GetState(); + auto* state = GetStateEnsureInitialized(); if (!state) { return EGL_FALSE; } @@ -380,7 +400,7 @@ namespace MobileGL::MG_Impl::EGLImpl { } char const* QueryString(EGLDisplay display, EGLint name) { - auto* state = GetState(); + auto* state = GetStateEnsureInitialized(); if (!state) { return nullptr; } @@ -643,7 +663,7 @@ namespace MobileGL::MG_Impl::EGLImpl { EGLDisplay GetPlatformDisplay(EGLenum platform, void* native_display, const EGLAttrib* attrib_list) { (void)attrib_list; - auto* state = GetState(); + auto* state = GetStateEnsureInitialized(); if (!state) { return EGL_NO_DISPLAY; } @@ -739,6 +759,7 @@ namespace MobileGL::MG_Impl::EGLImpl { if (!name) { return nullptr; } + MobileGL::EnsureInitialized(); MGLOG_D("eglGetProcAddress(%s)", name); void* proc = MG_Impl::GetProcAddress(name); diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp index 30a955d7..71e90b10 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp @@ -2144,6 +2144,7 @@ namespace MobileGL::MG_Impl::GLImpl { } namespace FramebufferImpl { - UniquePtr pDefaultFramebufferInfo; + // Leak-at-exit storage; see GlobalObjects.cpp. + UniquePtr& pDefaultFramebufferInfo = *new UniquePtr(); } // namespace FramebufferImpl } // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h index 08164f69..8e843d49 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h @@ -78,6 +78,6 @@ namespace MobileGL::MG_Impl::GLImpl { SharedPtr stencilAttachment; }; - extern UniquePtr pDefaultFramebufferInfo; + extern UniquePtr& pDefaultFramebufferInfo; } // namespace FramebufferImpl } // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp index c16df634..156f7d76 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.cpp @@ -14,7 +14,8 @@ #include namespace MobileGL::MG_Impl::GLImpl::TextureImpl { - UniquePtr pProxyTextureManager; + // Leak-at-exit storage; see GlobalObjects.cpp. + UniquePtr& pProxyTextureManager = *new UniquePtr(); Bool IsProxyTextureTarget(TextureUploadTarget target) { switch (target) { diff --git a/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.h b/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.h index 59196829..f83f3faf 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.h +++ b/MobileGL/MG_Impl/GLImpl/Texture/ProxyTexture.h @@ -23,5 +23,5 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl { UnorderedMap> m_proxyTexturesMap; }; - extern UniquePtr pProxyTextureManager; + extern UniquePtr& pProxyTextureManager; } // namespace MobileGL::MG_Impl::GLImpl::TextureImpl diff --git a/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp b/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp index 09663a0e..605b53de 100644 --- a/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp +++ b/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp @@ -138,8 +138,9 @@ namespace MobileGL::MG_Impl::WGLImpl { void EnsureInitialized() { // Initialize() loads backend libraries and glslang, which must not run // under the loader lock; first WGL call is the earliest safe moment. - static std::once_flag once; - std::call_once(once, [] { MobileGL::Initialize(); }); + // MobileGL::EnsureInitialized (not a local once_flag) so a fresh init + // can follow a full teardown from the last eglTerminate. + MobileGL::EnsureInitialized(); } EGLDisplay EnsureDisplay() { diff --git a/MobileGL/MG_State/EGLState/Core.cpp b/MobileGL/MG_State/EGLState/Core.cpp index dcc1f6f4..6e604182 100644 --- a/MobileGL/MG_State/EGLState/Core.cpp +++ b/MobileGL/MG_State/EGLState/Core.cpp @@ -368,6 +368,26 @@ namespace MobileGL { return true; } + Bool EGLContext::HasAnyInitializedDisplay() const { + const std::lock_guard lock(m_mutex); + for (const auto& [handle, displayObject] : m_displays) { + if (displayObject.Initialized) { + return true; + } + } + return false; + } + + Bool EGLContext::HasAnyCurrentContext() const { + const std::lock_guard lock(m_mutex); + for (const auto& [threadId, current] : m_threadCurrents) { + if (current.Context != nullptr) { + return true; + } + } + return false; + } + Bool EGLContext::ChooseConfig(EGLDisplayHandle display, const EGLint* attribList, EGLConfigHandle* configs, EGLint configSize, EGLint* numConfig) { const std::lock_guard lock(m_mutex); @@ -1415,6 +1435,7 @@ namespace MobileGL { } } // namespace EGLState - UniquePtr pEGLContext; + // Leak-at-exit storage; see GlobalObjects.cpp. + UniquePtr& pEGLContext = *new UniquePtr(); } // namespace MG_State } // namespace MobileGL diff --git a/MobileGL/MG_State/EGLState/Core.h b/MobileGL/MG_State/EGLState/Core.h index 41821186..f38a94ca 100644 --- a/MobileGL/MG_State/EGLState/Core.h +++ b/MobileGL/MG_State/EGLState/Core.h @@ -39,6 +39,10 @@ namespace MobileGL { Bool IsDisplayInitialized(EGLDisplayHandle display) const; Bool InitializeDisplay(EGLDisplayHandle display, EGLint* major, EGLint* minor); Bool TerminateDisplay(EGLDisplayHandle display); + // Whole-library idle checks used by EGLImpl::Terminate to decide + // when the last eglTerminate may tear MobileGL down entirely. + Bool HasAnyInitializedDisplay() const; + Bool HasAnyCurrentContext() const; // Config Bool ChooseConfig(EGLDisplayHandle display, const EGLint* attribList, EGLConfigHandle* configs, @@ -262,6 +266,6 @@ namespace MobileGL { }; } // namespace EGLState - extern UniquePtr pEGLContext; + extern UniquePtr& pEGLContext; } // namespace MG_State } // namespace MobileGL diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index 6101ce9c..856a5d18 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -721,5 +721,6 @@ namespace MobileGL::MG_State { } } // namespace GLState - UniquePtr pGLContext; + // Leak-at-exit storage; see GlobalObjects.cpp. + UniquePtr& pGLContext = *new UniquePtr(); } // namespace MobileGL::MG_State diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 51a93d90..01f1b5eb 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -252,7 +252,7 @@ namespace MobileGL { }; } // namespace GLState - extern UniquePtr pGLContext; + extern UniquePtr& pGLContext; // True when relaxed GL semantics apply. Strict core rules are enforced only when the // current EGL context explicitly requested a core profile (core bit in