mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
273 lines
14 KiB
C++
273 lines
14 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/Metrics/PipeStats.h>
|
|
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
|
|
#include <MG_State/GLState/ProgramState/ProgramTranslationCache.h>
|
|
#include <MG_Util/ShaderTranspiler/TranslationCache.h>
|
|
#if MOBILEGL_PIPE_VERIFY
|
|
#include <MG_Backend/MGPipe/PipeInputs.h> // MGPipeVerifyFlushSummary, before Debug::Close in DestroyImpl
|
|
#endif
|
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
|
#include <MG_Remote/Client/ServerProbe.h>
|
|
#endif
|
|
|
|
#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...");
|
|
}
|
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
|
// P5: the split roles come down FIRST - ARCHITECTURE.md:537's order puts the whole
|
|
// of it before MobileGL::Destroy(), and this function IS MobileGL::Destroy. A no-op
|
|
// in a monolith RUN; absent from a monolith BUILD, because G1 admits no new pull
|
|
// symbol and no resized one (the first version called it unconditionally and moved
|
|
// DestroyImpl by 32 bytes). See BackendObjects.h for why the position rather than
|
|
// the call is the load-bearing part.
|
|
MG_Backend::ShutdownSplitRoles();
|
|
#endif
|
|
// Before any subsystem the counters name goes away, and before the last frame's
|
|
// numbers can be lost: emits the final summary line and, when
|
|
// MOBILEGL_PIPE_STATS_FILE is set, the JSON dump. A no-op when the counters are
|
|
// off, and idempotent.
|
|
MG_Util::PipeStats::Shutdown();
|
|
// 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 MOBILEGL_BUILD_DISAGGREGATED
|
|
// A later re-initialization asks whether the server is there again.
|
|
MG_Remote::Client::ResetServerProbeCache();
|
|
#endif
|
|
#if MOBILEGL_PIPE_VERIFY
|
|
// BEFORE Close, and from here rather than from a static destructor (V1 fix round 2):
|
|
// Close() nulls the role's sink and Log.cpp's next write reopens it with "w", so the
|
|
// FATAL=0 summary the comparator used to write from __run_exit_handlers truncated the
|
|
// client half of every such run to that one line. Verify builds only - the guard is
|
|
// the compile definition, not a runtime knob, and the pull build gains no byte (G1).
|
|
MG_Pipe::MGPipeVerifyFlushSummary();
|
|
#endif
|
|
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");
|
|
// Immediately after the config load and before anything can count: the MGPipe
|
|
// boundary counters latch their enable flag here, so every counting site in the
|
|
// two backends is a load of an already-settled global for the rest of the run.
|
|
MG_Util::PipeStats::Init();
|
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
|
// F1 (P7 wave 2). THE WIRE COMES UP BEFORE THE FIRST FRONTEND OBJECT IS BORN, and the
|
|
// ORDER is the fix - not a wait, and not a friendlier default mask.
|
|
//
|
|
// MG_State::Init() constructs GLContext, and GLContext's TextureState constructor
|
|
// materialises one default texture object per target (GL 3.3 core 3.8,
|
|
// TextureState.cpp:62-71). TextureObjectBase's constructor emits resource_create for
|
|
// every one of them (TextureState/TextureObject.cpp:168-169), and that emission is
|
|
// gated, through PipeFill's FamilyIsLive, on
|
|
// CapsMirror::ServerConsumes(kMGPipeSubsystemTextureResources). In the old order the
|
|
// mirror was still GENERATION 0 when it was asked - it had no snapshot because the
|
|
// session had not been started yet, MG_Backend::Init() being what starts it - so it
|
|
// answered from the placeholder, every one of those creates was withheld, and the log
|
|
// said so once and then never again:
|
|
//
|
|
// MG_Remote client: the server does not consume MGPipe subsystem 0x400 - this
|
|
// family emits NOTHING and the legacy pull path runs for it (R-8). callMask=0x0,
|
|
// caps generation 0
|
|
//
|
|
// That is not a race and it never was one: the line reproduces byte-for-byte on
|
|
// lavapipe and on an Adreno 830, because the two Init calls are two statements in one
|
|
// thread. And "the legacy pull path runs for it" is false under split - the client has
|
|
// no pull path to fall back to - so the records are simply lost, and only a later
|
|
// respecify's self-healing create (TextureEmit.h:872) ever brings one back.
|
|
//
|
|
// A BOUNDED WAIT INSIDE ServerConsumes CANNOT FIX THIS AND WOULD DEADLOCK: during
|
|
// MG_State::Init() no handshake is in flight, and the one that will land is started
|
|
// LATER BY THIS SAME THREAD. The only correct answer is to have already brought it up,
|
|
// which is what this hook does. ServerConsumes now REFUSES to answer from a
|
|
// placeholder (CapsMirror.cpp), so a regression of this order is a named Fatal rather
|
|
// than a whole family of silently dropped records.
|
|
//
|
|
// In a build without MOBILEGL_BUILD_DISAGGREGATED this statement does not exist, so
|
|
// the pull build gains no symbol, no branch and no byte - which is what G1 measures.
|
|
const Bool splitBackendIsUp = MG_Backend::InitSplitRolesBeforeState();
|
|
#endif
|
|
MG_State::Init();
|
|
MGLOG_D("MG_State initialized");
|
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
|
if (!splitBackendIsUp)
|
|
#endif
|
|
{
|
|
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);
|
|
}
|
|
|
|
Bool ImplementationAvailable() {
|
|
if (g_isInitialized.load(std::memory_order_acquire)) {
|
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
|
// Initialized: under a split transport the session either came up (a backend object
|
|
// exists) or it did not, and a display on a session that never came up cannot draw.
|
|
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) {
|
|
return MG_Backend::pActiveBackendObject != nullptr;
|
|
}
|
|
#endif
|
|
return true;
|
|
}
|
|
#if defined(__linux__) && !defined(__ANDROID__)
|
|
// LIBGL_ALWAYS_SOFTWARE is how a process (or whoever launched it) asks the system's GL for a
|
|
// software renderer. MobileGL is a GPU forwarder, so it steps aside and lets the system's
|
|
// other implementation give the process what it asked for.
|
|
{
|
|
String software;
|
|
if (MG_ConfigLoader::LookupSetting("LIBGL_ALWAYS_SOFTWARE", software) && !software.empty() &&
|
|
software != "0" && software != "false") {
|
|
return false;
|
|
}
|
|
}
|
|
#endif
|
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
|
// Not yet: the bring-up would dial the configured server and wait for it. Ask first.
|
|
return MG_Remote::Client::ConfiguredServerAvailable();
|
|
#else
|
|
return true;
|
|
#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 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
|