Files
MobileGL/MobileGL/Init.cpp
T
swung0x48 6aed3b08f3 [Fix] (DirectVulkan, GLImpl, MG_State, ShaderTranspiler): second audit round over the remaining memo sites
Six more verified defects from the residual memo/cache mechanisms:

- Program resource cache (DirectVulkan reflection): glShaderStorageBlockBinding
  deliberately does not bump the backend state version, and the SSO pipeline
  composite is unnamed so the by-name in-place patch can never reach its slot -
  the composite kept serving pre-rebind SSBO bindings. The cache now keys on the
  program's block-binding version; a binding-only change re-applies the overrides
  by name instead of re-running spirv-reflect. SetShaderStorageBlockBinding also
  gains the equality bail-out its uniform-block sibling has, so the composite
  mirror's replay stops churning the version every draw.
- LinkProgram's allowVSOnlyPrograms function-static latch never set its own
  initialized flag (dead memo, re-read every call) - and completing it would have
  frozen a per-backend capability across re-initialization. Replaced with a fresh
  per-link read from the null-checked active backend.
- Query object registry: drained at full library teardown (DestroyAllQueryObjects,
  mirroring DestroyAllSyncObjects) - undeleted queries and their backend wrappers
  leaked across Destroy/Initialize cycles, stale ids stayed IsQuery == GL_TRUE in
  the re-initialized library, and a later delete could hand the old backend's
  wrapper to a different backend's DeleteBackendQuery.
- Converted vertex streams and the host-side EBO max-index scan now SyncGpuWrites
  before reading the coherent mapping: XFB/SSBO/image writes are merely recorded
  at that point, so the conversion read pre-write bytes (the restart-index
  rewrite already synced; these two host reads did not).
- Zero-stride converted bindings: both converters rejected stride 0, making the
  factory's documented single-element conversion unreachable and silently
  dropping every draw using such a binding; the stride is substituted with the
  element size for the one-element case.
- DemoteFloat64Pass block relayout: measurement queued into the module eagerly,
  so a mid-struct failure left a half-relaid-out block (compacted offsets before
  the failing member, 64-bit offsets after) while claiming the block was left
  alone. Decoration writes are now collected and committed only when the whole
  block measures successfully.
2026-08-19 16:41:28 -04:00

154 lines
6.9 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 <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();
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