// MobileGL - MobileGL/Init.cpp // 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 #include "Init.h" #include "Config.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace MobileGL { namespace { 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) { return; } if (logLifecycle) { MGLOG_I("MobileGL closing..."); } // First, before anything else is torn down. In-flight compile/link jobs own // their own inputs and are safe against everything below EXCEPT glslang's // process globals and the TShader/TProgram objects hanging off pGLContext, // both of which this function is about to destroy. This is the one // cancellation path in the whole design that waits. MG_Util::Async::ShaderCompilePool::Get().StopAndDrain(); // GL syncs die with their contexts, and every context is gone by the // time full teardown runs: drain the live-sync registry while the // backend function table can still release the backend handles (and // before a re-initialized library could pair them with the wrong // backend's DeleteSync). MG_Impl::GLImpl::DestroyAllSyncObjects(); // Queries die with their contexts for the same reason, and their registry // is the same shape of process-global map: drain it here too, while the // function table can still pair each backend handle with the backend that // minted it. MG_Impl::GLImpl::DestroyAllQueryObjects(); MG_Backend::pActiveBackendObject.reset(); MG_State::pGLContext.reset(); MG_State::pEGLContext.reset(); MG_Impl::GLImpl::TextureImpl::pProxyTextureManager.reset(); MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo.reset(); // Must run AFTER pGLContext.reset(). FinalizeProcess -> ShFinalize deletes // glslang's process-wide pool allocator and every cached built-in symbol table, // while the TShader/TProgram objects owned by the shader and program objects // still reference levels adopted from those tables. Finalizing first left live // glslang objects pointing at freed memory for the rest of the teardown. glslang::FinalizeProcess(); // Immediately after, and never apart from it: FinalizeProcess just deleted the // built-in symbol tables the prewarm latch stands for, so leaving it set would // make the next Initialize() skip a prewarm it genuinely needs. MG_Util::ShaderTranspiler::ShaderCompiler::ResetPrewarmLatch(); MG_Backend::gBackendFunctionsTable = {}; g_isInitialized = false; if (logLifecycle) { MG_Util::Debug::Close(); } // TODO: add and use Destroy functions for other subsystems } } void Initialize() { if (g_isInitialized) { MGLOG_D("MobileGL already initialized; skipping duplicate Initialize()"); return; } MG_Util::Debug::InitFile(); MGLOG_I("Initializing MobileGL..."); MG_ConfigLoader::Init(); MGLOG_I("Config loaded"); MG_State::Init(); MGLOG_D("MG_State initialized"); MG_Backend::Init(); MGLOG_D("MG_Backend initialized"); MG_Impl::Init(); MGLOG_D("MG_Impl initialized"); glslang::InitializeProcess(); // On the GL thread, before any worker can exist. glslang builds its built-in symbol // tables lazily under a process-wide lock held for the whole build, so without this // the first concurrent compiles of a shaderpack all serialize behind the very first // parse and asynchronous compilation looks like it is doing nothing. // // Gated on the flag, because the problem it solves only exists when there are // workers: with compilation synchronous, nothing ever contends for that lock and the // three throwaway parses buy nothing - they just add to every eglInitialize. Read the // flag here rather than inside PrewarmBuiltins so ShaderCompiler keeps no dependency // on the async subsystem (ProgramUtilTest compiles that file without it). if (MG_Util::Async::AsyncShaderCompileEnabled()) { MG_Util::ShaderTranspiler::ShaderCompiler::PrewarmBuiltins(); } MGLOG_D("glslang initialized"); g_isInitialized = true; 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); } // 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 backend-initializing 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. macOS has a lightweight // dyld constructor that installs NSOpenGL dispatch hooks only; full backend // initialization still enters here from the first hooked CGL context. } // namespace MobileGL