// MobileGL - MobileGL/Config.h
// 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

#pragma once
#include <Includes.h>
#include <MG_Backend/BackendObjects.h>

namespace MobileGL::MG_Config {
    inline const String ProjectName = "MobileGL";
    inline const String CoreName = "MobileGL Core";
    inline const String CoreVendor = "MobileGL-Dev (BZLZHH, Swung0x48, Tungsten)";
    inline const Version CoreVersion = {26, 8, 0, "-dev", VersionType::Development};
    inline const VersionStringFormatAttrib DefaultVersionStringFormatAttrib = {2, 2, 0, true, true};
    inline const Uint64 CacheVersion = 0;

    extern BackendType ActiveBackendType;

    // Tri-state override for device-specific quirks: Auto lets the detected device decide,
    // ForceOn/ForceOff bypass the detection in either direction. ForceOn only bypasses the
    // device gate - each quirk keeps its structural safety checks.
    enum class QuirkOverride : Uint8 {
        Auto = 0,
        ForceOn,
        ForceOff,
    };

    // Preferred DirectVulkan dispatch tier for the glMultiDraw* families. A preference,
    // never a demand: the renderer clamps it to what the device supports at device
    // creation, falling down the chain ext -> indirect -> unroll with one log line.
    enum class MultiDrawMode : Uint8 {
        Auto = 0, // unset: best supported tier
        Ext,      // VK_EXT_multi_draw: one vkCmdDrawMultiEXT / vkCmdDrawMultiIndexedEXT
        Indirect, // multiDrawIndirect feature: one vkCmdDraw*Indirect over a transient command array
        Unroll,   // one vkCmdDraw* per sub-draw
    };

    // Preferred DirectGLES emulation tier for glMultiDrawElements(BaseVertex). GLES has no
    // such entry point in core, so every tier below is an emulation; they differ only in
    // which driver capability they lean on and how many driver calls a batch costs. Like
    // the Magma knob this is a preference, clamped at resolution time to what the ES
    // driver actually supports, with one log line when it falls back.
    enum class GLESMultiDrawMode : Uint8 {
        Auto = 0,      // unset: best supported tier
        Ext,           // one glMultiDrawElementsBaseVertexEXT
        MultiIndirect, // one glMultiDrawElementsIndirectEXT over a scratch command buffer
        Indirect,      // one glDrawElementsIndirect per sub-draw over that same buffer
        BaseVertex,    // one glDrawElementsBaseVertex per sub-draw
        DrawElements,  // baseVertex folded into a scratch index buffer on the CPU, then plain
                       // glDrawElements per sub-draw (for drivers with no base-vertex draw at all)
        Compute,       // a compute shader flattens every sub-draw into one rebased index buffer,
                       // drawn by a single glDrawElements
    };

