mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
164 lines
7.6 KiB
C++
164 lines
7.6 KiB
C++
// 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 <MG_Backend/BackendObjects.h>
|
|
#include <MG_Backend/DirectVulkan/DirectVulkan.h>
|
|
#include <MG_State/GLState/Core.h>
|
|
#include <MG_State/EGLState/Core.h>
|
|
#include <MG_Impl/GLImpl/Texture/ProxyTexture.h>
|
|
#include <MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h>
|
|
#include <MG_Impl/GLImpl/Sync/GL_Sync.h>
|
|
#include <MG_Impl/GLImpl/Query/GL_Query.h>
|
|
#include <MG_Util/Async/ShaderCompilePool.h>
|
|
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
|
#include <MG_State/GLState/ProgramState/ProgramTranslationCache.h>
|
|
#include <MG_Util/ShaderTranspiler/TranslationCache.h>
|
|
|
|
#include <atomic>
|
|
#include <mutex>
|
|
|
|
namespace MobileGL {
|
|
namespace {
|
|
std::atomic<Bool> 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();
|
|
// The two-level translation memo. Nothing in it references a glslang object -
|
|
// both levels hold plain bytes - so this is RSS hygiene rather than a lifetime
|
|
// requirement, and it is safe either side of FinalizeProcess. Stats first: an
|
|
// fordebug build gets one line per level saying how the run went.
|
|
MG_Util::ShaderTranspiler::LogShaderTranslationCacheStats();
|
|
MG_Util::ShaderTranspiler::ClearShaderTranslationCaches();
|
|
MG_State::GLState::LogProgramTranslationCacheStats();
|
|
MG_State::GLState::ClearProgramTranslationCache();
|
|
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<std::mutex> 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
|