Files
MobileGL/MobileGL/ConfigLoader.cpp
T
BZLZHH 8191075133 [Feat] (MG_Util): the async-compile pool skeleton behind a default-off flag (P1 stage 1)
Standalone Asio (submodule, asio-1-38-2 @ 8806a680, ASIO_STANDALONE +
ASIO_NO_DEPRECATED, header-only - no linked artifact) and the job machinery
the async shader pipeline will run on: JobNode (state machine with deferred
errors, continuations firing exactly once, dependency counters, cancel
semantics split into request vs outcome) and ShaderCompilePool
(asio::thread_pool behind a pimpl so no header leaks asio; big-core count
via cpufreq at >=85% of peak clamped to [1,4]; lazily constructed, so with
the flag off no worker thread ever exists; StopAndDrain leads DestroyImpl).

MOBILEGL_ASYNC_SHADER_COMPILE / _THREADS config knobs, default OFF. Nothing
in the GL pipeline references the pool yet - grep-verified; the full
DirectGLES retrace and compile benches are byte- and time-identical. 25
threaded unit tests, clean across 20x gtest_repeat.
2026-08-08 05:28:51 -04:00

215 lines
9.8 KiB
C++

// MobileGL - MobileGL/ConfigLoader.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 "Config.h"
#include <cerrno>
#include <cstdlib>
#ifndef _WIN32
extern char** environ;
#endif
namespace MobileGL::MG_Config {
// Zero/default-initialized at static-init time (all fields have constexpr-friendly
// defaults), so it is safe to read even if MG_ConfigLoader::Init has not run yet.
FeaturesTable Features;
} // namespace MobileGL::MG_Config
namespace MobileGL::MG_ConfigLoader {
static UniquePtr<UnorderedMap<String, String>> acceptedEnvVariablesMap;
static Bool IsAcceptedPrefix(const String& key) {
return (key.compare(0, 6, "LIBGL_") == 0 || key.compare(0, 9, "MOBILEGL_") == 0);
}
inline void InitializeAcceptedEnvVariables() {
if (!acceptedEnvVariablesMap) {
acceptedEnvVariablesMap = MakeUnique<UnorderedMap<String, String>>();
} else {
acceptedEnvVariablesMap->clear();
}
char** envPtr = nullptr;
#ifdef _WIN32
envPtr = _environ;
#else // POSIX
envPtr = ::environ;
#endif
if (envPtr == nullptr) return;
for (char** env = envPtr; *env != nullptr; ++env) {
String entry(*env);
SizeT pos = entry.find('=');
if (pos != String::npos) {
String key = entry.substr(0, pos);
String value = entry.substr(pos + 1);
if (IsAcceptedPrefix(key)) {
(*acceptedEnvVariablesMap)[key] = value;
MGLOG_D("Config: Accepted env variable: %s=%s", key.c_str(), value.c_str());
}
}
}
}
inline void QueryEnvVariable(const String& key, String& outValue, const String& defaultValue) {
auto it = acceptedEnvVariablesMap->find(key);
if (it != acceptedEnvVariablesMap->end()) {
outValue = it->second;
} else {
outValue = defaultValue;
}
}
// Unified truthy rule for boolean feature env variables: set, non-empty, not "0",
// and not "false" (case-insensitive).
static Bool IsTruthyValue(const String& value) {
if (value.empty() || value == "0") {
return false;
}
String lowered = value;
std::transform(lowered.begin(), lowered.end(), lowered.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return lowered != "false";
}
inline Bool QueryEnvFlag(const String& key) {
auto it = acceptedEnvVariablesMap->find(key);
return it != acceptedEnvVariablesMap->end() && IsTruthyValue(it->second);
}
// Quirk overrides are tri-state: an unset variable keeps device auto-detection, a truthy
// value forces the quirk on, anything else set ("0", "false", "") forces it off.
inline MG_Config::QuirkOverride QueryEnvQuirkOverride(const String& key) {
auto it = acceptedEnvVariablesMap->find(key);
if (it == acceptedEnvVariablesMap->end()) {
return MG_Config::QuirkOverride::Auto;
}
return IsTruthyValue(it->second) ? MG_Config::QuirkOverride::ForceOn
: MG_Config::QuirkOverride::ForceOff;
}
// Multi-draw mode is a named-value preference: unset keeps Auto (best supported tier),
// a recognized name selects that tier as the ceiling, anything else warns and keeps Auto.
inline MG_Config::MultiDrawMode QueryEnvMultiDrawMode(const String& key) {
auto it = acceptedEnvVariablesMap->find(key);
if (it == acceptedEnvVariablesMap->end()) {
return MG_Config::MultiDrawMode::Auto;
}
String lowered = it->second;
std::transform(lowered.begin(), lowered.end(), lowered.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
if (lowered == "ext") return MG_Config::MultiDrawMode::Ext;
if (lowered == "indirect") return MG_Config::MultiDrawMode::Indirect;
if (lowered == "unroll") return MG_Config::MultiDrawMode::Unroll;
if (lowered.empty() || lowered == "auto") return MG_Config::MultiDrawMode::Auto;
MGLOG_W("Config: Ignoring invalid env variable %s='%s'; expected ext|indirect|unroll|auto, using auto",
key.c_str(), it->second.c_str());
return MG_Config::MultiDrawMode::Auto;
}
// Same contract as QueryEnvMultiDrawMode, over the DirectGLES tier names.
inline MG_Config::GLESMultiDrawMode QueryEnvGLESMultiDrawMode(const String& key) {
auto it = acceptedEnvVariablesMap->find(key);
if (it == acceptedEnvVariablesMap->end()) {
return MG_Config::GLESMultiDrawMode::Auto;
}
String lowered = it->second;
std::transform(lowered.begin(), lowered.end(), lowered.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
if (lowered == "ext") return MG_Config::GLESMultiDrawMode::Ext;
if (lowered == "multiindirect") return MG_Config::GLESMultiDrawMode::MultiIndirect;
if (lowered == "indirect") return MG_Config::GLESMultiDrawMode::Indirect;
if (lowered == "basevertex") return MG_Config::GLESMultiDrawMode::BaseVertex;
if (lowered == "drawelements") return MG_Config::GLESMultiDrawMode::DrawElements;
if (lowered == "compute") return MG_Config::GLESMultiDrawMode::Compute;
if (lowered.empty() || lowered == "auto") return MG_Config::GLESMultiDrawMode::Auto;
MGLOG_W("Config: Ignoring invalid env variable %s='%s'; expected "
"ext|multiindirect|indirect|basevertex|drawelements|compute|auto, using auto",
key.c_str(), it->second.c_str());
return MG_Config::GLESMultiDrawMode::Auto;
}
inline Uint32 QueryEnvUint32(const String& key, Uint32 defaultValue, Uint32 minValue, Uint32 maxValue) {
auto it = acceptedEnvVariablesMap->find(key);
if (it == acceptedEnvVariablesMap->end()) {
return defaultValue;
}
const String& value = it->second;
char* parseEnd = nullptr;
errno = 0;
const unsigned long parsedValue = std::strtoul(value.c_str(), &parseEnd, 10);
if (parseEnd == value.c_str() || *parseEnd != '\0' || errno == ERANGE || parsedValue < minValue ||
parsedValue > maxValue) {
MGLOG_W("Config: Ignoring invalid env variable %s='%s'; expected an integer in range [%u, %u], "
"using default %u",
key.c_str(), value.c_str(), minValue, maxValue, defaultValue);
return defaultValue;
}
return static_cast<Uint32>(parsedValue);
}
inline void InitFeatures() {
auto& features = MG_Config::Features;
features.DisableTimerQuery = QueryEnvFlag("MOBILEGL_DISABLE_TIMERQUERY");
features.UseAngle = QueryEnvFlag("MOBILEGL_USE_ANGLE");
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
QueryEnvVariable("MOBILEGL_TRACE_ANGLE_VARIANT", features.TraceAngleVariant, "");
#endif
features.DisableSubgroup = QueryEnvFlag("MOBILEGL_DISABLE_SUBGROUP");
features.MagmaR11G11B10FFallback = QueryEnvFlag("MOBILEGL_MAGMA_R11G11B10F_FALLBACK");
features.MagmaFramesInFlight = QueryEnvUint32("MOBILEGL_MAGMA_FRAMESINFLIGHT", 3, 1, 64);
features.AvoidSamplerMipmapMinFilter =
QueryEnvFlag("MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER");
features.CoherentAsFlush = QueryEnvFlag("MOBILEGL_COHERENT_AS_FLUSH");
features.TraceSkipAutodestroy = QueryEnvFlag("MOBILEGL_TRACE_SKIP_AUTODESTROY");
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
features.SubgroupPrefixScanQuirk = QueryEnvQuirkOverride("MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN");
features.MagmaDisableBlendedDepthWriteQuirk =
QueryEnvQuirkOverride("MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE");
features.DisableRobustBufferAccess = QueryEnvFlag("MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS");
features.MagmaMultiDrawMode = QueryEnvMultiDrawMode("MOBILEGL_MAGMA_MULTIDRAW_MODE");
features.EsprytMultiDrawMode = QueryEnvGLESMultiDrawMode("MOBILEGL_ESPRYT_MULTIDRAW_MODE");
features.AsyncShaderCompile = QueryEnvQuirkOverride("MOBILEGL_ASYNC_SHADER_COMPILE");
features.AsyncShaderCompileThreads = QueryEnvUint32("MOBILEGL_ASYNC_SHADER_COMPILE_THREADS", 0, 0, 64);
}
inline void InitBackendType() {
String backendTypeStr;
QueryEnvVariable("MOBILEGL_BACKEND_TYPE", backendTypeStr, "DirectGLES");
#define ENTRY(backendType) \
if (backendTypeStr == #backendType) { \
MG_Config::ActiveBackendType = BackendType::backendType; \
MGLOG_I("Config: Active backend type set to " #backendType); \
return; \
}
ENTRY(DirectGLES)
ENTRY(DirectVulkan)
ENTRY(Unknown)
MG_Config::ActiveBackendType = BackendType::Unknown;
#undef ENTRY
}
void Init() {
MGLOG_D("Loading configuration from environment variables...");
InitializeAcceptedEnvVariables();
InitBackendType();
InitFeatures();
// Destroy the map since we won't need it anymore
acceptedEnvVariablesMap.reset();
}
} // namespace MobileGL::MG_ConfigLoader