    // Feature toggles parsed once from environment variables in MG_ConfigLoader::Init()
    // (ConfigLoader.cpp), before the accepted-env map is destroyed. All Bool fields share
    // one truthy rule: the variable is set, non-empty, not "0", and not "false"
    // (case-insensitive).
    //
    // Env variables intentionally NOT mirrored here (kept as live std::getenv at their
    // call sites):
    //   - DISPLAY: X11 session variable, not MobileGL configuration.
    //   - MOBILEGL_LOG_FILE_PATH: log-file init runs before MG_ConfigLoader::Init
    //     (see MG_Util/Debug/Log.cpp).
    struct FeaturesTable {
        // MOBILEGL_DISABLE_TIMERQUERY: do not advertise or use GPU timer queries.
        Bool DisableTimerQuery = false;
        // MOBILEGL_ENABLE_GLES_TEXTURE_VIEW: advertise GL_ARB_texture_view on DirectGLES when
        // the host ES driver has EXT/OES_texture_view. Off by default: the host extension is
        // present on Adreno 830 and the functional half of KHR-GL4{2,3}.texture_view still fails
        // there, because the view's ES internalformat is normalized independently of the storage
        // it aliases (see BackendObject_DirectGLES::BuildAdvertisedExtensions). The flag exists
        // so that work can be done without editing the gate.
        Bool EnableGlesTextureView = false;
        // MOBILEGL_ENABLE_SPIRV_VALIDATION: validate generated and transformed SPIR-V.
        // Disabled by default because validation is a diagnostics-only cost.
        Bool EnableSpirvValidation = false;
        // MOBILEGL_USE_ANGLE: load ANGLE EGL/GLES libraries.
        Bool UseAngle = false;
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
        // MOBILEGL_TRACE_ANGLE_VARIANT: signed trace-APK ANGLE build short hash.
        String TraceAngleVariant;
#endif
        // MOBILEGL_DISABLE_SUBGROUP: force-disable Vulkan shader subgroup support,
        // including the opt-in emulated compute path below.
        Bool DisableSubgroup = false;
        // MOBILEGL_MAGMA_EMULATE_SUBGROUP: implement GL_KHR_shader_subgroup's compute
        // stage on a 32-lane VIRTUAL subgroup lowered to workgroup-shared memory
        // (ShaderTranspiler::EmulateSubgroupsPass). Strictly a last resort: it only ever
        // engages when this flag is set AND the device has no native subgroup support at
        // all - a device with real subgroup operations always uses them natively,
        // whatever their width (the known iterationRP defect is patched by
        // FixIterationRPSubgroupScratch below instead). Off by default.
        Bool MagmaEmulateSubgroup = false;
        // MOBILEGL_FIX_ITERATIONRP_SUBGROUP_SCRATCH: patch iterationRP's own bug - the
        // pack declares `shared vec2 prefixSumCache[32]` for a 512-invocation exposure
        // reduction and indexes it by gl_SubgroupID, so any device with sub-16-lane
        // subgroups (8-lane lavapipe -> 64 subgroups) writes shared memory out of
        // bounds. The pass grows that one array to what the device's topology needs and
        // touches nothing else; it only rewrites modules positively matching the pack's
        // reduction fingerprint (ShaderTranspiler::FixIterationRPSubgroupScratchPass),
        // so every other shader passes through byte-identical - as does iterationRP
        // itself on >= 16-lane devices. Auto is ON; ForceOff replays the pack's bug
        // verbatim.
        QuirkOverride FixIterationRPSubgroupScratch = QuirkOverride::Auto;
        // MOBILEGL_ITERATIONRP_FIX_BARRIER: repair Program 203's missing workgroup
        // rendezvous between its two reductions over prefixSumCache. Off by default and
        // fingerprint-gated by FixIterationRPBarrierPass when enabled.
        Bool IterationRPFixBarrier = false;
        // MOBILEGL_DERIVE_NUM_SUBGROUPS: replace compute gl_NumSubgroups loads with
        // ceil(workgroup invocations / gl_SubgroupSize) on the NATIVE subgroup path
        // (ShaderTranspiler::DeriveNumSubgroupsPass). Auto is ON: GL requires
        // gl_SubgroupID < gl_NumSubgroups, Adreno's builtin reports 1 while the same
        // dispatch emits IDs 0..7, and the derived value is the one Vulkan guarantees
        // whenever the pipeline can request REQUIRE_FULL_SUBGROUPS (which the renderer
        // does whenever local_size_x is a multiple of the native width). ForceOff returns
        // to the raw driver builtin.
        QuirkOverride DeriveNumSubgroups = QuirkOverride::Auto;
        // MOBILEGL_ADVERTISE_FP64: add GL_ARB_gpu_shader_fp64 to the advertised extension
        // string. `double` in a shader always WORKS - it is narrowed to 32 bits before any
        // module reaches a backend (ShaderTranspiler::DemoteFloat64Pass) - but the extension
        // promises 64-bit precision, and that is the one thing the narrowing cannot deliver.
        // Off by default so an application that checks the string before using doubles keeps
        // its float path; on for measuring what the conformance suite makes of the demoted
        // precision. See the DemoteFloat64Pass header and the "fp64" POST row.
        Bool AdvertiseFp64 = false;
        // MOBILEGL_MAGMA_R11G11B10F_FALLBACK: use fallback format for R11G11B10F on Vulkan.
        Bool MagmaR11G11B10FFallback = false;
        // MOBILEGL_MAGMA_FRAMESINFLIGHT: requested Magma frames in flight, defaulting to 3.
        Uint32 MagmaFramesInFlight = 3;
        // MOBILEGL_AVOID_SAMPLER_MIPMAP_MIN_FILTER: avoid mipmap min filters in samplers,
        // resolves certain rendering bugs on ANGLE + llvmpipe.
        Bool AvoidSamplerMipmapMinFilter = false;
        // MOBILEGL_AVOID_EXPLICIT_LOD_BIAS: leave an already-explicit LOD argument alone when
        // emulating GL_TEXTURE_LOD_BIAS, instead of adding the bias uniform to it. Injecting
        // the uniform turns a compile-time-constant LOD into a runtime expression, which
        // sends ANGLE + llvmpipe down a mip-selection path that dereferences a NULL
        // descriptor and kills the process. Deviates from spec (Vulkan adds the bias to
        // OpImageSampleExplicitLod), so it is an avoidance for that stack only.
        Bool AvoidExplicitLodBias = false;
        // MOBILEGL_COHERENT_AS_FLUSH: app-compat for engines (e.g. Flywheel) that write
        // GPU-read data through persistent GL_MAP_FLUSH_EXPLICIT_BIT maps they never
        // flush. Persistent FLUSH_EXPLICIT map requests are rewritten to coherent
        // semantics: writes reach the backend without glFlushMappedBufferRange, and
        // flush calls on rewritten maps become error-free no-ops. Non-persistent maps
        // keep spec FLUSH_EXPLICIT behavior.
        Bool CoherentAsFlush = false;
        // MOBILEGL_TRACE_SKIP_AUTODESTROY: skip teardown in the ELF destructor (Init.cpp).
        Bool TraceSkipAutodestroy = false;
        // MOBILEGL_DISABLE_UBO_RING: force the DirectGLES global-UBO upload back to the
        // per-draw glBufferSubData path instead of the persistent-mapped ring allocator
        // (negative control / driver-bug escape hatch).
        Bool DisableUboRing = false;
        // MOBILEGL_DISABLE_UNPACK_RING: force DirectGLES texture uploads back to
        // glTexSubImage from the client pointer instead of staging them through the
        // persistent-mapped unpack-PBO ring (negative control / driver-bug escape
        // hatch).
        Bool DisableUnpackRing = false;
        // MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION: make DirectGLES skip the native ES
        // depth/stencil reads and always go through the shader-sampling emulation. Core GL
        // ES has no depth or stencil readback, but some drivers accept it anyway (Mesa does,
        // Adreno does not), which means the emulation is dead code on exactly the stack the
        // headless suite runs on. This forces it live so the scenarios and the CTS can
        // exercise the path, and gives the device an A/B lever over the same choice.
        Bool EsprytForceDepthStencilReadbackEmulation = false;
        // MOBILEGL_RELAXED_SEMANTICS: relax strict core-profile rules (e.g. VAO-0 draws,
        // texture-name reuse after delete) even on contexts that explicitly requested a core
        // profile. Without it, relaxed semantics still apply to every context that did not
        // explicitly request a core profile via EGL_CONTEXT_OPENGL_PROFILE_MASK / a >=3.1
        // version request.
        Bool RelaxedSemantics = false;
        // MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE: overrides the DirectVulkan quirk that
        // strips depth writes from accumulation-blended pipelines (MIN/MAX or additive
        // ONE+ONE - the multi-pass depth-equality signature) on drivers without
        // cross-pipeline vertex position invariance. Sorted-transparency "over" blends,
        // gl_FragDepth writers, and fully color-masked attachments are exempt (see
        // PipelineFactory::ShouldSuppressDepthWrite). Auto detects Qualcomm.
        QuirkOverride MagmaDisableBlendedDepthWriteQuirk = QuirkOverride::Auto;
        // MOBILEGL_DISABLE_ROBUST_BUFFER_ACCESS: leave the Vulkan robustBufferAccess device
        // feature off. It is enabled by default to match GL's defined out-of-range fetch
        // behavior; this escape hatch exists to measure or dodge its GPU cost on a device.
        Bool DisableRobustBufferAccess = false;
        // MOBILEGL_MAGMA_MULTIDRAW_MODE: preferred DirectVulkan multi-draw dispatch tier
        // ("ext" | "indirect" | "unroll", see MultiDrawMode). Clamped to device support;
        // unset picks the best supported tier.
        MultiDrawMode MagmaMultiDrawMode = MultiDrawMode::Auto;
        // MOBILEGL_ESPRYT_MULTIDRAW_MODE: preferred DirectGLES glMultiDrawElements emulation
        // tier ("ext" | "multiindirect" | "indirect" | "basevertex" | "drawelements" |
        // "compute", see GLESMultiDrawMode). Clamped to driver support; unset picks the best
        // supported tier, which never includes "compute" - see the note on its resolution.
        GLESMultiDrawMode EsprytMultiDrawMode = GLESMultiDrawMode::Auto;
        // MOBILEGL_ASYNC_SHADER_COMPILE: overrides asynchronous shader compilation. Unset
        // keeps the built-in default (MG_Util::Async::kAsyncShaderCompileDefault); falsy
        // forces every glCompileShader/glLinkProgram to run synchronously on the calling
        // thread AND withdraws GL_KHR_parallel_shader_compile, so the single switch reverts
        // both the threading and the application-visible behaviour change.
        QuirkOverride AsyncShaderCompile = QuirkOverride::Auto;
        // MOBILEGL_ASYNC_SHADER_COMPILE_THREADS: shader-compile worker count. 0 (unset) means
        // auto, which is min(4, big cores); an explicit value is honoured as given.
        Uint32 AsyncShaderCompileThreads = 0;
        // MOBILEGL_ASYNC_OPTIMISTIC_SHADER_STATUS: while a compile job is still in flight,
        // glGetShaderiv(GL_COMPILE_STATUS) answers GL_TRUE and the shader info log reads
        // empty, WITHOUT joining the job (latched per compile - see
        // ShaderObject::TakeOptimisticCompileAnswer). A deliberate, bounded spec violation:
        // a real failure still fails the program link with the compile log quoted. It
        // exists for applications that compile hundreds of shaders serially and read the
        // status right after each glCompileShader - Iris's shader-pack load - where those
        // per-shader joins are what serializes the batch on its main path (Iris's gbuffer
        // phase issues no program-level query between programs; program-level LINK_STATUS
        // and the program info log still join truthfully, so paths that check each link
        // immediately stay serial by their own construction). Off by default; never
        // advertise it.
        QuirkOverride AsyncOptimisticShaderStatus = QuirkOverride::Auto;
        // MOBILEGL_SHADER_CACHE: the three-level, in-memory shader translation memo
        // (MG_Util/ShaderTranspiler/TranslationCache.h). The levels follow the GL
        // entry points - L1c memoizes one glCompileShader's PARSE VERDICT, L1 a
        // linked program's whole front end, L2 DirectGLES's emitted ESSL. Auto is
        // ON; ForceOff turns ALL THREE off and makes every translation run from
        // scratch. The escape hatch exists because a wrong cache hit is a silently
        // miscompiled shader: if a device ever renders differently with the cache
        // on, one run with this falsy says so.
        QuirkOverride ShaderTranslationCache = QuirkOverride::Auto;
        // MOBILEGL_FORCE_VIEWPORT_ARRAY_EMULATION: DirectGLES' gl_ViewportIndex routing
        // emulation - the builtin becomes a flat varying, the fragment stage gets a
        // per-pass gate, and a routed draw is REPLAYED once per distinct viewport state
        // with the real glViewport/glScissor/glDepthRangef set for it. Auto is ON, and
        // it is ON even where the driver advertises GL_OES_viewport_array, because that
        // extension only ever gave the SHADER a compilable name: MobileGL has never
        // programmed a driver's INDEXED viewport state (SyncRenderState pushes index 0
        // and nothing else), so on an extension-capable driver every index rasterized as
        // index 0 exactly as it did without one. ForceOff returns to that behaviour -
        // the pre-emulation path, extension passthrough where it exists and
        // LowerViewportIndexPass' demote-to-a-plain-global where it does not - and is
        // the negative control the emulation is measured against.
        QuirkOverride ViewportArrayEmulation = QuirkOverride::Auto;
    };
    extern FeaturesTable Features;
} // namespace MobileGL::MG_Config
