mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
F1. `MG_State::Init()` runs before `MG_Backend::Init()`, and the second is what
starts the session - so `GLContext`'s `TextureState` constructor materialised its
eleven default texture objects, each of them emitted `resource_create` through
`FamilyIsLive`, and `CapsMirror::ServerConsumes(0x400)` answered every one of them
from a generation-0 placeholder with `callMask=0`. All eleven records were dropped
and the whole event was one WARN line:
MG_Remote client: the server does not consume MGPipe subsystem 0x400 - this
family emits NOTHING and the legacy pull path runs for it (R-8). callMask=0x0,
caps generation 0
It is NOT a race. The line reproduces byte-for-byte on lavapipe and on the Adreno
830, because the two Init calls are two statements on one thread. And "the legacy
pull path runs for it" is false on the client under split: there is no pull path
on this side of the wire, so withholding is not a fallback, it is a drop that only
a later respecify's self-healing create ever undoes.
Two of the three options the brief offered do not apply, and both facts are worth
recording. (b) "extend the P6.5 fix to inproc" is already the tree: `FinishStartup`
step 7 is the one funnel every transport reaches and it will not return without a
real snapshot. (a) "a bounded wait in Consumes()" cannot work alone and would
deadlock: at `MG_State::Init()` no handshake is in flight, and the one that will
land is started later BY THE SAME THREAD.
So the fix is the ORDER, with the rule behind it as the gate:
* `MobileGL::Initialize` calls `MG_Backend::InitSplitRolesBeforeState()` before
`MG_State::Init()`. `Init()`'s split arm becomes one body, `InitSplitBackend()`,
shared with the direct callers that still reach `Init()`; the monolith arm keeps
its place after `MG_State::Init()`, because moving it would move pull `.text`.
* `CapsMirror::ServerConsumes()` refuses to answer from a placeholder. It waits,
bounded, when a bring-up is genuinely in flight on ANOTHER thread, and otherwise
dies as `Fatal{CapsBeforeFirstSnapshot, "<family>"}` through `SessionFail` -
a new family word, one row in `FatalFamilies.def`, no new abort site.
* Two arms are exempt and say why in place: the server arm (`MGPipeServerArm()` -
the apply thread and the spawn server, where the client's mirror is the wrong
object rather than a late one), and a process whose own configuration never
asked for a split transport (`MG_Config::SplitTransportRequestedByConfig`, set
by `MG_ConfigLoader::InitTransport`, which is what tells a real run apart from a
fixture that assigns `MG_Config::Transport` by hand).
The read accessors keep P5's ruling untouched: `LogBackendInfo` reads
`GetRendererInfo()` during `MG_Backend::Init()` and an abort there is a process that
cannot start. Only the DECISION changed. The warning was not made quieter and the
callMask was not defaulted to all-ones.
`MOBILEGL_TEST_DELAY_FIRST_CAPS_MS` (server side, disaggregated builds, inert unless
set) holds back the FIRST CapsSnapshot. On spawn/tcp that is a genuinely late
snapshot in another process and it exercises the client's step-7 wait; on inproc it
only makes `Start` slower, which is itself the proof that the only window a client
can read a placeholder in is the one before `Start` is called.
G1: `.text` 0xa52203 and the 30570-symbol list identical to p7-before/pull-syms.txt.
Census: 79 abort sites unchanged, 44 family words (was 43). Link ratchet: 186,
unchanged.
586 lines
42 KiB
C++
586 lines
42 KiB
C++
// 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, 9, 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_ESPRYT_ENABLE_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 EsprytEnableTextureView = 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_ESPRYT_USE_ANGLE: load ANGLE EGL/GLES libraries.
|
|
Bool EsprytUseAngle = false;
|
|
#if defined(MOBILEGL_TRACE_ANGLE_VARIANTS)
|
|
// MOBILEGL_TRACE_ANGLE_VARIANT: signed trace-APK ANGLE build short hash.
|
|
String TraceAngleVariant;
|
|
#endif
|
|
// MOBILEGL_MAGMA_DISABLE_SUBGROUP: force-disable Vulkan shader subgroup support,
|
|
// including the opt-in emulated compute path below.
|
|
Bool MagmaDisableSubgroup = 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
|
|
// MagmaFixIterationRPSubgroupScratch below instead). Off by default.
|
|
Bool MagmaEmulateSubgroup = false;
|
|
// MOBILEGL_MAGMA_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 MagmaFixIterationRPSubgroupScratch = QuirkOverride::Auto;
|
|
// MOBILEGL_MAGMA_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 MagmaIterationRPFixBarrier = false;
|
|
// MOBILEGL_MAGMA_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 MagmaDeriveNumSubgroups = 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_ESPRYT_AVOID_SAMPLER_MIPMAP_MIN_FILTER: avoid mipmap min filters in samplers,
|
|
// resolves certain rendering bugs on ANGLE + llvmpipe.
|
|
Bool EsprytAvoidSamplerMipmapMinFilter = false;
|
|
// MOBILEGL_ESPRYT_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 EsprytAvoidExplicitLodBias = false;
|
|
// MOBILEGL_ESPRYT_UNLOCATED_IO_BLOCKS: emit a tessellation/geometry program's
|
|
// inter-stage interface blocks WITHOUT their layout(location=) qualifier, letting ES
|
|
// match them by block name and member sequence instead. The Mali ES driver delivers
|
|
// nothing at all through a located block once a tessellation or geometry stage is in
|
|
// the pipeline; the driver POST measures that and turns this on by itself, so Auto is
|
|
// the right setting everywhere. ForceOn exists so the emulation can be exercised on a
|
|
// healthy driver - which is what the integration lane does, since llvmpipe and
|
|
// lavapipe carry a located block correctly and would otherwise never run this code -
|
|
// and ForceOff is the negative control. See StripIoBlockLocationsPass.
|
|
QuirkOverride EsprytUnlocatedIoBlocks = QuirkOverride::Auto;
|
|
// MOBILEGL_POINT_SIZE_DEMOTION: demote gl_PointSize out of tessellation/geometry
|
|
// stages into an ordinary varying (ShaderCompiler::
|
|
// DemoteTessellationGeometryPointSizeForProgram) instead of declining such programs
|
|
// on a device that advertises neither EXT/OES_tessellation_point_size /
|
|
// geometry_point_size (DirectGLES) nor shaderTessellationAndGeometryPointSize
|
|
// (DirectVulkan). Auto arms it exactly where the detection says the capability is
|
|
// absent, which is the right setting everywhere. ForceOn exists so the demotion can
|
|
// be exercised on a healthy driver - llvmpipe and lavapipe host the built-in
|
|
// natively and would otherwise never run this code, which is what the pinned
|
|
// integration lane uses - and ForceOff restores the plain declines (escape hatch /
|
|
// negative control). Cross-backend by design: the demotion runs in the shared
|
|
// phase-B chain, so one switch covers both. See DemotePointSizePass.
|
|
QuirkOverride PointSizeDemotion = QuirkOverride::Auto;
|
|
// 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_ESPRYT_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 EsprytDisableUboRing = false;
|
|
// MOBILEGL_ESPRYT_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 EsprytDisableUnpackRing = false;
|
|
// MOBILEGL_ESPRYT_DISABLE_UPLOAD_RING: force DirectGLES app buffer updates
|
|
// (glBufferSubData / map flushes) back to the immediate driver upload instead
|
|
// of queueing them for the staged-copy flush through the persistent-mapped
|
|
// upload ring (negative control / driver-bug escape hatch; the immediate
|
|
// upload stalls on drivers that resolve the WAR hazard on the CPU, e.g. Mali).
|
|
Bool EsprytDisableUploadRing = false;
|
|
// MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH: skip the glMapBufferRange(WRITE |
|
|
// INVALIDATE_RANGE) tier of the DirectGLES pending-range flush and go straight
|
|
// to the upload ring's staged glCopyBufferSubData (negative control / escape
|
|
// hatch for a driver whose range-invalidating map misbehaves). The map tier is
|
|
// what keeps a partial write into a large in-flight buffer priced by the RANGE:
|
|
// on Mali both the immediate glBufferSubData and a staged copy into a busy
|
|
// mutable store ghost the whole destination on the CPU.
|
|
Bool EsprytDisableInvalidateFlush = false;
|
|
// MOBILEGL_DISABLE_LARGE_BUFFER_ADOPTION: keep mesh-arena-sized buffer stores
|
|
// (>= 16MiB) on the CPU-shadow model instead of backing them with the backend's
|
|
// persistently+coherently mapped storage at definition time (negative control /
|
|
// escape hatch). Frontend-scoped: it engages only where the active backend
|
|
// provides AcquirePersistentMap. With adoption on, an app SubData into a busy
|
|
// 128MB arena is a plain memcpy into GPU-visible memory; every driver-mediated
|
|
// route for the same write stalls the thread or ghost-copies the whole arena on
|
|
// this class of Mali driver, and the arena stops costing its size again in RAM.
|
|
Bool DisableLargeBufferAdoption = 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_MAGMA_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 MagmaDisableRobustBufferAccess = 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_ESPRYT_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 EsprytViewportArrayEmulation = QuirkOverride::Auto;
|
|
// MOBILEGL_ESPRYT_WIDEN_PACKED16_STORAGE: DirectGLES stores GL_RGB565/GL_RGB5(A1)/GL_RGBA4
|
|
// images as 8-bit-per-channel ES storage (GL_RGB8/GL_RGBA8) instead of the driver's
|
|
// native 16-bit packed formats. Auto defers to a POST driver-bug probe
|
|
// (SelfTest::CopyImageMirrorsPacked16FieldOrder): some Mali drivers store SOME
|
|
// packed16 allocations with a MIRRORED field order (allocation-scoped and
|
|
// shape/context dependent - the failing 30x30x12 GL_TEXTURE_2D_ARRAYs are mirrored
|
|
// at every level), so glCopyImageSubData - a raw texel-block move - lands R/G/B/A
|
|
// reversed whenever exactly one endpoint sits in a mirrored allocation
|
|
// (KHR-GL4x.copy_image.functional rgb5/rgb5_a1/rgba4 x every *2d_array* pair).
|
|
// With no 16-bit packed ES image left there is no field order to disagree about; the
|
|
// client word still round-trips exactly, because the canonical shadow is already
|
|
// UNorm8 and an n-bit field encodes to UNorm8 and back losslessly for n <= 8.
|
|
// ForceOn widens on any driver (the llvmpipe suites use it to exercise the widened
|
|
// path); ForceOff keeps the native narrow storage even where the probe fires - the
|
|
// negative control that replays the corruption. Costs 2x the memory of the affected
|
|
// formats where it engages, which is why Auto is probe-gated rather than always-on.
|
|
QuirkOverride EsprytWidenPacked16Storage = QuirkOverride::Auto;
|
|
// MOBILEGL_MAGMA_PRIMGEN_QUERY_REROUTE: DirectVulkan's GL_PRIMITIVES_GENERATED
|
|
// reroute for draws made while transform feedback is INACTIVE. The stream query
|
|
// (VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT primitivesNeeded) is defined to count
|
|
// them, but a Mali driver - and Mesa lavapipe - answers 0 unless a capture span is
|
|
// open, which is exactly the shape the CTS uses to measure the tessellator, so ~29
|
|
// tessellation tests per tree size a capture buffer from the 0 and die on the
|
|
// zero-length map. Auto defers to a device probe at renderer bring-up
|
|
// (SelfTest::RunPrimitivesGeneratedNoXfbProbe), which measures two substitutes on
|
|
// the same capture-less draws and arms the best proven one: the dedicated
|
|
// VK_EXT_primitives_generated_query (exact semantics by definition; lavapipe passes
|
|
// it, rasterizer discard included), else a clipping-invocations pipeline-statistics
|
|
// pool (see the verdict vocabulary for its rasterizer-discard split). ForceOn pins
|
|
// the reroute structurally wherever a pool can exist (the arming-observable lane,
|
|
// immune to the probe's verdict moving), and ForceOff is the negative control that
|
|
// replays the driver's silence.
|
|
QuirkOverride MagmaPrimGenQueryReroute = QuirkOverride::Auto;
|
|
// --- MGPipe (the disaggregation plan's explicit frontend/backend boundary) ---
|
|
// MOBILEGL_PIPE_PUSH: per-subsystem bitmask selecting which state the frontend
|
|
// PUSHES over MGPipe instead of leaving the backend to pull it out of GLContext.
|
|
// 0 - the only shipped value until the migration lands - is "pull everything",
|
|
// i.e. exactly today's behaviour, and is the default of a PULL build, where the
|
|
// knob is meaningless anyway. A PUSH build defaults to every subsystem migrated so
|
|
// far (MG_Pipe::kMGPipeSubsystemsMigratedAtP5e = 0x3fff), so MOBILEGL_PIPE_PUSH=0 in
|
|
// the environment is the all-pull control and 0x1fff (kMGPipeSubsystemsMigratedAtP4a)
|
|
// is the "everything before P5e" control P5e's A/B is run against - each phase's
|
|
// constant survives as the next phase's control, which is why none of them is ever
|
|
// edited. Accepts decimal or 0x-prefixed hex, and operators pass it as hex, so the
|
|
// bits are listed here (MG_Pipe/MGPipe.h owns them):
|
|
// 0x01 render state (create/bind_render_state + set_dynamic_state)
|
|
// 0x02 pixel pack 0x04 patch state 0x08 vertex attrib defaults
|
|
// 0x10 residual values 0x20 Espryt slots 0x40 Magma vertex input
|
|
// 0x80 resources (the resource_* family: the seven BufferBackendOps hooks)
|
|
// 0x100 vertex input (vertex elements / vertex buffers / index buffer)
|
|
// 0x200 framebuffer (set_framebuffer_state) - requires 0x400
|
|
// 0x400 texture resources (texture + renderbuffer resource_*,
|
|
// set_texture_params) - requires 0x80 AND 0x800
|
|
// (the built-in sampler CSO a set_texture_params record names is minted by
|
|
// the sampler family alone, ID-15; the four rows are MG_Impl/Pipe/PipeFill.cpp's
|
|
// kMGPipeP4aFamilyDependencies, mirrored bit for bit by Espryt's resolvers)
|
|
// 0x800 samplers (sampler CSO, sampler view, set_sampler_views /
|
|
// bind_sampler_states / set_shader_images) - requires 0x400
|
|
// 0x1000 programs (shader CSO, set_draw/dispatch_program, global constants)
|
|
// 0x2000 buffer binding points (set_shader_buffers, the three indexed binding-point
|
|
// classes and the dirty bits 15/16/17) - requires 0x80
|
|
// (every MGPBufferRange::Res names a Buffer handle; P5e)
|
|
// A dependency that is not met is REFUSED with one ERROR naming both bits and the
|
|
// family runs its legacy arm; it is never half-run.
|
|
// 1<<63 NOT a subsystem, a BEHAVIOUR: turn OFF client-side content addressing of
|
|
// CSOs, so every pipeline-version change mints a fresh CSO and the map is
|
|
// never probed. The negative control the CSO design is measured against.
|
|
Uint64 PipePush = 0;
|
|
// MOBILEGL_PIPE_VERIFY: per-draw, per-FIELD shadow comparison of the pushed state
|
|
// against a snapshot taken from GLContext the old way, printing the first field
|
|
// that differs and the draw serial. Roughly 5-10x slower and never shipped; it is
|
|
// the semantic gate that replaces byte identity, and it catches the dangerous
|
|
// direction - a dirty bit that fires too RARELY - which no purity gate can see.
|
|
Bool PipeVerify = false;
|
|
#if MOBILEGL_PIPE_PUSH
|
|
// The three knobs of the MOBILEGL_PIPE_VERIFY build (P1 brief D2). Compiled only
|
|
// under MOBILEGL_PIPE_PUSH so the pull build's FeaturesTable does not change size.
|
|
// MOBILEGL_PIPE_VERIFY_FATAL: the first divergence aborts (default). 0 logs and
|
|
// counts instead, for triage and for the lane that must survive to read its own
|
|
// log. Tri-state parse like PipeLegacyMemos: only an explicit falsy value turns it
|
|
// off.
|
|
Bool PipeVerifyFatal = true;
|
|
// MOBILEGL_PIPE_VERIFY_CORRUPT: a field name from kMGPipeInputFieldNames[]; the
|
|
// comparator perturbs that field in the SNAPSHOT arm before the entry compare, so a
|
|
// green verify run goes red naming it (negative control A). Unknown name is
|
|
// Fatal{PipeVerifyBadKnob}.
|
|
String PipeVerifyCorrupt;
|
|
// MOBILEGL_PIPE_POISON_OMIT: <Verb>:<FieldName>; the filler skips the STAMP (not
|
|
// the value) of that field for that verb, an omission indistinguishable from a
|
|
// forgotten FillPoints.def row, so that verb's read of it is
|
|
// Fatal{UnmigratedPipeInput} (negative control B). Unknown name is
|
|
// Fatal{PipeVerifyBadKnob}.
|
|
String PipePoisonOmit;
|
|
// MOBILEGL_PIPE_HANDLE_ABA_CONTROL (negative control C, P2 brief D18): replace the
|
|
// OBJECT IDENTITY in every DirectVulkan vertex-input memo key with a constant, on
|
|
// whichever arm the run is on - the pre-handle (address, lifetime id) pair AND the
|
|
// handle arm's {slot, gen} generation - so a replacement object inherits its dead
|
|
// predecessor's resolved vertex bindings and HandleRecycleScenario.AbaControl asserts
|
|
// the WRONG pixels. That is what proves the reproducer still reproduces. D18 wrote
|
|
// this as "hash the raw BufferObject* instead of its lifetime id"; measured, the heap
|
|
// block is never handed back, so that spelling collided with nothing and the control
|
|
// went vacuous - see MagmaPipeArms.h's MagmaPipeAbaControlDefeatsIdentity for the
|
|
// measurement and for what the control still leaves standing. Under
|
|
// MOBILEGL_PIPE_PUSH only, so it cannot exist in a shipping pull build.
|
|
Bool PipeHandleAbaControl = false;
|
|
#endif
|
|
// MOBILEGL_PIPE_STATS: dump the boundary counters (bytes, calls, roundtrips,
|
|
// texture pulls, upload shapes, residual-block bytes, index mirror bytes).
|
|
Bool PipeStats = false;
|
|
// MOBILEGL_PIPE_LEGACY_MEMOS: keep the pre-handle registries and TwinLookupMemos
|
|
// alive so the first handle waves have a real old-versus-new arm to be compared
|
|
// against. ON by default for the whole migration window, deleted with the pull
|
|
// path itself.
|
|
Bool PipeLegacyMemos = true;
|
|
// MOBILEGL_PIPE_TEXEL_RETAIN_MB: LRU budget for texels retained against a
|
|
// server-initiated texture re-send. Default 0, i.e. OFF: MipmapStorage already
|
|
// holds a complete CPU shadow, so this cache buys latency, never correctness.
|
|
Uint32 PipeTexelRetainMb = 0;
|
|
// MOBILEGL_PIPE_INDEX_MIRROR_MB: budget for the server-side index host mirror,
|
|
// which is what lets primitive-restart rewriting and multi-draw flattening stay on
|
|
// the server without shipping index bytes per draw. Over budget it degrades to
|
|
// per-draw staging, counted separately in the stats.
|
|
Uint32 PipeIndexMirrorMb = 64;
|
|
// MOBILEGL_PIPE_STATS_PERIOD: frames per boundary-counter summary line. 120 is the
|
|
// steady-state cadence; the device retrace harness never reaches the teardown dump
|
|
// and a trimmed fixture (create-indirect) is shorter than 120 frames, so a run that
|
|
// needs its numbers at all sets this low enough to land at least one window.
|
|
Uint32 PipeStatsPeriod = 120;
|
|
// MOBILEGL_PIPE_STATS_FILE: where the boundary counters' teardown JSON dump goes.
|
|
// Empty (the default) means no dump; the per-120-frame summary line still goes to
|
|
// the log whenever PipeStats is on, so a device run needs no writable path.
|
|
String PipeStatsFile;
|
|
};
|
|
extern FeaturesTable Features;
|
|
|
|
// ---------------------------------------------------------------------------------
|
|
// P5: the transport selector and the MOBILEGL_IPC_* family (ARCHITECTURE.md 16, 附 A)
|
|
// ---------------------------------------------------------------------------------
|
|
//
|
|
// MOBILEGL_TRANSPORT = monolith | inproc | spawn | unix:<path> | pipe:<name>.
|
|
//
|
|
// WHY `Transport` IS NOT A FeaturesTable MEMBER. ARCHITECTURE.md:580 requires that with
|
|
// MOBILEGL_BUILD_DISAGGREGATED=OFF it be a `constexpr Monolith`, so that the single hook
|
|
// in MG_Backend/Init.cpp compiles away entirely rather than becoming a branch nobody can
|
|
// take. A FeaturesTable member is a runtime field in every build, which is the opposite
|
|
// of that; it would also resize MG_Config::Features and break G1 (the pull build's
|
|
// symbol set must not move) for the same reason the MOBILEGL_PIPE_VERIFY knobs above sit
|
|
// behind their own #if.
|
|
//
|
|
// ONE CONSEQUENCE, STATED SO IT IS NOT REDISCOVERED: in a build without the option,
|
|
// MOBILEGL_TRANSPORT=inproc is ACCEPTED BY THE ENVIRONMENT AND SILENTLY IGNORED - the
|
|
// parser below does not exist to complain about it, and putting a complaint in the
|
|
// unconditional part of ConfigLoader would move a pull-build symbol. That is the exact
|
|
// shape of "the split lane ran monolith and went green", so the gate against it is a
|
|
// BUILD-level check, not a runtime one: `nm --defined-only libMobileGL.so | grep -i
|
|
// MG_Remote` must be non-empty in build-split (CONTRACT-P5.md table 3, and the CI job
|
|
// P5 adds beside build-linux-verify).
|
|
enum class TransportMode : Uint8 {
|
|
Monolith = 0, // today's in-library backend; no MG_Remote object is constructed
|
|
InProcess = 1, // P5: a real apply thread in this process, over the same G3 codec
|
|
Spawn = 2, // P6: fork/exec MobileGLServer, socketpair
|
|
UnixSocket = 3,// P6: connect to an existing AF_UNIX endpoint (Endpoint = <path>)
|
|
NamedPipe = 4, // P6: Windows named pipe (Endpoint = <name>)
|
|
};
|
|
|
|
#if MOBILEGL_BUILD_DISAGGREGATED
|
|
// Parsed once by MG_ConfigLoader::Init(). Defaults to Monolith even here: building the
|
|
// transport in is not the same as using it, and every existing lane of a build-split
|
|
// must keep running monolith unless it is asked for one.
|
|
extern TransportMode Transport;
|
|
// The <path> of `unix:` / the <name> of `pipe:`. Empty for the other three modes.
|
|
extern String TransportEndpoint;
|
|
|
|
// P7 F1. DID **THIS PROCESS'S OWN CONFIGURATION** ASK FOR A SPLIT TRANSPORT.
|
|
//
|
|
// `Transport` alone cannot answer that, and the difference is exactly the one the F1 gate
|
|
// turns on. A unit fixture assigns `Transport` by hand to put the code under test on its
|
|
// split arm (ServerLoopTest's main does, and says why) while building no client at all; a
|
|
// real run gets it from MG_ConfigLoader::Init(), and that run WILL bring a client half up.
|
|
// Only the second may be held to "never decide a record family's fate from a placeholder
|
|
// caps mirror" - in the first there is no handshake, no mirror to adopt and nothing the
|
|
// rule could mean.
|
|
//
|
|
// Written once, by InitTransport(), and never cleared: a process does not stop having been
|
|
// configured. Read by MG_Remote::Client::CapsMirror::RequireFirstSnapshot.
|
|
extern Bool SplitTransportRequestedByConfig;
|
|
|
|
// The MOBILEGL_IPC_* family. A separate table rather than more FeaturesTable members,
|
|
// for the G1 reason above and because every field here is meaningless without the
|
|
// transport: a build that cannot reach the MG_Remote code cannot honour one of them.
|
|
//
|
|
// P5 lands exactly the knobs P5's own packages read. A later phase's knob is added HERE,
|
|
// through the integrator, and not invented at its call site - ARCHITECTURE.md:615 holds
|
|
// the full planned inventory (PRESENT_CREDIT, POLL_ESCALATE, SHADOW_SHM,
|
|
// INLINE_PAYLOADS, TRACE, ATTACH, RESPAWN, IDLE_EXIT_S), and every one of those belongs
|
|
// to P6 or later.
|
|
struct IpcTable {
|
|
// MOBILEGL_IPC_SERVER_PATH: where to find libMobileGLServer. P6 consumes it; P5
|
|
// lands the parse because t1's ctest ENVIRONMENT blocks and add_trace_replay_test's
|
|
// SPLIT variant already carry it, and an environment variable that nothing parses is
|
|
// indistinguishable from one that is parsed and ignored.
|
|
String ServerPath;
|
|
// Control endpoint and data-plane selection are independent of topology.
|
|
String Control = "fork"; // fork | unix:<path> | tcp://host:port
|
|
String Data = "auto"; // auto | shm | stream
|
|
// MOBILEGL_IPC_RING_MB: SEG_CMD size. A RECORD MAY BE AT MOST HALF OF THIS
|
|
// (RingProducer::MaxRecordBytes), so 8 MiB caps one record at 4 MiB; R-10 makes the
|
|
// codec publish a max-record-bytes counter rather than assume that is enough.
|
|
Uint32 RingMb = 8;
|
|
// MOBILEGL_IPC_STAGE_MB: SEG_STAGE size. Every blob and every var-tail's bytes live
|
|
// here ONE BLOB AT A TIME: a row whose content can outgrow the segment cuts it at the
|
|
// stage chunk budget (MGPipeStageChunkBytes, a quarter of this), and a record type with
|
|
// no cut is Fatal{RingOverrun, "SEG_STAGE"} rather than allowed to exceed it.
|
|
Uint32 StageMb = 32;
|
|
// MOBILEGL_IPC_SPIN_US: spin before parking on a doorbell, either direction.
|
|
Uint32 SpinUs = 50;
|
|
// MOBILEGL_IPC_PERSISTENT_BLOCK_KB: block granularity of the persistent-map push.
|
|
// 0 IS A NEGATIVE CONTROL, NOT "unlimited": it disables the push, and
|
|
// PersistentCoherentMapScenario must go RED under it (exit gate E3(a)).
|
|
Uint32 PersistentBlockKb = 64;
|
|
// MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS: 1 = the persistent-map push ships only
|
|
// blocks whose xxHash64 changed since the last push, instead of the whole mapped
|
|
// range every verb. 0 restores the whole-range push (A/B control).
|
|
Uint32 PersistentHashSuppress = 1;
|
|
// MOBILEGL_IPC_BATCH_WAITS: 1 = value-class records (kCtxState / kCtxCso / kCtxObject
|
|
// with no reply slot) are published without waiting for their own apply; the barrier
|
|
// is taken at the next pull-reading verb (kCtxVerb syncs, queries, screen rows, and
|
|
// every reply-slot row), which is the only place BARRIER-PULLED fields are read. 0
|
|
// restores the per-record barrier of R-1.
|
|
Uint32 BatchWaits = 1;
|
|
// MOBILEGL_IPC_ADOPT_TIER: 2 = emulate (client keeps the shadow and pushes), which
|
|
// is the only tier P5 implements and the reason persistent-map-push can be non-zero
|
|
// at all (R-6). 0 and 1 parse and are Fatal at use with "P11"; they exist now so the
|
|
// negative control has a spelling the day P11 writes it.
|
|
Uint32 AdoptTier = 2;
|
|
// MOBILEGL_IPC_VERB_BARRIER: 1 = the client blocks at every verb boundary until
|
|
// appliedSeq reaches its emitSeq (R-1). 0 is the negative control: it is EXPECTED to
|
|
// be red, because 31 of the 63 PipeInputs fields are still pulled from a live
|
|
// GLContext by the client's residual fill and a free-running queue lets the server
|
|
// read a FUTURE value of them.
|
|
Uint32 VerbBarrier = 1;
|
|
// MOBILEGL_IPC_RUN_AHEAD (P5e, MG_Remote/CONTRACT-P5E.md §1): 1 = after publishing an
|
|
// UNBARRIERED record the client returns immediately instead of waiting for its apply.
|
|
// It is one half of a conjunction and never a switch on its own - the client arms
|
|
// run-ahead only when the server also publishes kCapRunAheadApply, so on Magma, and on
|
|
// Espryt before the P5e integration commit, 1 means exactly what 0 means and logs once
|
|
// saying so.
|
|
//
|
|
// 0 IS THE A/B CONTROL AND NOT A NEGATIVE ONE: the server code, the fill decision and
|
|
// every record are identical on both arms, and the only difference is whether the
|
|
// client waits. That is what makes "is the picture the same" a question about the wait
|
|
// rule alone. MOBILEGL_IPC_VERB_BARRIER=0 keeps its own meaning and stays the
|
|
// lockstep arm's negative control; under run-ahead it is the one that must go red, at
|
|
// the first barriered row's stale pull.
|
|
//
|
|
// Forced to 0 by MOBILEGL_PIPE_VERIFY, beside BatchWaits: the comparator needs a
|
|
// client-filled gPipeInputs block for every verb, and run-ahead is precisely the
|
|
// arm that stops filling it.
|
|
Uint32 RunAhead = 1;
|
|
// MOBILEGL_IPC_PRESENT_CREDIT (P5e, ruling 4 / ID-92): how many presents the client may
|
|
// have in flight before it waits for a swap to come back. 1 = the client publishes
|
|
// frame N+1's records while the server applies and swaps frame N - one frame of
|
|
// overlap, at most one frame of added latency - and the CREDIT, never the ring's bytes,
|
|
// is what paces a run-ahead client. 2 is a device MEASUREMENT arm: it buys no CPU on a
|
|
// client that is already CPU-bound and costs a frame of latency, which is why the
|
|
// default is 1 and not "as deep as the ring".
|
|
Uint32 PresentCredit = 1;
|
|
// MOBILEGL_IPC_STRICT_ERRORS: promote a BARRIER-PULLED field read - and, in a split
|
|
// build, the seven sticky forwards that are otherwise exempt - from "count it in
|
|
// rsp" to Fatal (R-7.3).
|
|
Bool StrictErrors = false;
|
|
// MOBILEGL_IPC_ROLE_SPLIT_STATE (P5f f1, P5F-WIRE-COMPLETENESS.md §4): the dual-block
|
|
// rehearsal. 1 = the client's residual fill writes a CLIENT-ROLE PipeInputs block and
|
|
// the backend/applier keep reading the SERVER-ROLE one, so every path that today works
|
|
// only because the two roles share one object turns into a named
|
|
// Fatal{UnmigratedPipeInput, "<field>@<verb>"} instead of a silent cross-role read.
|
|
// Meaningless under monolith transport (the two roles are one thread there, so the
|
|
// selection folds to the single shared block) and forced off by MOBILEGL_PIPE_VERIFY
|
|
// (the comparator owns the one fill block it compares against). 0 is not merely the
|
|
// default, it is the negative control: the lane's distinctness case must go red
|
|
// without it (P5F §6).
|
|
Bool RoleSplitState = false;
|
|
// MOBILEGL_IPC_AUDIT: after a record retires, the server fills the SEG_STAGE bytes
|
|
// it referenced with 0xDD (R-2.5). This is the ONLY mechanical control that an
|
|
// inproc implementation did not quietly keep using a pointer past its lifetime.
|
|
Bool Audit = false;
|
|
// MOBILEGL_IPC_SERVER_AFFINITY: `auto` (the default, big-core detection borrowed
|
|
// from ShaderCompilePool), `off`, or an explicit CPU mask. Kept as the raw string
|
|
// because the resolved mask is logged by whoever starts the apply thread, and the
|
|
// string is what an operator typed.
|
|
String ServerAffinity = "auto";
|
|
};
|
|
extern IpcTable Ipc;
|
|
#else
|
|
// The whole point: in a build without MG_Remote this folds at compile time, so
|
|
// `if (MG_Config::Transport != MG_Config::TransportMode::Monolith)` in Init.cpp is a
|
|
// discarded statement and the pull build gains no symbol, no branch and no byte.
|
|
inline constexpr TransportMode Transport = TransportMode::Monolith;
|
|
#endif
|
|
} // namespace MobileGL::MG_Config
|