mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
iterationRP's Program 203 declares shared vec2 prefixSumCache[32] for a 512-invocation workgroup indexed by gl_SubgroupID; any device narrower than 16 lanes partitions into more than 32 subgroups and the pack writes shared memory out of bounds (heap corruption on lavapipe's CPU rasterizer, ssim 0.028 on the CI retrace). Fix it where the fault lies - in the fixture - and keep the GL contract sound everywhere else: - FixIterationRPSubgroupScratchPass: fingerprint-gated SPIR-V pass that grows exactly that array to ceil(invocations/width) entries on sub-16-lane devices; every other module passes through byte-identical. - DeriveNumSubgroupsPass stays default-on for the Adreno topology bug and is made spec-sound: pipelines request REQUIRE_FULL_SUBGROUPS whenever the workgroup shape makes the flag legal (computeFullSubgroups enabled, local_size_x a multiple of the native width, subgroup count within maxComputeWorkgroupSubgroups). - EmulateSubgroupsPass: 32-lane virtual-subgroup lowering kept in-tree as a last resort, enabled only by MOBILEGL_MAGMA_EMULATE_SUBGROUP=1 on devices with no native subgroup support; fails closed on extended subgroup instructions and on modules whose added scratch would exceed maxComputeSharedMemorySize. - IterationRPFirstReductionScenario skips gracefully outside the pack's 16..256-lane source domain; the new IterationRPScratchFixScenario runs the fixture-shaped reduction on any width and asserts the exact width-independent total. DriverPost keeps reporting FAIL on out-of-domain devices. - Program203 -> IterationRP rename throughout; the per-trace num_subgroups_quirk plumbing is removed from the trace replayer, JNI chain, and CI workflows.
225 lines
10 KiB
C++
225 lines
10 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.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");
|
|
}
|
|
|
|
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
|