mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
229 lines
11 KiB
C++
229 lines
11 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.EnableSpirvValidation = QueryEnvFlag("MOBILEGL_ENABLE_SPIRV_VALIDATION");
|
|
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.MagmaEmulateSubgroup = QueryEnvFlag("MOBILEGL_MAGMA_EMULATE_SUBGROUP");
|
|
features.FixIterationRPSubgroupScratch =
|
|
QueryEnvQuirkOverride("MOBILEGL_FIX_ITERATIONRP_SUBGROUP_SCRATCH");
|
|
features.IterationRPFixBarrier = QueryEnvFlag("MOBILEGL_ITERATIONRP_FIX_BARRIER");
|
|
features.DeriveNumSubgroups = QueryEnvQuirkOverride("MOBILEGL_DERIVE_NUM_SUBGROUPS");
|
|
features.AdvertiseFp64 = QueryEnvFlag("MOBILEGL_ADVERTISE_FP64");
|
|
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.AvoidExplicitLodBias = QueryEnvFlag("MOBILEGL_AVOID_EXPLICIT_LOD_BIAS");
|
|
features.CoherentAsFlush = QueryEnvFlag("MOBILEGL_COHERENT_AS_FLUSH");
|
|
features.TraceSkipAutodestroy = QueryEnvFlag("MOBILEGL_TRACE_SKIP_AUTODESTROY");
|
|
features.DisableUboRing = QueryEnvFlag("MOBILEGL_DISABLE_UBO_RING");
|
|
features.EsprytForceDepthStencilReadbackEmulation =
|
|
QueryEnvFlag("MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION");
|
|
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
|
|
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);
|
|
features.AsyncOptimisticShaderStatus =
|
|
QueryEnvQuirkOverride("MOBILEGL_ASYNC_OPTIMISTIC_SHADER_STATUS");
|
|
features.ShaderTranslationCache = QueryEnvQuirkOverride("MOBILEGL_SHADER_CACHE");
|
|
features.ViewportArrayEmulation =
|
|
QueryEnvQuirkOverride("MOBILEGL_FORCE_VIEWPORT_ARRAY_EMULATION");
|
|
}
|
|
|
|
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
|