Compare commits

...
64 Commits
Author SHA1 Message Date
swung0x48 222f6209a0 [CI] (.github/actions, .github/workflows): create swap even when the runner image has none 2026-11-15 05:45:04 -04:00
swung0x48 b2248b240f [CI, Docs] (.github/workflows, userguide): build the dev APK split so it defaults to inproc 2026-11-14 22:07:43 -04:00
swung0x48 8db74d69ae [Chore] (android-plugin/app, MobileGL, userguide): bump the version to 26.11 2026-11-08 20:20:39 -04:00
swung0x48 04c8773d77 [Fix, Test] (MG_Impl/GLImpl, MG_Test/Texture, MG_Util/Converters): report proxy texture level sizes
- Record the size a proxy glTexImage* asks for and answer it from
  glGetTexLevelParameter, or zeros when it passes the texture size limits
- Map GL_PROXY_TEXTURE_1D and GL_PROXY_TEXTURE_3D to their upload targets
2026-11-08 19:43:21 -04:00
swung0x48 8497eee197 [CI] (.github/workflows): run dev in the split CI shape and pin the verify build to monolith 2026-11-07 08:08:50 -04:00
swung0x48 13e9d0df5c [Fix] (MG_Backend/DirectVulkan): take a deferred window clear's sRGB decision at the glClear 2026-11-07 08:08:48 -04:00
swung0x48 4256a66d09 [Fix] (MG_Impl/Pipe): zero the pixel pack record's padding before it is sent 2026-11-07 07:39:14 -04:00
swung0x48 cefb70d1a7 [Fix] (MG_Util/ShaderTranspiler, MG_Util/SelfTest, MG_Test): split clip distance code to fit the size and format checks 2026-11-07 07:27:58 -04:00
swung0x48 cdf43a4a59 [Fix] (MG_Backend/DirectVulkan, MG_Util/ShaderTranspiler): apply GL_CLIP_DISTANCEi in the shader where Magma clips natively 2026-11-07 02:47:30 -04:00
swung0x48 e8dc4e2320 [Test] (MG_Test): pin the emulated eight clip distances on both backends without native support 2026-11-07 02:28:22 -04:00
swung0x48 6182704c03 [Feat] (MG_Util/ShaderTranspiler, MG_Backend, MG_Util/SelfTest): emulate gl_ClipDistance where the backend cannot clip natively and report it in POST 2026-11-07 02:06:25 -04:00
BZLZHH d3ec9b8179 [Docs] (docs/Performance): add a skill for reading Mali GPU counters 2026-11-01 19:51:20 +08:00
BZLZHH 05dfff9100 [Perf] (MG_Backend/DirectVulkan, MG_IntegrationTest/Scenarios): defer window clears while a window pass is open
- A whole-window clear with a window pass open ends that pass and folds
  into the next one, instead of running a load-clear-store pass of its own
- A present drops the depth/stencil half of a pending window clear
2026-11-01 19:20:48 +08:00
BZLZHH 6c59b0391c [Perf] (MG_Backend/DirectVulkan): defer window clears into the next window pass
- A whole-window glClear with no pass open on the window waits for the next
  window pass, which loads it with LOAD_OP_CLEAR; other uses run it first
- The first window pass after a present loads undefined content as DONT_CARE
2026-11-01 13:42:44 +08:00
BZLZHH d6fd101d2a [Build] (CMakeLists.txt, docs/Disaggregated): default to the split in-process build 2026-10-31 20:59:18 +08:00
BZLZHH 259acb2c57 Merge pull request #21 from MobileGL-Dev/feat/disaggregated
[Feat] (Disaggregated): replace MobileGL's monolithic architecture with a disaggregated client/server one
2026-10-31 20:49:41 +08:00
BZLZHH 15ba86ce58 [CI] (scripts/ci): re-pin revision 6 after the comment-only schema edit 2026-10-31 20:25:11 +08:00
BZLZHH 8084a93d85 [Fix] (android-plugin/app): match the announcement wording the supervisor prints 2026-10-31 20:14:30 +08:00
BZLZHH 6d3b00e820 [Chore] (docs, MobileGL, scripts, tools, .github): use US spelling in identifiers and file names 2026-10-31 20:06:22 +08:00
BZLZHH 4f50bedde0 [Docs, Chore] (docs, tools, MobileGL, scripts, CONTRIBUTING.md, .gitignore): translate the documentation and use US spelling 2026-10-31 19:58:19 +08:00
BZLZHH 47892afddc [CI, Docs] (AGENTS.md, CONTRIBUTING.md, scripts): let wide commits summarize their scope 2026-10-31 19:37:32 +08:00
BZLZHH de7b7ea21b [CI, Docs] (CONTRIBUTING.md, scripts): allow an abbreviated scope on wide commits 2026-10-31 19:33:44 +08:00
BZLZHH 6e36bf2c1b [Chore, Docs] (...): drop the archived notes and move the skill under tools
- the archive freeze rule goes with the directory
- code comments lose citations to the removed notes
2026-10-31 19:29:06 +08:00
swung0x48 b5d64175a1 [Fix] (MG_IntegrationTest/Harness): list the canned narrow check unarmed
- The driver-watch control makes no GL call, so it never arms the comparator
- The verify-split arm proof needs it named, on both backends
2026-10-19 06:12:19 -04:00
swung0x48 1a53cbd31a [Test] (MG_IntegrationTest/Scenarios, MG_Test/Backend): run every narrow case
- the client-memory case runs on every arm, as the client array scenario does
- a capture program meets both signednesses in one capture without a relink
- a relink that turns an ivec4 input into uvec4 starts over
- the recreated program and array must reuse their names
- the ESSL check counts the width declarations
2026-10-19 04:01:07 -04:00
swung0x48 3d2e259080 [Fix] (MG_Backend/DirectGLES, MG_Test/Backend): re-arm seeds per link
- a failed armed build disables the seeds for that link only; a relink arms again
- a refusal applies to the link it was built for, so a relink's first draw expects seeds
- a relink without integer inputs drops the arming
- the program twin is looked up only for a plan that needs seeds
- the seed state resets after the unlinked-program return, not before it
2026-10-19 04:00:54 -04:00
swung0x48 d1293354d1 [Fix] (CMakeLists.txt, MG_Backend/DirectGLES, MG_Pipe, MG_Test/Backend, MG_Test/ShaderTranspiler, MG_Util/ShaderTranspiler): give integer attributes their GL value in Espryt
- every integer array reaches the driver with its input's signedness, at the pointer, zero-stride and client-memory call sites, so each fetch is one ES defines
- 8/16-bit data of the other signedness is widened back by the program's runtime-width seeds, armed on the first draw that needs them and set as two uniforms per draw
- capture programs with integer inputs are armed at their first build; arming is part of the ESSL translation key
- a build whose seeds cannot be verified keeps the driver's own value for narrow data; one that fails to link is rebuilt without them
- the program's integer input masks are memoized on its shader record
2026-10-19 02:53:01 -04:00
swung0x48 381124a0b6 [Feat] (CMakeLists.txt, MG_Test/ShaderTranspiler, MG_Util/ShaderTranspiler): seed narrow integer inputs from runtime widths
- ExtendNarrowIntegerVertexInputsPass gains a RuntimeWidths mode: every 32-bit integer input below location 32 is widened from two Private uint globals by shifts only
- ShaderCompiler::ExtendNarrowIntegerVertexInputsForEssl reports whether it rewrote the module
- the constant-mask mode is unchanged byte for byte
2026-10-19 02:32:21 -04:00
swung0x48 fd48928260 [Test] (MG_IntegrationTest, MG_IntegrationTest/Harness, MG_IntegrationTest/Scenarios): check the array types Espryt hands the driver
- IntegerAttributeTypePeek wraps the ES draw entry points and flags an enabled array whose type has the other signedness from its input
- every narrow-integer case asserts it on DirectGLES monolith and in-process; elsewhere it logs why and keeps the value checks
- new cases: two programs on one array, a first build for matching data, one source built twice, relink, recreate, bound location, DSA, zero stride, client memory, 32-bit data, multi-draw, pipeline stage, type query
- red on DirectGLES until the driver gets the input's signedness
2026-10-19 02:23:35 -04:00
swung0x48 58b0550512 [Refactor] (MG_Backend/DirectVulkan, MG_Util/ShaderTranspiler): share the integer vertex input masks
- IntegerVertexInputMasks, the four narrow data masks and ResolveNarrowIntegerVertexInputs move to MG_Util so another backend can plan the same correction
- Magma keeps its VkFormat Note on a derived mask type; behaviour is unchanged
2026-10-19 02:10:58 -04:00
swung0x48 044a3f1c7c [Test] (tools/trace_replay): add a 1.20.1 iterationT in-world fixture
- Sodium 0.5 feeds unsigned-byte lightmaps to Iris 1.7's ivec4; sign-extended, all terrain is black
- with Magma's narrow-integer variant disabled the frame scores 0.25 against either golden
- the golden is the CI software renderers' picture: the outline relies on rounding GL leaves open
2026-10-18 21:09:40 -04:00
swung0x48 78c21544d8 [Test] (MG_IntegrationTest/Scenarios): draw each array twice in a row in the one-program narrow case 2026-10-18 15:55:10 -04:00
swung0x48 5a48bd80b1 [Perf] (MG_Backend/DirectVulkan, MG_Pipe, MG_Test/Pipeline): start a draw from the variant its last draw settled on
- the program record remembers the last variant per flags, so a narrow-integer draw
  costs one lookup and the plain program it never draws with ages out
2026-10-18 15:55:09 -04:00
swung0x48 2093d845b8 [Test] (MG_IntegrationTest, MG_IntegrationTest/Scenarios): capture narrow integer attributes read across signedness
- every 8/16-bit pairing plus 32-bit and matching controls, one instanced, via transform feedback
- one program over a signed and an unsigned array in both orders, on the monolith and split arms
2026-10-18 15:55:08 -04:00
swung0x48 cc0c2aa766 [Fix] (MG_Backend/DirectVulkan, MG_Test/Pipeline): give 8/16-bit integer attributes their GL value in Magma
- the fetch format takes the input's signedness, so UNSIGNED_BYTE 240 read as int was -16
  (Sodium lightmaps into a_LightAndData: dark terrain under shaderpacks)
- a program variant keyed on two cached masks re-extends exactly those locations
- the vertex input resolve moves ahead of the variant pick; vertex-stage transforms get a helper
2026-10-18 15:55:07 -04:00
swung0x48 3528628681 [Feat] (CMakeLists.txt, MG_Test/ShaderTranspiler, MG_Util/ShaderTranspiler): add a pass extending narrow integer vertex inputs GL's way
- listed locations are masked to 8/16 bits or sign-extended from them at the top of main
- each input becomes a Private copy seeded once; access chains are retyped, arrays go per element
2026-10-18 15:55:07 -04:00
swung0x48 fe60711c24 [Fix, Test] (MG_Backend/DirectVulkan, MG_Test/ShaderTranspiler): fix up geometry emits in helpers
- clip-space fix-up and XFB gl_Position mirror go before every EmitVertex
  and EmitStreamVertex in the entry point's call tree, not only main's
- helpers are not inlined, so a helper-emitted vertex skipped the depth
  remap (clipped below GL z=0) and was never captured by XFB
- TransformSpirvForVulkanPositionFix exported for the unit cases
2026-10-18 15:55:06 -04:00
swung0x48 c56c7e76c8 [Fix] (CMakeLists.txt, MG_Test/ShaderTranspiler, MG_Util/ShaderTranspiler): keep geometry outputs across EmitVertex
- RetainGeometryOutputsPass shadows each output whose pre-emit value can be
  observed later and copies it into the real output before every emit
- runs in SanitizeAndOptimizeBinary, so Magma and Espryt both get it
- iterationT terrain: flat textureResolution written once, emitted 3 times
2026-10-18 15:55:05 -04:00
swung0x48 0c2f8df727 [Fix] (CMakeLists.txt, MG_Test/ShaderTranspiler, MG_Util/ShaderTranspiler): strip unused compatibility members from per-vertex blocks
- glslang gives them no BuiltIn, so Magma declined every compatibility
  program that wrote gl_Position, including the Iris block outline
2026-10-18 15:55:05 -04:00
swung0x48 b4b2ccfe7b [Fix] (MG_Backend/DirectGLES, MG_Test/Backend): advertise 32 fragment texture units
- Iris budgets a whole program's samplers against GL_MAX_TEXTURE_IMAGE_UNITS;
  the ES driver's 16 per stage left iterationT with no pipeline on Espryt
- report min(32, max(per-stage, combined)) so unit numbers stay legal
- add CapabilityLimitsTest for the Adreno, narrow and wide cases
2026-10-18 15:55:03 -04:00
swung0x48 c4eb620982 [Docs] (MG_Backend/DirectVulkan): fix two comments citing the deleted floor line and sink tallies 2026-10-18 10:02:09 -04:00
swung0x48 584641dc5e [Test] (docs/ci, MG_IntegrationTest/Harness): keep the ambient CtWire log to the three cases that count
- a log on the ambient RoleViolation entries would arm its guard-line check, which they never ran
2026-10-18 10:02:09 -04:00
swung0x48 21925f99d4 [Refactor, Test] (MG_Impl/Pipe, MG_Test/Pipe): rename ResetForTest to ForgetSession where the session end calls it 2026-10-18 10:02:08 -04:00
swung0x48 12a96be87c [Refactor] (MG_Backend/DirectVulkan): drop the always-printed MGWIRE-FLOOR line from the decline dump 2026-10-18 10:02:07 -04:00
swung0x48 fd2cdf9e21 [Refactor] (MG_Backend/DirectVulkan): delete the MOBILEGL_MAGMA_WIREBUF_PROBE investigation probe 2026-10-18 10:02:06 -04:00
swung0x48 0d11af0d86 [Refactor] (MG_Util/ShaderTranspiler): delete the uncalled TranslationCache::SetCapsForTesting 2026-10-18 10:02:05 -04:00
swung0x48 9ad0d4835d [Refactor] (android-plugin/app, MobileGL): delete the unread MOBILEGL_TRACE_SKIP_AUTODESTROY knob 2026-10-18 10:02:05 -04:00
swung0x48 ee30726fde [Refactor, Test] (docs/ci, docs/Disaggregated, MG_IntegrationTest, MG_IntegrationTest/Harness, MG_IntegrationTest/Scenarios, MG_Remote/Server, MG_Test/Util, MG_Test/Wire, MG_Util/Metrics, scripts/ci, tools/trace_replay): delete the MOBILEGL_TEST_SERVER_COUNTERS knob
- CtWireScenario reads `ar` / `od` off the server's PipeStats line on every arm
- the serial == resets assertion could not fail and is dropped
- ServerVerbSink's unread BindContexts, Presents and LastPresentSerial go too
2026-10-18 10:02:04 -04:00
swung0x48 9824beabd1 [Docs] (MG_Pipe, MobileGL): describe the bad-knob Fatal and the arm-time setting lookup 2026-10-18 10:02:03 -04:00
swung0x48 ead1a78bec [Test] (.github/workflows, docs/ci, MG_IntegrationTest, MG_IntegrationTest/Scenarios, MG_Test/Pipe): restore the fatal-arm, per-verb capability and verb-scope checks
- Armed requires fatal=1; the monolith control envs share the ambient lane's base list
- the capability field is corrupted through the knob at Clear, DrawArrays and ReadPixels
- the library's poison omission is checked to match its verb, not its class
2026-10-18 10:02:02 -04:00
swung0x48 b2daab61b7 [Refactor, Test] (docs/ci, docs/Disaggregated, MG_Backend/MGPipe, MG_Impl/Pipe, MG_IntegrationTest, MG_IntegrationTest/Harness, MG_IntegrationTest/Scenarios, MG_Pipe, MG_Test/Pipe, MobileGL, scripts/data, tools/trace_replay): delete the MOBILEGL_PIPE_POISON_OMIT knob
- the PoisonOmitted and VerifySplitPoisonOmitted entries set the harness marker MGITEST_POISON_OMIT; the re-executed child arms the pair through Harness/PoisonOmissionArm.h
- MGPipeSetPoisonOmission, the stamp withhold, the name lookups and the server half (now one atomic word) compile only with MOBILEGL_PIPE_VERIFY
- three PipeInputsTest cases withhold the stamp test-side; PoisonOmitKnobArmsTheOmission and BadPoisonOmitKnobIsFatalNamingTheKnob are retired
- skip census: the two retired cases leave the unit-strict and unit-verify names
2026-10-18 10:02:02 -04:00
swung0x48 9b5a60b2f7 [Refactor, Test] (.github/workflows, docs/ci, docs/Disaggregated, MG_Impl/Pipe, MG_IntegrationTest, MG_IntegrationTest/Scenarios, MG_Test/Pipe, MobileGL, scripts/data): read MOBILEGL_PIPE_VERIFY_CORRUPT only in verify builds
- ArmVerify reads it through LookupSetting; Features.PipeVerifyCorrupt is gone
- unit PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator replaces negative control A2
- integration-verify's controls A and A2 and integration-verify-split's control A with its log copy step are retired
- PipeVerifyArmingScenario.Armed no longer skips when the knob is set
- skip census: the new case is a listed skip in the unit, unit-split and unit-strict lanes
2026-10-18 10:02:01 -04:00
swung0x48 f5df1f893e [Refactor] (docs/Disaggregated, MobileGL): force the verify IPC settings only in builds with the comparator 2026-10-18 10:02:00 -04:00
swung0x48 ebd7926604 [CI] (.github/workflows, docs/ci, scripts/ci): keep the split lane's fallback run when the lane step never ran
- 'Split lane fallback (lane step skipped)' runs the ownership check and the DirectGLES.Split. entries only when split-baseline.xml is missing, as the retired controls step did
- the integration-split header and junit_tally.py's docstring stop naming the retired controls
2026-10-18 10:01:59 -04:00
swung0x48 95ecdd21e5 [Test] (docs/ci, MG_IntegrationTest, MG_IntegrationTest/Harness, MG_IntegrationTest/Scenarios, scripts/data): check the push control across a Present and only where it can run
- the forgotten mapping's next-frame write must also miss the server; the re-announce must ship the latest write
- the case is left out of the ambient, Verify, VerifySplit and Magma Full registrations, where it only skipped
- DirectVulkan.Split pins MOBILEGL_IPC_ADOPT_TIER=2 for it, so its emulated declaration holds
2026-10-18 10:01:58 -04:00
swung0x48 87b83384c8 [Refactor, Test] (docs/ci, docs/Disaggregated, MG_IntegrationTest/Scenarios, MG_State/GLState, MG_Test, MG_Test/Buffer, MG_Util/Metrics, MobileGL, scripts/data): fix the persistent-map push block at 64 KiB
- retires SplitBufferSet.AZeroBlockSizeTurnsThePushOffRatherThanMakingItUnlimited
2026-10-18 10:01:57 -04:00
swung0x48 9032561722 [Refactor, Test] (docs/ci, docs/Disaggregated, MG_Backend/MGPipe, MG_Remote/Client, MG_Remote/Transport, MG_Test/Wire, MobileGL, scripts/data, tools/trace_replay): delete the MOBILEGL_IPC_VERB_BARRIER knob
- retires RemoteRunAhead.ClearingTheVerbBarrierDisarmsRunAheadEvenWithTheCapBit
2026-10-18 10:01:56 -04:00
swung0x48 81994940c4 [CI] (.github/workflows, docs/ci, docs/Disaggregated, MG_IntegrationTest, MG_IntegrationTest/Harness, scripts/ci, scripts/data): retire the split barrier and map-push negative controls
- skip census: lane junit-integration-split/control retired with its step
2026-10-18 10:01:56 -04:00
swung0x48 82a8fb816e [Test] (MG_IntegrationTest/Harness, MG_IntegrationTest/Scenarios, MG_Test/Wire, scripts/data): add in-test controls for the map push and the wait probe
- PersistentCoherentMapScenario.ThePushIsTheOnlyRouteAWriteTakesToTheServer
- RemoteWaitBoundaryControl.AnUnwaitedPublishIsSeenAsNoWait
2026-10-18 10:01:54 -04:00
swung0x48 1d13ea8280 [CI] (.github/workflows): retry a timed-out retrace result upload once 2026-10-18 08:44:22 -04:00
swung0x48 4422732e98 [CI] (.github/workflows): let the emulator retrace jobs use the whole runner pool
- APK is the later of the two runs (64 against 38 min), so its jobs no longer leave runners idle
2026-10-18 07:26:23 -04:00
swung0x48 da1ec09c27 [CI] (.github/workflows, android-plugin): cache a pre-booted AVD, not boot snapshots
- snapshots doubled the cache restore (25 to 52 s) and device-offline retries went from 3 to 10
- the AVD job boots the new AVD once and powers it off; retrace jobs cold-boot it as before
2026-10-18 07:06:46 -04:00
swung0x48 8f64fa7686 [CI] (.github/workflows, android-plugin): boot retrace emulators from a cached snapshot
- the AVD job boots the new AVD once per GPU mode and saves a snapshot into the cache
- retrace jobs load their mode's snapshot, log whether it loaded, and skip SDK setup on a hit
2026-10-18 05:42:48 -04:00
swung0x48 a26d9a4482 [CI] (.github/workflows): run 16 emulator retrace jobs at a time
- 231 jobs at 4 a time took 182 of the run's 198 minutes while 16 runners sat idle
2026-10-18 05:32:39 -04:00
1607 changed files with 15598 additions and 207281 deletions
@@ -1,34 +0,0 @@
// dmahash <pid> : for each dma-buf fd of <pid> at least 1 MB, print inode, size and a sampled checksum.
#include <dirent.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char** argv) {
char dir[64];
snprintf(dir, sizeof dir, "/proc/%s/fd", argv[1]);
DIR* d = opendir(dir);
if (!d) { perror("opendir"); return 1; }
struct dirent* e;
while ((e = readdir(d))) {
char p[128], l[128] = {0};
snprintf(p, sizeof p, "%s/%s", dir, e->d_name);
if (readlink(p, l, sizeof l - 1) <= 0 || strncmp(l, "/dmabuf", 7) != 0) continue;
int fd = open(p, O_RDONLY);
if (fd < 0) continue;
struct stat st; fstat(fd, &st);
if (st.st_size < (1 << 20)) { close(fd); continue; }
const uint8_t* m = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (m == MAP_FAILED) { printf("fd %s ino %lu mmap failed\n", e->d_name, (unsigned long)st.st_ino); close(fd); continue; }
uint64_t h = 1469598103934665603ull; unsigned nz = 0;
for (off_t i = 0; i < st.st_size; i += 64) { h = (h ^ m[i]) * 1099511628211ull; nz += m[i] != 0; }
printf("fd %s ino %lu size %ld hash %016llx nz %u\n", e->d_name, (unsigned long)st.st_ino, (long)st.st_size,
(unsigned long long)h, nz);
munmap((void*)m, st.st_size); close(fd);
}
return 0;
}
@@ -1,95 +0,0 @@
/* texfmt: does a texture keep its alpha? One opaque red texel and one transparent texel per
* format, sampled across a 64x64 ES3 pbuffer cleared white with SRC_ALPHA blending. Expect the
* left half red and the right half WHITE; a black right half means the alpha was lost. */
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#include <GLES2/gl2ext.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define P(T, n) T n = (T)eglGetProcAddress(#n)
static const char* vs = "#version 300 es\nlayout(location=0) in vec2 p; out vec2 uv; void main(){uv=p*0.5+0.5; gl_Position=vec4(p,0,1);}";
static const char* fs = "#version 300 es\nprecision mediump float; uniform sampler2D t; uniform int mode; in vec2 uv; out vec4 o;"
"void main(){vec4 c=texture(t,uv); o = mode==1 ? vec4(1.0,0.0,0.0,c.a) : (mode==2 ? vec4(c.r,0.0,0.0,c.r) : c);}";
int main(void) {
EGLDisplay d = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(d, 0, 0); eglBindAPI(EGL_OPENGL_ES_API);
EGLint ca[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE};
EGLConfig c; EGLint n; eglChooseConfig(d, ca, &c, 1, &n);
EGLint xa[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE};
EGLContext x = eglCreateContext(d, c, EGL_NO_CONTEXT, xa);
EGLint pa[] = {EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE};
EGLSurface s = eglCreatePbufferSurface(d, c, pa); eglMakeCurrent(d, s, s, x);
P(PFNGLCREATESHADERPROC, glCreateShader); P(PFNGLSHADERSOURCEPROC, glShaderSource); P(PFNGLCOMPILESHADERPROC, glCompileShader);
P(PFNGLCREATEPROGRAMPROC, glCreateProgram); P(PFNGLATTACHSHADERPROC, glAttachShader); P(PFNGLLINKPROGRAMPROC, glLinkProgram);
P(PFNGLUSEPROGRAMPROC, glUseProgram); P(PFNGLGETUNIFORMLOCATIONPROC, glGetUniformLocation); P(PFNGLUNIFORM1IPROC, glUniform1i);
P(PFNGLGENBUFFERSPROC, glGenBuffers); P(PFNGLBINDBUFFERPROC, glBindBuffer); P(PFNGLBUFFERDATAPROC, glBufferData);
P(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer); P(PFNGLENABLEVERTEXATTRIBARRAYPROC, glEnableVertexAttribArray);
P(PFNGLDRAWARRAYSPROC, glDrawArrays); P(PFNGLCLEARCOLORPROC, glClearColor); P(PFNGLCLEARPROC, glClear);
P(PFNGLREADPIXELSPROC, glReadPixels); P(PFNGLVIEWPORTPROC, glViewport); P(PFNGLGENTEXTURESPROC, glGenTextures);
P(PFNGLBINDTEXTUREPROC, glBindTexture); P(PFNGLTEXIMAGE2DPROC, glTexImage2D); P(PFNGLTEXPARAMETERIPROC, glTexParameteri);
P(PFNGLENABLEPROC, glEnable); P(PFNGLBLENDFUNCPROC, glBlendFunc); P(PFNGLGETERRORPROC, glGetError);
P(PFNGLTEXSTORAGE2DPROC, glTexStorage2D); P(PFNGLTEXSUBIMAGE2DPROC, glTexSubImage2D); P(PFNGLGETSTRINGPROC, glGetString);
printf("GL_VERSION %s\n", glGetString(GL_VERSION));
printf("BGRA ext: %s\n", strstr((const char*)glGetString(GL_EXTENSIONS), "GL_EXT_texture_format_BGRA8888") ? "yes" : "no");
GLuint v = glCreateShader(GL_VERTEX_SHADER), f = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(v, 1, &vs, 0); glCompileShader(v); glShaderSource(f, 1, &fs, 0); glCompileShader(f);
GLuint pr = glCreateProgram(); glAttachShader(pr, v); glAttachShader(pr, f); glLinkProgram(pr); glUseProgram(pr);
float q[] = {-1,-1, 1,-1, -1,1, 1,1};
GLuint vbo; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof q, q, GL_STATIC_DRAW); glVertexAttribPointer(0, 2, GL_FLOAT, 0, 0, 0); glEnableVertexAttribArray(0);
GLint lm = glGetUniformLocation(pr, "mode");
glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
struct { const char* name; GLint ifmt; GLenum fmt; int bpp; int mode; int storage; GLenum sized; unsigned char px[8]; } cases[] = {
{"RGBA/TexImage", GL_RGBA, GL_RGBA, 4, 0, 0, 0, {255,0,0,255, 0,0,0,0}},
{"BGRA_EXT/TexImage", GL_BGRA_EXT, GL_BGRA_EXT, 4, 0, 0, 0, {0,0,255,255, 0,0,0,0}},
{"BGRA8_EXT/TexStorage+Sub", 0, GL_BGRA_EXT, 4, 0, 1, GL_BGRA8_EXT, {0,0,255,255, 0,0,0,0}},
{"RGBA8/TexStorage+Sub", 0, GL_RGBA, 4, 0, 1, GL_RGBA8, {255,0,0,255, 0,0,0,0}},
{"ALPHA", GL_ALPHA, GL_ALPHA, 1, 1, 0, 0, {255, 0}},
{"LUMINANCE_ALPHA", GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, 2, 1, 0, 0, {255,255, 0,0}},
{"R8/TexStorage+Sub", 0, GL_RED, 1, 2, 1, GL_R8, {255, 0}},
};
for (unsigned i = 0; i < sizeof cases / sizeof cases[0]; ++i) {
GLuint t; glGenTextures(1, &t); glBindTexture(GL_TEXTURE_2D, t);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
while (glGetError()) {}
if (cases[i].storage) {
glTexStorage2D(GL_TEXTURE_2D, 1, cases[i].sized, 2, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, cases[i].fmt, GL_UNSIGNED_BYTE, cases[i].px);
} else {
glTexImage2D(GL_TEXTURE_2D, 0, cases[i].ifmt, 2, 1, 0, cases[i].fmt, GL_UNSIGNED_BYTE, cases[i].px);
}
GLenum err = glGetError();
glUniform1i(lm, cases[i].mode);
glViewport(0, 0, 64, 64); glClearColor(1, 1, 1, 1); glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
unsigned char l[4], r[4];
glReadPixels(16, 32, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, l); glReadPixels(48, 32, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, r);
const int ok = l[0] > 200 && l[1] < 60 && l[2] < 60 && r[0] > 200 && r[1] > 200 && r[2] > 200;
printf("%-26s err=0x%04x left=%3d,%3d,%3d right=%3d,%3d,%3d %s\n", cases[i].name, err, l[0], l[1], l[2], r[0], r[1], r[2], ok ? "ok" : "BAD");
}
/* ANGLE's emulation of ALPHA8 / LUMINANCE_ALPHA8 on a desktop-GL driver: R8 / RG8 + swizzle. */
P(PFNGLTEXPARAMETERIPROC, glTexParameteri2);
struct { const char* name; GLenum sized, fmt; int bpp; GLint sw[4]; unsigned char px[4]; unsigned char wantL[3], wantR[3]; } sz[] = {
{"R8 swizzled as ALPHA8", GL_R8, GL_RED, 1, {GL_ZERO, GL_ZERO, GL_ZERO, GL_RED}, {255, 0}, {0,0,0}, {255,255,255}},
{"RG8 swizzled as LUM_ALPHA", GL_RG8, GL_RG, 2, {GL_RED, GL_RED, GL_RED, GL_GREEN}, {255,255, 0,0}, {255,255,255}, {255,255,255}},
{"RG8 as LUM_ALPHA (lum 0)", GL_RG8, GL_RG, 2, {GL_RED, GL_RED, GL_RED, GL_GREEN}, {0,255, 0,0}, {0,0,0}, {255,255,255}},
};
glUniform1i(lm, 0);
for (unsigned i = 0; i < sizeof sz / sizeof sz[0]; ++i) {
GLuint t; glGenTextures(1, &t); glBindTexture(GL_TEXTURE_2D, t);
glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexStorage2D(GL_TEXTURE_2D, 1, sz[i].sized, 2, 1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, sz[i].fmt, GL_UNSIGNED_BYTE, sz[i].px);
glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, sz[i].sw[0]); glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, sz[i].sw[1]);
glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, sz[i].sw[2]); glTexParameteri2(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, sz[i].sw[3]);
GLenum err = glGetError();
glClearColor(1, 1, 1, 1); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
unsigned char l[4], r[4];
glReadPixels(16, 32, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, l); glReadPixels(48, 32, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, r);
int ok = 1;
for (int k = 0; k < 3; ++k) ok = ok && abs(l[k] - sz[i].wantL[k]) < 40 && abs(r[k] - sz[i].wantR[k]) < 40;
printf("%-26s err=0x%04x left=%3d,%3d,%3d right=%3d,%3d,%3d %s\n", sz[i].name, err, l[0], l[1], l[2], r[0], r[1], r[2], ok ? "ok" : "BAD");
}
return 0;
}
+29
View File
@@ -0,0 +1,29 @@
name: Set swap space
description: Replace the runner's swap with a swap file of the given size
inputs:
swap-size-gb:
description: Size of the swap file in GiB
required: true
runs:
using: composite
steps:
# Hosted images differ: some ship /swapfile, some ship no swap at all. Turn off whatever is
# active, then create the file at the path the image used, or /swapfile when there was none.
- shell: bash
env:
SWAP_SIZE_GB: ${{ inputs.swap-size-gb }}
run: |
set -euo pipefail
active="$(swapon --show=NAME --noheadings)"
swap_file="$(printf '%s\n' "$active" | grep '^/' | tail -n 1 || true)"
swap_file="${swap_file:-/swapfile}"
if [ -n "$active" ]; then
printf '%s\n' "$active" | xargs -r -n1 sudo swapoff
fi
sudo rm -f "$swap_file"
sudo fallocate -l "${SWAP_SIZE_GB}G" "$swap_file"
sudo chmod 600 "$swap_file"
sudo mkswap "$swap_file"
sudo swapon "$swap_file"
free -h
swapon --show
+1 -1
View File
@@ -45,7 +45,7 @@ runs:
sudo tee /etc/apt/apt.conf.d/80-mobilegl-ci-network > /dev/null sudo tee /etc/apt/apt.conf.d/80-mobilegl-ci-network > /dev/null
fi fi
# Cores go through zstd: a unit run's intentional aborts (death tests) left hundreds # Cores go through zstd: a unit run's intentional aborts (death tests) left hundreds
# of raw cores in /tmp and filled the disk. The handler keeps honouring each # of raw cores in /tmp and filled the disk. The handler keeps honoring each
# process's RLIMIT_CORE (%c), so only steps that raise it with ulimit -c get cores; # process's RLIMIT_CORE (%c), so only steps that raise it with ulimit -c get cores;
# read one with 'zstd -d core.<exe>.<pid>.zst' first. # read one with 'zstd -d core.<exe>.<pid>.zst' first.
if [ ! -f /.dockerenv ] && command -v sudo > /dev/null && command -v zstd > /dev/null; then if [ ! -f /.dockerenv ] && command -v sudo > /dev/null && command -v zstd > /dev/null; then
+81 -24
View File
@@ -23,7 +23,7 @@ env:
# Software-rendered CI submissions may exceed the interactive GPU hang budget. # Software-rendered CI submissions may exceed the interactive GPU hang budget.
MOBILEGL_GPU_HANG_BUDGET_MS: "0" MOBILEGL_GPU_HANG_BUDGET_MS: "0"
# ONE LIVE RUN PER BRANCH, as in test.yml: a superseded push is cancelled rather than left queued # ONE LIVE RUN PER BRANCH, as in test.yml: a superseded push is canceled rather than left queued
# behind its successor; a manual dispatch gets a group of its own; dev queues rather than cancels. # behind its successor; a manual dispatch gets a group of its own; dev queues rather than cancels.
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.ref }} group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && github.run_id || github.ref }}
@@ -42,7 +42,7 @@ jobs:
CCACHE_MAXSIZE: 4G CCACHE_MAXSIZE: 4G
CCACHE_NOHASHDIR: "true" CCACHE_NOHASHDIR: "true"
MOBILEGL_CMAKE_COMPILER_LAUNCHER: ccache MOBILEGL_CMAKE_COMPILER_LAUNCHER: ccache
MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }} MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }}
steps: steps:
- name: Checkout repo - name: Checkout repo
@@ -251,7 +251,7 @@ jobs:
name: trace case matrix name: trace case matrix
runs-on: ubuntu-26.04 runs-on: ubuntu-26.04
env: env:
MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }} MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }}
outputs: outputs:
android: ${{ steps.trace-cases.outputs.android }} android: ${{ steps.trace-cases.outputs.android }}
names: ${{ steps.trace-cases.outputs.names }} names: ${{ steps.trace-cases.outputs.names }}
@@ -379,7 +379,7 @@ jobs:
${{ env.ANDROID_SDK_ROOT }}/platform-tools ${{ env.ANDROID_SDK_ROOT }}/platform-tools
${{ env.ANDROID_SDK_ROOT }}/platforms/android-35 ${{ env.ANDROID_SDK_ROOT }}/platforms/android-35
${{ env.ANDROID_SDK_ROOT }}/system-images/android-35/google_apis/x86_64 ${{ env.ANDROID_SDK_ROOT }}/system-images/android-35/google_apis/x86_64
key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v2-${{ hashFiles('android-plugin/run-avd-ci.sh') }} key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v4-${{ hashFiles('android-plugin/run-avd-ci.sh') }}
- name: Create AVD - name: Create AVD
if: steps.android-avd-cache.outputs.cache-hit != 'true' if: steps.android-avd-cache.outputs.cache-hit != 'true'
@@ -391,6 +391,33 @@ jobs:
--profile pixel_6 \ --profile pixel_6 \
--avd-name "${AVD_NAME}" --avd-name "${AVD_NAME}"
- name: Enable KVM
if: steps.android-avd-cache.outputs.cache-hit != 'true'
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
# Boot the new AVD once so the cached AVD is past its first-boot setup (measured 37 s first
# boot, 26 s for the next cold boot). A quick-boot snapshot was tried instead: it doubled the
# cache restore and tripled "device offline" retries, so retrace jobs cold-boot. If the
# pre-boot fails, the AVD is created afresh so the cache never holds a half-booted one.
- name: Pre-boot AVD
if: steps.android-avd-cache.outputs.cache-hit != 'true'
run: |
log="${RUNNER_TEMP}/avd-preboot.log"
pid="${RUNNER_TEMP}/avd-preboot.pid"
if ! sh android-plugin/run-avd-ci.sh preboot --avd-name "${AVD_NAME}" --gpu software \
--emulator-log "${log}" --pid-file "${pid}" --boot-timeout 300; then
echo "::warning::the AVD pre-boot failed; caching a never-booted AVD instead"
tail -50 "${log}" || true
sh android-plugin/run-avd-ci.sh stop --avd-name "${AVD_NAME}" --emulator-log "${log}" --pid-file "${pid}"
timeout 60 tail --pid="$(cat "${pid}")" -f /dev/null || true
sh android-plugin/run-avd-ci.sh create --api-level 35 --target google_apis --arch x86_64 \
--profile pixel_6 --avd-name "${AVD_NAME}"
fi
du -sh "${ANDROID_AVD_HOME}"
retrace: retrace:
name: retrace (${{ matrix.backend.name }} / ${{ matrix.transport || 'monolith' }}, ${{ matrix.case.name }}) name: retrace (${{ matrix.backend.name }} / ${{ matrix.transport || 'monolith' }}, ${{ matrix.case.name }})
runs-on: ubuntu-26.04 runs-on: ubuntu-26.04
@@ -410,17 +437,20 @@ jobs:
ANDROID_SDK_ROOT: ${{ github.workspace }}/.android/sdk ANDROID_SDK_ROOT: ${{ github.workspace }}/.android/sdk
strategy: strategy:
fail-fast: false fail-fast: false
max-parallel: 4 # The account runs at most 20 jobs at once, shared with Test. These ~230 emulator jobs are
# the longer of the two runs, so they may take the whole pool: what is minimized is when the
# later of APK and Test finishes, not Test alone.
max-parallel: 20
matrix: ${{ fromJSON(needs.trace-cases.outputs.android) }} matrix: ${{ fromJSON(needs.trace-cases.outputs.android) }}
steps: steps:
- name: Set swap space
uses: pierotofy/set-swap-space@v1.0
with:
swap-size-gb: 8
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Set swap space
uses: ./.github/actions/set-swap-space
with:
swap-size-gb: 8
- name: Download trace fixture - name: Download trace fixture
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
with: with:
@@ -438,8 +468,29 @@ jobs:
echo "EMULATOR_LOG=${RUNNER_TEMP}/mobilegl-emulator.log" >> "$GITHUB_ENV" echo "EMULATOR_LOG=${RUNNER_TEMP}/mobilegl-emulator.log" >> "$GITHUB_ENV"
echo "EMULATOR_PID_FILE=${RUNNER_TEMP}/mobilegl-emulator.pid" >> "$GITHUB_ENV" echo "EMULATOR_PID_FILE=${RUNNER_TEMP}/mobilegl-emulator.pid" >> "$GITHUB_ENV"
- name: Restore Android AVD cache
id: android-avd-cache
uses: actions/cache/restore@v5
with:
path: |
${{ env.ANDROID_AVD_HOME }}
${{ env.ANDROID_SDK_ROOT }}/emulator
${{ env.ANDROID_SDK_ROOT }}/platform-tools
${{ env.ANDROID_SDK_ROOT }}/platforms/android-35
${{ env.ANDROID_SDK_ROOT }}/system-images/android-35/google_apis/x86_64
key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v4-${{ hashFiles('android-plugin/run-avd-ci.sh') }}
# The cache holds everything a retrace job runs (emulator, adb, image, AVD); the SDK manager
# is only needed to create the AVD when the cache missed.
- name: Use the cached SDK tools
if: steps.android-avd-cache.outputs.cache-hit == 'true'
run: |
echo "${ANDROID_SDK_ROOT}/platform-tools" >> "$GITHUB_PATH"
echo "${ANDROID_SDK_ROOT}/emulator" >> "$GITHUB_PATH"
- name: Setup Android SDK - name: Setup Android SDK
id: netsetup415 id: netsetup415
if: steps.android-avd-cache.outputs.cache-hit != 'true'
continue-on-error: true continue-on-error: true
timeout-minutes: 15 timeout-minutes: 15
uses: android-actions/setup-android@v4 uses: android-actions/setup-android@v4
@@ -458,20 +509,9 @@ jobs:
cmdline-tools-version: 13114758 cmdline-tools-version: 13114758
- name: Accept Android SDK licenses - name: Accept Android SDK licenses
if: steps.android-avd-cache.outputs.cache-hit != 'true'
run: yes | sdkmanager --licenses >/dev/null run: yes | sdkmanager --licenses >/dev/null
- name: Restore Android AVD cache
id: android-avd-cache
uses: actions/cache/restore@v5
with:
path: |
${{ env.ANDROID_AVD_HOME }}
${{ env.ANDROID_SDK_ROOT }}/emulator
${{ env.ANDROID_SDK_ROOT }}/platform-tools
${{ env.ANDROID_SDK_ROOT }}/platforms/android-35
${{ env.ANDROID_SDK_ROOT }}/system-images/android-35/google_apis/x86_64
key: ${{ runner.os }}-mobilegl-avd-api35-google_apis-x86_64-pixel_6-v2-${{ hashFiles('android-plugin/run-avd-ci.sh') }}
- name: Download retrace APK - name: Download retrace APK
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
with: with:
@@ -659,20 +699,37 @@ jobs:
--pid-file "${EMULATOR_PID_FILE}" --pid-file "${EMULATOR_PID_FILE}"
- name: Upload Android retrace result - name: Upload Android retrace result
id: upload-retrace-result
if: always() if: always()
continue-on-error: true
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: MobileGL-android-retrace-result-${{ matrix.lane || 'monolith-control' }}-${{ matrix.transport || 'monolith' }}-${{ env.date_today }}-${{ github.sha }}-${{ matrix.backend.name }}-${{ matrix.case.name }} name: MobileGL-android-retrace-result-${{ matrix.lane || 'monolith-control' }}-${{ matrix.transport || 'monolith' }}-${{ env.date_today }}-${{ github.sha }}-${{ matrix.backend.name }}-${{ matrix.case.name }}
path: android-retrace-result/** path: android-retrace-result/**
if-no-files-found: warn if-no-files-found: warn
# The artifact service sometimes times out (ETIMEDOUT on CreateArtifact, one job in 231 on
# 2026-10-18), which turned a passing case red; one delayed retry absorbs that.
- name: Wait before retrying the upload
if: always() && steps.upload-retrace-result.outcome == 'failure'
run: sleep 30
- name: Upload Android retrace result (retry)
if: always() && steps.upload-retrace-result.outcome == 'failure'
uses: actions/upload-artifact@v7
with:
name: MobileGL-android-retrace-result-${{ matrix.lane || 'monolith-control' }}-${{ matrix.transport || 'monolith' }}-${{ env.date_today }}-${{ github.sha }}-${{ matrix.backend.name }}-${{ matrix.case.name }}
path: android-retrace-result/**
if-no-files-found: warn
overwrite: true
retrace-summary: retrace-summary:
name: retrace summary name: retrace summary
runs-on: ubuntu-26.04 runs-on: ubuntu-26.04
needs: retrace needs: retrace
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
env: env:
MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }} MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }}
steps: steps:
- name: Checkout repo - name: Checkout repo
@@ -772,7 +829,7 @@ jobs:
# Fail before deletion if either paginated inventory cannot be read. # Fail before deletion if either paginated inventory cannot be read.
# 50 rows per page: the jobs endpoint answers HTTP 502 for 90+ rows on a re-run attempt of a run this size. # 50 rows per page: the jobs endpoint answers HTTP 502 for 90+ rows on a re-run attempt of a run this size.
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=50" \ gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=50" \
--jq '.jobs[] | select(.name | startswith("retrace (")) | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out" or .conclusion == "action_required") | .name' \ --jq '.jobs[] | select(.name | startswith("retrace (")) | select(.conclusion == "failure" or .conclusion == "canceled" or .conclusion == "timed_out" or .conclusion == "action_required") | .name' \
> "${RUNNER_TEMP}/cleanup-jobs.txt" > "${RUNNER_TEMP}/cleanup-jobs.txt"
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" \ gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" \
--jq '.artifacts[] | select(.name | startswith("MobileGL-trace-fixture-") or startswith("MobileGL-android-retrace-result-") or startswith("trace-fixture-") or startswith("retrace-result-")) | [.id, .name] | @tsv' \ --jq '.artifacts[] | select(.name | startswith("MobileGL-trace-fixture-") or startswith("MobileGL-android-retrace-result-") or startswith("trace-fixture-") or startswith("retrace-result-")) | [.id, .name] | @tsv' \
+106 -192
View File
@@ -46,7 +46,7 @@ jobs:
build-type: ${{ steps.configure.outputs.build-type }} build-type: ${{ steps.configure.outputs.build-type }}
env: env:
BUILD_DIR: build-linux BUILD_DIR: build-linux
MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }} MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }}
CCACHE_BASEDIR: ${{ github.workspace }} CCACHE_BASEDIR: ${{ github.workspace }}
CCACHE_COMPRESS: "true" CCACHE_COMPRESS: "true"
CCACHE_DIR: ${{ github.workspace }}/.ccache CCACHE_DIR: ${{ github.workspace }}/.ccache
@@ -54,16 +54,16 @@ jobs:
CCACHE_NOHASHDIR: "true" CCACHE_NOHASHDIR: "true"
steps: steps:
- name: Set swap space
uses: pierotofy/set-swap-space@v1.0
with:
swap-size-gb: 32
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Set swap space
uses: ./.github/actions/set-swap-space
with:
swap-size-gb: 32
- name: Configure runner environment - name: Configure runner environment
uses: ./.github/actions/setup-test-runner uses: ./.github/actions/setup-test-runner
@@ -252,15 +252,16 @@ jobs:
CCACHE_COMPRESS: "true" CCACHE_COMPRESS: "true"
CCACHE_NOHASHDIR: "true" CCACHE_NOHASHDIR: "true"
steps: steps:
- name: Set swap space
uses: pierotofy/set-swap-space@v1.0
with:
swap-size-gb: 32
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Set swap space
uses: ./.github/actions/set-swap-space
with:
swap-size-gb: 32
- name: Configure runner environment - name: Configure runner environment
uses: ./.github/actions/setup-test-runner uses: ./.github/actions/setup-test-runner
@@ -343,7 +344,7 @@ jobs:
# Initialize()'s glslang table prewarm. The retrace legs keep it: they replay real apps. # Initialize()'s glslang table prewarm. The retrace legs keep it: they replay real apps.
env: env:
MOBILEGL_DISABLE_SHADER_PREWARM: "1" MOBILEGL_DISABLE_SHADER_PREWARM: "1"
MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }} MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }}
steps: steps:
- name: Checkout repo - name: Checkout repo
@@ -425,7 +426,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
include: ${{ fromJSON((github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && '[{"lane":"monolith-control","transport":"monolith"},{"lane":"inproc-acceptance","transport":"inproc"}]' || '[{"lane":"monolith-control","transport":"monolith"}]') }} include: ${{ fromJSON((github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && '[{"lane":"monolith-control","transport":"monolith"},{"lane":"inproc-acceptance","transport":"inproc"}]' || '[{"lane":"monolith-control","transport":"monolith"}]') }}
env: env:
# Short-lived test processes skip the glslang prewarm, as in the test job. # Short-lived test processes skip the glslang prewarm, as in the test job.
MOBILEGL_DISABLE_SHADER_PREWARM: "1" MOBILEGL_DISABLE_SHADER_PREWARM: "1"
@@ -433,7 +434,7 @@ jobs:
MOBILEGL_IPC_ROLE_SPLIT_STATE: "1" MOBILEGL_IPC_ROLE_SPLIT_STATE: "1"
MOBILEGL_IPC_STRICT_ERRORS: "1" MOBILEGL_IPC_STRICT_ERRORS: "1"
MOBILEGL_IPC_RUN_AHEAD: "1" MOBILEGL_IPC_RUN_AHEAD: "1"
MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }} MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }}
steps: steps:
- name: Checkout repo - name: Checkout repo
@@ -610,16 +611,16 @@ jobs:
CCACHE_NOHASHDIR: "true" CCACHE_NOHASHDIR: "true"
steps: steps:
- name: Set swap space
uses: pierotofy/set-swap-space@v1.0
with:
swap-size-gb: 32
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Set swap space
uses: ./.github/actions/set-swap-space
with:
swap-size-gb: 32
- name: Configure runner environment - name: Configure runner environment
uses: ./.github/actions/setup-test-runner uses: ./.github/actions/setup-test-runner
@@ -684,6 +685,9 @@ jobs:
# switches to Debug under ACTIONS_STEP_DEBUG; this job deliberately does not - a Debug # switches to Debug under ACTIONS_STEP_DEBUG; this job deliberately does not - a Debug
# build flips CXX_VISIBILITY_PRESET and arms MOBILEGL_PIPE_POISON through a second, unrelated # build flips CXX_VISIBILITY_PRESET and arms MOBILEGL_PIPE_POISON through a second, unrelated
# arm of its #if, so the debug switch would change what the lane is measuring.) # arm of its #if, so the debug switch would change what the lane is measuring.)
# Both disaggregation options default ON, so they are pinned OFF here: a disaggregated
# library expects a server-role log that this monolith lane never writes.
# integration-verify-split is the split arm.
run: | run: |
cmake -S . -B "${BUILD_DIR}" -G Ninja \ cmake -S . -B "${BUILD_DIR}" -G Ninja \
-DCMAKE_C_COMPILER="${MOBILEGL_CI_CC}" \ -DCMAKE_C_COMPILER="${MOBILEGL_CI_CC}" \
@@ -698,6 +702,8 @@ jobs:
-DMOBILEGL_ITEST_VK_ICD="${VK_ICD_FILENAMES}" \ -DMOBILEGL_ITEST_VK_ICD="${VK_ICD_FILENAMES}" \
-DMOBILEGL_BUILD_TRACE_REPLAY=OFF \ -DMOBILEGL_BUILD_TRACE_REPLAY=OFF \
-DMOBILEGL_PIPE_VERIFY=ON \ -DMOBILEGL_PIPE_VERIFY=ON \
-DMOBILEGL_BUILD_DISAGGREGATED=OFF \
-DMOBILEGL_BUILD_DISAGGREGATED_INPROC=OFF \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCMAKE_POLICY_VERSION_MINIMUM=3.5
- name: Build - name: Build
@@ -799,9 +805,9 @@ jobs:
if-no-files-found: error if-no-files-found: error
# The verify lane itself, plus the controls that keep it falsifiable: its own control entries, # The verify lane itself, plus the controls that keep it falsifiable: its own control entries,
# checked by name, and the two corrupted-snapshot steps. They are always on, not a manual # checked by name, and the comparator's unit cases on its library. They are always on, not a
# exercise: a gate that can only be shown to work by someone remembering to break it on purpose # manual exercise: a gate that can only be shown to work by someone remembering to break it on
# is a gate that has already stopped working. # purpose is a gate that has already stopped working.
integration-verify: integration-verify:
runs-on: ubuntu-26.04 runs-on: ubuntu-26.04
timeout-minutes: 180 timeout-minutes: 180
@@ -879,7 +885,8 @@ jobs:
# skip in the `test` job's build and assert only here, as do poison-gated cases on branches # skip in the `test` job's build and assert only here, as do poison-gated cases on branches
# where that build is a monolith. The packaging step above ships MG_Test whole, so the cost # where that build is a monolith. The packaging step above ships MG_Test whole, so the cost
# is the run itself. --no-tests=error, so a packaging change that dropped the unit binaries # is the run itself. --no-tests=error, so a packaging change that dropped the unit binaries
# fails here instead of passing with nothing run. # fails here instead of passing with nothing run. Among them is the comparator's per-field
# control, PipeInputsTest.EveryCorruptedFieldIsNamedByTheComparator.
- name: Unit tests on the verify runtime (G6, G10) - name: Unit tests on the verify runtime (G6, G10)
working-directory: build-verify working-directory: build-verify
run: ctest --output-on-failure -L unit --no-tests=error -j "$(nproc)" --output-junit "${RUNNER_TEMP}/unit-verify.xml" run: ctest --output-on-failure -L unit --no-tests=error -j "$(nproc)" --output-junit "${RUNNER_TEMP}/unit-verify.xml"
@@ -904,8 +911,10 @@ jobs:
# The lane's own control entries, by name: each must have run and passed, and a skip is a # The lane's own control entries, by name: each must have run and passed, and a skip is a
# failure here although ctest reports it green. VerifyArming asserts in-test that the # failure here although ctest reports it green. VerifyArming asserts in-test that the
# comparator printed its armed line, VerifyCorrupted that a corrupted field is reported by # comparator printed its armed line with fatal=1, under the ambient entries' own environment
# name, and PoisonOmitted that an omitted stamp kills a forked child with its own Fatal. # (MG_IntegrationTest/CMakeLists.txt builds both from one list), VerifyCorrupted that a
# corrupted field is reported by name, and PoisonOmitted that an omitted stamp kills a
# forked child with its own Fatal.
- name: Verify lane control entries - name: Verify lane control entries
run: | run: |
python3 scripts/ci/junit_tally.py "${RUNNER_TEMP}/verify.xml" \ python3 scripts/ci/junit_tally.py "${RUNNER_TEMP}/verify.xml" \
@@ -916,59 +925,6 @@ jobs:
--require-entry-passed DirectGLES.PoisonOmitted.PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb \ --require-entry-passed DirectGLES.PoisonOmitted.PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb \
--require-entry-passed DirectVulkan.PoisonOmitted.PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb --require-entry-passed DirectVulkan.PoisonOmitted.PoisonOmissionScenario.OmittedFieldAbortsOnThatVerb
# NEGATIVE CONTROL A (gate G4). The knob perturbs one field in the snapshot arm before the
# entry compare, so a working comparator must abort the run. This step passes when ctest
# FAILS - `if ctest ...; then error` - which is the only shape that can catch a comparator
# that silently compares nothing.
#
# The knob reaches the test process through the JOB environment: no ctest ENVIRONMENT
# property on the ambient Verify. entries names it (MG_IntegrationTest/CMakeLists.txt says
# so out loud), and a property entry would otherwise override this and the control would
# prove nothing. Same precedent as MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH in `integration`.
- name: Negative control A (corrupted snapshot)
working-directory: build-verify
env:
MOBILEGL_ITEST_REQUIRE_GPU: "1"
MOBILEGL_PIPE_VERIFY_CORRUPT: GetRenderStateParameters
run: |
FILTER='DirectGLES\.Verify\..*ClearThenReadPixels'
# An empty selection would ALSO make ctest exit non-zero (--no-tests=error), and this
# step reads non-zero as "the control worked" - so the selection is counted first. A
# control that passes because it ran nothing is worse than no control.
matched=$(ctest -N -L integration-verify -R "${FILTER}" | grep -cE '^ *Test *#[0-9]+:')
if [ "${matched}" -lt 1 ]; then
echo "::error::negative control A selected ${matched} tests; its filter no longer matches anything"
exit 1
fi
if ctest --output-on-failure -L integration-verify -R "${FILTER}" --no-tests=error; then
echo "::error::MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters left ${matched} verify entries GREEN. The comparator is not comparing, so every green entry above is green for no reason."
exit 1
fi
echo "the corrupted field turned ${matched} selected entries red, as it must"
# NEGATIVE CONTROL A2 (P13 W3c): THE CAPABILITY CROSS-CHECK MOVED HERE. ResidualValueBlock
# carried the 35 capability bits redundantly so the applier could trip on a capability no
# record carried; P13 deleted it, and the intent is the comparator's now. So a corrupted
# capability field must turn the same selection red, or the block's deletion lost its
# check rather than moving it.
- name: Negative control A2 (corrupted capability)
working-directory: build-verify
env:
MOBILEGL_ITEST_REQUIRE_GPU: "1"
MOBILEGL_PIPE_VERIFY_CORRUPT: IsCapabilityEnabled
run: |
FILTER='DirectGLES\.Verify\..*ClearThenReadPixels'
matched=$(ctest -N -L integration-verify -R "${FILTER}" | grep -cE '^ *Test *#[0-9]+:')
if [ "${matched}" -lt 1 ]; then
echo "::error::negative control A2 selected ${matched} tests; its filter no longer matches anything"
exit 1
fi
if ctest --output-on-failure -L integration-verify -R "${FILTER}" --no-tests=error; then
echo "::error::MOBILEGL_PIPE_VERIFY_CORRUPT=IsCapabilityEnabled left ${matched} verify entries GREEN. The comparator does not compare the capability field, so deleting the residual block (P13 W3c) lost the check it carried."
exit 1
fi
echo "the corrupted capability field turned ${matched} selected entries red, as it must"
- name: Upload verify lane logs - name: Upload verify lane logs
if: ${{ always() }} if: ${{ always() }}
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
@@ -995,7 +951,7 @@ jobs:
if-no-files-found: ignore if-no-files-found: ignore
# The comparator on the split (inproc) arm, both backends, with its control entries checked by # The comparator on the split (inproc) arm, both backends, with its control entries checked by
# name and the corrupted-snapshot control red on that arm. # name.
# #
# ITS OWN BUILD, because neither existing one can carry it: build-linux-verify configures # ITS OWN BUILD, because neither existing one can carry it: build-linux-verify configures
# without MOBILEGL_BUILD_DISAGGREGATED (so MG_Remote and the transport parser do not exist and # without MOBILEGL_BUILD_DISAGGREGATED (so MG_Remote and the transport parser do not exist and
@@ -1115,49 +1071,6 @@ jobs:
done done
python3 scripts/ci/junit_tally.py "${RUNNER_TEMP}/verify-split.xml" "${args[@]}" python3 scripts/ci/junit_tally.py "${RUNNER_TEMP}/verify-split.xml" "${args[@]}"
# THE GREEN LANE'S LOGS, KEPT BEFORE THE CONTROLS OVERWRITE THEM. The control step below
# re-runs ambient VerifySplit. entries with a knob exported, and an ambient entry writes to
# ITS OWN per-entry path under verify-split-logs/ - which the library opens "w". So by the
# time the upload step at the end of this job runs, the selected files are the CONTROL
# runs' logs sitting under the LANE's names: an artifact showing deliberate divergences,
# with the green run they were meant to be evidence of already gone. `if: always()` on the
# upload made that worse rather than better, because a failure investigation is exactly
# when the overwritten half is the half somebody needs.
- name: Keep lane logs before controls
if: ${{ always() }}
working-directory: build-verify-split/MobileGL/MG_IntegrationTest
run: |
rm -rf verify-split-logs-lane
cp -a verify-split-logs verify-split-logs-lane
cp -p "${RUNNER_TEMP}/verify-split.xml" verify-split-logs-lane/ || true
echo "kept $(ls verify-split-logs-lane | wc -l) file(s) from the green lane run"
# NEGATIVE CONTROL A ON THE SPLIT ARM - integration-verify's step A, same knob, same shape
# (this step passes when ctest FAILS), aimed at the ambient VerifySplit. entries of BOTH
# backends. Measured at landing: 10 of 10 red, each with
# Fatal{PipeVerifyDiffer, "GetRenderStateParameters@Clear"|"@DrawArrays", ..., where=entry}.
- name: Negative control A (corrupted snapshot)
working-directory: build-verify-split
env:
MOBILEGL_ITEST_REQUIRE_GPU: "1"
MOBILEGL_PIPE_VERIFY_CORRUPT: GetRenderStateParameters
run: |
FILTER='(DirectGLES|DirectVulkan)\.VerifySplit\..*ClearThenReadPixels'
matched=$(ctest -N -L '^integration-verify-split$' -R "${FILTER}" | grep -cE '^ *Test *#[0-9]+:')
if [ "${matched}" -lt 2 ]; then
echo "::error::split negative control A selected ${matched} tests; its filter no longer matches both backends"
exit 1
fi
if ctest --output-on-failure -L '^integration-verify-split$' -R "${FILTER}" --no-tests=error; then
echo "::error::MOBILEGL_PIPE_VERIFY_CORRUPT=GetRenderStateParameters left ${matched} verify-split entries GREEN. The comparator is not comparing on the split arm."
exit 1
fi
echo "the corrupted field turned ${matched} selected split entries red, as it must"
# BOTH SETS, AND THEY ARE DIFFERENT EVIDENCE. verify-split-logs-lane/ is the green run the
# arm proof read; verify-split-logs/ is whatever the control step left behind, which for
# its entries is a deliberately red re-run under the same name. Uploading only the
# second - which is what this step did before the copy above existed - loses the first.
# The run log needs a signed-in reader; annotations do not. Name every failed ctest entry, # The run log needs a signed-in reader; annotations do not. Name every failed ctest entry,
# with the lines of its output that say why, where anyone reading the run can see them. # with the lines of its output that say why, where anyone reading the run can see them.
- name: Annotate failed entries - name: Annotate failed entries
@@ -1170,7 +1083,6 @@ jobs:
with: with:
name: integration-verify-split-logs name: integration-verify-split-logs
path: | path: |
build-verify-split/MobileGL/MG_IntegrationTest/verify-split-logs-lane/**
build-verify-split/MobileGL/MG_IntegrationTest/verify-split-logs/** build-verify-split/MobileGL/MG_IntegrationTest/verify-split-logs/**
build-verify-split/MobileGL/MG_IntegrationTest/pipe-*split*.log* build-verify-split/MobileGL/MG_IntegrationTest/pipe-*split*.log*
if-no-files-found: warn if-no-files-found: warn
@@ -1205,7 +1117,7 @@ jobs:
name: build guard (split reuse) name: build guard (split reuse)
runs-on: ubuntu-26.04 runs-on: ubuntu-26.04
timeout-minutes: 150 timeout-minutes: 150
if: ${{ github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup' }} if: ${{ github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup' }}
# Consumers read only its outputs, and any verdict but 'identical' (none at all included) # Consumers read only its outputs, and any verdict but 'identical' (none at all included)
# keeps both builds. # keeps both builds.
continue-on-error: true continue-on-error: true
@@ -1214,7 +1126,7 @@ jobs:
image: ${{ steps.guard.outputs.image }} image: ${{ steps.guard.outputs.image }}
compiler: ${{ steps.guard.outputs.compiler }} compiler: ${{ steps.guard.outputs.compiler }}
env: env:
MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }} MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }}
# Every step may fail without failing the job: a setup step that failed leaves a configure # Every step may fail without failing the job: a setup step that failed leaves a configure
# that fails, which the guard reports as 'unknown'. # that fails, which the guard reports as 'unknown'.
@@ -1411,7 +1323,7 @@ jobs:
- name: Set swap space - name: Set swap space
if: env.SPLIT_REUSE != 'true' if: env.SPLIT_REUSE != 'true'
uses: pierotofy/set-swap-space@v1.0 uses: ./.github/actions/set-swap-space
with: with:
swap-size-gb: 32 swap-size-gb: 32
@@ -1551,7 +1463,9 @@ jobs:
if-no-files-found: error if-no-files-found: error
# The split lane itself: the three-arm shape ARCHITECTURE.md §13.2 asks for verbatim, plus the # The split lane itself: the three-arm shape ARCHITECTURE.md §13.2 asks for verbatim, plus the
# first real run of the five MG_Test/Wire suites, plus P5's two exit-gate negative controls. # first real run of the five MG_Test/Wire suites. P5's two exit-gate negative controls are in-test
# now (RemoteWaitBoundaryControl.AnUnwaitedPublishIsSeenAsNoWait and
# PersistentCoherentMapScenario.ThePushIsTheOnlyRouteAWriteTakesToTheServer).
integration-split: integration-split:
runs-on: ubuntu-26.04 runs-on: ubuntu-26.04
timeout-minutes: 120 timeout-minutes: 120
@@ -1567,7 +1481,7 @@ jobs:
SPLIT_REUSE: ${{ needs.build-guard.outputs.verdict == 'identical' && 'true' || 'false' }} SPLIT_REUSE: ${{ needs.build-guard.outputs.verdict == 'identical' && 'true' || 'false' }}
SPLIT_BUILD_DIR: ${{ needs.build-guard.outputs.verdict == 'identical' && 'build-linux' || 'build-split' }} SPLIT_BUILD_DIR: ${{ needs.build-guard.outputs.verdict == 'identical' && 'build-linux' || 'build-split' }}
SPLIT_RUNTIME: ${{ needs.build-guard.outputs.verdict == 'identical' && 'mobilegl-linux-runtime' || 'mobilegl-linux-runtime-split' }} SPLIT_RUNTIME: ${{ needs.build-guard.outputs.verdict == 'identical' && 'mobilegl-linux-runtime' || 'mobilegl-linux-runtime-split' }}
MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }} MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }}
steps: steps:
- name: Checkout repo - name: Checkout repo
@@ -1636,7 +1550,7 @@ jobs:
- name: Spawn/split lane parity (9.2) - name: Spawn/split lane parity (9.2)
run: python3 scripts/ci/spawn_lane_parity.py "${SPLIT_BUILD_DIR}" run: python3 scripts/ci/spawn_lane_parity.py "${SPLIT_BUILD_DIR}"
# P8-A (ID-P8-2): every monolith case, tails normalised, is on a gated split arm or is named # P8-A (ID-P8-2): every monolith case, tails normalized, is on a gated split arm or is named
# with a reason class in scripts/data/split_coverage_exemptions.txt. The same `ctest -N` # with a reason class in scripts/data/split_coverage_exemptions.txt. The same `ctest -N`
# cost as the parity step above, and the question it cannot answer: parity compares the # cost as the parity step above, and the question it cannot answer: parity compares the
# split arms with EACH OTHER, so a case missing from all three passes it. # split arms with EACH OTHER, so a case missing from all three passes it.
@@ -1645,7 +1559,7 @@ jobs:
python3 scripts/ci/split_coverage.py --self-test python3 scripts/ci/split_coverage.py --self-test
python3 scripts/ci/split_coverage.py "${SPLIT_BUILD_DIR}" python3 scripts/ci/split_coverage.py "${SPLIT_BUILD_DIR}"
# P8-SE: PipeCatalogueTest's unmigrated-emulation list equals the MGPipeUnmigratedEmulation # P8-SE: PipeCatalogTest's unmigrated-emulation list equals the MGPipeUnmigratedEmulation
# call sites in the tree, one site per name. A source lint: no build needed. # call sites in the tree, one site per name. A source lint: no build needed.
- name: Unmigrated-emulation list against its call sites (P8-SE) - name: Unmigrated-emulation list against its call sites (P8-SE)
run: | run: |
@@ -1689,11 +1603,7 @@ jobs:
ulimit -c unlimited ulimit -c unlimited
export MOBILEGL_FLATC_EXECUTABLE="${RUNNER_TEMP}/flatc-build/flatc" export MOBILEGL_FLATC_EXECUTABLE="${RUNNER_TEMP}/flatc-build/flatc"
test -x "${MOBILEGL_FLATC_EXECUTABLE}" test -x "${MOBILEGL_FLATC_EXECUTABLE}"
# The exit code is kept beside the JUnit: the controls step tallies this run as its baseline. ctest --output-on-failure -L integration-split --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/split-baseline.xml"
rc=0
ctest --output-on-failure -L integration-split --no-tests=error -j 4 --output-junit "${RUNNER_TEMP}/split-baseline.xml" || rc=$?
echo "${rc}" > "${RUNNER_TEMP}/split-baseline.rc"
[ "${rc}" -eq 0 ] || exit "${rc}"
python3 ../scripts/ci/junit_tally.py "${RUNNER_TEMP}/split-baseline.xml" --require-split-ran python3 ../scripts/ci/junit_tally.py "${RUNNER_TEMP}/split-baseline.xml" --require-split-ran
# Exit gate 9.3: the spawn arm green at the same count. It runs the same cases through a # Exit gate 9.3: the spawn arm green at the same count. It runs the same cases through a
# SECOND PROCESS, so a failure here that the line above did not see is a transport fact # SECOND PROCESS, so a failure here that the line above did not see is a transport fact
@@ -1782,20 +1692,28 @@ jobs:
print(f"the two arms agree on all {len(a)} entries, name and status") print(f"the two arms agree on all {len(a)} entries, name and status")
PY PY
# Negative controls for the verb barrier (MOBILEGL_IPC_VERB_BARRIER=0) and the persistent-map # The DirectGLES.Split. entries and their log-ownership check, once, in a job whose 'Split
# push (MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0): each knob must turn its selected entries red. # lanes' step never ran because an earlier step failed: without this, a red unit lane or
# The body is scripts/ci/split_negative_controls.sh, so scripts/ci/control_smoke_test.sh can # flatc build would hide every split integration result of the run. When the lane step ran
# run the same lines off a runner. It needs a green baseline with at least one passed entry, # it left split-baseline.xml, and this step does nothing.
# tallied from the lane step's JUnit and exit code (re-run only when that step never ran), - name: Split lane fallback (lane step skipped)
# and accepts a red only when it carries the control's own diagnostic.
- name: Negative controls (barrier, map push)
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
working-directory: ${{ env.SPLIT_BUILD_DIR }} working-directory: ${{ env.SPLIT_BUILD_DIR }}
env: env:
MOBILEGL_ITEST_REQUIRE_GPU: "1" MOBILEGL_ITEST_REQUIRE_GPU: "1"
CONTROL_TMPDIR: ${{ runner.temp }} run: |
SPLIT_BASELINE_JUNIT: ${{ runner.temp }}/split-baseline.xml if [ -f "${RUNNER_TEMP}/split-baseline.xml" ]; then
run: bash "${GITHUB_WORKSPACE}/scripts/ci/split_negative_controls.sh" echo "the lane step ran and left split-baseline.xml; nothing to fall back to"
exit 0
fi
echo "::notice::the lane step left no split-baseline.xml, so the DirectGLES.Split. entries run here"
python3 "${GITHUB_WORKSPACE}/MobileGL/MG_IntegrationTest/Harness/split_log_paths.py" check ctest "$PWD"
# A directory of its own: the job uploads RUNNER_TEMP/*.xml for the skip census, which
# has no row for this run.
mkdir -p "${RUNNER_TEMP}/split-fallback"
ctest --output-on-failure -L integration-split -R '^DirectGLES\.Split\.' -j 4 --no-tests=error \
--output-junit "${RUNNER_TEMP}/split-fallback/isplit.xml"
python3 "${GITHUB_WORKSPACE}/scripts/ci/junit_tally.py" "${RUNNER_TEMP}/split-fallback/isplit.xml" --require-split-ran
# build-linux ran the server link-closure ratchet on the build these lanes took, with # build-linux ran the server link-closure ratchet on the build these lanes took, with
# continue-on-error so that a red there does not skip build-linux's own consumers. # continue-on-error so that a red there does not skip build-linux's own consumers.
@@ -1862,7 +1780,7 @@ jobs:
SPLIT_REUSE: ${{ needs.build-guard.outputs.verdict == 'identical' && 'true' || 'false' }} SPLIT_REUSE: ${{ needs.build-guard.outputs.verdict == 'identical' && 'true' || 'false' }}
SPLIT_BUILD_DIR: ${{ needs.build-guard.outputs.verdict == 'identical' && 'build-linux' || 'build-split' }} SPLIT_BUILD_DIR: ${{ needs.build-guard.outputs.verdict == 'identical' && 'build-linux' || 'build-split' }}
SPLIT_RUNTIME: ${{ needs.build-guard.outputs.verdict == 'identical' && 'mobilegl-linux-runtime' || 'mobilegl-linux-runtime-split' }} SPLIT_RUNTIME: ${{ needs.build-guard.outputs.verdict == 'identical' && 'mobilegl-linux-runtime' || 'mobilegl-linux-runtime-split' }}
MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }} MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }}
steps: steps:
- name: Checkout repo - name: Checkout repo
@@ -2613,16 +2531,16 @@ jobs:
MOBILEGL_LIBRARY: ${{ github.workspace }}/build-linux/libMobileGL.so MOBILEGL_LIBRARY: ${{ github.workspace }}/build-linux/libMobileGL.so
steps: steps:
- name: Set swap space
uses: pierotofy/set-swap-space@v1.0
with:
swap-size-gb: 32
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
submodules: recursive submodules: recursive
- name: Set swap space
uses: ./.github/actions/set-swap-space
with:
swap-size-gb: 32
- name: Configure runner environment - name: Configure runner environment
uses: ./.github/actions/setup-test-runner uses: ./.github/actions/setup-test-runner
@@ -2749,7 +2667,7 @@ jobs:
name: trace case matrix name: trace case matrix
runs-on: ubuntu-26.04 runs-on: ubuntu-26.04
env: env:
MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }} MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }}
outputs: outputs:
matrix: ${{ steps.trace-cases.outputs.matrix }} matrix: ${{ steps.trace-cases.outputs.matrix }}
names: ${{ steps.trace-cases.outputs.names }} names: ${{ steps.trace-cases.outputs.names }}
@@ -2912,15 +2830,15 @@ jobs:
matrix: ${{ fromJSON(needs.trace-cases.outputs.matrix) }} matrix: ${{ fromJSON(needs.trace-cases.outputs.matrix) }}
steps: steps:
- name: Set swap space
if: runner.environment != 'self-hosted'
uses: pierotofy/set-swap-space@v1.0
with:
swap-size-gb: 16
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Set swap space
if: runner.environment != 'self-hosted'
uses: ./.github/actions/set-swap-space
with:
swap-size-gb: 16
- name: Configure runner environment - name: Configure runner environment
uses: ./.github/actions/setup-test-runner uses: ./.github/actions/setup-test-runner
with: with:
@@ -3065,7 +2983,7 @@ jobs:
needs: [retrace] needs: [retrace]
if: ${{ !cancelled() && needs.retrace.result != 'skipped' }} if: ${{ !cancelled() && needs.retrace.result != 'skipped' }}
env: env:
MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }} MOBILEGL_CI_DISAGGREGATED: ${{ (github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && 'ON' || 'OFF' }}
steps: steps:
- name: Checkout repo - name: Checkout repo
@@ -3153,15 +3071,15 @@ jobs:
matrix: ${{ fromJSON(needs.trace-cases.outputs.verify-matrix) }} matrix: ${{ fromJSON(needs.trace-cases.outputs.verify-matrix) }}
steps: steps:
- name: Set swap space
if: runner.environment != 'self-hosted'
uses: pierotofy/set-swap-space@v1.0
with:
swap-size-gb: 16
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Set swap space
if: runner.environment != 'self-hosted'
uses: ./.github/actions/set-swap-space
with:
swap-size-gb: 16
- name: Configure runner environment - name: Configure runner environment
uses: ./.github/actions/setup-test-runner uses: ./.github/actions/setup-test-runner
with: with:
@@ -3345,15 +3263,15 @@ jobs:
matrix: ${{ fromJSON(needs.build-guard.outputs.verdict == 'identical' && needs.trace-cases.outputs.split-spawn-matrix || needs.trace-cases.outputs.split-matrix) }} matrix: ${{ fromJSON(needs.build-guard.outputs.verdict == 'identical' && needs.trace-cases.outputs.split-spawn-matrix || needs.trace-cases.outputs.split-matrix) }}
steps: steps:
- name: Set swap space
if: runner.environment != 'self-hosted'
uses: pierotofy/set-swap-space@v1.0
with:
swap-size-gb: 16
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Set swap space
if: runner.environment != 'self-hosted'
uses: ./.github/actions/set-swap-space
with:
swap-size-gb: 16
# build-linux's build when build-guard proved the two configurations identical, # build-linux's build when build-guard proved the two configurations identical,
# build-linux-split's otherwise; integration-split prints the guard's evidence. A build-linux # build-linux-split's otherwise; integration-split prints the guard's evidence. A build-linux
# that is not Release is never reused (scripts/ci/split_reuse_build_type.sh). # that is not Release is never reused (scripts/ci/split_reuse_build_type.sh).
@@ -3573,7 +3491,7 @@ jobs:
- build-linux-monolith-control - build-linux-monolith-control
- build-retrace - build-retrace
- trace-fixtures - trace-fixtures
if: ${{ !cancelled() && (github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && needs.build-linux-monolith-control.result == 'success' && needs.build-retrace.result == 'success' && needs.trace-fixtures.result != 'skipped' }} if: ${{ !cancelled() && (github.ref_name == 'dev' || github.ref_name == 'feat/disaggregated' || github.ref_name == 'codex/fix-github-ci' || github.ref_name == 'cleanup') && needs.build-linux-monolith-control.result == 'success' && needs.build-retrace.result == 'success' && needs.trace-fixtures.result != 'skipped' }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -3581,15 +3499,15 @@ jobs:
case: [OpenRA] case: [OpenRA]
steps: steps:
- name: Set swap space
if: runner.environment != 'self-hosted'
uses: pierotofy/set-swap-space@v1.0
with:
swap-size-gb: 16
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Set swap space
if: runner.environment != 'self-hosted'
uses: ./.github/actions/set-swap-space
with:
swap-size-gb: 16
- name: Configure runner environment - name: Configure runner environment
uses: ./.github/actions/setup-test-runner uses: ./.github/actions/setup-test-runner
with: with:
@@ -3714,7 +3632,7 @@ jobs:
# an unchecked process substitution. # an unchecked process substitution.
# 50 rows per page: the jobs endpoint answers HTTP 502 for 90+ rows on a re-run attempt of a run this size. # 50 rows per page: the jobs endpoint answers HTTP 502 for 90+ rows on a re-run attempt of a run this size.
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=50" \ gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/jobs?per_page=50" \
--jq '.jobs[] | select((.name | startswith("retrace (")) or (.name | startswith("retrace verify (")) or (.name | startswith("retrace split (")) or (.name | startswith("retrace shipped library ("))) | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out" or .conclusion == "action_required") | .name' \ --jq '.jobs[] | select((.name | startswith("retrace (")) or (.name | startswith("retrace verify (")) or (.name | startswith("retrace split (")) or (.name | startswith("retrace shipped library ("))) | select(.conclusion == "failure" or .conclusion == "canceled" or .conclusion == "timed_out" or .conclusion == "action_required") | .name' \
> "${RUNNER_TEMP}/cleanup-jobs.txt" > "${RUNNER_TEMP}/cleanup-jobs.txt"
gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" \ gh api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}/artifacts?per_page=100" \
--jq '.artifacts[] | select(.name | startswith("trace-fixture-") or startswith("retrace-result-") or startswith("retrace-split-result-") or startswith("retrace-shipped-result-")) | [.id, .name] | @tsv' \ --jq '.artifacts[] | select(.name | startswith("trace-fixture-") or startswith("retrace-result-") or startswith("retrace-split-result-") or startswith("retrace-shipped-result-")) | [.id, .name] | @tsv' \
@@ -3776,7 +3694,7 @@ jobs:
# The seven generators all read MG_Pipe/*.def, so regenerating and diffing is what # The seven generators all read MG_Pipe/*.def, so regenerating and diffing is what
# keeps the two interface tables, the wire records, the verify comparators, the # keeps the two interface tables, the wire records, the verify comparators, the
# PipeInputs field ids, the read-inventory coverage and the render-state member list # PipeInputs field ids, the read-inventory coverage and the render-state member list
# from drifting apart from the catalogue. The generated files are committed # from drifting apart from the catalog. The generated files are committed
# deliberately: the build must not depend on python. # deliberately: the build must not depend on python.
- name: Configure runner environment - name: Configure runner environment
uses: ./.github/actions/setup-test-runner uses: ./.github/actions/setup-test-runner
@@ -3869,15 +3787,11 @@ jobs:
python3 scripts/gen_pipe_field_ownership.py --check python3 scripts/gen_pipe_field_ownership.py --check
python3 scripts/gen_pipe_field_ownership.py --self-test python3 scripts/gen_pipe_field_ownership.py --self-test
# Negative controls must themselves be able to go red (ARCHITECTURE.md §13.2). # Negative controls must themselves be able to go red (ARCHITECTURE.md §13.2). The Python
# control_smoke_test.sh runs the split control script against a stubbed ctest: an unrelated # tests run the trace and TCP lane tooling on stubs and synthetic data; test_run_trace_case.py
# failure or an empty selection must make it report FAILED, and the scenarios' real # feeds run_trace_case.cmake canned logs. No branch guard; no build, GPU or device.
# diagnostics must make it report PASSED. The Python tests run the trace and TCP lane tooling
# on stubs and synthetic data; test_run_trace_case.py feeds run_trace_case.cmake canned logs.
# No branch guard; no build, GPU or device.
- name: Control and trace-tool smoke tests (R-16) - name: Control and trace-tool smoke tests (R-16)
run: | run: |
bash scripts/ci/control_smoke_test.sh
python3 scripts/ci/test_trace_infrastructure.py python3 scripts/ci/test_trace_infrastructure.py
python3 scripts/ci/tcp_lane_tools_test.py python3 scripts/ci/tcp_lane_tools_test.py
python3 tools/trace_replay/test_run_tcp_matrix.py python3 tools/trace_replay/test_run_tcp_matrix.py
@@ -3915,11 +3829,6 @@ jobs:
CCACHE_NOHASHDIR: "true" CCACHE_NOHASHDIR: "true"
steps: steps:
- name: Set swap space
uses: pierotofy/set-swap-space@v1.0
with:
swap-size-gb: 32
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
@@ -3927,6 +3836,11 @@ jobs:
# back to OFF when 3rdparty/flatbuffers/include is missing. # back to OFF when 3rdparty/flatbuffers/include is missing.
submodules: recursive submodules: recursive
- name: Set swap space
uses: ./.github/actions/set-swap-space
with:
swap-size-gb: 32
- name: Configure runner environment - name: Configure runner environment
uses: ./.github/actions/setup-test-runner uses: ./.github/actions/setup-test-runner
+1 -1
View File
@@ -1,5 +1,5 @@
################################################################################ ################################################################################
# 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。 # This .gitignore file was automatically created by Microsoft(R) Visual Studio.
################################################################################ ################################################################################
/build /build
+1
View File
@@ -10,6 +10,7 @@ Read `CONTRIBUTING.md` before changing anything. Every rule in it applies to age
3. Write the message as UTF-8 without a byte-order mark, to a file outside the repository. 3. Write the message as UTF-8 without a byte-order mark, to a file outside the repository.
4. Check it with `python3 scripts/check_text_hygiene.py message <file>`. 4. Check it with `python3 scripts/check_text_hygiene.py message <file>`.
5. Commit with `git commit -F <file>` once the check passes. 5. Commit with `git commit -F <file>` once the check passes.
- If the scope list printed in step 2 has more than three entries, the message check skips the scope: summarize the touched areas yourself, for example `(...)`.
- On Windows, run `python` instead of `python3`. - On Windows, run `python` instead of `python3`.
- Do not copy the format of older commits. Much of the history before October 2026 breaks these rules. - Do not copy the format of older commits. Much of the history before October 2026 breaks these rules.
- When you brief a sub-agent, give it the exact commit format and these rules, not a paraphrase. - When you brief a sub-agent, give it the exact commit format and these rules, not a paraphrase.
+26 -19
View File
@@ -14,27 +14,27 @@ option(MOBILEGL_ENABLE_TRACY "Enable tracy for profiling"
option(MOBILEGL_BUILD_TRACE_REPLAY "Build desktop apitrace replay runner" OFF) option(MOBILEGL_BUILD_TRACE_REPLAY "Build desktop apitrace replay runner" OFF)
option(MOBILEGL_TRACE_ANGLE_VARIANTS "Enable signed trace-APK ANGLE variant loading" OFF) option(MOBILEGL_TRACE_ANGLE_VARIANTS "Enable signed trace-APK ANGLE variant loading" OFF)
option(MOBILEGL_IOS "Build MobileGL for iOS instead of macOS when APPLE is set" OFF) option(MOBILEGL_IOS "Build MobileGL for iOS instead of macOS when APPLE is set" OFF)
# The disaggregated (two-process) shape. OFF is the shipping default and OFF # The disaggregated (two-process) shape, ON by default: the split is the development default and
# must stay byte-comparable to a tree without MG_Remote at all: nothing under # the shipping library configures with -DMOBILEGL_BUILD_DISAGGREGATED=OFF. That OFF build must
# MobileGL/MG_Remote/ is compiled, no include path is added, and no library is # stay byte-comparable to a tree without MG_Remote at all: nothing under MobileGL/MG_Remote/ is
# linked, so `nm --defined-only libMobileGL.so | grep -i MG_Remote` is empty. # compiled, no include path is added, and no library is linked, so
# That emptiness is one of the two byte-level equalities the plan's validation # `nm --defined-only libMobileGL.so | grep -i MG_Remote` is empty. That emptiness is one of the
# gates keep (section 10.3). # two byte-level equalities the plan's validation gates keep (section 10.3).
option(MOBILEGL_BUILD_DISAGGREGATED "Build the MG_Remote transport layer (two-process shape)" OFF) option(MOBILEGL_BUILD_DISAGGREGATED "Build the MG_Remote transport layer (two-process shape)" ON)
# The CI / debugging shape (ARCHITECTURE.md §16): both roles in ONE process, talking over the # The in-process split shape (ARCHITECTURE.md §16), ON by default with the option above: both
# same SEG_CMD ring and the same G3 codec a spawned server would use. It IMPLIES # roles in ONE process, talking over the same SEG_CMD ring and the same G3 codec a spawned server
# MOBILEGL_BUILD_DISAGGREGATED (below) and additionally admits the role-isolation shims that # would use. It IMPLIES MOBILEGL_BUILD_DISAGGREGATED (below) and additionally admits the
# only make sense when the two roles share an address space. It is a SUPERSET, never a # role-isolation shims that only make sense when the two roles share an address space. It is a
# substitute: MOBILEGL_TRANSPORT=inproc is what selects the shape at run time, and this option # SUPERSET, never a substitute: MOBILEGL_TRANSPORT=inproc is what selects the shape at run time,
# only decides whether the shims are compiled in. # and this option only decides whether the shims are compiled in.
option(MOBILEGL_BUILD_DISAGGREGATED_INPROC "Compile the in-process (one-process, two-role) split shims; implies MOBILEGL_BUILD_DISAGGREGATED" OFF) option(MOBILEGL_BUILD_DISAGGREGATED_INPROC "Compile the in-process (one-process, two-role) split shims; implies MOBILEGL_BUILD_DISAGGREGATED" ON)
option(MOBILEGL_BUILD_SERVER_SPIKE "Build the P0 spike-A MobileGLServer delivery-chain executable (Android only)" OFF) option(MOBILEGL_BUILD_SERVER_SPIKE "Build the P0 spike-A MobileGLServer delivery-chain executable (Android only)" OFF)
option(MOBILEGL_BUILD_EXTMEM_PROBE "Package tools/spikes/extmem_probe as libMobileGLExtmemProbe.so for the trace APK's exec hook (P11 A3, Android only)" OFF) option(MOBILEGL_BUILD_EXTMEM_PROBE "Package tools/spikes/extmem_probe as libMobileGLExtmemProbe.so for the trace APK's exec hook (P11 A3, Android only)" OFF)
option(MOBILEGL_EXTMEM_PROBE_AS_SERVER "Spike only (P11 B2 step 1): also package the probe as libMobileGLServer.so, the real server as libMobileGLServerReal.so" OFF) option(MOBILEGL_EXTMEM_PROBE_AS_SERVER "Spike only (P11 B2 step 1): also package the probe as libMobileGLServer.so, the real server as libMobileGLServerReal.so" OFF)
# The PipeInputs strangler (ARCHITECTURE.md 9.2) is finished: since P13 every build pushes, and # The PipeInputs strangler (ARCHITECTURE.md 9.2) is finished: since P13 every build pushes, and
# there is neither an option nor a macro for it (W3a removed the option, W3b the `#if` guards and # there is neither an option nor a macro for it (W3a removed the option, W3b the `#if` guards and
# the definition). A -DMOBILEGL_PIPE_PUSH=OFF (or the OFF that every pre-P13 build tree holds in its # the definition). A -DMOBILEGL_PIPE_PUSH=OFF (or the OFF that every pre-P13 build tree holds in its
# cache, the old default) is named and dropped rather than honoured: a warning, not an error, so an # cache, the old default) is named and dropped rather than honored: a warning, not an error, so an
# existing tree reconfigures. The RUNTIME MOBILEGL_PIPE_PUSH environment bitmask is unrelated. # existing tree reconfigures. The RUNTIME MOBILEGL_PIPE_PUSH environment bitmask is unrelated.
if (DEFINED CACHE{MOBILEGL_PIPE_PUSH} AND NOT "$CACHE{MOBILEGL_PIPE_PUSH}") if (DEFINED CACHE{MOBILEGL_PIPE_PUSH} AND NOT "$CACHE{MOBILEGL_PIPE_PUSH}")
message(WARNING "MobileGL: MOBILEGL_PIPE_PUSH=OFF asks for the pull build, which P13 retired; " message(WARNING "MobileGL: MOBILEGL_PIPE_PUSH=OFF asks for the pull build, which P13 retired; "
@@ -78,7 +78,7 @@ if (ANDROID)
# Read the level back from the compiler target triple first. Its trailing # Read the level back from the compiler target triple first. Its trailing
# number (aarch64-none-linux-android26) is precisely what clang turns into # number (aarch64-none-linux-android26) is precisely what clang turns into
# __ANDROID_API__, so it cannot disagree with the compile itself, and it is # __ANDROID_API__, so it cannot disagree with the compile itself, and it is
# already past every NDK normalisation step - codename aliases, "latest", # already past every NDK normalization step - codename aliases, "latest",
# and per-ABI minimum pull-ups. ANDROID_PLATFORM_LEVEL is the fallback for # and per-ABI minimum pull-ups. ANDROID_PLATFORM_LEVEL is the fallback for
# generators/languages where the triple variable is not populated. # generators/languages where the triple variable is not populated.
# #
@@ -257,7 +257,7 @@ string(SUBSTRING "${GIT_COMMIT_HASH_FULL}" 0 7 GIT_COMMIT_HASH_SHORT)
# #
# GIT_COMMIT_HASH_SHORT MAY NOT MOVE. It is baked into glGetString(GL_VERSION) # GIT_COMMIT_HASH_SHORT MAY NOT MOVE. It is baked into glGetString(GL_VERSION)
# (MG_Impl/GLImpl/Getter/GL_Getter.cpp), so redefining it changes an APPLICATION-VISIBLE GL # (MG_Impl/GLImpl/Getter/GL_Getter.cpp), so redefining it changes an APPLICATION-VISIBLE GL
# string and the pull build's .rodata - a behaviour leak and a G1 break at once. The handshake # string and the pull build's .rodata - a behavior leak and a G1 break at once. The handshake
# gets its own pair instead, consumed only by the ABI fingerprint, which no pull build compiles. # gets its own pair instead, consumed only by the ABI fingerprint, which no pull build compiles.
# #
# PRESENT IS A FIRST-CLASS INPUT, not the emptiness of VALUE. "this build has no stamp" and # PRESENT IS A FIRST-CLASS INPUT, not the emptiness of VALUE. "this build has no stamp" and
@@ -400,6 +400,7 @@ set(SOURCE_FILES
MobileGL/MG_Util/ShaderTranspiler/TranslationCache.cpp MobileGL/MG_Util/ShaderTranspiler/TranslationCache.cpp
MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripCompatPerVertexMembersPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameSamplerFunctionParameterPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameSamplerFunctionParameterPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameBuiltinShadowingFunctionsPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameBuiltinShadowingFunctionsPass.cpp
@@ -410,6 +411,7 @@ set(SOURCE_FILES
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerViewportIndexPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerViewportIndexPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeferGeometryEmitsPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeferGeometryEmitsPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RetainGeometryOutputsPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/UniquifyIoBlockNamesPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/UniquifyIoBlockNamesPass.cpp
@@ -417,6 +419,8 @@ set(SOURCE_FILES
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroBaseVertexPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroBaseVertexPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ExtendNarrowIntegerVertexInputsPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/NarrowIntegerRuntimeWidths.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerPixelCenterIntegerPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerPixelCenterIntegerPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroLayerOutputPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroLayerOutputPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FullCoverageSampleMaskPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FullCoverageSampleMaskPass.cpp
@@ -440,6 +444,7 @@ set(SOURCE_FILES
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeResourceArrayIndexPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeResourceArrayIndexPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenAtomicCounterBlockPass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenAtomicCounterBlockPass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemotePointSizePass.cpp MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemotePointSizePass.cpp
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateClipDistancePass.cpp
MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp
MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp
@@ -447,6 +452,7 @@ set(SOURCE_FILES
MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp
MobileGL/MG_Util/SelfTest/PersistentBufferOrderingProbe.cpp MobileGL/MG_Util/SelfTest/PersistentBufferOrderingProbe.cpp
MobileGL/MG_Util/SelfTest/DriverPost.cpp MobileGL/MG_Util/SelfTest/DriverPost.cpp
MobileGL/MG_Util/SelfTest/DriverPostClipDistance.cpp
MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitness.cpp MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitness.cpp
MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.cpp MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.cpp
@@ -525,6 +531,7 @@ set(SOURCE_FILES
MobileGL/MG_Backend/DirectGLES/ManagersTextures.cpp MobileGL/MG_Backend/DirectGLES/ManagersTextures.cpp
MobileGL/MG_Backend/DirectGLES/ManagersVertexArrays.cpp MobileGL/MG_Backend/DirectGLES/ManagersVertexArrays.cpp
MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp
MobileGL/MG_Backend/DirectGLES/NarrowIntegerSeeds.cpp
MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp
MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp
@@ -628,7 +635,7 @@ if (MOBILEGL_BUILD_DISAGGREGATED AND
# A NORMAL variable, deliberately not `CACHE BOOL ... FORCE`: forcing OFF into the cache # A NORMAL variable, deliberately not `CACHE BOOL ... FORCE`: forcing OFF into the cache
# made the plain re-configure after `git submodule update` stay OFF with no message at # made the plain re-configure after `git submodule update` stay OFF with no message at
# all. Shadowing the cache entry for this configure only keeps the operator's ON where it # all. Shadowing the cache entry for this configure only keeps the operator's ON where it
# was, so the next configure - with the submodule there - honours it. # was, so the next configure - with the submodule there - honors it.
set(MOBILEGL_BUILD_DISAGGREGATED OFF) set(MOBILEGL_BUILD_DISAGGREGATED OFF)
# And with it the shim option, or the `-DMOBILEGL_BUILD_DISAGGREGATED_INPROC=1` below # And with it the shim option, or the `-DMOBILEGL_BUILD_DISAGGREGATED_INPROC=1` below
# would still be defined over a build with no MG_Remote in it at all. # would still be defined over a build with no MG_Remote in it at all.
@@ -654,7 +661,7 @@ list(APPEND SOURCE_FILES
# VisitFields tables. Push-only for the same G1 reason as the three above - in # VisitFields tables. Push-only for the same G1 reason as the three above - in
# monolith the archive never crosses (create_shader_state hands the two structs over # monolith the archive never crosses (create_shader_state hands the two structs over
# by pointer beside the record), so the codec is live code only in the VERIFY lane, # by pointer beside the record), so the codec is live code only in the VERIFY lane,
# where the applier serialises, deserialises and field-compares before storing. # where the applier serializes, deserializes and field-compares before storing.
MobileGL/MG_State/GLState/ProgramState/ProgramArtifactsCodec.cpp MobileGL/MG_State/GLState/ProgramState/ProgramArtifactsCodec.cpp
# P5's contract (integrator ruling R-17, package c1): the MONOLITH arm of the # P5's contract (integrator ruling R-17, package c1): the MONOLITH arm of the
# client->wire routing - the thirty-seven adapters that install gMGPipeScreen / # client->wire routing - the thirty-seven adapters that install gMGPipeScreen /
+8 -10
View File
@@ -16,10 +16,10 @@ Commands below use `python3`. On Windows, run `python` instead.
- **Subject only by default.** Add `- ` detail lines only when a detail really needs to be in the commit message. Leave one blank line after the subject. A detail that wraps continues on at most two lines indented by two spaces. No paragraphs and no trailers. - **Subject only by default.** Add `- ` detail lines only when a detail really needs to be in the commit message. Leave one blank line after the subject. A detail that wraps continues on at most two lines indented by two spaces. No paragraphs and no trailers.
- **Content** after `): ` is at most 72 characters of plain English in the imperative mood ("flush once per frame", not "flushes" or "flushed"). - **Content** after `): ` is at most 72 characters of plain English in the imperative mood ("flush once per frame", not "flushes" or "flushed").
- **Type** is one or more of these, comma-separated (`[Fix, Test]`): - **Type** is one or more of these, comma-separated (`[Fix, Test]`):
- `Feat`: new behaviour. - `Feat`: new behavior.
- `Fix`: corrects behaviour. - `Fix`: corrects behavior.
- `Perf`: faster or lighter, same behaviour. - `Perf`: faster or lighter, same behavior.
- `Refactor`: restructures code, same behaviour. - `Refactor`: restructures code, same behavior.
- `Test`: tests only. - `Test`: tests only.
- `Docs`: documentation and comments only. - `Docs`: documentation and comments only.
- `Build`: CMake, Gradle, toolchains. - `Build`: CMake, Gradle, toolchains.
@@ -29,7 +29,7 @@ Commands below use `python3`. On Windows, run `python` instead.
- **Scope** is each changed path without a leading `MobileGL/`, cut to its first two parts: `MG_Backend/DirectGLES`, `MG_Test/Wire`, `scripts/ci`, `docs/Disaggregated`. - **Scope** is each changed path without a leading `MobileGL/`, cut to its first two parts: `MG_Backend/DirectGLES`, `MG_Test/Wire`, `scripts/ci`, `docs/Disaggregated`.
- A shorter path is used whole: `MG_Backend` for a file directly in a subsystem, `scripts` for a file directly in `scripts/`, and the file name for a file at the repository root (`CMakeLists.txt`). - A shorter path is used whole: `MG_Backend` for a file directly in a subsystem, `scripts` for a file directly in `scripts/`, and the file name for a file at the repository root (`CMakeLists.txt`).
- A file directly in `MobileGL/` uses `MobileGL`, and a submodule uses its path (`3rdparty/glslang`). - A file directly in `MobileGL/` uses `MobileGL`, and a submodule uses its path (`3rdparty/glslang`).
- List every scope the commit touches, comma-separated: `(MG_Backend/DirectGLES, MG_Test/Wire)`. - List every scope the commit touches, comma-separated: `(MG_Backend/DirectGLES, MG_Test/Wire)`. A commit that touches more than three scopes skips this check: summarize the touched areas yourself, for example `(...)`.
- `python3 scripts/check_text_hygiene.py scope` prints the scope for the staged changes. - `python3 scripts/check_text_hygiene.py scope` prints the scope for the staged changes.
- **No** planning ids (phase or decision codes such as `P13` or `ID-54`), local paths, external project names or non-English text anywhere in the message. - **No** planning ids (phase or decision codes such as `P13` or `ID-54`), local paths, external project names or non-English text anywhere in the message.
- **Merges** use `[Merge] (area): what the merge takes`. The scope is free text there. Write the subject yourself, since git's default merge message fails the check. The commits a merge brings in are not checked again. - **Merges** use `[Merge] (area): what the merge takes`. The scope is free text there. Write the subject yourself, since git's default merge message fails the check. The commits a merge brings in are not checked again.
@@ -43,12 +43,12 @@ Example:
## Comments, strings and documentation ## Comments, strings and documentation
- Code, comments, log strings and commit messages are in English. Documentation may be in Chinese or English. - Code, comments, log strings and commit messages are in English, and documentation may be in English. American spelling is preferred in all of them: documentation, comments, strings and identifiers (color, behavior, center, cancel, initialize, normalize, organize, analyze, license, defense, artifact, modeling, labeled, gray, honor, neighbor, catalog).
- A comment states what the code does now and why, in a few lines. Rationale longer than about 15 lines belongs in a design document under `docs/`, with a one-line pointer in the code. - A comment states what the code does now and why, in a few lines. Rationale longer than about 15 lines belongs in a design document under `docs/`, with a one-line pointer in the code.
- In code and comments, cite a symbol, file or section, never a line number: `ARCHITECTURE.md §1.3`, `VulkanRenderer::BeginFrame()`. Documents may keep `file:line` citations; the documentation citation check resolves them. - In code and comments, cite a symbol, file or section, never a line number: `ARCHITECTURE.md §1.3`, `VulkanRenderer::BeginFrame()`. Documents may keep `file:line` citations; the documentation citation check resolves them.
- State the rule, not its history. No "previously" or "after review", and no planning ids in comments, strings, identifiers or file names. - State the rule, not its history. No "previously" or "after review", and no planning ids in comments, strings, identifiers or file names.
- Plain wording, without ALL-CAPS emphasis. - Plain wording, without ALL-CAPS emphasis.
- In code and comments (C, C++, GLSL, CMake, shell, Python, YAML), do not name external projects such as Mesa, Zink or gallium; describe the technique instead. Naming the driver or hardware where a behaviour was observed (llvmpipe, ANGLE, Adreno, Mali) is fine, and so are file, package and extension names that contain these words (`50_mesa.json`, `GL_MESA_*`). - In code and comments (C, C++, GLSL, CMake, shell, Python, YAML), do not name external projects such as Mesa, Zink or gallium; describe the technique instead. Naming the driver or hardware where a behavior was observed (llvmpipe, ANGLE, Adreno, Mali) is fine, and so are file, package and extension names that contain these words (`50_mesa.json`, `GL_MESA_*`).
## Code ## Code
@@ -69,7 +69,7 @@ Example:
5. `MG_Test`, `MG_IntegrationTest` and `MG_Benchmark`. 5. `MG_Test`, `MG_IntegrationTest` and `MG_Benchmark`.
- Each backend is isolated. Files under `MG_Backend/DirectGLES/` or `MG_Backend/DirectVulkan/` are included only by the same backend, by the registry files directly in `MG_Backend/`, by the root `.cpp` files and by tests. - Each backend is isolated. Files under `MG_Backend/DirectGLES/` or `MG_Backend/DirectVulkan/` are included only by the same backend, by the registry files directly in `MG_Backend/`, by the root `.cpp` files and by tests.
- Platform-specific APIs stay behind an abstraction, never raw in logic code. - Platform-specific APIs stay behind an abstraction, never raw in logic code.
- A fix that restores intended behaviour ships unconditionally. Add a switch only for a real trade-off. - A fix that restores intended behavior ships unconditionally. Add a switch only for a real trade-off.
- A cache keyed on object identity uses a monotonic lifetime id, never a GL name or a pointer, because both are recycled. - A cache keyed on object identity uses a monotonic lifetime id, never a GL name or a pointer, because both are recycled.
## What goes in the repository ## What goes in the repository
@@ -78,7 +78,6 @@ Example:
- Generated code, only together with a CI step that regenerates it and fails on any difference. - Generated code, only together with a CI step that regenerates it and fails on any difference.
- Binary test fixtures. - Binary test fixtures.
- Not raw logs, and not working papers such as agent briefs, review rounds and drafts. Keep those outside the repository and commit the conclusion: a design-document change, a `DEBTS.md` entry, a measurement under `docs/Performance`. - Not raw logs, and not working papers such as agent briefs, review rounds and drafts. Keep those outside the repository and commit the conclusion: a design-document change, a `DEBTS.md` entry, a measurement under `docs/Performance`.
- `docs/archive/` keeps the planning notes and agent contracts of the disaggregation work as they were. It is frozen: no file there is added, edited or removed.
## Checks ## Checks
@@ -88,7 +87,6 @@ These fail on any violation:
- commit messages, as described above; - commit messages, as described above;
- a new file under `docs/Disaggregated/notes/` or a new `.log` file (moving a file is not adding it); - a new file under `docs/Disaggregated/notes/` or a new `.log` file (moving a file is not adding it);
- any change under `docs/archive/`;
- a missing or wrong license header; - a missing or wrong license header;
- a text file that is not UTF-8; - a text file that is not UTF-8;
- an external project name in a comment; - an external project name in a comment;
+33 -16
View File
@@ -173,7 +173,7 @@ namespace MobileGL {
// vertex, tessellation control, tessellation evaluation and geometry at 0, and only // vertex, tessellation control, tessellation evaluation and geometry at 0, and only
// fragment (8 in GL, 4 in ES) and compute are guaranteed to have any. Every real ARM // fragment (8 in GL, 4 in ES) and compute are guaranteed to have any. Every real ARM
// GLES driver takes that allowance - a Mali-G925 reports 0 for all four - so a // GLES driver takes that allowance - a Mali-G925 reports 0 for all four - so a
// backend that cannot honour a graphics-stage storage block MUST report 0 here // backend that cannot honor a graphics-stage storage block MUST report 0 here
// rather than a hopeful number. Advertising a non-zero count the driver will refuse // rather than a hopeful number. Advertising a non-zero count the driver will refuse
// does not make the block work; it only moves the failure from an honest // does not make the block work; it only moves the failure from an honest
// "unsupported" at query time to a backend link error the frontend never surfaces, // "unsupported" at query time to a backend link error the frontend never surfaces,
@@ -214,16 +214,16 @@ namespace MobileGL {
Int MaxComputeImageUniforms = 8; Int MaxComputeImageUniforms = 8;
Int MaxDrawBuffers = 8; Int MaxDrawBuffers = 8;
Int MaxColorAttachments = 8; Int MaxColorAttachments = 8;
// GL_MAX_CLIP_DISTANCES. Zero is a legal answer here, not a placeholder, and a // GL_MAX_CLIP_DISTANCES. What the backend can actually CLIP with, natively or through
// backend that cannot host a clip distance MUST report it: advertising eight the // EmulatesClipDistance below - never a number the backend would then refuse:
// backend will refuse does not make gl_ClipDistance work, it only moves the failure // advertising eight that no stage can honor only moves the failure from query time to
// from an honest "unsupported" at query time to a backend shader-compile error the // a backend shader-compile error the frontend never surfaces. DirectGLES fills it from
// frontend never surfaces, after which every draw with that program silently renders // GL_EXT_clip_cull_distance, DirectVulkan from the shaderClipDistance device feature,
// nothing. DirectGLES fills it from GL_EXT_clip_cull_distance, DirectVulkan from the // and both answer 8 when they emulate. Zero stays a legal answer for a backend that
// shaderClipDistance device feature. The DEFAULT stays at the GL 4.3 core minimum // neither hosts nor emulates the built-in (MOBILEGL_CLIP_DISTANCE_EMULATION=0 on such
// because it describes the no-backend case (standalone shader compiles, unit tests), // a device). The DEFAULT is the GL 4.3 core minimum because it describes the
// where there is no device to be honest about and BuildTBuiltInResource still has to // no-backend case (standalone shader compiles, unit tests), where
// hand glslang a workable gl_MaxClipDistances. // BuildTBuiltInResource still has to hand glslang a workable gl_MaxClipDistances.
Int MaxClipDistances = 8; Int MaxClipDistances = 8;
// GL_MAX_CULL_DISTANCES and GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES, under exactly // GL_MAX_CULL_DISTANCES and GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES, under exactly
// the contract stated for MaxClipDistances above: ZERO IS A LEGAL ANSWER and a // the contract stated for MaxClipDistances above: ZERO IS A LEGAL ANSWER and a
@@ -242,7 +242,7 @@ namespace MobileGL {
// GL_LAYER_PROVOKING_VERTEX / GL_VIEWPORT_INDEX_PROVOKING_VERTEX: which vertex of a // GL_LAYER_PROVOKING_VERTEX / GL_VIEWPORT_INDEX_PROVOKING_VERTEX: which vertex of a
// primitive supplies gl_Layer and gl_ViewportIndex. GL 4.6 table 23.65 makes // primitive supplies gl_Layer and gl_ViewportIndex. GL 4.6 table 23.65 makes
// GL_UNDEFINED_VERTEX a legal answer for both, and it is the honest default - naming // GL_UNDEFINED_VERTEX a legal answer for both, and it is the honest default - naming
// a convention is a statement about behaviour, so a backend that does not pin one // a convention is a statement about behavior, so a backend that does not pin one
// must not claim it does. DirectGLES fills the layer one from the ES 3.2 query and // must not claim it does. DirectGLES fills the layer one from the ES 3.2 query and
// the viewport one from GL_OES_viewport_array, and leaves UNDEFINED where the // the viewport one from GL_OES_viewport_array, and leaves UNDEFINED where the
// capability is absent: without the viewport array extension only viewport 0 is ever // capability is absent: without the viewport array extension only viewport 0 is ever
@@ -271,7 +271,7 @@ namespace MobileGL {
// same image and lets an implementation answer GL_FRAMEBUFFER_UNSUPPORTED // same image and lets an implementation answer GL_FRAMEBUFFER_UNSUPPORTED
// otherwise, which is what DirectVulkan (one combined attachment) and the // otherwise, which is what DirectVulkan (one combined attachment) and the
// real ES drivers behind DirectGLES both do. Defaults to true so a backend // real ES drivers behind DirectGLES both do. Defaults to true so a backend
// that never sets it keeps the permissive behaviour. // that never sets it keeps the permissive behavior.
Bool SupportsDistinctDepthStencilAttachments = true; Bool SupportsDistinctDepthStencilAttachments = true;
// Whether attaching a single layer of a 3D or array texture to a framebuffer actually // Whether attaching a single layer of a 3D or array texture to a framebuffer actually
// renders to that layer. DirectGLES hands the layer straight to // renders to that layer. DirectGLES hands the layer straight to
@@ -312,9 +312,9 @@ namespace MobileGL {
// profile") and the demotion there is mathematically mandatory, always. // profile") and the demotion there is mathematically mandatory, always.
// Defaults to false so a backend that never sets it - and the no-backend case, which // Defaults to false so a backend that never sets it - and the no-backend case, which
// is what standalone shader compiles and the unit tests run under - keeps the // is what standalone shader compiles and the unit tests run under - keeps the
// demotion, which is the behaviour that works everywhere. // demotion, which is the behavior that works everywhere.
Bool SupportsShaderFloat64 = false; Bool SupportsShaderFloat64 = false;
// Whether glVertexAttribLFormat / glVertexArrayAttribLFormat can be honoured, i.e. // Whether glVertexAttribLFormat / glVertexArrayAttribLFormat can be honored, i.e.
// whether a 64-bit vertex attribute can actually reach a shader unconverted. Detected, // whether a 64-bit vertex attribute can actually reach a shader unconverted. Detected,
// never assumed: DirectVulkan needs VkPhysicalDeviceFeatures::shaderFloat64 (the // never assumed: DirectVulkan needs VkPhysicalDeviceFeatures::shaderFloat64 (the
// attribute travels as its 32-bit word pair, so no VK_FORMAT_R64* is required, but the // attribute travels as its 32-bit word pair, so no VK_FORMAT_R64* is required, but the
@@ -349,6 +349,23 @@ namespace MobileGL {
Bool SupportsTessellationPointSize = true; Bool SupportsTessellationPointSize = true;
// The geometry-stage twin (OpCapability GeometryPointSize). // The geometry-stage twin (OpCapability GeometryPointSize).
Bool SupportsGeometryPointSize = true; Bool SupportsGeometryPointSize = true;
// The backend cannot clip with gl_ClipDistance natively - no
// GL_EXT_clip_cull_distance, an extension the POST clip probe caught not clipping or
// not honoring the per-distance enables, or shaderClipDistance == VK_FALSE - so
// ProgramSpirvTask lowers the built-in to varyings plus a fragment discard
// (ShaderCompiler::EmulateClipDistanceForProgram) and the source pipeline declares the
// reserved enables uniform the draw path feeds. MOBILEGL_CLIP_DISTANCE_EMULATION
// overrides the detection in either direction at backend init. Defaults FALSE: true
// ARMS a rewrite, so the no-backend answer is the one that leaves modules untouched.
Bool EmulatesClipDistance = false;
// The backend HOSTS gl_ClipDistance but has no per-distance enable state: Vulkan
// clips by every distance a shader writes, so GL_CLIP_DISTANCEi would mean nothing.
// The producing stage then writes `enabled(i) ? d[i] : 1.0` back into the built-in
// (EmulateClipDistanceOptions::nativeBuiltin) from the same reserved enables uniform
// the emulation uses. DirectVulkan sets it whenever it clips natively; DirectGLES
// never does - ES drivers it keeps native passed the POST probe, which checks the
// driver honors GL_CLIP_DISTANCEi_EXT itself. Exclusive with EmulatesClipDistance.
Bool MasksNativeClipDistances = false;
Uint64 MaxShaderStorageBlockSize = 128 * 1024 * 1024; Uint64 MaxShaderStorageBlockSize = 128 * 1024 * 1024;
Uint32 SubgroupSize = 0; Uint32 SubgroupSize = 0;
Uint32 SubgroupSupportedStages = 0; Uint32 SubgroupSupportedStages = 0;
@@ -382,7 +399,7 @@ namespace MobileGL {
Bool FragmentShadingRateAttachmentWithDefaultFramebuffer = false; Bool FragmentShadingRateAttachmentWithDefaultFramebuffer = false;
// What glGetFragmentShadingRatesEXT answers, one mask per sample count 1, 2, 4 .. 64 // What glGetFragmentShadingRatesEXT answers, one mask per sample count 1, 2, 4 .. 64
// (index = log2(samples)): bit (log2(w) << 2 | log2(h)) is set when that rate is // (index = log2(samples)): bit (log2(w) << 2 | log2(h)) is set when that rate is
// honoured exactly at that sample count, i.e. gl_ShadingRateEXT reads it back // honored exactly at that sample count, i.e. gl_ShadingRateEXT reads it back
// unclamped. Zero masks mean no rate at all, which is what a sample count the device // unclamped. Zero masks mean no rate at all, which is what a sample count the device
// cannot shade coarsely answers. // cannot shade coarsely answers.
Uint16 FragmentShadingRateMasks[7] = {}; Uint16 FragmentShadingRateMasks[7] = {};
+26 -45
View File
@@ -14,7 +14,7 @@ namespace MobileGL::MG_Config {
inline const String ProjectName = "MobileGL"; inline const String ProjectName = "MobileGL";
inline const String CoreName = "MobileGL Core"; inline const String CoreName = "MobileGL Core";
inline const String CoreVendor = "MobileGL-Dev (BZLZHH, Swung0x48, Tungsten)"; inline const String CoreVendor = "MobileGL-Dev (BZLZHH, Swung0x48, Tungsten)";
inline const Version CoreVersion = {26, 10, 0, "-dev", VersionType::Development}; inline const Version CoreVersion = {26, 11, 0, "-dev", VersionType::Development};
inline const VersionStringFormatAttrib DefaultVersionStringFormatAttrib = {2, 2, 0, true, true}; inline const VersionStringFormatAttrib DefaultVersionStringFormatAttrib = {2, 2, 0, true, true};
inline const Uint64 CacheVersion = 0; inline const Uint64 CacheVersion = 0;
@@ -164,7 +164,7 @@ namespace MobileGL::MG_Config {
// surface's minimum (the frames-in-flight count still raises the request when it is // surface's minimum (the frames-in-flight count still raises the request when it is
// larger), clamped to the surface's maximum. With more images, the one the display has // larger), clamped to the surface's maximum. With more images, the one the display has
// just released is reused later, by when its release has usually signaled, so fewer // just released is reused later, by when its release has usually signaled, so fewer
// frames start their window pass behind it. Each image costs a full-size colour buffer // frames start their window pass behind it. Each image costs a full-size color buffer
// plus the default framebuffer's depth/stencil buffer of the same size. // plus the default framebuffer's depth/stencil buffer of the same size.
Uint32 MagmaExtraSwapchainImages = 0; Uint32 MagmaExtraSwapchainImages = 0;
// MOBILEGL_MAGMA_MAX_DRAWS_PER_COMMAND_BUFFER: GL draws and dispatches Magma records into // MOBILEGL_MAGMA_MAX_DRAWS_PER_COMMAND_BUFFER: GL draws and dispatches Magma records into
@@ -237,6 +237,14 @@ namespace MobileGL::MG_Config {
// negative control). Cross-backend by design: the demotion runs in the shared // negative control). Cross-backend by design: the demotion runs in the shared
// phase-B chain, so one switch covers both. See DemotePointSizePass. // phase-B chain, so one switch covers both. See DemotePointSizePass.
QuirkOverride PointSizeDemotion = QuirkOverride::Auto; QuirkOverride PointSizeDemotion = QuirkOverride::Auto;
// MOBILEGL_CLIP_DISTANCE_EMULATION: lower gl_ClipDistance to ordinary varyings plus a
// fragment discard (ShaderCompiler::EmulateClipDistanceForProgram) on a backend that
// cannot clip natively - no GL_EXT_clip_cull_distance or a driver the POST clip probe
// caught misbehaving (DirectGLES), shaderClipDistance == VK_FALSE (DirectVulkan).
// Auto arms it exactly there. ForceOn exercises it on a healthy driver (the pinned
// integration lane); ForceOff restores the native-or-nothing answer, which reports
// GL_MAX_CLIP_DISTANCES = 0 on a device without native support.
QuirkOverride ClipDistanceEmulation = QuirkOverride::Auto;
// MOBILEGL_COHERENT_AS_FLUSH: app-compat for engines (e.g. Flywheel) that write // 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 // GPU-read data through persistent GL_MAP_FLUSH_EXPLICIT_BIT maps they never
// flush. Persistent FLUSH_EXPLICIT map requests are rewritten to coherent // flush. Persistent FLUSH_EXPLICIT map requests are rewritten to coherent
@@ -244,8 +252,6 @@ namespace MobileGL::MG_Config {
// flush calls on rewritten maps become error-free no-ops. Non-persistent maps // flush calls on rewritten maps become error-free no-ops. Non-persistent maps
// keep spec FLUSH_EXPLICIT behavior. // keep spec FLUSH_EXPLICIT behavior.
Bool CoherentAsFlush = false; 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 // 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 // per-draw glBufferSubData path instead of the persistent-mapped ring allocator
// (negative control / driver-bug escape hatch). // (negative control / driver-bug escape hatch).
@@ -318,10 +324,10 @@ namespace MobileGL::MG_Config {
// keeps the built-in default (MG_Util::Async::kAsyncShaderCompileDefault); falsy // keeps the built-in default (MG_Util::Async::kAsyncShaderCompileDefault); falsy
// forces every glCompileShader/glLinkProgram to run synchronously on the calling // forces every glCompileShader/glLinkProgram to run synchronously on the calling
// thread AND withdraws GL_KHR_parallel_shader_compile, so the single switch reverts // thread AND withdraws GL_KHR_parallel_shader_compile, so the single switch reverts
// both the threading and the application-visible behaviour change. // both the threading and the application-visible behavior change.
QuirkOverride AsyncShaderCompile = QuirkOverride::Auto; QuirkOverride AsyncShaderCompile = QuirkOverride::Auto;
// MOBILEGL_ASYNC_SHADER_COMPILE_THREADS: shader-compile worker count. 0 (unset) means // 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. // auto, which is min(4, big cores); an explicit value is honored as given.
Uint32 AsyncShaderCompileThreads = 0; Uint32 AsyncShaderCompileThreads = 0;
// MOBILEGL_DISABLE_SHADER_PREWARM: skip the glslang built-in table prewarm in // MOBILEGL_DISABLE_SHADER_PREWARM: skip the glslang built-in table prewarm in
// Initialize(), which moves ~20-100 ms of table building from the first shader compiles // Initialize(), which moves ~20-100 ms of table building from the first shader compiles
@@ -358,7 +364,7 @@ namespace MobileGL::MG_Config {
// extension only ever gave the SHADER a compilable name: MobileGL has never // extension only ever gave the SHADER a compilable name: MobileGL has never
// programmed a driver's INDEXED viewport state (SyncRenderState pushes index 0 // programmed a driver's INDEXED viewport state (SyncRenderState pushes index 0
// and nothing else), so on an extension-capable driver every index rasterized as // 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 - // index 0 exactly as it did without one. ForceOff returns to that behavior -
// the pre-emulation path, extension passthrough where it exists and // the pre-emulation path, extension passthrough where it exists and
// LowerViewportIndexPass' demote-to-a-plain-global where it does not - and is // LowerViewportIndexPass' demote-to-a-plain-global where it does not - and is
// the negative control the emulation is measured against. // the negative control the emulation is measured against.
@@ -422,7 +428,7 @@ namespace MobileGL::MG_Config {
// MOBILEGL_PIPE_PUSH: per-subsystem bitmask selecting which state the frontend // 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. // 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", // 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 // i.e. exactly today's behavior, and is the default of a PULL build, where the
// knob is meaningless anyway. A PUSH build defaults to every subsystem migrated so // knob is meaningless anyway. A PUSH build defaults to every subsystem migrated so
// far (MG_Pipe::kMGPipeSubsystemsMigratedAtP5e = 0x3fff), so MOBILEGL_PIPE_PUSH=0 in // far (MG_Pipe::kMGPipeSubsystemsMigratedAtP5e = 0x3fff), so MOBILEGL_PIPE_PUSH=0 in
// the environment is the all-pull control and 0x1fff (kMGPipeSubsystemsMigratedAtP4a) // the environment is the all-pull control and 0x1fff (kMGPipeSubsystemsMigratedAtP4a)
@@ -449,7 +455,7 @@ namespace MobileGL::MG_Config {
// (every MGPBufferRange::Res names a Buffer handle; P5e) // (every MGPBufferRange::Res names a Buffer handle; P5e)
// A dependency that is not met is REFUSED with one ERROR naming both bits and the // 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. // family runs its legacy arm; it is never half-run.
// 1<<63 NOT a subsystem, a BEHAVIOUR: turn OFF client-side content addressing of // 1<<63 NOT a subsystem, a BEHAVIOR: turn OFF client-side content addressing of
// CSOs, so every pipeline-version change mints a fresh CSO and the map is // 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. // never probed. The negative control the CSO design is measured against.
// P13 W3b (ID-P13-3): bits 0-13 are FIXED ON. Their pre-handle arms are gone, so a mask // P13 W3b (ID-P13-3): bits 0-13 are FIXED ON. Their pre-handle arms are gone, so a mask
@@ -463,24 +469,12 @@ namespace MobileGL::MG_Config {
// the semantic gate that replaces byte identity, and it catches the dangerous // 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. // direction - a dirty bit that fires too RARELY - which no purity gate can see.
Bool PipeVerify = false; Bool PipeVerify = false;
// 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 // 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 // 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 // log. Tri-state parse: only an explicit falsy value turns it off. The comparator's
// off. // negative control, MOBILEGL_PIPE_VERIFY_CORRUPT, has no field here: only a verify
// build reads it, when the comparator arms (MG_Impl/Pipe/PipeFill.cpp, ArmVerify).
Bool PipeVerifyFatal = true; 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_STATS: dump the boundary counters (bytes, calls, roundtrips, // MOBILEGL_PIPE_STATS: dump the boundary counters (bytes, calls, roundtrips,
// texture pulls, upload shapes, residual-block bytes, index mirror bytes). // texture pulls, upload shapes, residual-block bytes, index mirror bytes).
Bool PipeStats = false; Bool PipeStats = false;
@@ -503,7 +497,7 @@ namespace MobileGL::MG_Config {
extern FeaturesTable Features; extern FeaturesTable Features;
// --------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------
// P5: the transport selector and the MOBILEGL_IPC_* family (ARCHITECTURE.md 16, 附 A) // P5: the transport selector and the MOBILEGL_IPC_* family (ARCHITECTURE.md 16, appendix A)
// --------------------------------------------------------------------------------- // ---------------------------------------------------------------------------------
// //
// MOBILEGL_TRANSPORT = monolith | inproc | spawn | unix:<path> | pipe:<name>. // MOBILEGL_TRANSPORT = monolith | inproc | spawn | unix:<path> | pipe:<name>.
@@ -513,8 +507,7 @@ namespace MobileGL::MG_Config {
// in MG_Backend/Init.cpp compiles away entirely rather than becoming a branch nobody can // 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 // 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 // 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 // symbol set must not move).
// behind their own #if.
// //
// ONE CONSEQUENCE, STATED SO IT IS NOT REDISCOVERED: in a build without the option, // 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 // MOBILEGL_TRANSPORT=inproc is ACCEPTED BY THE ENVIRONMENT AND SILENTLY IGNORED - the
@@ -550,10 +543,6 @@ namespace MobileGL::MG_Config {
// by design": no forced sync, so a one-frame respecify-and-draw loop grows without bound // by design": no forced sync, so a one-frame respecify-and-draw loop grows without bound
// and MagmaWireReclaimScenario's watermark cases must go red. // and MagmaWireReclaimScenario's watermark cases must go red.
Uint32 WireDeferredMb = 64; Uint32 WireDeferredMb = 64;
// 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 // 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 // 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). // range every verb. 0 restores the whole-range push (A/B control).
@@ -592,7 +581,7 @@ namespace MobileGL::MG_Config {
// The MOBILEGL_IPC_* family. A separate table rather than more FeaturesTable members, // 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 // 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. // transport: a build that cannot reach the MG_Remote code cannot honor one of them.
// //
// P5 lands exactly the knobs P5's own packages read. A later phase's knob is added HERE, // 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 appendix A holds // through the integrator, and not invented at its call site - ARCHITECTURE.md appendix A holds
@@ -662,7 +651,7 @@ namespace MobileGL::MG_Config {
// (WireTables.cpp), half of the stream link's 24-entry answer store. // (WireTables.cpp), half of the stream link's 24-entry answer store.
// Measurements: docs/Performance/ipc-create-window.md. // Measurements: docs/Performance/ipc-create-window.md.
Uint32 CreateWindow = 10; Uint32 CreateWindow = 10;
// MOBILEGL_IPC_PBO_READBACK_SYNC (P9 W1, docs/archive/contracts/CONTRACT-P9.md §1): 1 = a glReadPixels // MOBILEGL_IPC_PBO_READBACK_SYNC (P9 W1 §1): 1 = a glReadPixels
// or glGetTexImage into a bound GL_PIXEL_PACK_BUFFER takes the reply-slot form again - // or glGetTexImage into a bound GL_PIXEL_PACK_BUFFER takes the reply-slot form again -
// the client waits for the pixels and uploads them into the buffer itself, as before P9. // the client waits for the pixels and uploads them into the buffer itself, as before P9.
// 0 (default) sends read_pixels_to_buffer / get_texture_image_to_buffer, waits for // 0 (default) sends read_pixels_to_buffer / get_texture_image_to_buffer, waits for
@@ -670,7 +659,7 @@ namespace MobileGL::MG_Config {
// red-once of the wait-count gate (PackBufferReadbackScenario): it must show a reply // red-once of the wait-count gate (PackBufferReadbackScenario): it must show a reply
// wait per read. // wait per read.
Uint32 PboReadbackSync = 0; Uint32 PboReadbackSync = 0;
// MOBILEGL_IPC_POLL_ESCALATE (P10, docs/archive/contracts/CONTRACT-P10.md §1): a glClientWaitSync with a // MOBILEGL_IPC_POLL_ESCALATE (P10 §1): a glClientWaitSync with a
// zero timeout or a glGetSynciv(GL_SYNC_STATUS) is answered by the client from what the // zero timeout or a glGetSynciv(GL_SYNC_STATUS) is answered by the client from what the
// server reported (kEventFenceSignaled) - "signaled" when it has been, "not yet" otherwise // server reported (kEventFenceSignaled) - "signaled" when it has been, "not yet" otherwise
// - and only the Nth "not yet" in a row on one fence crosses as a real round trip, so a // - and only the Nth "not yet" in a row on one fence crosses as a real round trip, so a
@@ -696,13 +685,7 @@ namespace MobileGL::MG_Config {
// platform and the backend's POST self-test allow it; 0 = never grant it (the session // platform and the backend's POST self-test allow it; 0 = never grant it (the session
// runs T2 and says so). Read by the server role only; kept through ServerSpawn. // runs T2 and says so). Read by the server role only; kept through ServerSpawn.
Uint32 AllowAdoptT0 = 1; Uint32 AllowAdoptT0 = 1;
// MOBILEGL_IPC_VERB_BARRIER: 1 = the client blocks at every verb boundary until // MOBILEGL_IPC_RUN_AHEAD (P5e §1): 1 = after publishing an
// 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, docs/archive/contracts/CONTRACT-P5E.md §1): 1 = after publishing an
// UNBARRIERED record the client returns immediately instead of waiting for its apply. // 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 // 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 // run-ahead only when the server also publishes kCapRunAheadApply, so on Magma, and on
@@ -712,9 +695,7 @@ namespace MobileGL::MG_Config {
// 0 IS THE A/B CONTROL AND NOT A NEGATIVE ONE: the server code, the fill decision and // 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 // 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 // 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 // rule alone.
// 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 // 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 // client-filled gPipeInputs block for every verb, and run-ahead is precisely the
@@ -780,7 +761,7 @@ namespace MobileGL::MG_Config {
// own) and no SetWindowHandle, and take the surface's real geometry back from the server. // own) and no SetWindowHandle, and take the surface's real geometry back from the server.
// Meaningful only when this client talks to a remote server (MOBILEGL_TRANSPORT=spawn, the // Meaningful only when this client talks to a remote server (MOBILEGL_TRANSPORT=spawn, the
// fork or tcp:// control); under monolith / inproc it is parsed, logged as ignored, and // fork or tcp:// control); under monolith / inproc it is parsed, logged as ignored, and
// changes nothing. `offscreen` (the default) is today's behaviour byte for byte. // changes nothing. `offscreen` (the default) is today's behavior byte for byte.
// Pbuffers stay pbuffers in both modes (D4: one mode per session, decided by the first). // Pbuffers stay pbuffers in both modes (D4: one mode per session, decided by the first).
IpcSurface Surface = IpcSurface::Offscreen; IpcSurface Surface = IpcSurface::Offscreen;
}; };
+22 -31
View File
@@ -137,8 +137,9 @@ namespace MobileGL::MG_ConfigLoader {
} }
// Everything the system files say, the backend file ahead of client.conf (first emplace // Everything the system files say, the backend file ahead of client.conf (first emplace
// wins). Re-read on every call: callers are few (Init, and a handful of lookups before it), // wins). Re-read on every call: callers are few (Init, a handful of lookups before it, and a
// and a file that changed between two processes' starts must not be served stale. // verify build's comparator when it arms), and a file that changed between two processes'
// starts must not be served stale.
void ReadSystemConfig(UnorderedMap<String, String>& out, Bool log) { void ReadSystemConfig(UnorderedMap<String, String>& out, Bool log) {
if (IsServerProcess()) return; if (IsServerProcess()) return;
ReadBackendFile(FilePathSetting("MOBILEGL_BACKEND_FILE", kDefaultBackendFile), out); ReadBackendFile(FilePathSetting("MOBILEGL_BACKEND_FILE", kDefaultBackendFile), out);
@@ -401,8 +402,8 @@ namespace MobileGL::MG_ConfigLoader {
features.EsprytAtomicSubgroupScratch = QueryEnvQuirkOverride("MOBILEGL_ESPRYT_ATOMIC_SUBGROUP_SCRATCH"); features.EsprytAtomicSubgroupScratch = QueryEnvQuirkOverride("MOBILEGL_ESPRYT_ATOMIC_SUBGROUP_SCRATCH");
features.EsprytInvalidImagePlaceholder = QueryEnvQuirkOverride("MOBILEGL_ESPRYT_INVALID_IMAGE_PLACEHOLDER"); features.EsprytInvalidImagePlaceholder = QueryEnvQuirkOverride("MOBILEGL_ESPRYT_INVALID_IMAGE_PLACEHOLDER");
features.PointSizeDemotion = QueryEnvQuirkOverride("MOBILEGL_POINT_SIZE_DEMOTION"); features.PointSizeDemotion = QueryEnvQuirkOverride("MOBILEGL_POINT_SIZE_DEMOTION");
features.ClipDistanceEmulation = QueryEnvQuirkOverride("MOBILEGL_CLIP_DISTANCE_EMULATION");
features.CoherentAsFlush = QueryEnvFlag("MOBILEGL_COHERENT_AS_FLUSH"); features.CoherentAsFlush = QueryEnvFlag("MOBILEGL_COHERENT_AS_FLUSH");
features.TraceSkipAutodestroy = QueryEnvFlag("MOBILEGL_TRACE_SKIP_AUTODESTROY");
features.EsprytDisableUboRing = QueryEnvFlag("MOBILEGL_ESPRYT_DISABLE_UBO_RING"); features.EsprytDisableUboRing = QueryEnvFlag("MOBILEGL_ESPRYT_DISABLE_UBO_RING");
features.EsprytDisableUnpackRing = QueryEnvFlag("MOBILEGL_ESPRYT_DISABLE_UNPACK_RING"); features.EsprytDisableUnpackRing = QueryEnvFlag("MOBILEGL_ESPRYT_DISABLE_UNPACK_RING");
features.EsprytDisableUploadRing = QueryEnvFlag("MOBILEGL_ESPRYT_DISABLE_UPLOAD_RING"); features.EsprytDisableUploadRing = QueryEnvFlag("MOBILEGL_ESPRYT_DISABLE_UPLOAD_RING");
@@ -452,8 +453,6 @@ namespace MobileGL::MG_ConfigLoader {
// Defaults ON: read as a tri-state so only an explicitly falsy value turns it off. // Defaults ON: read as a tri-state so only an explicitly falsy value turns it off.
features.PipeVerifyFatal = features.PipeVerifyFatal =
QueryEnvQuirkOverride("MOBILEGL_PIPE_VERIFY_FATAL") != MG_Config::QuirkOverride::ForceOff; QueryEnvQuirkOverride("MOBILEGL_PIPE_VERIFY_FATAL") != MG_Config::QuirkOverride::ForceOff;
QueryEnvVariable("MOBILEGL_PIPE_VERIFY_CORRUPT", features.PipeVerifyCorrupt, "");
QueryEnvVariable("MOBILEGL_PIPE_POISON_OMIT", features.PipePoisonOmit, "");
features.PipeStats = QueryEnvFlag("MOBILEGL_PIPE_STATS"); features.PipeStats = QueryEnvFlag("MOBILEGL_PIPE_STATS");
features.PipeIndexMirrorMb = QueryEnvUint32("MOBILEGL_PIPE_INDEX_MIRROR_MB", 64, 0, 4096); features.PipeIndexMirrorMb = QueryEnvUint32("MOBILEGL_PIPE_INDEX_MIRROR_MB", 64, 0, 4096);
features.PipeStatsPeriod = QueryEnvUint32("MOBILEGL_PIPE_STATS_PERIOD", 120, 1, 1000000); features.PipeStatsPeriod = QueryEnvUint32("MOBILEGL_PIPE_STATS_PERIOD", 120, 1, 1000000);
@@ -528,7 +527,7 @@ namespace MobileGL::MG_ConfigLoader {
"ring to an apply thread"); "ring to an apply thread");
return; return;
} }
// The three P6 forms. Recognised precisely, so the diagnostic can say "rejected" // The three P6 forms. Recognized precisely, so the diagnostic can say "rejected"
// rather than "unknown", which are different bugs on the operator's side. // rather than "unknown", which are different bugs on the operator's side.
// P6 `sm`/`cp`: spawn is IMPLEMENTED. It launches a server process on a // P6 `sm`/`cp`: spawn is IMPLEMENTED. It launches a server process on a
// rendezvous of its own and connects to it - the two processes are // rendezvous of its own and connects to it - the two processes are
@@ -577,7 +576,7 @@ namespace MobileGL::MG_ConfigLoader {
QueryEnvVariable("MOBILEGL_IPC_CONTROL", ipc.Control, "fork"); QueryEnvVariable("MOBILEGL_IPC_CONTROL", ipc.Control, "fork");
QueryEnvVariable("MOBILEGL_IPC_DATA", ipc.Data, "auto"); QueryEnvVariable("MOBILEGL_IPC_DATA", ipc.Data, "auto");
// Both ring floors are 1 MiB, not 0: a ring caps ONE record at half its size, and // Both ring floors are 1 MiB, not 0: a ring caps ONE record at half its size, and
// the catalogue's largest fixed payload (MGPFramebufferState, 304 bytes) plus a // the catalog's largest fixed payload (MGPFramebufferState, 304 bytes) plus a
// create_shader_state archive already needs far more than a toy ring. The ceilings // create_shader_state archive already needs far more than a toy ring. The ceilings
// are sanity, not policy. // are sanity, not policy.
ipc.RingMb = QueryEnvUint32("MOBILEGL_IPC_RING_MB", 8, 1, 1024); ipc.RingMb = QueryEnvUint32("MOBILEGL_IPC_RING_MB", 8, 1, 1024);
@@ -601,22 +600,25 @@ namespace MobileGL::MG_ConfigLoader {
// The verify harness compares the pushed block against the applier per verb; a // The verify harness compares the pushed block against the applier per verb; a
// batched queue lets the comparer read a supplied field mid-apply, which is a // batched queue lets the comparer read a supplied field mid-apply, which is a
// torn read rather than a divergence. The batch is therefore off whenever the // torn read rather than a divergence. The batch is therefore off whenever the
// shadow comparer is armed. // shadow comparer is armed, which only a verify build compiles.
#if MOBILEGL_PIPE_VERIFY
if (MG_Config::Features.PipeVerify) ipc.BatchWaits = 0; if (MG_Config::Features.PipeVerify) ipc.BatchWaits = 0;
#endif
// UNSET is T0 with quiet fallbacks (the default since 2026-09-29); 0 asks for T0 by name, 1 is // UNSET is T0 with quiet fallbacks (the default since 2026-09-29); 0 asks for T0 by name, 1 is
// refused by name, 2 is T2 - settled at the handshake from the data plane (P11 A1/B2, // refused by name, 2 is T2 - settled at the handshake from the data plane (P11 A1/B2,
// MG_Remote/Transport/AdoptTier.h). An unparseable value is warned about and reads as unset. // MG_Remote/Transport/AdoptTier.h). An unparseable value is warned about and reads as unset.
ipc.AdoptTier = QueryEnvUint32("MOBILEGL_IPC_ADOPT_TIER", MG_Config::kAdoptTierUnset, 0, 2); ipc.AdoptTier = QueryEnvUint32("MOBILEGL_IPC_ADOPT_TIER", MG_Config::kAdoptTierUnset, 0, 2);
// P11 B2: the server's own T0 allow switch (Config.h). // P11 B2: the server's own T0 allow switch (Config.h).
ipc.AllowAdoptT0 = QueryEnvUint32("MOBILEGL_IPC_ALLOW_ADOPT_T0", 1, 0, 1); ipc.AllowAdoptT0 = QueryEnvUint32("MOBILEGL_IPC_ALLOW_ADOPT_T0", 1, 0, 1);
ipc.VerbBarrier = QueryEnvUint32("MOBILEGL_IPC_VERB_BARRIER", 1, 0, 1); // P5e (§1). The wait rule's A/B, parsed here like every
// P5e (docs/archive/contracts/CONTRACT-P5E.md §1). The wait rule's A/B, parsed here like every
// other IPC knob and armed only where the server publishes kCapRunAheadApply. // other IPC knob and armed only where the server publishes kCapRunAheadApply.
ipc.RunAhead = QueryEnvUint32("MOBILEGL_IPC_RUN_AHEAD", 1, 0, 1); ipc.RunAhead = QueryEnvUint32("MOBILEGL_IPC_RUN_AHEAD", 1, 0, 1);
// ... and forced OFF by the verify harness for BatchWaits' reason, one step further: // ... and forced OFF by the verify harness for BatchWaits' reason, one step further:
// run-ahead's whole point is that the client stops filling gPipeInputs for an // run-ahead's whole point is that the client stops filling gPipeInputs for an
// unbarriered record, and the comparator has nothing left to compare when it does. // unbarriered record, and the comparator has nothing left to compare when it does.
#if MOBILEGL_PIPE_VERIFY
if (MG_Config::Features.PipeVerify) ipc.RunAhead = 0; if (MG_Config::Features.PipeVerify) ipc.RunAhead = 0;
#endif
// The present credit (ruling 4). 1 is one frame of overlap; 8 is the ceiling because a // The present credit (ruling 4). 1 is one frame of overlap; 8 is the ceiling because a
// deeper queue buys nothing on a CPU-bound client and pays for it in latency. 0 is NOT // deeper queue buys nothing on a CPU-bound client and pays for it in latency. 0 is NOT
// admitted: a credit of zero would mean "publish no present at all". // admitted: a credit of zero would mean "publish no present at all".
@@ -646,12 +648,14 @@ namespace MobileGL::MG_ConfigLoader {
// own and needs no applier quiesce. 0 still turns it off (the negative control does). // own and needs no applier quiesce. 0 still turns it off (the negative control does).
ipc.RoleSplitState = ipc.RoleSplitState =
QueryEnvQuirkOverride("MOBILEGL_IPC_ROLE_SPLIT_STATE") != MG_Config::QuirkOverride::ForceOff; QueryEnvQuirkOverride("MOBILEGL_IPC_ROLE_SPLIT_STATE") != MG_Config::QuirkOverride::ForceOff;
#if MOBILEGL_PIPE_VERIFY
if (MG_Config::Features.PipeVerify && ipc.RoleSplitState) { if (MG_Config::Features.PipeVerify && ipc.RoleSplitState) {
MGLOG_W("Config: MOBILEGL_IPC_ROLE_SPLIT_STATE=1 is incompatible with " MGLOG_W("Config: MOBILEGL_IPC_ROLE_SPLIT_STATE=1 is incompatible with "
"MOBILEGL_PIPE_VERIFY (the comparator owns the single fill block); " "MOBILEGL_PIPE_VERIFY (the comparator owns the single fill block); "
"the dual-block rehearsal is OFF for this run"); "the dual-block rehearsal is OFF for this run");
ipc.RoleSplitState = false; ipc.RoleSplitState = false;
} }
#endif
// P6 `dl` (CONTRACT-P6 5.3): MOBILEGL_IPC_RESPAWN IS A NAMED REFUSAL, NOT A NO-OP. // P6 `dl` (CONTRACT-P6 5.3): MOBILEGL_IPC_RESPAWN IS A NAMED REFUSAL, NOT A NO-OP.
// //
@@ -700,7 +704,7 @@ namespace MobileGL::MG_ConfigLoader {
const Bool serverProcess = role != nullptr && std::strcmp(role, "server") == 0; const Bool serverProcess = role != nullptr && std::strcmp(role, "server") == 0;
if (ipc.Surface == MG_Config::IpcSurface::Server && !serverProcess && if (ipc.Surface == MG_Config::IpcSurface::Server && !serverProcess &&
MG_Config::Transport != MG_Config::TransportMode::Spawn) { MG_Config::Transport != MG_Config::TransportMode::Spawn) {
// Said, not silently honoured: there is no remote server here to own a window. // Said, not silently honored: there is no remote server here to own a window.
MGLOG_W("Config: MOBILEGL_IPC_SURFACE=server is IGNORED - it asks a remote server " MGLOG_W("Config: MOBILEGL_IPC_SURFACE=server is IGNORED - it asks a remote server "
"(MOBILEGL_TRANSPORT=spawn, a fork or tcp:// control) to own the window surface, " "(MOBILEGL_TRANSPORT=spawn, a fork or tcp:// control) to own the window surface, "
"and this run has none. Window surfaces stay the client's own"); "and this run has none. Window surfaces stay the client's own");
@@ -708,31 +712,25 @@ namespace MobileGL::MG_ConfigLoader {
} }
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return; if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
// One line, on the arm where these numbers decide behaviour, because every one of // One line, on the arm where these numbers decide behavior, because every one of
// them is a number a bug report has to quote. // them is a number a bug report has to quote.
// CREATE-WINDOW IS IN THIS LINE BECAUSE A RUN HAS TO SAY WHICH WINDOW IT USED. It was // CREATE-WINDOW IS IN THIS LINE BECAUSE A RUN HAS TO SAY WHICH WINDOW IT USED. It was
// missing while the value was a number nobody varied, and the moment it became one that // missing while the value was a number nobody varied, and the moment it became one that
// changes the wall clock by seconds (the P12 sweep: 28.3 s at 1 down to 10.8 s at 6) a // changes the wall clock by seconds (the P12 sweep: 28.3 s at 1 down to 10.8 s at 6) a
// log that does not name it makes every run ambiguous about its own configuration. // log that does not name it makes every run ambiguous about its own configuration.
MGLOG_I("Config: IPC ring=%uMiB stage=%uMiB wire-deferred=%uMiB spin=%uus event-wait=%ums " MGLOG_I("Config: IPC ring=%uMiB stage=%uMiB wire-deferred=%uMiB spin=%uus event-wait=%ums "
"persistent-block=%uKiB create-window=%u pbo-readback-sync=%u poll-escalate=%u " "create-window=%u pbo-readback-sync=%u poll-escalate=%u "
"adopt-tier=%s allow-t0=%u verb-barrier=%u run-ahead=%u present-credit=%u control-timeout=%ums " "adopt-tier=%s allow-t0=%u run-ahead=%u present-credit=%u control-timeout=%ums "
"cold-start=%ums strict=%d audit=%d role-split-state=%d affinity='%s' surface=%s", "cold-start=%ums strict=%d audit=%d role-split-state=%d affinity='%s' surface=%s",
ipc.RingMb, ipc.StageMb, MG_Config::RecordArm.WireDeferredMb, ipc.SpinUs, ipc.EventWaitMs, ipc.RingMb, ipc.StageMb, MG_Config::RecordArm.WireDeferredMb, ipc.SpinUs, ipc.EventWaitMs,
MG_Config::RecordArm.PersistentBlockKb, ipc.CreateWindow, ipc.PboReadbackSync, ipc.PollEscalate, ipc.CreateWindow, ipc.PboReadbackSync, ipc.PollEscalate,
ipc.AdoptTier == 0 ? "0" ipc.AdoptTier == 0 ? "0"
: ipc.AdoptTier == 1 ? "1" : ipc.AdoptTier == 1 ? "1"
: ipc.AdoptTier == 2 ? "2" : ipc.AdoptTier == 2 ? "2"
: "unset(T0)", : "unset(T0)",
ipc.AllowAdoptT0, ipc.VerbBarrier, ipc.RunAhead, ipc.PresentCredit, ipc.ControlTimeoutMs, ipc.AllowAdoptT0, ipc.RunAhead, ipc.PresentCredit, ipc.ControlTimeoutMs, ipc.ColdStartMs,
ipc.ColdStartMs, static_cast<int>(ipc.StrictErrors), static_cast<int>(ipc.Audit), static_cast<int>(ipc.StrictErrors), static_cast<int>(ipc.Audit), static_cast<int>(ipc.RoleSplitState),
static_cast<int>(ipc.RoleSplitState), ipc.ServerAffinity.c_str(), ipc.ServerAffinity.c_str(), MG_Config::ServerOwnedWindowSurfaces() ? "server" : "offscreen");
MG_Config::ServerOwnedWindowSurfaces() ? "server" : "offscreen");
if (ipc.VerbBarrier == 0) {
MGLOG_W("Config: MOBILEGL_IPC_VERB_BARRIER=0 is the R-1 NEGATIVE CONTROL and is "
"expected to fail: the client still pulls 31 of 63 PipeInputs fields from a "
"live GLContext, so an unbarriered queue lets the server read future values");
}
} }
#endif #endif
@@ -742,9 +740,6 @@ namespace MobileGL::MG_ConfigLoader {
// P7 wave 4 M2 (Config.h has the semantics). 0 is admitted ON PURPOSE as the negative // P7 wave 4 M2 (Config.h has the semantics). 0 is admitted ON PURPOSE as the negative
// control: no forced sync, and MagmaWireReclaimScenario's watermark case must go red. // control: no forced sync, and MagmaWireReclaimScenario's watermark case must go red.
ipc.WireDeferredMb = QueryEnvUint32("MOBILEGL_IPC_WIRE_DEFERRED_MB", 64, 0, 65536); ipc.WireDeferredMb = QueryEnvUint32("MOBILEGL_IPC_WIRE_DEFERRED_MB", 64, 0, 65536);
// 0 is admitted ON PURPOSE and is the negative control of exit gate E3(a): it turns
// the persistent-map push OFF, and PersistentCoherentMapScenario must go red.
ipc.PersistentBlockKb = QueryEnvUint32("MOBILEGL_IPC_PERSISTENT_BLOCK_KB", 64, 0, 65536);
// Whole-range push is the 0 arm; with it on (default) only blocks whose // Whole-range push is the 0 arm; with it on (default) only blocks whose
// xxHash64 changed since the last push are shipped. // xxHash64 changed since the last push are shipped.
ipc.PersistentHashSuppress = QueryEnvUint32("MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS", 1, 0, 1); ipc.PersistentHashSuppress = QueryEnvUint32("MOBILEGL_IPC_PERSISTENT_HASH_SUPPRESS", 1, 0, 1);
@@ -758,10 +753,6 @@ namespace MobileGL::MG_ConfigLoader {
"server never forces a sync for orphaned wire buffer stores, so a long frame " "server never forces a sync for orphaned wire buffer stores, so a long frame "
"that respecifies and draws holds every one of them until the frame ends"); "that respecifies and draws holds every one of them until the frame ends");
} }
if (ipc.PersistentBlockKb == 0) {
MGLOG_W("Config: MOBILEGL_IPC_PERSISTENT_BLOCK_KB=0 is the E3(a) NEGATIVE CONTROL: "
"the persistent-map push is OFF and a coherent-map scenario must go red");
}
} }
void Init() { void Init() {
+1 -1
View File
@@ -145,7 +145,7 @@ namespace MobileGL {
// ORDER is the fix - not a wait, and not a friendlier default mask. // ORDER is the fix - not a wait, and not a friendlier default mask.
// //
// MG_State::Init() constructs GLContext, and GLContext's TextureState constructor // MG_State::Init() constructs GLContext, and GLContext's TextureState constructor
// materialises one default texture object per target (GL 3.3 core 3.8, // materializes one default texture object per target (GL 3.3 core 3.8,
// TextureState.cpp:62-71). TextureObjectBase's constructor emits resource_create for // TextureState.cpp:62-71). TextureObjectBase's constructor emits resource_create for
// every one of them (TextureState/TextureObject.cpp:168-169), and that emission is // every one of them (TextureState/TextureObject.cpp:168-169), and that emission is
// gated, through PipeFill's FamilyIsLive, on // gated, through PipeFill's FamilyIsLive, on
+2 -1
View File
@@ -28,7 +28,8 @@ namespace MobileGL {
void Init(); void Init();
// One setting resolved the way Init() resolves it - the environment, then the system // One setting resolved the way Init() resolves it - the environment, then the system
// configuration files (ConfigLoader.cpp) - for the few readers that must decide something // configuration files (ConfigLoader.cpp) - for the few readers that must decide something
// before MobileGL initializes. False when neither names it. // before MobileGL initializes, and for a verify build's comparator, which reads
// MOBILEGL_PIPE_VERIFY_CORRUPT when it arms. False when neither names it.
Bool LookupSetting(const char* key, String& outValue); Bool LookupSetting(const char* key, String& outValue);
} // namespace MG_ConfigLoader } // namespace MG_ConfigLoader
+1 -1
View File
@@ -218,7 +218,7 @@ namespace MobileGL::MG_Backend {
void (*BeginTransformFeedback)(GLenum primitiveMode); void (*BeginTransformFeedback)(GLenum primitiveMode);
void (*EndTransformFeedback)(); void (*EndTransformFeedback)();
// ARB_transform_feedback2. A backend that leaves these null keeps the single // ARB_transform_feedback2. A backend that leaves these null keeps the single
// implicit capture span the frontend has always modelled; the frontend state // implicit capture span the frontend has always modeled; the frontend state
// (paused flag, per-object bindings) is tracked either way. // (paused flag, per-object bindings) is tracked either way.
void (*PauseTransformFeedback)(); void (*PauseTransformFeedback)();
void (*ResumeTransformFeedback)(); void (*ResumeTransformFeedback)();
+2 -2
View File
@@ -61,7 +61,7 @@ namespace MobileGL::MG_Backend {
(void)region; (void)region;
return true; return true;
} }
// IMPLICIT SYNC BY FLUSH (docs/archive/notes/anland/plan-x11-gpu.md): the session // IMPLICIT SYNC BY FLUSH: the session
// on this (the apply) thread ends a frame at a glFlush. Every shared image it used since // on this (the apply) thread ends a frame at a glFlush. Every shared image it used since
// its last boundary is published as written and read by one fence of everything it // its last boundary is published as written and read by one fence of everything it
// submitted (SharedImages::ReadTracker::PublishFrameAsWrite), and from now on its first // submitted (SharedImages::ReadTracker::PublishFrameAsWrite), and from now on its first
@@ -146,7 +146,7 @@ namespace MobileGL::MG_Backend {
return false; return false;
} }
// The same for a YUV fourcc, whose planes and hints `layout` gives. A YUV buffer the // The same for a YUV fourcc, whose planes and hints `layout` gives. A YUV buffer the
// server did not export is taken too (its labelled CPU-copy fallback). // server did not export is taken too (its labeled CPU-copy fallback).
virtual Bool ImportSharedImagePlanes(int fd, Uint32 width, Uint32 height, Uint32 fourcc, virtual Bool ImportSharedImagePlanes(int fd, Uint32 width, Uint32 height, Uint32 fourcc,
const SharedImageImportLayout& layout, Uint64* outId) { const SharedImageImportLayout& layout, Uint64* outId) {
(void)fd, (void)width, (void)height, (void)fourcc, (void)layout, (void)outId; (void)fd, (void)width, (void)height, (void)fourcc, (void)layout, (void)outId;
@@ -266,7 +266,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
reasons.push_back("GL_DEPTH_COMPONENT32 native probe failed on OpenGL ES"); reasons.push_back("GL_DEPTH_COMPONENT32 native probe failed on OpenGL ES");
} }
if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) { if (options & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) {
reasons.push_back("no colour-renderable three-channel format on OpenGL ES"); reasons.push_back("no color-renderable three-channel format on OpenGL ES");
} }
// A format is either 8- or 16-bit signed normalized, so at most one of the two ever // A format is either 8- or 16-bit signed normalized, so at most one of the two ever
// survives GetApplicablePixelFormatNormalizeOptions and the reason is not duplicated. // survives GetApplicablePixelFormatNormalizeOptions and the reason is not duplicated.
@@ -369,7 +369,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// did - is not merely untidy: the two texture pnames are ES 3.1 state, so on an ES 3.0 // did - is not merely untidy: the two texture pnames are ES 3.1 state, so on an ES 3.0
// context the loader's rejected-probe clamp leaves them at 1 (see the multisample clamps // context the loader's rejected-probe clamp leaves them at 1 (see the multisample clamps
// in the GLES loader) and the walk below would never run past one sample, recording {1} // in the GLES loader) and the walk below would never run past one sample, recording {1}
// for EVERY colour format while GL_MAX_SAMPLES - ES 3.0 core, so genuinely answered - // for EVERY color format while GL_MAX_SAMPLES - ES 3.0 core, so genuinely answered -
// reports 4. Once the frontend validates against this list, that would reject every // reports 4. Once the frontend validates against this list, that would reject every
// multisample renderbuffer on such a context. // multisample renderbuffer on such a context.
Int GetGLESRenderbufferFormatMaxSamples(const MG_External::GLESCapabilities& capabilities, GLenum imageFormat) { Int GetGLESRenderbufferFormatMaxSamples(const MG_External::GLESCapabilities& capabilities, GLenum imageFormat) {
@@ -707,7 +707,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) { for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTextureTargetCount; ++targetIndex) {
const auto target = static_cast<TextureTarget>(targetIndex); const auto target = static_cast<TextureTarget>(targetIndex);
// Colour-attachable targets need a colour-renderable fallback; the ordinary // Color-attachable targets need a color-renderable fallback; the ordinary
// fallback for a three-channel format is another three-channel one, which ES // fallback for a three-channel format is another three-channel one, which ES
// accepts as a texture but never as an attachment. Recompute the fallback per // accepts as a texture but never as an attachment. Recompute the fallback per
// target so those formats get widened where the target demands it. // target so those formats get widened where the target demands it.
@@ -814,7 +814,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const SizeT renderbufferTargetIndex = GetRenderbufferFormatCapabilityTargetIndex(); const SizeT renderbufferTargetIndex = GetRenderbufferFormatCapabilityTargetIndex();
// A renderbuffer exists only to be attached, so it needs the same three-channel // A renderbuffer exists only to be attached, so it needs the same three-channel
// widening the colour-attachable texture targets get - and on the same terms: the // widening the color-attachable texture targets get - and on the same terms: the
// native storage is probed first, so a driver that renders to it keeps it. // native storage is probed first, so a driver that renders to it keeps it.
const Flags<PixelFormatNormalizeOptionBit> renderbufferOptions = const Flags<PixelFormatNormalizeOptionBit> renderbufferOptions =
TextureImpl::GetRenderTargetNormalizeOptions(capabilities, renderbufferTargetIndex); TextureImpl::GetRenderTargetNormalizeOptions(capabilities, renderbufferTargetIndex);
@@ -1053,7 +1053,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// built). // built).
UpdateAdvertisedCapabilityExtensions(m_GLESCapabilities); UpdateAdvertisedCapabilityExtensions(m_GLESCapabilities);
UpdateDynamicBackendParameters(); UpdateDynamicBackendParameters();
// The colour-attachment cap the framebuffer family checks is known from here on. // The color-attachment cap the framebuffer family checks is known from here on.
DirectGLES::ResolveRecordArmFamilies(); DirectGLES::ResolveRecordArmFamilies();
PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities()); PopulateFormatCapabilities(m_GLESFunctions, m_GLESCapabilities, MutableFormatCapabilities());
PrintFormatCapabilities(GetFormatCapabilities()); PrintFormatCapabilities(GetFormatCapabilities());
@@ -1540,7 +1540,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
E_GL_ARB_texture_rg, E_GL_ARB_texture_rg,
// GL_DEPTH_COMPONENT32F and GL_DEPTH32F_STENCIL8. // GL_DEPTH_COMPONENT32F and GL_DEPTH32F_STENCIL8.
E_GL_ARB_depth_buffer_float, E_GL_ARB_depth_buffer_float,
// The floating-point colour formats. Unlike the rest of this block this string DOES // The floating-point color formats. Unlike the rest of this block this string DOES
// gate CTS cases - KHR-GL4*.internalformat.texture2d.*{16f,32f} is keyed on it with no // gate CTS cases - KHR-GL4*.internalformat.texture2d.*{16f,32f} is keyed on it with no
// core-version fallback, so eight cases per version list were NotSupported on formats // core-version fallback, so eight cases per version list were NotSupported on formats
// the backend has always had. // the backend has always had.
@@ -1557,7 +1557,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// - multiple capture buffers per stream, gl_SkipComponents / gl_NextBuffer, the // - multiple capture buffers per stream, gl_SkipComponents / gl_NextBuffer, the
// indexed queries and glDrawTransformFeedbackStream (4.0); // indexed queries and glDrawTransformFeedbackStream (4.0);
E_GL_ARB_transform_feedback3, E_GL_ARB_transform_feedback3,
// - GL_RGB10_A2UI (3.3), GL_RGB9_E5 (3.0) and the integer colour formats (3.0); // - GL_RGB10_A2UI (3.3), GL_RGB9_E5 (3.0) and the integer color formats (3.0);
E_GL_ARB_texture_rgb10_a2ui, E_GL_EXT_texture_shared_exponent, E_GL_EXT_texture_integer, E_GL_ARB_texture_rgb10_a2ui, E_GL_EXT_texture_shared_exponent, E_GL_EXT_texture_integer,
// - the eleven pipeline-statistics query targets (4.6). Accepted by glBeginQuery and // - the eleven pipeline-statistics query targets (4.6). Accepted by glBeginQuery and
// answered with GL_QUERY_COUNTER_BITS = 0, the spec's own "this counter is not // answered with GL_QUERY_COUNTER_BITS = 0, the spec's own "this counter is not
@@ -1609,7 +1609,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// recorded trace can never cover: Iris and Sodium change their SUBMISSION SCHEDULE // recorded trace can never cover: Iris and Sodium change their SUBMISSION SCHEDULE
// the moment they see it - they enqueue whole pipeline batches and poll // the moment they see it - they enqueue whole pipeline batches and poll
// GL_COMPLETION_STATUS_KHR instead of compiling one program at a time - so // GL_COMPLETION_STATUS_KHR instead of compiling one program at a time - so
// MOBILEGL_ASYNC_SHADER_COMPILE=0 has to withdraw the application-visible behaviour // MOBILEGL_ASYNC_SHADER_COMPILE=0 has to withdraw the application-visible behavior
// change as well as the threading, or the kill switch would only be half a switch. // change as well as the threading, or the kill switch would only be half a switch.
if (MG_Util::Async::AsyncShaderCompileEnabled()) { if (MG_Util::Async::AsyncShaderCompileEnabled()) {
extensions.push_back(E_GL_KHR_parallel_shader_compile); extensions.push_back(E_GL_KHR_parallel_shader_compile);
@@ -1619,7 +1619,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// reaches this backend - so an application that simply uses doubles needs nothing // reaches this backend - so an application that simply uses doubles needs nothing
// advertised. What the extension additionally promises is 64-bit PRECISION, which no // advertised. What the extension additionally promises is 64-bit PRECISION, which no
// mobile GPU has and the narrowing cannot fake, so advertising it by default would // mobile GPU has and the narrowing cannot fake, so advertising it by default would
// make an application that checks the string take a path MobileGL cannot honour. // make an application that checks the string take a path MobileGL cannot honor.
if (MG_Config::Features.AdvertiseFp64) { if (MG_Config::Features.AdvertiseFp64) {
extensions.push_back(E_GL_ARB_gpu_shader_fp64); extensions.push_back(E_GL_ARB_gpu_shader_fp64);
} }
@@ -1650,7 +1650,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// DEPTH_STENCIL_TEXTURE_MODE = STENCIL_INDEX and through a view with DEPTH_COMPONENT, in // DEPTH_STENCIL_TEXTURE_MODE = STENCIL_INDEX and through a view with DEPTH_COMPONENT, in
// a single shading pass). A copy-based fallback satisfies neither half, and fails // a single shading pass). A copy-based fallback satisfies neither half, and fails
// silently; withholding the string and answering glTextureView with INVALID_OPERATION is // silently; withholding the string and answering glTextureView with INVALID_OPERATION is
// the only behaviour that cannot be mistaken for success. // the only behavior that cannot be mistaken for success.
// //
// The host extension is necessary and NOT sufficient, which is why this second gate // The host extension is necessary and NOT sufficient, which is why this second gate
// exists. Adreno 830 has EXT_texture_view, and on it the whole functional half of // exists. Adreno 830 has EXT_texture_view, and on it the whole functional half of
@@ -1832,8 +1832,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
// GL_MAX_TEXTURE_IMAGE_UNITS must never exceed host-side fixed arrays sized off it (e.g. // GL_MAX_TEXTURE_IMAGE_UNITS must never exceed host-side fixed arrays sized off it (e.g.
// Minecraft's 128-entry Blaze3D GlStateManager.TEXTURES[], iterated by Iris), and the combined // Minecraft's 128-entry Blaze3D GlStateManager.TEXTURES[], iterated by Iris), and the combined
// limit must stay within our texture-unit state array capacity. // limit must stay within our texture-unit state array capacity.
//
// The fragment limit is raised to the desktop value (32) even where the ES driver counts 16
// per stage: Iris sizes its WHOLE-PROGRAM unit budget off this query (vertex and fragment
// samplers share it, and it refuses the next sampler as soon as the budget is full), so 16
// leaves shader packs such as iterationT without a pipeline at all. Unit NUMBERS up to the
// driver's combined limit are legal in every stage, and a stage that really samples more
// textures than the driver allows still fails at link with the driver's own log.
m_dynamicParameters.MaxTextureImageUnits = m_dynamicParameters.MaxTextureImageUnits =
std::min(m_GLESCapabilities.MaxTextureImageUnits, std::min(std::max(m_GLESCapabilities.MaxTextureImageUnits, m_GLESCapabilities.MaxCombinedTextureImageUnits),
static_cast<Int>(MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS)); static_cast<Int>(MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS));
m_dynamicParameters.MaxVertexTextureImageUnits = m_dynamicParameters.MaxVertexTextureImageUnits =
std::min(m_GLESCapabilities.MaxVertexTextureImageUnits, std::min(m_GLESCapabilities.MaxVertexTextureImageUnits,
@@ -1884,7 +1891,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
// (MaxShaderStorageBufferBindings is assigned above, before the per-stage clamp reads it.) // (MaxShaderStorageBufferBindings is assigned above, before the per-stage clamp reads it.)
// This is the number glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE) hands the application, and // This is the number glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE) hands the application, and
// on a host without buffer textures it is knowingly a floor MobileGL cannot honour rather // on a host without buffer textures it is knowingly a floor MobileGL cannot honor rather
// than a driver answer (m_GLESCapabilities.MaxTextureBufferSizeIsDriverReported says // than a driver answer (m_GLESCapabilities.MaxTextureBufferSizeIsDriverReported says
// which). Reporting 0 instead was considered and rejected: MobileGL advertises an OpenGL // which). Reporting 0 instead was considered and rejected: MobileGL advertises an OpenGL
// 4.x context, where buffer textures are core and the limit has a spec minimum of 65536, // 4.x context, where buffer textures are core and the limit has a spec minimum of 65536,
@@ -1989,18 +1996,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
m_dynamicParameters.MaxDrawBuffers = m_GLESCapabilities.MaxDrawBuffers; m_dynamicParameters.MaxDrawBuffers = m_GLESCapabilities.MaxDrawBuffers;
m_dynamicParameters.MaxColorAttachments = m_GLESCapabilities.MaxColorAttachments; m_dynamicParameters.MaxColorAttachments = m_GLESCapabilities.MaxColorAttachments;
m_dynamicParameters.MaxClipDistances = m_GLESCapabilities.MaxClipDistances; // Eight when emulated - the GL 3.0+ minimum, and what EmulateClipDistancePass carries -
// else the driver's own number (0 without the extension and with the emulation forced
// off: the native-or-nothing answer).
m_dynamicParameters.EmulatesClipDistance = m_GLESCapabilities.EmulatesClipDistance;
m_dynamicParameters.MaxClipDistances =
m_GLESCapabilities.EmulatesClipDistance ? 8 : m_GLESCapabilities.MaxClipDistances;
// The loader already gated both on GL_EXT_clip_cull_distance and left 0 without it, which // The loader already gated both on GL_EXT_clip_cull_distance and left 0 without it, which
// is the answer that keeps glslang from accepting a gl_CullDistance the ESSL compiler // is the answer that keeps glslang from accepting a gl_CullDistance the ESSL compiler
// would reject. // would reject.
m_dynamicParameters.MaxCullDistances = m_GLESCapabilities.MaxCullDistances; m_dynamicParameters.MaxCullDistances = m_GLESCapabilities.MaxCullDistances;
m_dynamicParameters.MaxCombinedClipAndCullDistances = m_GLESCapabilities.MaxCombinedClipAndCullDistances; m_dynamicParameters.MaxCombinedClipAndCullDistances =
std::max(m_GLESCapabilities.MaxCombinedClipAndCullDistances, m_dynamicParameters.MaxClipDistances);
m_dynamicParameters.MaxViewports = m_GLESCapabilities.MaxViewports; m_dynamicParameters.MaxViewports = m_GLESCapabilities.MaxViewports;
// Whatever the driver said about which vertex supplies gl_Layer, and GL_UNDEFINED_VERTEX // Whatever the driver said about which vertex supplies gl_Layer, and GL_UNDEFINED_VERTEX
// for gl_ViewportIndex on every driver without GL_OES_viewport_array - which is both test // for gl_ViewportIndex on every driver without GL_OES_viewport_array - which is both test
// devices. That is not a shortfall being hidden: without the extension only viewport 0 is // devices. That is not a shortfall being hidden: without the extension only viewport 0 is
// ever rasterized, so no vertex "selects" a viewport index and naming a convention would // ever rasterized, so no vertex "selects" a viewport index and naming a convention would
// describe behaviour this backend does not implement. // describe behavior this backend does not implement.
m_dynamicParameters.LayerProvokingVertex = m_GLESCapabilities.LayerProvokingVertex; m_dynamicParameters.LayerProvokingVertex = m_GLESCapabilities.LayerProvokingVertex;
m_dynamicParameters.ViewportIndexProvokingVertex = m_GLESCapabilities.ViewportIndexProvokingVertex; m_dynamicParameters.ViewportIndexProvokingVertex = m_GLESCapabilities.ViewportIndexProvokingVertex;
// GL_EXT_fragment_shading_rate is a straight pass-through: the pipeline rate and the // GL_EXT_fragment_shading_rate is a straight pass-through: the pipeline rate and the
@@ -20,7 +20,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// deliberate and it is not this phase's to change: ARCHITECTURE.md assigns the two // deliberate and it is not this phase's to change: ARCHITECTURE.md assigns the two
// backend-specific post-processings of the resolved sampler set - this one and Magma's // backend-specific post-processings of the resolved sampler set - this one and Magma's
// feedback-loop detection - to the server, acting ON the already-resolved set, and the // feedback-loop detection - to the server, acting ON the already-resolved set, and the
// ROADMAP puts "raw-depth-fetch sampler 原生化" in P3b/P4b beside Magma's placeholder // ROADMAP puts "native raw-depth-fetch sampler" in P3b/P4b beside Magma's placeholder
// textures. What P4a changes is only what the substitution READS: the resolved view's // textures. What P4a changes is only what the substitution READS: the resolved view's
// format answers IsDepthFormatInternalFormat and the sampler CSO record's parameters // format answers IsDepthFormatInternalFormat and the sampler CSO record's parameters
// answer compareMode / minFilter / mipmapMode / magFilter. // answer compareMode / minFilter / mipmapMode / magFilter.
+6 -6
View File
@@ -187,7 +187,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// glTexBuffer / glTexBufferRange through whichever spelling this driver's buffer-texture // glTexBuffer / glTexBufferRange through whichever spelling this driver's buffer-texture
// support actually ships: the unsuffixed names are ES 3.2 core, while an EXT/OES driver // support actually ships: the unsuffixed names are ES 3.2 core, while an EXT/OES driver
// exports glTexBuffer{,Range}EXT / OES. Callers must have checked // exports glTexBuffer{,Range}EXT / OES. Callers must have checked
// AreBufferTexturesSupported() first. CallTexBufferRange reports whether it could honour // AreBufferTexturesSupported() first. CallTexBufferRange reports whether it could honor
// the range - no tier is required to expose the range form, and the whole-buffer form is // the range - no tier is required to expose the range form, and the whole-buffer form is
// the documented fallback. // the documented fallback.
void CallTexBuffer(GLenum target, GLenum internalFormat, GLuint buffer); void CallTexBuffer(GLenum target, GLenum internalFormat, GLuint buffer);
@@ -448,7 +448,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// hold" query and no way to ask a context for its share group, so a case that must tell "the // hold" query and no way to ask a context for its share group, so a case that must tell "the
// same native context still serves" from "a fresh one was built", or a sharing pair from two // same native context still serves" from "a fresh one was built", or a sharing pair from two
// unrelated contexts, reads it here. These are pure reads plus one key override; neither is on // unrelated contexts, reads it here. These are pure reads plus one key override; neither is on
// a production path and neither changes behaviour. // a production path and neither changes behavior.
EGLContext NativeContextHandleFor(Uint64 sessionKey, Uint64 contextToken); EGLContext NativeContextHandleFor(Uint64 sessionKey, Uint64 contextToken);
// Overrides the key the calling thread resolves to, for the life of the call. The backend's // Overrides the key the calling thread resolves to, for the life of the call. The backend's
// ordinary answer comes from the server layer's probe; this is how a case drives exactly the // ordinary answer comes from the server layer's probe; this is how a case drives exactly the
@@ -484,8 +484,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Pushes the frontend's render-state block to the ES driver, diffed against what was // Pushes the frontend's render-state block to the ES driver, diffed against what was
// last pushed. // last pushed.
// //
// `forColorClear` names the CALLER, and the only thing it changes is the colour write // `forColorClear` names the CALLER, and the only thing it changes is the color write
// mask handed to the driver. A draw into a colour attachment the backend widened from // mask handed to the driver. A draw into a color attachment the backend widened from
// three channels to four gets that buffer's alpha channel masked OFF, so nothing can // three channels to four gets that buffer's alpha channel masked OFF, so nothing can
// move the stored alpha away from the 1.0 the application's three-channel format // move the stored alpha away from the 1.0 the application's three-channel format
// implies (see FramebufferImpl::g_alphaWidenedDrawBufferMask). A CLEAR is how that 1.0 // implies (see FramebufferImpl::g_alphaWidenedDrawBufferMask). A CLEAR is how that 1.0
@@ -494,7 +494,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// followed by a draw re-pushes the mask instead of early-outing on an unchanged // followed by a draw re-pushes the mask instead of early-outing on an unchanged
// frontend version. // frontend version.
// //
// The application's own colour mask is never modified: glGet(GL_COLOR_WRITEMASK) // The application's own color mask is never modified: glGet(GL_COLOR_WRITEMASK)
// answers from the frontend state, which this function only reads. // answers from the frontend state, which this function only reads.
void SyncRenderState(Bool forColorClear = false); void SyncRenderState(Bool forColorClear = false);
void InvalidateSyncedRenderState(); void InvalidateSyncedRenderState();
@@ -516,7 +516,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} // namespace RenderStateImpl } // namespace RenderStateImpl
#if MOBILEGL_BUILD_DISAGGREGATED #if MOBILEGL_BUILD_DISAGGREGATED
// ---- SHARED IMAGES (docs/archive/notes/anland/plan-ahb-dmabuf.md) ---- // ---- SHARED IMAGES ----
// //
// A shared image reaches the driver only through the Android import path: its AHardwareBuffer // A shared image reaches the driver only through the Android import path: its AHardwareBuffer
// becomes an EGLImage (EGL_ANDROID_get_native_client_buffer + EGL_ANDROID_image_native_buffer) // becomes an EGLImage (EGL_ANDROID_get_native_client_buffer + EGL_ANDROID_image_native_buffer)
@@ -398,12 +398,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// PrepareForDraw turns into BindBackendVAOId(0) exactly as a null frontend VAO did; // PrepareForDraw turns into BindBackendVAOId(0) exactly as a null frontend VAO did;
// logging about it would fire on every draw of a session that legitimately has no // logging about it would fire on every draw of a session that legitimately has no
// vertex-elements CSO yet. // vertex-elements CSO yet.
void SyncCurrentVAOFromRecords(BackendVertexArrayObject* vaoTwin) { void SyncCurrentVAOFromRecords(BackendVertexArrayObject* vaoTwin, const IntegerAttributePlan& plan) {
#ifdef TRACY_ENABLE #ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND); ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif #endif
if (!vaoTwin) return; if (!vaoTwin) return;
vaoTwin->SyncToBackendFromApplier(); vaoTwin->SyncToBackendFromApplier(plan);
} }
// GL: a shader input whose generic attribute array is DISABLED reads that attribute's *current // GL: a shader input whose generic attribute array is DISABLED reads that attribute's *current
@@ -636,7 +636,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// BOUNDS-CHECKED, exactly as ProgramObject::GetAttribType is not: the frontend's // BOUNDS-CHECKED, exactly as ProgramObject::GetAttribType is not: the frontend's
// form indexes attribTypes raw because the mask it walked came off the same // form indexes attribTypes raw because the mask it walked came off the same
// artefacts instance, and here the two vectors arrived over a wire. A short tail // artifacts instance, and here the two vectors arrived over a wire. A short tail
// is an archive whose halves disagree; 0 classifies as Unsupported and says so // is an archive whose halves disagree; 0 classifies as Unsupported and says so
// below rather than reading past the end. // below rather than reading past the end.
const GLenum attribType = location < link.attribTypes.size() ? link.attribTypes[location] : 0; const GLenum attribType = location < link.attribTypes.size() ? link.attribTypes[location] : 0;
@@ -686,7 +686,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// declined to run it. // declined to run it.
// //
// P4a e3 (D-G4) leaves BOTH properties of this gate exactly as they are, and says so // P4a e3 (D-G4) leaves BOTH properties of this gate exactly as they are, and says so
// because they are the kind of thing a later optimisation deletes: // because they are the kind of thing a later optimization deletes:
// //
// 1. the g_imageUnitHighWaterMark == 0 early-out is what makes every Minecraft draw // 1. the g_imageUnitHighWaterMark == 0 early-out is what makes every Minecraft draw
// pay one integer test, and it is asked BEFORE anything reads a record; // pay one integer test, and it is asked BEFORE anything reads a record;
@@ -910,7 +910,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
BufferImpl::SyncNeccessaryBuffers(currentVAO, vaoTwin, vaoConfigVersion, syncBit & DrawSyncBit::IndexBuffer, BufferImpl::SyncNeccessaryBuffers(currentVAO, vaoTwin, vaoConfigVersion, syncBit & DrawSyncBit::IndexBuffer,
syncBit & DrawSyncBit::IndirectBuffer); syncBit & DrawSyncBit::IndirectBuffer);
VertexArrayImpl::SyncCurrentVAOFromRecords(vaoTwin); // The draw's integer arrays and the program's narrow-integer seeds (NarrowIntegerSeeds.h).
IntegerAttributePlan integerPlan = ResolveIntegerAttributePlanForDraw(MG_Pipe::MGPipeApplier().DrawProgram);
VertexArrayImpl::SyncCurrentVAOFromRecords(vaoTwin, integerPlan);
TextureImpl::SyncNeccessaryTextures(textureKeys); TextureImpl::SyncNeccessaryTextures(textureKeys);
// A draw reads and writes through its image units too, so the unit bindings have to be // A draw reads and writes through its image units too, so the unit bindings have to be
// as current as the sampled ones. Gated (see the sweep): a program with no image binding // as current as the sampled ones. Gated (see the sweep): a program with no image binding
@@ -927,7 +929,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// at all; the remaining uses of it below are the two callees' own, and both have a // at all; the remaining uses of it below are the two callees' own, and both have a
// handle arm of their own. P5e (pa) finished it: the pull itself is gone on that arm and // handle arm of their own. P5e (pa) finished it: the pull itself is gone on that arm and
// `currentProgram` is a null SharedPtr there, which this branch never reaches for. // `currentProgram` is a null SharedPtr there, which this branch never reaches for.
PrgramImpl::SyncCurrentProgramByHandle(MG_Pipe::MGPipeApplier().DrawProgram, true); PrgramImpl::SyncCurrentProgramByHandle(MG_Pipe::MGPipeApplier().DrawProgram, true, &integerPlan);
if (DropRefusedNarrowIntegerSeeds(integerPlan, MG_Pipe::MGPipeApplier().DrawProgram))
VertexArrayImpl::SyncCurrentVAOFromRecords(vaoTwin, integerPlan);
RenderStateImpl::SyncRenderState(); RenderStateImpl::SyncRenderState();
BindCurrentFBO(FramebufferTarget::Draw); BindCurrentFBO(FramebufferTarget::Draw);
@@ -951,6 +955,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
BindCurrentTextures(textureKeys, currentProgram); BindCurrentTextures(textureKeys, currentProgram);
BindCurrentProgramWithResources(currentProgram, textureKeys, MG_Pipe::MGPipeApplier().DrawProgram); BindCurrentProgramWithResources(currentProgram, textureKeys, MG_Pipe::MGPipeApplier().DrawProgram);
PushNarrowIntegerWidthsForDraw(integerPlan, MG_Pipe::MGPipeApplier().DrawProgram);
// Last: opening the capture span needs the program current and the capture // Last: opening the capture span needs the program current and the capture
// buffers bound, and ES rejects most binding changes once it is open. // buffers bound, and ES rejects most binding changes once it is open.
@@ -1705,7 +1710,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The last sample a usampler2DMS fetch may name, from the texture // The last sample a usampler2DMS fetch may name, from the texture
// actually bound: a stencil texture's real count is not an integer // actually bound: a stencil texture's real count is not an integer
// colour texture's. Keyed like the bias above - a texture's storage or // color texture's. Keyed like the bias above - a texture's storage or
// a unit's binding moving is what invalidates this pass's memo. // a unit's binding moving is what invalidates this pass's memo.
if (samplerBinding.msSampleMaxLocation >= 0) { if (samplerBinding.msSampleMaxLocation >= 0) {
const Int sampleMax = const Int sampleMax =
@@ -107,12 +107,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
FramebufferImpl::InvalidateSyncedFramebufferSerial(target); FramebufferImpl::InvalidateSyncedFramebufferSerial(target);
} }
// Sized internal format of the currently bound READ framebuffer's read colour // Sized internal format of the currently bound READ framebuffer's read color
// attachment, 0 when it cannot be determined. // attachment, 0 when it cannot be determined.
static GLenum QueryReadColorAttachmentInternalFormat() { static GLenum QueryReadColorAttachmentInternalFormat() {
GLint attachmentType = 0; GLint attachmentType = 0;
GLint attachmentName = 0; GLint attachmentName = 0;
// Ask the point the backend read buffer actually names, not COLOR_ATTACHMENT0. The colour map // Ask the point the backend read buffer actually names, not COLOR_ATTACHMENT0. The color map
// in BackendFramebufferObject is a permutation, so the read attachment's image only sits at // in BackendFramebufferObject is a permutation, so the read attachment's image only sits at
// CA0 when that map is identity; querying CA0 unconditionally would size the resolve // CA0 when that map is identity; querying CA0 unconditionally would size the resolve
// renderbuffer from a different attachment's format and either convert wrongly or fail the // renderbuffer from a different attachment's format and either convert wrongly or fail the
@@ -561,7 +561,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The guard already left the pipeline neutral (nothing culled, tested, blended or // The guard already left the pipeline neutral (nothing culled, tested, blended or
// coverage-masked) on the borrowed texture unit; what is left is this pass's own // coverage-masked) on the borrowed texture unit; what is left is this pass's own
// choices - the destination rectangle, and colour writes off because it writes only // choices - the destination rectangle, and color writes off because it writes only
// depth and stencil. // depth and stencil.
VertexArrayImpl::BindBackendVAOId(s_vertexArray); VertexArrayImpl::BindBackendVAOId(s_vertexArray);
g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, s_texture); g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, s_texture);
@@ -626,7 +626,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// into a scratch framebuffer of the source's own native format, then run the caller's blit // into a scratch framebuffer of the source's own native format, then run the caller's blit
// - same mask, same filter, same orientation - from the scratch into the real destination. // - same mask, same filter, same orientation - from the scratch into the real destination.
// Overlapping rectangles stay undefined, as GL leaves them. A LINEAR blit that scales reads // Overlapping rectangles stay undefined, as GL leaves them. A LINEAR blit that scales reads
// clamped texels at the rectangle's edge where GL would read the neighbouring ones. // clamped texels at the rectangle's edge where GL would read the neighboring ones.
// What a framebuffer attachment point resolves to on the DRIVER side: the native object and, // What a framebuffer attachment point resolves to on the DRIVER side: the native object and,
// for a texture, the level, layer and cube face. A null name means the point is empty. // for a texture, the level, layer and cube face. A null name means the point is empty.
@@ -661,7 +661,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
a.Face == b.Face; a.Face == b.Face;
} }
// The colour attachment point the bound READ framebuffer reads from, GL_NONE when it reads // The color attachment point the bound READ framebuffer reads from, GL_NONE when it reads
// nothing. // nothing.
static GLenum DriverReadColorAttachment() { static GLenum DriverReadColorAttachment() {
GLint readBuffer = GL_NONE; GLint readBuffer = GL_NONE;
@@ -878,7 +878,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previousDraw); g_GLESFuncs.glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previousDraw);
g_GLESFuncs.glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &previousRead); g_GLESFuncs.glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &previousRead);
// Every point is (re)attached on every call: the scratch is shared between colour and // Every point is (re)attached on every call: the scratch is shared between color and
// depth/stencil shapes, and a stale attachment would make it incomplete or blit an // depth/stencil shapes, and a stale attachment would make it incomplete or blit an
// aspect the source does not have. // aspect the source does not have.
g_GLESFuncs.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, s_scratchFramebuffer); g_GLESFuncs.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, s_scratchFramebuffer);
@@ -934,7 +934,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return; return;
} }
// Two ES restrictions desktop GL does not have are worth a second attempt: a // Two ES restrictions desktop GL does not have are worth a second attempt: a
// multisample resolve that also converts colour format, and any blit into a // multisample resolve that also converts color format, and any blit into a
// multisample draw framebuffer. Both need to know how the two sides are sampled. // multisample draw framebuffer. Both need to know how the two sides are sampled.
GLint readSamples = 0; GLint readSamples = 0;
GLint drawSamples = 0; GLint drawSamples = 0;
@@ -968,32 +968,32 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (ReplicateBlitIntoMultisampleDraw(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask)) { if (ReplicateBlitIntoMultisampleDraw(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask)) {
if ((mask & GL_COLOR_BUFFER_BIT) != 0) { if ((mask & GL_COLOR_BUFFER_BIT) != 0) {
MGLOG_E_ONCE( MGLOG_E_ONCE(
"BlitFramebuffer: colour replicate into a multisample draw framebuffer is not emulated"); "BlitFramebuffer: color replicate into a multisample draw framebuffer is not emulated");
} }
} }
return; return;
} }
// The combined call raised an error, so by GL 4.6 2.3.1 it wrote nothing at all: BOTH // The combined call raised an error, so by GL 4.6 2.3.1 it wrote nothing at all: BOTH
// aspect groups still owe their copy, and each has to be retried on its own. Re-issuing // aspect groups still owe their copy, and each has to be retried on its own. Re-issuing
// the depth/stencil half only as a rider on a SUCCESSFUL colour resolve dropped it // the depth/stencil half only as a rider on a SUCCESSFUL color resolve dropped it
// silently whenever the colour half could not be emulated - and on a framebuffer whose // silently whenever the color half could not be emulated - and on a framebuffer whose
// only attachment is depth it never can, because the colour emulation has no attachment // only attachment is depth it never can, because the color emulation has no attachment
// to take a format from (KHR-GL33.framebuffer_blit's depth config test blits // to take a format from (KHR-GL33.framebuffer_blit's depth config test blits
// COLOR|DEPTH|STENCIL across depth-only framebuffers and kept reading the clear value). // COLOR|DEPTH|STENCIL across depth-only framebuffers and kept reading the clear value).
const GLbitfield colourBit = mask & static_cast<GLbitfield>(GL_COLOR_BUFFER_BIT); const GLbitfield colorBit = mask & static_cast<GLbitfield>(GL_COLOR_BUFFER_BIT);
const GLbitfield dsBits = mask & static_cast<GLbitfield>(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); const GLbitfield dsBits = mask & static_cast<GLbitfield>(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// The colour group's one emulation is the multisample resolve that also converts format, // The color group's one emulation is the multisample resolve that also converts format,
// which is the shape this names. It used to double as an early-out for the whole // which is the shape this names. It used to double as an early-out for the whole
// function, which is what cost a depth-only mask its single-aspect retry. // function, which is what cost a depth-only mask its single-aspect retry.
const Bool multisampleResolve = readSamples > 0 && drawSamples <= 0; const Bool multisampleResolve = readSamples > 0 && drawSamples <= 0;
if (colourBit != 0) { if (colorBit != 0) {
DrainBlitErrors(); DrainBlitErrors();
g_GLESFuncs.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, colourBit, filter); g_GLESFuncs.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, colorBit, filter);
if (g_GLESFuncs.glGetError() != GL_NO_ERROR) { if (g_GLESFuncs.glGetError() != GL_NO_ERROR) {
const Bool emulated = multisampleResolve && const Bool emulated = multisampleResolve &&
ResolveThenBlit(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, filter); ResolveThenBlit(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, filter);
if (!emulated) { if (!emulated) {
MGLOG_E_ONCE("BlitFramebuffer: the colour aspect was dropped - the driver rejected it on its " MGLOG_E_ONCE("BlitFramebuffer: the color aspect was dropped - the driver rejected it on its "
"own and no emulation applies"); "own and no emulation applies");
} }
} }
@@ -1002,7 +1002,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
DrainBlitErrors(); DrainBlitErrors();
g_GLESFuncs.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, dsBits, filter); g_GLESFuncs.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, dsBits, filter);
if (g_GLESFuncs.glGetError() != GL_NO_ERROR) { if (g_GLESFuncs.glGetError() != GL_NO_ERROR) {
// Nothing to fall back on yet: ResolveThenBlit is colour-only and the replicate // Nothing to fall back on yet: ResolveThenBlit is color-only and the replicate
// pass runs in the opposite direction, so a driver that declines a multisample // pass runs in the opposite direction, so a driver that declines a multisample
// depth/stencil resolve leaves the destination holding its clear value. The log // depth/stencil resolve leaves the destination holding its clear value. The log
// is the whole diagnostic - the frontend performs no validation of its own, so // is the whole diagnostic - the frontend performs no validation of its own, so
@@ -1283,7 +1283,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}); });
MGLOG_D("ES %s(%d, %d, %d, %d, %d, %d, %d, %d, 0x%x, %s)", __func__, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, MGLOG_D("ES %s(%d, %d, %d, %d, %d, %d, %d, %d, 0x%x, %s)", __func__, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0,
dstX1, dstY1, mask, MG_Util::ConvertGLEnumToString(filter).c_str()); dstX1, dstY1, mask, MG_Util::ConvertGLEnumToString(filter).c_str());
// A no-op on every driver that honours a non-zero destination array layer, which is all // A no-op on every driver that honors a non-zero destination array layer, which is all
// of them but the probed one. Whatever it performs itself is taken out of the mask. // of them but the probed one. Whatever it performs itself is taken out of the mask.
// P5e (fb, §5.4): the bound arm's two binding-slot reads were the last GetBoundObject // P5e (fb, §5.4): the bound arm's two binding-slot reads were the last GetBoundObject
// pair on this path; under a transport the two bound RECORDS answer instead // pair on this path; under a transport the two bound RECORDS answer instead
@@ -367,9 +367,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (g_GLESFuncs.glMapBufferRange == nullptr || g_GLESFuncs.glUnmapBuffer == nullptr) return; if (g_GLESFuncs.glMapBufferRange == nullptr || g_GLESFuncs.glUnmapBuffer == nullptr) return;
const SizeT packedStride = program->GetTransformFeedbackPackedStride(); const SizeT packedStride = program->GetTransformFeedbackPackedStride();
const SizeT modelledVertices = const SizeT modeledVertices =
static_cast<SizeT>(MG_Pipe::gPipeInputs->GetTransformFeedbackCapturedVertices()); static_cast<SizeT>(MG_Pipe::gPipeInputs->GetTransformFeedbackCapturedVertices());
const SizeT vertices = std::min<SizeT>(modelledVertices, xfb.scatterCapacityVertices); const SizeT vertices = std::min<SizeT>(modeledVertices, xfb.scatterCapacityVertices);
if (packedStride == 0 || vertices == 0) { if (packedStride == 0 || vertices == 0) {
// The scatter path redirected the DRIVER's capture into the scratch buffer, // The scatter path redirected the DRIVER's capture into the scratch buffer,
// so bailing here leaves the application's buffers holding their pre-draw // so bailing here leaves the application's buffers holding their pre-draw
@@ -378,9 +378,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// CountPrimitivesForDraw does not know and for the instanced/indirect entry // CountPrimitivesForDraw does not know and for the instanced/indirect entry
// points that never call it. // points that never call it.
MGLOG_E_ONCE("EndTransformFeedback: scattered capture discarded - packedStride=%zu, " MGLOG_E_ONCE("EndTransformFeedback: scattered capture discarded - packedStride=%zu, "
"CPU-modelled captured vertices=%zu, scratch capacity=%zu. The capture buffers keep " "CPU-modeled captured vertices=%zu, scratch capacity=%zu. The capture buffers keep "
"their pre-draw contents.", "their pre-draw contents.",
packedStride, modelledVertices, xfb.scatterCapacityVertices); packedStride, modeledVertices, xfb.scatterCapacityVertices);
return; return;
} }
@@ -431,7 +431,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// (glBufferData(size, NULL) with no resource_subdata since), which // (glBufferData(size, NULL) with no resource_subdata since), which
// M-3 declares legal: every byte the application has not staged is // M-3 declares legal: every byte the application has not staged is
// UNDEFINED by its own declaration, and `staged` is already // UNDEFINED by its own declaration, and `staged` is already
// value-initialised to zero - which is byte for byte what the monolith // value-initialized to zero - which is byte for byte what the monolith
// arm scatters over when it reads a fresh MappedData(). Skipping the // arm scatters over when it reads a fresh MappedData(). Skipping the
// Memcpy is therefore the WHOLE of the difference; skipping the TARGET // Memcpy is therefore the WHOLE of the difference; skipping the TARGET
// threw the varyings away and left the application's buffer holding its // threw the varyings away and left the application's buffer holding its
@@ -669,7 +669,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The frontend VAO is taken ONLY when the applier's own vertex-buffer window says a // The frontend VAO is taken ONLY when the applier's own vertex-buffer window says a
// client-sourced attribute exists, which is what retires the per-draw // client-sourced attribute exists, which is what retires the per-draw
// GetBoundVertexArray row of the ordinary (VBO-backed) DrawArrays - the strict lane's // GetBoundVertexArray row of the ordinary (VBO-backed) DrawArrays - the strict lane's
// `GetBoundVertexArray@DrawArrays` marker - without touching this path's behaviour. // `GetBoundVertexArray@DrawArrays` marker - without touching this path's behavior.
// `static` on purpose (G1): the pull build must not gain an exported name for a // `static` on purpose (G1): the pull build must not gain an exported name for a
// function that only re-homes two identical inline blocks the two DrawArrays entry // function that only re-homes two identical inline blocks the two DrawArrays entry
// points used to carry. The same spelling governs the three below, and for a second // points used to carry. The same spelling governs the three below, and for a second
@@ -343,14 +343,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
// default framebuffer is answered out of that guess, and being wrong is not cosmetic: GL // default framebuffer is answered out of that guess, and being wrong is not cosmetic: GL
// blits depth/stencil only between IDENTICAL formats, so a caller that reads // blits depth/stencil only between IDENTICAL formats, so a caller that reads
// GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, allocates the buffer it was told about and blits // GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, allocates the buffer it was told about and blits
// gets GL_INVALID_OPERATION and a silently dropped blit - colour bits included, because a // gets GL_INVALID_OPERATION and a silently dropped blit - color bits included, because a
// rejected glBlitFramebuffer transfers nothing at all. DirectVulkan already publishes its // rejected glBlitFramebuffer transfers nothing at all. DirectVulkan already publishes its
// real format when it creates the swapchain (SwapchainObject::Create); this is the // real format when it creates the swapchain (SwapchainObject::Create); this is the
// DirectGLES half, and here the answer can simply be asked of the ES default framebuffer. // DirectGLES half, and here the answer can simply be asked of the ES default framebuffer.
// //
// Only the format is published. The placeholder's 512x512 extent is left alone: all three // Only the format is published. The placeholder's 512x512 extent is left alone: all three
// attachments share it, and FramebufferObject::CheckCompleteness requires them to agree, so // attachments share it, and FramebufferObject::CheckCompleteness requires them to agree, so
// resizing depth/stencil without colour would report the default framebuffer incomplete. // resizing depth/stencil without color would report the default framebuffer incomplete.
static void PublishDefaultFramebufferDepthStencilFormat() { static void PublishDefaultFramebufferDepthStencilFormat() {
auto& defaultFBOInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo; auto& defaultFBOInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
// P8-SE: THE PLACEHOLDER IS ONLY THE MONOLITH ARM'S TARGET. With a transport the answer is // P8-SE: THE PLACEHOLDER IS ONLY THE MONOLITH ARM'S TARGET. With a transport the answer is
@@ -358,7 +358,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// server process has no frontend at all, so pDefaultFramebufferInfo is null there. Gating on // server process has no frontend at all, so pDefaultFramebufferInfo is null there. Gating on
// it returned before the post, the client kept Init.cpp's GL_DEPTH32F_STENCIL8 guess for a // it returned before the post, the client kept Init.cpp's GL_DEPTH32F_STENCIL8 guess for a
// D24S8 pbuffer, and a blit into a buffer allocated from that guess lost depth and stencil // D24S8 pbuffer, and a blit into a buffer allocated from that guess lost depth and stencil
// (docs/archive/notes/p8/SE.md). Inproc only passed because client and server share the process. // Inproc only passed because client and server share the process.
if (!g_GLESFuncs.glGetFramebufferAttachmentParameteriv) return; if (!g_GLESFuncs.glGetFramebufferAttachmentParameteriv) return;
if (!defaultFBOInfo && MG_Pipe::gMGPipeCallbacks.OnSurfaceChanged == nullptr) return; if (!defaultFBOInfo && MG_Pipe::gMGPipeCallbacks.OnSurfaceChanged == nullptr) return;
@@ -1550,7 +1550,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false; return false;
} }
// Clearing the global owner works from ANY thread (a release request can // Clearing the global owner works from ANY thread (a release request can
// legally arrive on a thread other than the current owner); erring towards // legally arrive on a thread other than the current owner); erring toward
// "not current" only defers buffer ops, which is always safe. // "not current" only defers buffer ops, which is always safe.
InvalidateEglVerifiedStamp(); InvalidateEglVerifiedStamp();
t_native.boundContext = EGL_NO_CONTEXT; t_native.boundContext = EGL_NO_CONTEXT;
@@ -982,7 +982,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P8-B2: RGB16F / RGB32F MIP GENERATION ON THE SERVER, FROM THE SERVER'S OWN LEVEL. // P8-B2: RGB16F / RGB32F MIP GENERATION ON THE SERVER, FROM THE SERVER'S OWN LEVEL.
// //
// ES generates a chain only for a colour-renderable, filterable format, and whether the // ES generates a chain only for a color-renderable, filterable format, and whether the
// driver's storage for these two is one depends on the driver: Espryt stores them natively // driver's storage for these two is one depends on the driver: Espryt stores them natively
// where the driver renders the three-channel form and widens them to RGBA16F / RGBA32F where // where the driver renders the three-channel form and widens them to RGBA16F / RGBA32F where
// it does not (TextureImpl::BackendTextureFormatAddsAlpha), and a widened RGBA32F still needs // it does not (TextureImpl::BackendTextureFormatAddsAlpha), and a widened RGBA32F still needs
@@ -1249,7 +1249,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
DebugImpl::OpenGLScopeMarker marker(__func__); DebugImpl::OpenGLScopeMarker marker(__func__);
#endif #endif
// P5e (tx2), CONTRACT-P5E §5.2 / ruling 14 (ID-92's neighbour): GENERATE_MIPMAP RESOLVES // P5e (tx2), CONTRACT-P5E §5.2 / ruling 14 (ID-92's neighbor): GENERATE_MIPMAP RESOLVES
// ITS TEXTURE FROM VerbMipRes, NEVER FROM THE ACTIVE UNIT. // ITS TEXTURE FROM VerbMipRes, NEVER FROM THE ACTIVE UNIT.
// //
// The verb's own record names the texture (MGPMipPlan::Res, stashed as VerbMipRes) and // The verb's own record names the texture (MGPMipPlan::Res, stashed as VerbMipRes) and
@@ -1458,7 +1458,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// format, not only the redundant-encoding ones this mirror serves, and marks the store // format, not only the redundant-encoding ones this mirror serves, and marks the store
// driver-written where it cannot. The store is what answers a split glGetTexImage the // driver-written where it cannot. The store is what answers a split glGetTexImage the
// driver refuses to read back (DirectGLESReadback.cpp's store route); every other split // driver refuses to read back (DirectGLESReadback.cpp's store route); every other split
// read of the destination reads the GPU level itself (docs/archive/notes/p8/E.md). // read of the destination reads the GPU level itself.
// //
// BEHIND #if MOBILEGL_BUILD_DISAGGREGATED so the pull build's code does not move (G1), // BEHIND #if MOBILEGL_BUILD_DISAGGREGATED so the pull build's code does not move (G1),
// and the arm is the TRANSPORT and not the build - build-split runs its unit and // and the arm is the TRANSPORT and not the build - build-split runs its unit and
@@ -1491,7 +1491,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_E_ONCE("%s: source or destination image failed to sync; declining the copy", __func__); MGLOG_E_ONCE("%s: source or destination image failed to sync; declining the copy", __func__);
return; return;
} }
// P8-E (docs/archive/notes/p8/E.md): THE SERVER'S STAGED STORE FOLLOWS THE COPY. Both endpoints are synced, // P8-E: THE SERVER'S STAGED STORE FOLLOWS THE COPY. Both endpoints are synced,
// so every byte the store holds for them is on the driver, and the copy below is a raw // so every byte the store holds for them is on the driver, and the copy below is a raw
// texel-block move - the store makes the same move, which keeps the destination level // texel-block move - the store makes the same move, which keeps the destination level
// answerable from here when the driver will not read it back (ReadTextureImageWire's store // answerable from here when the driver will not read it back (ReadTextureImageWire's store
@@ -1634,7 +1634,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The frontend has already recorded the rebinding on the program object // The frontend has already recorded the rebinding on the program object
// (SetShaderStorageBlockBinding) - that record is what GL_BUFFER_BINDING reports and what // (SetShaderStorageBlockBinding) - that record is what GL_BUFFER_BINDING reports and what
// BackendProgramObjectImpl::SyncToBackend replays onto every driver program it builds. So // BackendProgramObjectImpl::SyncToBackend replays onto every driver program it builds. So
// the only work left here is an optimisation: push the change straight onto a driver // the only work left here is an optimization: push the change straight onto a driver
// program that is ALREADY built and already current with this link, so the next draw does // program that is ALREADY built and already current with this link, so the next draw does
// not have to be preceded by a rebuild. // not have to be preceded by a rebuild.
// //
@@ -1667,7 +1667,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
namespace { namespace {
// A colour attachment the backend widened from three channels to four has to end up // A color attachment the backend widened from three channels to four has to end up
// holding alpha 1.0 - the value GL reports for a channel the application's format does // holding alpha 1.0 - the value GL reports for a channel the application's format does
// not have - so an explicit per-buffer clear of it writes 1.0 rather than whatever the // not have - so an explicit per-buffer clear of it writes 1.0 rather than whatever the
// application passed (SubstituteWidenedClearAlpha, in Managers.h). Draws can never move // application passed (SubstituteWidenedClearAlpha, in Managers.h). Draws can never move
@@ -59,7 +59,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P5e (mv): THE ARM SELECTOR of this family - VertexInputReadsRecords() - moved to // P5e (mv): THE ARM SELECTOR of this family - VertexInputReadsRecords() - moved to
// Managers.h, unchanged. MultiDraw.cpp is a second translation unit on the same draw // Managers.h, unchanged. MultiDraw.cpp is a second translation unit on the same draw
// path and has to select the same arm; a copy of the conjunction there would be two // path and has to select the same arm; a copy of the conjunction there would be two
// selectors for one family. Its rationale travelled with it. // selectors for one family. Its rationale traveled with it.
// P5e (vi): "does this draw fetch any attribute out of the application's own memory", // P5e (vi): "does this draw fetch any attribute out of the application's own memory",
// answered FROM THE RECORD instead of from the frontend VAO. The emitter publishes // answered FROM THE RECORD instead of from the frontend VAO. The emitter publishes
@@ -238,7 +238,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// feedback records per emitted primitive, so a replayed draw would write its vertices N // feedback records per emitted primitive, so a replayed draw would write its vertices N
// times; rasterizer discard means there are no fragments to gate at all, so replaying // times; rasterizer discard means there are no fragments to gate at all, so replaying
// would be pure cost with nothing to show for it. Both fall back to a single pass with an // would be pure cost with nothing to show for it. Both fall back to a single pass with an
// open gate, i.e. to the pre-emulation behaviour, rather than to wrong data. // open gate, i.e. to the pre-emulation behavior, rather than to wrong data.
if (MG_Pipe::gPipeInputs->IsTransformFeedbackActive() || if (MG_Pipe::gPipeInputs->IsTransformFeedbackActive() ||
MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard)) { MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard)) {
return 1; return 1;
@@ -521,7 +521,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
#endif #endif
TextureImpl::SyncNeccessaryTextures(); TextureImpl::SyncNeccessaryTextures();
FramebufferImpl::SyncCurrentFBO(); FramebufferImpl::SyncCurrentFBO();
// A colour clear is exactly the operation that is allowed to write a widened // A color clear is exactly the operation that is allowed to write a widened
// attachment's alpha - it is what puts the 1.0 there that every later draw is masked // attachment's alpha - it is what puts the 1.0 there that every later draw is masked
// away from. SyncCurrentFBO ran first, so g_alphaWidenedDrawBufferMask already describes // away from. SyncCurrentFBO ran first, so g_alphaWidenedDrawBufferMask already describes
// the framebuffer this clear will land on. // the framebuffer this clear will land on.
@@ -565,17 +565,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Bool outOfRange = cc.x() < 0.f || cc.x() > 1.f || cc.y() < 0.f || cc.y() > 1.f || cc.z() < 0.f || const Bool outOfRange = cc.x() < 0.f || cc.x() > 1.f || cc.y() < 0.f || cc.y() > 1.f || cc.z() < 0.f ||
cc.z() > 1.f || cc.w() < 0.f || cc.w() > 1.f; cc.z() > 1.f || cc.w() < 0.f || cc.w() > 1.f;
// A widened attachment's stored alpha has to end up 1.0, and glClear applies ONE // A widened attachment's stored alpha has to end up 1.0, and glClear applies ONE
// clear colour to every draw buffer - so a framebuffer that mixes a widened // clear color to every draw buffer - so a framebuffer that mixes a widened
// attachment with a native one cannot be served by doctoring glClearColor. Take the // attachment with a native one cannot be served by doctoring glClearColor. Take the
// same per-draw-buffer glClearBufferfv route the out-of-range case already uses and // same per-draw-buffer glClearBufferfv route the out-of-range case already uses and
// substitute the alpha only where it belongs. Scissor and the colour write mask apply // substitute the alpha only where it belongs. Scissor and the color write mask apply
// to glClearBufferfv exactly as they do to glClear, so a scissored clear stays // to glClearBufferfv exactly as they do to glClear, so a scissored clear stays
// scissored and an application that masked alpha off still gets its way (the storage // scissored and an application that masked alpha off still gets its way (the storage
// then keeps the 1.0 an earlier clear left, which is the same answer). // then keeps the 1.0 an earlier clear left, which is the same answer).
// //
// glClearBufferfv on an INTEGER colour buffer is GL_INVALID_OPERATION, so a // glClearBufferfv on an INTEGER color buffer is GL_INVALID_OPERATION, so a
// framebuffer with one of those as a draw buffer keeps plain glClear - which ES // framebuffer with one of those as a draw buffer keeps plain glClear - which ES
// leaves undefined for integer colour buffers anyway, and which an application that // leaves undefined for integer color buffers anyway, and which an application that
// wants a defined answer must replace with glClearBufferuiv/iv (those DO substitute // wants a defined answer must replace with glClearBufferuiv/iv (those DO substitute
// the widened alpha). The out-of-range trigger is left exactly as it was. // the widened alpha). The out-of-range trigger is left exactly as it was.
const Uint32 widenedDrawBuffers = FramebufferImpl::g_alphaWidenedDrawBufferMask; const Uint32 widenedDrawBuffers = FramebufferImpl::g_alphaWidenedDrawBufferMask;
@@ -387,7 +387,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
const MG_Pipe::MGPipeShaderCsoRecord* ResolveGlobalConstantsRecordForHandle(MG_Pipe::MGPipeHandle handle, const MG_Pipe::MGPipeShaderCsoRecord* ResolveGlobalConstantsRecordForHandle(MG_Pipe::MGPipeHandle handle,
Uint uboSize); Uint uboSize);
void SyncCurrentProgramByHandle(MG_Pipe::MGPipeHandle cso, Bool forDraw); // `plan` (a draw's) arms the program's narrow-integer seeds when it needs them.
void SyncCurrentProgramByHandle(MG_Pipe::MGPipeHandle cso, Bool forDraw,
const IntegerAttributePlan* plan = nullptr);
} // namespace PrgramImpl } // namespace PrgramImpl
// Defined with the surface registry, below. // Defined with the surface registry, below.
@@ -609,9 +611,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
SamplerImpl::UnbindSampler(TextureImpl::TempTextureUnit); SamplerImpl::UnbindSampler(TextureImpl::TempTextureUnit);
// The neutral baseline every emulation pass wants: nothing culled, nothing // The neutral baseline every emulation pass wants: nothing culled, nothing
// clipped, nothing tested, no coverage games, and colour writes open. Callers // clipped, nothing tested, no coverage games, and color writes open. Callers
// turn back on only what they need (the replicate pass wants the depth and // turn back on only what they need (the replicate pass wants the depth and
// stencil tests, and masks colour off because it writes neither). // stencil tests, and masks color off because it writes neither).
for (Uint i = 0; i < m_capabilityCount; ++i) { for (Uint i = 0; i < m_capabilityCount; ++i) {
g_GLESFuncs.glDisable(m_capabilities[i].cap); g_GLESFuncs.glDisable(m_capabilities[i].cap);
} }
@@ -650,7 +652,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glDisable(m_capabilities[i].cap); g_GLESFuncs.glDisable(m_capabilities[i].cap);
} }
} }
// The per-draw-buffer colour masks are not covered by the non-indexed // The per-draw-buffer color masks are not covered by the non-indexed
// glColorMask above. Restore what the SYNC actually pushed, not the raw // glColorMask above. Restore what the SYNC actually pushed, not the raw
// application masks: a widened attachment's alpha write is forced off by // application masks: a widened attachment's alpha write is forced off by
// SyncRenderState and memoized in g_syncedColorMaskAlphaWidenMask, and the // SyncRenderState and memoized in g_syncedColorMaskAlphaWidenMask, and the
@@ -884,7 +886,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
// DirectGLESReadback.cpp defines it: E's extent-scoped stand-in for a // DirectGLESReadback.cpp defines it: E's extent-scoped stand-in for a
// driver that refuses to read a colour level back, asked here too so a host reaches the store // driver that refuses to read a color level back, asked here too so a host reaches the store
// route below (P8-SE). // route below (P8-SE).
Bool WireTextureReadbackRefusedForTesting(const IntVec3& extent); Bool WireTextureReadbackRefusedForTesting(const IntVec3& extent);
using FramebufferImpl::SubstituteWidenedClearAlpha; using FramebufferImpl::SubstituteWidenedClearAlpha;
@@ -111,10 +111,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// it on the way). ES rejects a depth/stencil blit between differing formats, so // it on the way). ES rejects a depth/stencil blit between differing formats, so
// the scratch has to match the source exactly; see DescribeReadDepthStencilSource. // the scratch has to match the source exactly; see DescribeReadDepthStencilSource.
// 2. Convert. Draw a full-screen triangle that samples the staged texture into a // 2. Convert. Draw a full-screen triangle that samples the staged texture into a
// scratch R32UI colour target: depth as floatBitsToUint (bit-exact for every depth // scratch R32UI color target: depth as floatBitsToUint (bit-exact for every depth
// format, and an integer colour target needs no float-renderable extension), // format, and an integer color target needs no float-renderable extension),
// stencil through GL_DEPTH_STENCIL_TEXTURE_MODE = GL_STENCIL_INDEX. // stencil through GL_DEPTH_STENCIL_TEXTURE_MODE = GL_STENCIL_INDEX.
// 3. Read. glReadPixels the colour target with GL_RGBA_INTEGER / GL_UNSIGNED_INT - // 3. Read. glReadPixels the color target with GL_RGBA_INTEGER / GL_UNSIGNED_INT -
// the pair ES guarantees for an unsigned-integer attachment - and hand the values // the pair ES guarantees for an unsigned-integer attachment - and hand the values
// to the existing re-encoders, which already own the client-side (format, type) // to the existing re-encoders, which already own the client-side (format, type)
// layout and the PACK pixel-store parameters. // layout and the PACK pixel-store parameters.
@@ -407,7 +407,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (s_colorTexture == 0) return false; if (s_colorTexture == 0) return false;
g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, s_colorTexture); g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, s_colorTexture);
ClearGLErrors(); ClearGLErrors();
// R32UI is colour-renderable in ES 3.0 core - no float-renderability extension // R32UI is color-renderable in ES 3.0 core - no float-renderability extension
// needed - and carries a depth bit pattern or a stencil index without loss. // needed - and carries a depth bit pattern or a stencil index without loss.
g_GLESFuncs.glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, s_colorWidth, s_colorHeight); g_GLESFuncs.glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, s_colorWidth, s_colorHeight);
const Bool ok = g_GLESFuncs.glGetError() == GL_NO_ERROR; const Bool ok = g_GLESFuncs.glGetError() == GL_NO_ERROR;
@@ -458,7 +458,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false; return false;
} }
// Runs one conversion pass over a staged slot and reads its colour target back. // Runs one conversion pass over a staged slot and reads its color target back.
// Requires the slot's texture bound on the borrowed unit and s_colorFramebuffer // Requires the slot's texture bound on the borrowed unit and s_colorFramebuffer
// bound as DRAW. // bound as DRAW.
static Bool ConvertAndRead(const StageSlot& slot, Bool stencilAspect, GLsizei width, GLsizei height, static Bool ConvertAndRead(const StageSlot& slot, Bool stencilAspect, GLsizei width, GLsizei height,
@@ -625,7 +625,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Neither native spelling exists on this driver (which is the ordinary case: ES has // Neither native spelling exists on this driver (which is the ordinary case: ES has
// no depth readback in core and GL_NV_read_depth is rare), so sample the attachment // no depth readback in core and GL_NV_read_depth is rare), so sample the attachment
// instead. The scoped pack state above is irrelevant to that path - it reads its own // instead. The scoped pack state above is irrelevant to that path - it reads its own
// scratch colour target - but harmless, and leaving it in place keeps the restore in // scratch color target - but harmless, and leaving it in place keeps the restore in
// one place. // one place.
if (DepthStencilSamplingReadImpl::Read(x, y, width, height, &outDepth, /*outStencil=*/nullptr)) { if (DepthStencilSamplingReadImpl::Read(x, y, width, height, &outDepth, /*outStencil=*/nullptr)) {
return true; return true;
@@ -636,7 +636,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false; return false;
} }
// GL_DEPTH_COMPONENT readback into the client's layout, honouring the PACK pixel-store // GL_DEPTH_COMPONENT readback into the client's layout, honoring the PACK pixel-store
// parameters. GL 4.6 core 18.2.8 table 8.6: the normalized depth is written as-is for the // parameters. GL 4.6 core 18.2.8 table 8.6: the normalized depth is written as-is for the
// floating-point client types and scaled into the full range of whichever integer width the // floating-point client types and scaled into the full range of whichever integer width the
// client asked for otherwise - 2^n - 1 for an unsigned width, 2^(n-1) - 1 for a signed one. // client asked for otherwise - 2^n - 1 for an unsigned width, 2^(n-1) - 1 for a signed one.
@@ -764,7 +764,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false; return false;
} }
// GL_STENCIL_INDEX readback into the client's integer layout, honouring the PACK // GL_STENCIL_INDEX readback into the client's integer layout, honoring the PACK
// pixel-store parameters. Handles the widths desktop clients ask for; the stencil // pixel-store parameters. Handles the widths desktop clients ask for; the stencil
// values themselves are always 8 bits. // values themselves are always 8 bits.
static Bool ReadPixelsStencilViaNative(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type, void* pixels) { static Bool ReadPixelsStencilViaNative(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type, void* pixels) {
@@ -964,7 +964,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
// Overwrites the alpha of a 4-channel wide read with the format's implied 1.0. Used for an // Overwrites the alpha of a 4-channel wide read with the format's implied 1.0. Used for an
// attachment the backend widened from three channels to keep it colour-renderable: the storage // attachment the backend widened from three channels to keep it color-renderable: the storage
// has a real alpha channel holding whatever the draw wrote, but the format the application // has a real alpha channel holding whatever the draw wrote, but the format the application
// asked for has none, and GL reads a missing channel back as one. // asked for has none, and GL reads a missing channel back as one.
static void ForceWideReadAlphaToOne(Vector<Uint8>& data, SizeT pixelCount, GLenum componentType) { static void ForceWideReadAlphaToOne(Vector<Uint8>& data, SizeT pixelCount, GLenum componentType) {
@@ -1033,7 +1033,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// (format, type) layout. Returns false when the combination is not convertible (the caller keeps its // (format, type) layout. Returns false when the combination is not convertible (the caller keeps its
// "not implemented" skip); returns true when the request was handled, even if it degraded to a logged no-op. // "not implemented" skip); returns true when the request was handled, even if it degraded to a logged no-op.
// `forceOpaqueAlpha`: the source image is a three-channel format the backend widened to four to // `forceOpaqueAlpha`: the source image is a three-channel format the backend widened to four to
// keep it colour-renderable, so its alpha channel holds whatever the draw wrote and has to be // keep it color-renderable, so its alpha channel holds whatever the draw wrote and has to be
// answered with the 1.0 the application's format implies. Passed in rather than derived here: // answered with the 1.0 the application's format implies. Passed in rather than derived here:
// glReadPixels reads the bound READ framebuffer, but glGetTexImage reads a texture through a // glReadPixels reads the bound READ framebuffer, but glGetTexImage reads a texture through a
// scratch framebuffer, so the frontend's READ binding describes a different image entirely - // scratch framebuffer, so the frontend's READ binding describes a different image entirely -
@@ -1230,7 +1230,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
ForceWideReadAlphaToOne(wide, static_cast<SizeT>(width) * static_cast<SizeT>(height), wideType); ForceWideReadAlphaToOne(wide, static_cast<SizeT>(width) * static_cast<SizeT>(height), wideType);
} }
// GL clamps a read from a fixed-point colour buffer to [0,1] (GL_CLAMP_READ_COLOR // GL clamps a read from a fixed-point color buffer to [0,1] (GL_CLAMP_READ_COLOR
// defaults to GL_FIXED_ONLY). Formats the backend substitutes with a floating-point // defaults to GL_FIXED_ONLY). Formats the backend substitutes with a floating-point
// one keep the out-of-range value the app stored, so apply the clamp here - a // one keep the out-of-range value the app stored, so apply the clamp here - a
// GL_R16_SNORM target holding -0.125 must still read back as 0. // GL_R16_SNORM target holding -0.125 must still read back as 0.
@@ -1283,13 +1283,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true; return true;
} }
// ---- Bit-exact readback of a 32-bit packed colour level --------------------------------------- // ---- Bit-exact readback of a 32-bit packed color level ---------------------------------------
// //
// glGetTexImage of a packed format read with its OWN client type owes the application the words // glGetTexImage of a packed format read with its OWN client type owes the application the words
// the image HOLDS, and neither of the two routes above can promise that once anything other than // the image HOLDS, and neither of the two routes above can promise that once anything other than
// a glTexImage has written the level: // a glTexImage has written the level:
// //
// * the colour-attachment route reads GL_RGBA/GL_FLOAT and re-encodes, which canonicalizes an // * the color-attachment route reads GL_RGBA/GL_FLOAT and re-encodes, which canonicalizes an
// RGB9_E5 shared exponent (0xf8fc0000 -> 0xe7e00000, same value, different bits) and // RGB9_E5 shared exponent (0xf8fc0000 -> 0xe7e00000, same value, different bits) and
// collapses an R11F_G11F_B10F NaN to the canonical payload 1 // collapses an R11F_G11F_B10F NaN to the canonical payload 1
// (MG_Util::EncodeFloatToUnsignedSmallFloat) - and a copy-image from RGB9_E5 lands exactly // (MG_Util::EncodeFloatToUnsignedSmallFloat) - and a copy-image from RGB9_E5 lands exactly
@@ -1301,7 +1301,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// source (which has no shadow to mirror from at all), a cube or 1D-array endpoint, a // source (which has no shadow to mirror from at all), a cube or 1D-array endpoint, a
// self-copy - and the decline is silent, so the stale words are served as truth. // self-copy - and the decline is silent, so the stale words are served as truth.
// //
// glCopyImageSubData is a raw texel-block move and EXT_copy_image puts every 32-bit colour // glCopyImageSubData is a raw texel-block move and EXT_copy_image puts every 32-bit color
// format in one compatibility class, so copying the level into a scratch GL_R32UI image and // format in one compatibility class, so copying the level into a scratch GL_R32UI image and
// reading THAT back as unsigned integers hands over the stored words themselves, whoever wrote // reading THAT back as unsigned integers hands over the stored words themselves, whoever wrote
// them. This is what lets the shadow stop being the authority for these formats: it is tried // them. This is what lets the shadow stop being the authority for these formats: it is tried
@@ -1387,7 +1387,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
// GL_RGBA_INTEGER/GL_UNSIGNED_INT is the one combination ES guarantees for an integer // GL_RGBA_INTEGER/GL_UNSIGNED_INT is the one combination ES guarantees for an integer
// colour buffer, so the read lands four words per texel and the red one is compacted out // color buffer, so the read lands four words per texel and the red one is compacted out
// here. The PACK scope is the tight default rather than the application's, so a row comes // here. The PACK scope is the tight default rather than the application's, so a row comes
// back packed at exactly `width * 4` words. One glGetError covers the whole loop: it // back packed at exactly `width * 4` words. One glGetError covers the whole loop: it
// accumulates, and a failure anywhere means the caller falls back rather than trusting a // accumulates, and a failure anywhere means the caller falls back rather than trusting a
@@ -1795,8 +1795,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return width == extent.x() && height == extent.y() && (!layered || depth == extent.z()); return width == extent.x() && height == extent.y() && (!layered || depth == extent.z());
} }
// P8-E (docs/archive/notes/p8/E.md). MGITEST_ESPRYT_REFUSE_TEXTURE_READBACK_EXTENT=<w>x<h>, READ BY THE SERVER: // P8-E. MGITEST_ESPRYT_REFUSE_TEXTURE_READBACK_EXTENT=<w>x<h>, READ BY THE SERVER:
// a colour level of exactly that extent is treated as one the driver refused to attach - what an // a color level of exactly that extent is treated as one the driver refused to attach - what an
// Adreno does for RGB16 and the 16-bit SNORM formats, and what no host driver can be made to do. // Adreno does for RGB16 and the 16-bit SNORM formats, and what no host driver can be made to do.
// Scoped by the extent so the tcp lane's shared supervisor can carry it for the whole lane: only // Scoped by the extent so the tcp lane's shared supervisor can carry it for the whole lane: only
// the scenario that builds that extent (CopyImageStoreReadbackScenario) ever meets it. // the scenario that builds that extent (CopyImageStoreReadbackScenario) ever meets it.
@@ -1954,7 +1954,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
// The (format, type) pairs the depth/stencil helpers below can service. They are not // The (format, type) pairs the depth/stencil helpers below can service. They are not
// covered by the colour tables above - GetReadbackChannelMapping has no entry for any // covered by the color tables above - GetReadbackChannelMapping has no entry for any
// depth or stencil format, so without this gate a read the helpers CAN serve (a // depth or stencil format, so without this gate a read the helpers CAN serve (a
// GL_UNSIGNED_SHORT depth, a GL_SHORT stencil) is turned away before it reaches them. // GL_UNSIGNED_SHORT depth, a GL_SHORT stencil) is turned away before it reaches them.
// The signed widths and GL_HALF_FLOAT belong in it for the same reason: the helpers scale a // The signed widths and GL_HALF_FLOAT belong in it for the same reason: the helpers scale a
@@ -2025,7 +2025,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The read buffer is what glReadPixels reads, so the frontend's READ binding is exactly // The read buffer is what glReadPixels reads, so the frontend's READ binding is exactly
// the right thing to ask here. // the right thing to ask here.
const Bool forceOpaqueAlpha = FramebufferImpl::IsAlphaWidenedFallbackReadAttachment(); const Bool forceOpaqueAlpha = FramebufferImpl::IsAlphaWidenedFallbackReadAttachment();
// An attachment widened from three channels to stay colour-renderable also has to leave // An attachment widened from three channels to stay color-renderable also has to leave
// the fast pair: only the conversion path knows to answer its alpha with the 1.0 the // the fast pair: only the conversion path knows to answer its alpha with the 1.0 the
// application's format implies instead of whatever the draw wrote into the added channel. // application's format implies instead of whatever the draw wrote into the added channel.
const Bool nativeFastPair = !packSwapBytes && !forceOpaqueAlpha && const Bool nativeFastPair = !packSwapBytes && !forceOpaqueAlpha &&
@@ -10,7 +10,7 @@
namespace MobileGL::MG_Backend::DirectGLES { namespace MobileGL::MG_Backend::DirectGLES {
#if MOBILEGL_BUILD_DISAGGREGATED #if MOBILEGL_BUILD_DISAGGREGATED
// ---- SHARED IMAGES (DirectGLES.h; docs/archive/notes/anland/plan-ahb-dmabuf.md) ------ // ---- SHARED IMAGES (DirectGLES.h) ------
namespace SharedImageImpl { namespace SharedImageImpl {
#if defined(__ANDROID__) #if defined(__ANDROID__)
@@ -56,7 +56,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
DupNativeFenceFdFn DupNativeFenceFd = nullptr; DupNativeFenceFdFn DupNativeFenceFd = nullptr;
// YUV images: sampled as external textures (GL_OES_EGL_image_external_essl3), and // YUV images: sampled as external textures (GL_OES_EGL_image_external_essl3), and
// through GL_EXT_YUV_target's raw Y'CbCr sampler where the driver has it, so the // through GL_EXT_YUV_target's raw Y'CbCr sampler where the driver has it, so the
// conversion is the import's own (its colour hints) rather than the driver's guess. // conversion is the import's own (its color hints) rather than the driver's guess.
Bool ExternalEssl3 = false; Bool ExternalEssl3 = false;
Bool YuvTarget = false; Bool YuvTarget = false;
}; };
@@ -573,7 +573,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
conversion.TargetOffset = g_GLESFuncs.glGetUniformLocation(program, "uOffset"); conversion.TargetOffset = g_GLESFuncs.glGetUniformLocation(program, "uOffset");
} }
MGLOG_I("Shared images: YUV images convert through %s", MGLOG_I("Shared images: YUV images convert through %s",
ownConversion ? "GL_EXT_YUV_target with the import's colour hints" ownConversion ? "GL_EXT_YUV_target with the import's color hints"
: "the driver's external-sampler conversion"); : "the driver's external-sampler conversion");
} }
if (conversion.Framebuffer == 0) g_GLESFuncs.glGenFramebuffers(1, &conversion.Framebuffer); if (conversion.Framebuffer == 0) g_GLESFuncs.glGenFramebuffers(1, &conversion.Framebuffer);
@@ -30,7 +30,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// //
// The cache stays on the LEGACY arm, gated on the same subsystem bit as the rest of this // The cache stays on the LEGACY arm, gated on the same subsystem bit as the rest of this
// slice, so that MOBILEGL_PIPE_PUSH=0 keeps being the faithful all-subsystems-pull control // slice, so that MOBILEGL_PIPE_PUSH=0 keeps being the faithful all-subsystems-pull control
// ConfigLoader.cpp documents - "reproduces P1's behaviour exactly" has to include this // ConfigLoader.cpp documents - "reproduces P1's behavior exactly" has to include this
// path, or the integrator's A/B measures e4 on both arms and attributes it to neither. In // path, or the integrator's A/B measures e4 on both arms and attributes it to neither. In
// the pull build gPipeInputs is the live GLContext, there is no poison to bypass and the // the pull build gPipeInputs is the live GLContext, there is no poison to bypass and the
// frontend getter still linear-scans, so the cache is exactly the code it was. // frontend getter still linear-scans, so the cache is exactly the code it was.
@@ -339,9 +339,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// direct_state_access.renderbuffers_storage. One unconditional push settles the whole // direct_state_access.renderbuffers_storage. One unconditional push settles the whole
// block rather than the one cap that happened to be noticed. // block rather than the one cap that happened to be noticed.
static Bool g_forceFullRenderStateResync = true; static Bool g_forceFullRenderStateResync = true;
// Which draw buffers' alpha channel the colour mask last pushed to the driver had forced // Which draw buffers' alpha channel the color mask last pushed to the driver had forced
// OFF - i.e. the value of `appliedWidenMask` in the last SyncRenderState that reached the // OFF - i.e. the value of `appliedWidenMask` in the last SyncRenderState that reached the
// colour-mask block. NOT derivable from the frontend parameter block: it depends on the // color-mask block. NOT derivable from the frontend parameter block: it depends on the
// bound DRAW framebuffer's attachment formats and on whether the caller is a draw or a // bound DRAW framebuffer's attachment formats and on whether the caller is a draw or a
// clear, neither of which bumps the frontend render-state version. Without it a // clear, neither of which bumps the frontend render-state version. Without it a
// clear-then-draw pair on an unchanged parameter block early-outs and the draw inherits // clear-then-draw pair on an unchanged parameter block early-outs and the draw inherits
@@ -405,7 +405,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint16 currentRenderStateVersion = MG_Pipe::gPipeInputs->GetRenderStateParametersVersion(); Uint16 currentRenderStateVersion = MG_Pipe::gPipeInputs->GetRenderStateParametersVersion();
const Bool forceFullPush = g_forceFullRenderStateResync; const Bool forceFullPush = g_forceFullRenderStateResync;
g_forceFullRenderStateResync = false; g_forceFullRenderStateResync = false;
// The alpha discipline for widened colour attachments (see the header comment on // The alpha discipline for widened color attachments (see the header comment on
// SyncRenderState): a DRAW must not be able to move the stored alpha off 1.0, a CLEAR // SyncRenderState): a DRAW must not be able to move the stored alpha off 1.0, a CLEAR
// is what puts it there. So the draw path masks alpha off on every widened draw // is what puts it there. So the draw path masks alpha off on every widened draw
// buffer and the clear path masks nothing. // buffer and the clear path masks nothing.
@@ -514,7 +514,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
} }
if (tailSpanDirty && g_GLESCapabilities.SupportsClipDistance) { if (tailSpanDirty && g_GLESCapabilities.SupportsClipDistance && !g_GLESCapabilities.EmulatesClipDistance) {
// gl_ClipDistance clipping is per-distance enable state in GL, and ES reaches it // gl_ClipDistance clipping is per-distance enable state in GL, and ES reaches it
// only through GL_EXT_clip_cull_distance. That extension reuses the desktop enum // only through GL_EXT_clip_cull_distance. That extension reuses the desktop enum
// values for CLIP_DISTANCE0_EXT..7_EXT, but the token is absent from the ES // values for CLIP_DISTANCE0_EXT..7_EXT, but the token is absent from the ES
@@ -539,7 +539,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
{ // sRGB framebuffer writes. GLES core always encodes a write into an sRGB attachment, { // sRGB framebuffer writes. GLES core always encodes a write into an sRGB attachment,
// while GL_FRAMEBUFFER_SRGB is disabled by default in desktop GL and the frontend // while GL_FRAMEBUFFER_SRGB is disabled by default in desktop GL and the frontend
// never turns it on, so the driver has to be told to write raw. Without this a render // never turns it on, so the driver has to be told to write raw. Without this a render
// into an sRGB colour buffer comes back encoded once too often (the shader's own // into an sRGB color buffer comes back encoded once too often (the shader's own
// decode on the next fetch then leaves the value one conversion short). // decode on the next fetch then leaves the value one conversion short).
const Bool srgbWrites = MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb); const Bool srgbWrites = MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb);
if (g_GLESCapabilities.SupportsSrgbWriteControl && if (g_GLESCapabilities.SupportsSrgbWriteControl &&
@@ -853,7 +853,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// framebuffer, so a widened draw buffer 0 costs every other buffer its // framebuffer, so a widened draw buffer 0 costs every other buffer its
// alpha writes. ES 3.2 makes glColorMaski core and ES 3.1 has it as // alpha writes. ES 3.2 makes glColorMaski core and ES 3.1 has it as
// EXT/OES; the only devices that reach this line are ES 3.0-class, where // EXT/OES; the only devices that reach this line are ES 3.0-class, where
// MRT with a mixed widened/native colour attachment set is already rare. // MRT with a mixed widened/native color attachment set is already rare.
const BoolVec4& m = driverMask0; const BoolVec4& m = driverMask0;
g_GLESFuncs.glColorMask(ToGLBoolean(m.x()), ToGLBoolean(m.y()), ToGLBoolean(m.z()), g_GLESFuncs.glColorMask(ToGLBoolean(m.x()), ToGLBoolean(m.y()), ToGLBoolean(m.z()),
ToGLBoolean(m.w())); ToGLBoolean(m.w()));
@@ -1146,7 +1146,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// //
// The ORDERING that made this read come from the frontend is preserved and is what // The ORDERING that made this read come from the frontend is preserved and is what
// makes reading a record legal here: the client emits every set_* BEFORE the verb and // makes reading a record legal here: the client emits every set_* BEFORE the verb and
// the server specialises AT the verb, so by the time this runs the record already // the server specializes AT the verb, so by the time this runs the record already
// describes the framebuffer this draw will use - which is exactly the property the // describes the framebuffer this draw will use - which is exactly the property the
// frontend read was buying (a program compiled against a stale count would otherwise // frontend read was buying (a program compiled against a stale count would otherwise
// not be relinked until the draw after the one that needed it). // not be relinked until the draw after the one that needed it).
@@ -1434,7 +1434,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// and the clamp masks, the broadcast count, ImageUnitFormatsStillMatch and the three // and the clamp masks, the broadcast count, ImageUnitFormatsStillMatch and the three
// patch clauses are unchanged - they are backend globals, fb's row and VALUE rows of the // patch clauses are unchanged - they are backend globals, fb's row and VALUE rows of the
// residual block, none of which this family owns. // residual block, none of which this family owns.
void SyncCurrentProgramByHandle(MG_Pipe::MGPipeHandle cso, Bool forDraw) { void SyncCurrentProgramByHandle(MG_Pipe::MGPipeHandle cso, Bool forDraw, const IntegerAttributePlan* plan) {
#ifdef TRACY_ENABLE #ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND); ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif #endif
@@ -1468,8 +1468,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_lastUsedBackendProgramId = 0; g_lastUsedBackendProgramId = 0;
return; return;
} }
// Sticky for the twin's lifetime: a draw that needs the seeds rebuilds the program once.
if (plan != nullptr && plan->NeedsSeeds()) twin->NarrowIntegerSeeds().wanted = true;
if (!twin->GetBackendProgramId() || twin->GetSyncedShaderCsoSerial() != record->Serial || if (!twin->GetBackendProgramId() || twin->GetSyncedShaderCsoSerial() != record->Serial ||
twin->GetSyncedBindingsSerial() != record->BindingsSerial || twin->NarrowIntegerSeeds().Stale() || twin->GetSyncedBindingsSerial() != record->BindingsSerial ||
twin->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask || twin->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask ||
twin->GetUnormFallbackClampOutputMask() != g_unormFallbackClampOutputMask || twin->GetUnormFallbackClampOutputMask() != g_unormFallbackClampOutputMask ||
twin->GetFragColorBroadcastCount() != g_fragColorBroadcastCount || twin->GetFragColorBroadcastCount() != g_fragColorBroadcastCount ||
@@ -601,7 +601,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// spelling the driver provides (Adreno exposes only eglSwapBuffersWithDamageKHR), so // spelling the driver provides (Adreno exposes only eglSwapBuffersWithDamageKHR), so
// the null check below is the whole chain: a driver with neither presents with plain // the null check below is the whole chain: a driver with neither presents with plain
// eglSwapBuffers. Passing n_rects == 0 is not a partial present - the extension // eglSwapBuffers. Passing n_rects == 0 is not a partial present - the extension
// defines it as "the entire surface is implicitly damaged and the behaviour is // defines it as "the entire surface is implicitly damaged and the behavior is
// equivalent to calling eglSwapBuffers" - and the wire protocol carries no damage // equivalent to calling eglSwapBuffers" - and the wire protocol carries no damage
// rectangles for the server to forward, so the full surface is what is meant anyway. // rectangles for the server to forward, so the full surface is what is meant anyway.
// //
@@ -35,7 +35,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// two never-synced textures as equal. The identity has to exist before the twin does. // two never-synced textures as equal. The identity has to exist before the twin does.
// //
// Split by the RUNTIME arm, not by the build, for exactly the reason g_fbSlotCache is: // Split by the RUNTIME arm, not by the build, for exactly the reason g_fbSlotCache is:
// MOBILEGL_PIPE_PUSH=0 has to reproduce P1's behaviour (ConfigLoader.cpp), and a // MOBILEGL_PIPE_PUSH=0 has to reproduce P1's behavior (ConfigLoader.cpp), and a
// legacy-arm run that debounced on lifetime ids would be running P2's mechanism while // legacy-arm run that debounced on lifetime ids would be running P2's mechanism while
// the A/B attributed the result to P1. The two answers are equivalent - OwnerEquals on // the A/B attributed the result to P1. The two answers are equivalent - OwnerEquals on
// two empty pointers is true and LifetimeIdOf(nullptr) == 0 == 0; a live-versus-expired // two empty pointers is true and LifetimeIdOf(nullptr) == 0 == 0; a live-versus-expired
@@ -454,7 +454,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// //
// A texture that is an attachment of the READ framebuffer ONLY reaches // A texture that is an attachment of the READ framebuffer ONLY reaches
// SyncAttachmentObject, which calls SyncMipmapsToBackend AND NOTHING ELSE - so its // SyncAttachmentObject, which calls SyncMipmapsToBackend AND NOTHING ELSE - so its
// swizzle, its GL_DEPTH_STENCIL_TEXTURE_MODE, its LOD clamp and its border colour // swizzle, its GL_DEPTH_STENCIL_TEXTURE_MODE, its LOD clamp and its border color
// never reach the driver. The list that pushes texture PARAMETERS for an attachment is // never reach the driver. The list that pushes texture PARAMETERS for an attachment is
// the one below, and it reads the DRAW binding slot alone. That is the gap // the one below, and it reads the DRAW binding slot alone. That is the gap
// ARCHITECTURE.md's D10 names, and the roadmap makes closing it a mandatory, // ARCHITECTURE.md's D10 names, and the roadmap makes closing it a mandatory,
@@ -464,7 +464,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// //
// DELIBERATELY NOT GATED ON THE FRAMEBUFFER SUBSYSTEM BIT. It is a deliverable of the // DELIBERATELY NOT GATED ON THE FRAMEBUFFER SUBSYSTEM BIT. It is a deliverable of the
// phase rather than an arm of it, so both arms of the object A/B carry it and the A/B // phase rather than an arm of it, so both arms of the object A/B carry it and the A/B
// keeps measuring the arm rather than the arm plus a behaviour change. It is inside // keeps measuring the arm rather than the arm plus a behavior change. It is inside
// MOBILEGL_PIPE_PUSH, so the pull build is untouched. // MOBILEGL_PIPE_PUSH, so the pull build is untouched.
// //
// Its own list and its own key rather than an extension of the draw list's, because // Its own list and its own key rather than an extension of the draw list's, because
@@ -562,7 +562,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// and the alias contest between two real textures on one native target was arbitrated // and the alias contest between two real textures on one native target was arbitrated
// by the program's own sampler types. So the window IS the work list. // by the program's own sampler types. So the window IS the work list.
// //
// NAMED BEHAVIOUR DELTA (P5e-5, contract §5.2): this syncs the PROGRAM-RESOLVED // NAMED BEHAVIOR DELTA (P5e-5, contract §5.2): this syncs the PROGRAM-RESOLVED
// texture per unit, where the frontend walk syncs every slot of every touched unit. // texture per unit, where the frontend walk syncs every slot of every touched unit.
// Coverage is unchanged - views UNION image units UNION the FBO attachment lists // Coverage is unchanged - views UNION image units UNION the FBO attachment lists
// UNION the waited texture ops still cover every texture anything reads - so it // UNION the waited texture ops still cover every texture anything reads - so it
@@ -0,0 +1,121 @@
// MobileGL - MobileGL/MG_Backend/DirectGLES/IntegerAttributePlan.h
// Copyright (c) 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_Util/ShaderTranspiler/NarrowIntegerVertexInputs.h>
namespace MobileGL::MG_Backend::DirectGLES {
using MG_Util::ShaderTranspiler::IntegerVertexInputMasks;
// What one draw does with its integer vertex arrays. ES defines an integer attribute's value
// only when the array's type has the input's signedness, so every integer array feeding an
// integer input of the other signedness is ISSUED with the input's signedness (retypeMask).
// For 32-bit data that is the same bits, which is GL's value. For 8/16-bit data the driver
// then extends by the input's rule, and the program's seeds (ExtendNarrowIntegerVertexInputs-
// Pass's RuntimeWidths mode) widen the low 8 or 16 bits again by the data's rule: widths8 and
// widths16 are the locations to widen, and they are the two uniforms the draw sets.
struct IntegerAttributePlan {
Uint32 retypeMask = 0;
// Integer arrays feeding integer inputs: the only locations whose issued type is
// observable, so a VAO keeps whatever it holds for every other location.
Uint32 careMask = 0;
Uint32 widths8 = 0;
Uint32 widths16 = 0;
constexpr Bool NeedsSeeds() const { return (widths8 | widths16) != 0; }
// The plan for a program without working seeds: a narrow array keeps the application's
// type (the value the driver gives today) rather than a retype nothing widens back.
constexpr IntegerAttributePlan WithoutSeeds() const {
IntegerAttributePlan plan = *this;
plan.retypeMask &= ~(widths8 | widths16);
plan.widths8 = 0;
plan.widths16 = 0;
return plan;
}
constexpr Bool operator==(const IntegerAttributePlan&) const = default;
};
// The locations whose integer array data is of each width and signedness.
struct IntegerAttributeDataMasks {
MG_Util::ShaderTranspiler::NarrowIntegerAttributeMasks narrow;
Uint32 unsigned32 = 0;
Uint32 signed32 = 0;
constexpr void Note(Uint32 location, DataType type) {
if (location >= 32) return;
const Uint32 bit = 1u << location;
switch (type) {
case DataType::Uint8:
narrow.unsigned8 |= bit;
break;
case DataType::Uint16:
narrow.unsigned16 |= bit;
break;
case DataType::Uint32:
unsigned32 |= bit;
break;
case DataType::Int8:
narrow.signed8 |= bit;
break;
case DataType::Int16:
narrow.signed16 |= bit;
break;
case DataType::Int32:
signed32 |= bit;
break;
default:
break;
}
}
constexpr Uint32 Unsigned() const { return narrow.unsigned8 | narrow.unsigned16 | unsigned32; }
constexpr Uint32 Signed() const { return narrow.signed8 | narrow.signed16 | signed32; }
};
// `seedsUsable` is false once the program's armed build could not give it working seeds.
constexpr IntegerAttributePlan ResolveIntegerAttributePlan(const IntegerVertexInputMasks& inputs,
const IntegerAttributeDataMasks& data,
Bool seedsUsable) {
IntegerAttributePlan plan;
plan.careMask = (inputs.signedInputs | inputs.unsignedInputs) & (data.Unsigned() | data.Signed());
plan.retypeMask = (inputs.signedInputs & data.Unsigned()) | (inputs.unsignedInputs & data.Signed());
const auto narrow = MG_Util::ShaderTranspiler::ResolveNarrowIntegerVertexInputs(inputs, data.narrow);
plan.widths8 = narrow.zeroExtend8 | narrow.signExtend8;
plan.widths16 = narrow.zeroExtend16 | narrow.signExtend16;
return seedsUsable ? plan : plan.WithoutSeeds();
}
// The array type to issue: the application's, or its counterpart of the other signedness.
constexpr GLenum IntegerAttribTypeForInput(GLenum applicationType, Bool retype) {
if (!retype) return applicationType;
switch (applicationType) {
case GL_BYTE:
return GL_UNSIGNED_BYTE;
case GL_UNSIGNED_BYTE:
return GL_BYTE;
case GL_SHORT:
return GL_UNSIGNED_SHORT;
case GL_UNSIGNED_SHORT:
return GL_SHORT;
case GL_INT:
return GL_UNSIGNED_INT;
case GL_UNSIGNED_INT:
return GL_INT;
default:
return applicationType;
}
}
// What a vertex array issues after this plan: the plan's choice wherever it can be observed,
// what it already holds everywhere else, so a program switch that only changes which
// locations are read does not re-issue any pointer.
constexpr Uint32 HeldIntegerRetype(Uint32 held, const IntegerAttributePlan& plan) {
return (held & ~plan.careMask) | (plan.retypeMask & plan.careMask);
}
} // namespace MobileGL::MG_Backend::DirectGLES
+7 -6
View File
@@ -212,7 +212,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// InProcessTeardown() - touches neither the driver nor another TU's statics. // InProcessTeardown() - touches neither the driver nor another TU's statics.
// //
// P14 S6: "EVERY TABLE" IS NOW "EVERY BUCKET OF THIS SESSION". The tables are keyed by // P14 S6: "EVERY TABLE" IS NOW "EVERY BUCKET OF THIS SESSION". The tables are keyed by
// {session, share group}, so the same rebuild of the eight registries would take a NEIGHBOUR // {session, share group}, so the same rebuild of the eight registries would take a NEIGHBOR
// session's twins with it - and with S2's in-process supervisor serving several sessions at // session's twins with it - and with S2's in-process supervisor serving several sessions at
// once that is not a hypothetical: this runs from ~BackendObject_DirectGLES, on the apply // once that is not a hypothetical: this runs from ~BackendObject_DirectGLES, on the apply
// thread of the session that is ending. So the drop is scoped to the calling thread's // thread of the session that is ending. So the drop is scoped to the calling thread's
@@ -374,6 +374,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (shaderType != GL_VERTEX_SHADER) { if (shaderType != GL_VERTEX_SHADER) {
return source; return source;
} }
source = PromoteNarrowIntegerWidthGlobals(std::move(source));
for (const char* name : {DRAW_ID_UNIFORM_NAME, BASE_VERTEX_UNIFORM_NAME}) { for (const char* name : {DRAW_ID_UNIFORM_NAME, BASE_VERTEX_UNIFORM_NAME}) {
for (const char* declPrefix : for (const char* declPrefix :
{"highp int ", "mediump int ", "lowp int ", "int ", "highp uint ", "mediump uint ", "uint "}) { {"highp int ", "mediump int ", "lowp int ", "int ", "highp uint ", "mediump uint ", "uint "}) {
@@ -532,7 +533,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Fragment stage. Returns false when the stage has no entry point to gate onto, which the // Fragment stage. Returns false when the stage has no entry point to gate onto, which the
// caller reports: the program still links and still renders, it just renders every index // caller reports: the program still links and still renders, it just renders every index
// with the first replay pass's state - i.e. it degrades to the pre-emulation behaviour // with the first replay pass's state - i.e. it degrades to the pre-emulation behavior
// rather than to a black screen. // rather than to a black screen.
Bool InjectViewportIndexPassGate(String& source) { Bool InjectViewportIndexPassGate(String& source) {
// Built beside the input and swapped in only on success, so a stage this pass declines // Built beside the input and swapped in only on success, so a stage this pass declines
@@ -844,7 +845,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Bit 12 depends on NOTHING, and that is said out loud rather than left as an absence: // Bit 12 depends on NOTHING, and that is said out loud rather than left as an absence:
// a ShaderCso handle names no texture and no buffer, the archive rides beside the // a ShaderCso handle names no texture and no buffer, the archive rides beside the
// record as a companion pointer, and the eight extra inputs the server still // record as a companion pointer, and the eight extra inputs the server still
// specialises on (D-H5) are read from state this backend already holds. // specializes on (D-H5) are read from state this backend already holds.
const Bool bitSet = (MG_Config::Features.PipePush & MG_Pipe::kMGPipeSubsystemPrograms) != 0; const Bool bitSet = (MG_Config::Features.PipePush & MG_Pipe::kMGPipeSubsystemPrograms) != 0;
const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm( const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm(
bitSet, /*legacyMemosEnabled=*/true, /*legacyArmSurvivesLegacyMemos=*/false); bitSet, /*legacyMemosEnabled=*/true, /*legacyArmSurvivesLegacyMemos=*/false);
@@ -878,7 +879,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
"MGPFramebufferState::Color[%u]'s wire width", "MGPFramebufferState::Color[%u]'s wire width",
cap, MG_Pipe::kMGPipeMaxColorAttachments); cap, MG_Pipe::kMGPipeMaxColorAttachments);
BufferImpl::StopOnArmlessPipeSubsystem( BufferImpl::StopOnArmlessPipeSubsystem(
"kMGPipeSubsystemFramebuffer (bit 9) cannot carry this driver's colour attachments, and " "kMGPipeSubsystemFramebuffer (bit 9) cannot carry this driver's color attachments, and "
"the record arm is the only arm since P13 W6"); "the record arm is the only arm since P13 W6");
} }
} }
@@ -913,7 +914,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!TextureImpl::IsMultisampleTextureTarget(target)) return 0; if (!TextureImpl::IsMultisampleTextureTarget(target)) return 0;
// The same three inputs the storage allocation clamps with (SyncToBackend's multisample // The same three inputs the storage allocation clamps with (SyncToBackend's multisample
// arm): the record's format and requested count, and the format's ES transfer format, // arm): the record's format and requested count, and the format's ES transfer format,
// which only decides the integer-vs-colour fallback when the probe has no answer. // which only decides the integer-vs-color fallback when the probe has no answer.
const auto internalFormat = static_cast<TextureInternalFormat>(record->Desc.InternalFormat); const auto internalFormat = static_cast<TextureInternalFormat>(record->Desc.InternalFormat);
GLenum glInternalFormat = 0, glFormat = 0, glType = 0; GLenum glInternalFormat = 0, glFormat = 0, glType = 0;
TextureImpl::GenerateTextureFormatInfo(internalFormat, &glInternalFormat, &glFormat, &glType, target); TextureImpl::GenerateTextureFormatInfo(internalFormat, &glInternalFormat, &glFormat, &glType, target);
@@ -930,7 +931,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
// N-7: D KEYS ON THE UPLOAD HALF; WIRE (AccumulatePendingUpload, PipeApply.cpp) KEYS ON // N-7: D KEYS ON THE UPLOAD HALF; WIRE (AccumulatePendingUpload, PipeApply.cpp) KEYS ON
// THE WHOLE PACKED FIELD. That asymmetry is resolved HERE, in D's favour and with the // THE WHOLE PACKED FIELD. That asymmetry is resolved HERE, in D's favor and with the
// reason, rather than by widening the two comparisons: D's question is per (uploadTarget, // reason, rather than by widening the two comparisons: D's question is per (uploadTarget,
// level) because that is the granularity its upload loop iterates - it is handed a // level) because that is the granularity its upload loop iterates - it is handed a
// TextureUploadTarget and a level and has no second key to offer - while wire's is per // TextureUploadTarget and a level and has no second key to offer - while wire's is per
+38 -38
View File
@@ -30,8 +30,8 @@
// so a forward declaration would not do. Push-only, like everything else P4a adds to this // so a forward declaration would not do. Push-only, like everything else P4a adds to this
// header, so the pull build's include graph is unchanged (D-P). // header, so the pull build's include graph is unchanged (D-P).
#include <MG_Pipe/PipeApply.h> #include <MG_Pipe/PipeApply.h>
// P13 W5: the record arm's deaths (the slot tables' handle refusals below), in every build. #include <MG_Pipe/PipeSessionFail.h> // P13 W5: the slot tables' handle refusals, in every build
#include <MG_Pipe/PipeSessionFail.h> #include "NarrowIntegerSeeds.h"
namespace MobileGL::MG_Backend::DirectGLES { namespace MobileGL::MG_Backend::DirectGLES {
String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType); String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType);
@@ -198,7 +198,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLenum IndexType() const { return m_indexType; } GLenum IndexType() const { return m_indexType; }
private: private:
// Declared before m_capOverride so it is initialised first (members initialise in // Declared before m_capOverride so it is initialized first (members initialize in
// declaration order): the whole decision is made once, and both the cap override and the // declaration order): the whole decision is made once, and both the cap override and the
// constructor body read the same answer. // constructor body read the same answer.
RestartSubstitutionKind m_kind = RestartSubstitutionKind::None; RestartSubstitutionKind m_kind = RestartSubstitutionKind::None;
@@ -248,7 +248,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Indices whose whole state tuple (viewport rectangle, scissor rectangle, scissor-test enable, // Indices whose whole state tuple (viewport rectangle, scissor rectangle, scissor-test enable,
// depth range) is identical share ONE pass, so the overwhelmingly common case - every index // depth range) is identical share ONE pass, so the overwhelmingly common case - every index
// still holding what glViewport/glScissor/glDepthRange broadcast to all sixteen - collapses // still holding what glViewport/glScissor/glDepthRange broadcast to all sixteen - collapses
// to a single pass with an all-ones gate mask, i.e. one draw and no behaviour change at all. // to a single pass with an all-ones gate mask, i.e. one draw and no behavior change at all.
// //
// Whether emulation runs. Off only under MOBILEGL_ESPRYT_FORCE_VIEWPORT_ARRAY_EMULATION falsy, which // Whether emulation runs. Off only under MOBILEGL_ESPRYT_FORCE_VIEWPORT_ARRAY_EMULATION falsy, which
// restores the pre-emulation path as a negative control. // restores the pre-emulation path as a negative control.
@@ -527,7 +527,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool ResolveProgramSubsystemArm(); Bool ResolveProgramSubsystemArm();
// P13 W6: every family runs the record arm - the frontend arms these resolvers used to choose // P13 W6: every family runs the record arm - the frontend arms these resolvers used to choose
// between are gone, so no draw-path site asks them any more (the integration peeks still read // between are gone, so no draw-path site asks them any more (the integration peeks still read
// the latches). The one refusal a running process can still meet - a driver whose colour- // the latches). The one refusal a running process can still meet - a driver whose color-
// attachment cap exceeds the wire's width - is checked once, at capability bring-up. // attachment cap exceeds the wire's width - is checked once, at capability bring-up.
void ResolveRecordArmFamilies(); void ResolveRecordArmFamilies();
@@ -843,7 +843,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// glBufferStorageExternalEXT, so `id` is immutable, persistentMapped and // glBufferStorageExternalEXT, so `id` is immutable, persistentMapped and
// persistentPtr is this server's own coherent map of the client's pages. The // persistentPtr is this server's own coherent map of the client's pages. The
// reference is this twin's own, released only after a fence that follows the // reference is this twin's own, released only after a fence that follows the
// store's last use has signalled (RetireT0Import). Survives an ES context loss: // store's last use has signaled (RetireT0Import). Survives an ES context loss:
// the next ensure re-imports it. Null for every store T0 did not adopt. // the next ensure re-imports it. Null for every store T0 did not adopt.
void* externalAhb = nullptr; void* externalAhb = nullptr;
// P8-C: A SHADER WROTE THIS STORE ON THIS SIDE since the R-11 staged copy (or, for // P8-C: A SHADER WROTE THIS STORE ON THIS SIDE since the R-11 staged copy (or, for
@@ -1230,9 +1230,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// above, so VertexArrayImpl::SyncCurrentVAOFromRecords calls this ENTRY directly // above, so VertexArrayImpl::SyncCurrentVAOFromRecords calls this ENTRY directly
// instead of passing a null SharedPtr into a function whose signature promises one. // instead of passing a null SharedPtr into a function whose signature promises one.
// CONTRACT-P5E §4.1's rule ("the object parameter is deleted from the transport // CONTRACT-P5E §4.1's rule ("the object parameter is deleted from the transport
// overload, not defaulted to null") is what this obeys. The body is unchanged and // overload, not defaulted to null") is what this obeys. `plan`: IntegerAttributePlan.h.
// was already record-only - it is this family's existence proof (scout S1 §2). void SyncToBackendFromApplier(const IntegerAttributePlan& plan);
void SyncToBackendFromApplier();
// P14: the applier's buffer-set serials move whenever ANY VAO's set is published, so // P14: the applier's buffer-set serials move whenever ANY VAO's set is published, so
// switching between two VAOs moves them although neither twin's buffers changed. Called // switching between two VAOs moves them although neither twin's buffers changed. Called
// before the draw's syncs: when the window this twin last emitted is the applier's // before the draw's syncs: when the window this twin last emitted is the applier's
@@ -1455,12 +1454,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// one monotone Uint64, no wrap, nothing to patch. This is the Track H re-key // one monotone Uint64, no wrap, nothing to patch. This is the Track H re-key
// ARCHITECTURE.md 9.5 counts. // ARCHITECTURE.md 9.5 counts.
Uint64 m_syncedIndexSerial = 0; Uint64 m_syncedIndexSerial = 0;
// Byte shift currently baked into the instanced arrays' offsets by the baseInstance // Byte shift baked into the instanced arrays' offsets by the baseInstance emulation:
// emulation (see SetPendingFetchBaseInstance). It is draw state, not VAO state, so it // draw state, not VAO state, kept because the next sync has to correct what was EMITTED.
// is deliberately NOT covered by the config version: the frontend never bumps for it.
// Kept here because it describes what was last EMITTED, which is what the next sync
// has to correct.
Uint32 m_syncedFetchBaseInstance = 0; Uint32 m_syncedFetchBaseInstance = 0;
// The integer arrays last issued with the other signedness (HeldIntegerRetype).
Uint32 m_syncedRetypeMask = 0;
// BufferImpl::g_bufferBackendIdGeneration as of this twin's last emit. A // BufferImpl::g_bufferBackendIdGeneration as of this twin's last emit. A
// mismatch means some live buffer's driver id was re-minted since; the ids // mismatch means some live buffer's driver id was re-minted since; the ids
// baked into the driver VAO's attribute/element bindings may be dead even // baked into the driver VAO's attribute/element bindings may be dead even
@@ -1555,7 +1553,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// applier stored, so the answer is the same whichever side resolved it first. // applier stored, so the answer is the same whichever side resolved it first.
Bool BackendUsesNativeBaseInstance(); Bool BackendUsesNativeBaseInstance();
// ---- P5e SEAM (docs/archive/contracts/CONTRACT-P5E.md §4.2; declared by c0e, bodied by id/vi) ---- // ---- P5e SEAM (§4.2; declared by c0e, bodied by id/vi) ----
// //
// THE VAO TWIN, RESOLVED FROM THE HANDLE THE RECORD CARRIED - MGPipeApplierState:: // THE VAO TWIN, RESOLVED FROM THE HANDLE THE RECORD CARRIED - MGPipeApplierState::
// BoundVertexElements - instead of from the frontend VertexArrayObject the draw's // BoundVertexElements - instead of from the frontend VertexArrayObject the draw's
@@ -1634,7 +1632,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
// Components per texel the frontend format's client data carries, for the three-channel // Components per texel the frontend format's client data carries, for the three-channel
// formats that can be widened to a four-channel colour-renderable target; 0 for everything // formats that can be widened to a four-channel color-renderable target; 0 for everything
// else. See PrepareChannelWidenedUpload. // else. See PrepareChannelWidenedUpload.
Uint GetWidenableClientComponentCount(TextureInternalFormat format); Uint GetWidenableClientComponentCount(TextureInternalFormat format);
@@ -1644,7 +1642,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool IsIntegerWidenableFormat(TextureInternalFormat format); Bool IsIntegerWidenableFormat(TextureInternalFormat format);
// Repacks three-component client data as four components with an alpha of 1.0 in // Repacks three-component client data as four components with an alpha of 1.0 in
// `uploadType`, for a format the backend widened to keep a colour attachment renderable. // `uploadType`, for a format the backend widened to keep a color attachment renderable.
// Returns `data` untouched when no widening applies. Pure CPU and context-free so a unit // Returns `data` untouched when no widening applies. Pure CPU and context-free so a unit
// test can exercise the exact packing the driver is handed; `widenedData` is the caller's // test can exercise the exact packing the driver is handed; `widenedData` is the caller's
// scratch buffer and has to outlive the returned pointer. // scratch buffer and has to outlive the returned pointer.
@@ -1730,7 +1728,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// flag, allocator or pixel-store object participates. P9 W2: a level // flag, allocator or pixel-store object participates. P9 W2: a level
// the driver does not hold (defined after the last sync) is never read // the driver does not hold (defined after the last sync) is never read
// back, and a readback the driver refuses falls back to the staged // back, and a readback the driver refuses falls back to the staged
// level when the store covers it (docs/archive/notes/p9/W2-REMINT.md). // level when the store covers it.
void RequireImageBindableStorageByHandle(MG_Pipe::MGPipeHandle res, void RequireImageBindableStorageByHandle(MG_Pipe::MGPipeHandle res,
const MG_Pipe::MGPipeResourceRecord& record); const MG_Pipe::MGPipeResourceRecord& record);
#if MOBILEGL_BUILD_DISAGGREGATED #if MOBILEGL_BUILD_DISAGGREGATED
@@ -1923,7 +1921,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
SamplerParameters m_cacheSamplerParameters; SamplerParameters m_cacheSamplerParameters;
UintVec2 m_cacheLodRange = {0, 1000}; UintVec2 m_cacheLodRange = {0, 1000};
// All three representations plus the form, because none of them alone identifies the // All three representations plus the form, because none of them alone identifies the
// border colour the driver texture is holding: two integer borders can share one float // border color the driver texture is holding: two integer borders can share one float
// (anything differing above 2^24), and a Float -> Int transition can leave every number // (anything differing above 2^24), and a Float -> Int transition can leave every number
// unchanged while still needing a different driver entry point. // unchanged while still needing a different driver entry point.
FloatVec4 m_cacheBorderColor = {0.0f, 0.0f, 0.0f, 0.0f}; FloatVec4 m_cacheBorderColor = {0.0f, 0.0f, 0.0f, 0.0f};
@@ -1936,7 +1934,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// texture that never asks for the stencil aspect never emits the call. The // texture that never asks for the stencil aspect never emits the call. The
// depth/stencil readback and replicate-blit emulations also write this parameter // depth/stencil readback and replicate-blit emulations also write this parameter
// raw, but only ever on their own scratch textures (never on an application // raw, but only ever on their own scratch textures (never on an application
// texture), so they cannot desynchronise this cache. // texture), so they cannot desynchronize this cache.
GLenum m_cacheDepthStencilTextureMode = GL_DEPTH_COMPONENT; GLenum m_cacheDepthStencilTextureMode = GL_DEPTH_COMPONENT;
Uint16 m_syncedSamplerVersion = 0; Uint16 m_syncedSamplerVersion = 0;
Uint16 m_syncedTextureParamsVersion = 0; Uint16 m_syncedTextureParamsVersion = 0;
@@ -1977,7 +1975,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Hands back the whole RECORD rather than its Params, because the parameter push // Hands back the whole RECORD rather than its Params, because the parameter push
// reads two things from beside them: Desc.InternalFormat, which decides the two // reads two things from beside them: Desc.InternalFormat, which decides the two
// channel-widening swizzle compositions, and Params.BuiltinSampler, which is where // channel-widening swizzle compositions, and Params.BuiltinSampler, which is where
// the border colour lives (it is sampler state, GL 4.6 table 23.18, and P4a does // the border color lives (it is sampler state, GL 4.6 table 23.18, and P4a does
// not duplicate it onto MGPTextureParams). // not duplicate it onto MGPTextureParams).
const MG_Pipe::MGPipeResourceRecord* ResolvePushedTextureParams( const MG_Pipe::MGPipeResourceRecord* ResolvePushedTextureParams(
const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject); const SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject);
@@ -1996,7 +1994,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// m_syncedShapeParamsVersion) AND m_syncedContentVersion: the applier bumps it on // m_syncedShapeParamsVersion) AND m_syncedContentVersion: the applier bumps it on
// every respecify and every sub-data it applies to this resource, which is exactly // every respecify and every sub-data it applies to this resource, which is exactly
// the union those four covered, without the coarse "any texture's churn re-opens // the union those four covered, without the coarse "any texture's churn re-opens
// every gate" behaviour the sampling-resolution generation had. // every gate" behavior the sampling-resolution generation had.
Uint64 m_syncedResourceSerial = 0; Uint64 m_syncedResourceSerial = 0;
// The record's ParamsSerial at the last SyncTextureParamsToBackend. Replaces // The record's ParamsSerial at the last SyncTextureParamsToBackend. Replaces
// m_syncedTextureParamsVersion; MGPTextureParams::ForceResync replaces // m_syncedTextureParamsVersion; MGPTextureParams::ForceResync replaces
@@ -2053,7 +2051,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// table's answer is an array element and only a GetOrCreate that GROWS the table moves // table's answer is an array element and only a GetOrCreate that GROWS the table moves
// it, which this function has already done by the time it returns. // it, which this function has already done by the time it returns.
BackendTextureObject* ResolveTextureTwin(MG_Pipe::MGPipeHandle res); BackendTextureObject* ResolveTextureTwin(MG_Pipe::MGPipeHandle res);
// ---- P5e SEAM (docs/archive/contracts/CONTRACT-P5E.md §4.2, §5.2; declared by c0e, bodied by // ---- P5e SEAM (the contract §4.2, §5.2; declared by c0e, bodied by
// id/tx2) -------------------------------------------------------------------------- // id/tx2) --------------------------------------------------------------------------
// //
// EVERY DRAW-PATH TEXTURE ENTRY, BY HANDLE. The caller holds a Texture handle - a // EVERY DRAW-PATH TEXTURE ENTRY, BY HANDLE. The caller holds a Texture handle - a
@@ -2080,7 +2078,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// glCopyImageSubData passes: it tries FollowCopyImageInStagedStore first and marks the store // glCopyImageSubData passes: it tries FollowCopyImageInStagedStore first and marks the store
// itself when that fails. // itself when that fails.
void NoteDriverSideTextureWriteByHandle(MG_Pipe::MGPipeHandle texture, Bool storeFollowsTheWrite = false); void NoteDriverSideTextureWriteByHandle(MG_Pipe::MGPipeHandle texture, Bool storeFollowsTheWrite = false);
// P8-E (docs/archive/notes/p8/E.md): make glCopyImageSubData's texel-block move in the server's staged // P8-E: make glCopyImageSubData's texel-block move in the server's staged
// store too (StagedTextureStore::FollowCopy), so the destination level's bytes stay its // store too (StagedTextureStore::FollowCopy), so the destination level's bytes stay its
// content. Box in each image's own texel grid. False when the store cannot follow: a cube // content. Box in each image's own texel grid. False when the store cannot follow: a cube
// map or 1D array endpoint (copy-image puts faces / layers on z, the store does not), a view, // map or 1D array endpoint (copy-image puts faces / layers on z, the store does not), a view,
@@ -2139,7 +2137,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
~BackendFramebufferObject(); ~BackendFramebufferObject();
BackendFramebufferObject(const BackendFramebufferObject&) = delete; BackendFramebufferObject(const BackendFramebufferObject&) = delete;
BackendFramebufferObject& operator=(const BackendFramebufferObject&) = delete; BackendFramebufferObject& operator=(const BackendFramebufferObject&) = delete;
// P5e SEAM (docs/archive/contracts/CONTRACT-P5E.md §5.4; declared by c0e, bodied by fb): the same // P5e SEAM (§5.4; declared by c0e, bodied by fb): the same
// sync keyed on the framebuffer HANDLE. The record's eleven surfaces ARE the point // sync keyed on the framebuffer HANDLE. The record's eleven surfaces ARE the point
// set - the emitter refuses a point at or above the wire width - so the attachment // set - the emitter refuses a point at or above the wire width - so the attachment
// walk needs no frontend FramebufferObject and no m_pushedSyncHandle handshake: the // walk needs no frontend FramebufferObject and no m_pushedSyncHandle handshake: the
@@ -2188,7 +2186,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
framebuffer, as a GL_COLOR_ATTACHMENTm enum. ES only accepts glDrawBuffers bufs[s] == framebuffer, as a GL_COLOR_ATTACHMENTm enum. ES only accepts glDrawBuffers bufs[s] ==
GL_COLOR_ATTACHMENTs, so a GL draw-buffer slot s naming attachment a forces a's image GL_COLOR_ATTACHMENTs, so a GL draw-buffer slot s naming attachment a forces a's image
under backend slot s. This table is the single owner of that decision and is kept a under backend slot s. This table is the single owner of that decision and is kept a
PERMUTATION of the backend colour slots: every other attachment keeps its identity PERMUTATION of the backend color slots: every other attachment keeps its identity
slot when that slot survived, and is parked on the lowest free slot when it did not. slot when that slot survived, and is parked on the lowest free slot when it did not.
Deriving the point per-query from the draw-buffer array instead handed the identity Deriving the point per-query from the draw-buffer array instead handed the identity
point to any attachment that was not a draw buffer - i.e. exactly the point a point to any attachment that was not a draw buffer - i.e. exactly the point a
@@ -2250,7 +2248,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// false accounts of one physical state: a composite framebuffer synced as DRAW in a // false accounts of one physical state: a composite framebuffer synced as DRAW in a
// depth pre-pass detaches the points its other attachment state attaches, and the // depth pre-pass detaches the points its other attachment state attaches, and the
// end-of-frame sync of that same record on the READ side then found its own slot // end-of-frame sync of that same record on the READ side then found its own slot
// already stamped by an earlier identical sync, walked nothing, left the colour point // already stamped by an earlier identical sync, walked nothing, left the color point
// detached, and the driver refused the following glBlitFramebuffer with // detached, and the driver refused the following glBlitFramebuffer with
// INVALID_OPERATION. Keying on the object is what makes "this hash is applied" mean // INVALID_OPERATION. Keying on the object is what makes "this hash is applied" mean
// "these points are on the driver now". // "these points are on the driver now".
@@ -2307,7 +2305,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// describe that point at all (Color8..Color31, and the default framebuffer's FRONT/BACK // describe that point at all (Color8..Color31, and the default framebuffer's FRONT/BACK
// tokens). MGPFramebufferState carries Color[8] + Depth + Stencil, which is every point // tokens). MGPFramebufferState carries Color[8] + Depth + Stencil, which is every point
// a framebuffer can hold on the handle arm - D-C3 refuses bit 9 outright on a driver // a framebuffer can hold on the handle arm - D-C3 refuses bit 9 outright on a driver
// reporting more than 8 colour attachments. P5e (fb) exports it: the twin's attachment // reporting more than 8 color attachments. P5e (fb) exports it: the twin's attachment
// walk, the named blit's aspect plan and the detach walk all ask the same question of // walk, the named blit's aspect plan and the detach walk all ask the same question of
// the same record, and a second copy of the Color/Depth/Stencil dispatch beside each of // the same record, and a second copy of the Color/Depth/Stencil dispatch beside each of
// them is how one of them ends up describing a point differently from the others. // them is how one of them ends up describing a point differently from the others.
@@ -2315,13 +2313,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
FramebufferAttachmentType point); FramebufferAttachmentType point);
// True when the read buffer names a fixed-point (norm/snorm) attachment that the // True when the read buffer names a fixed-point (norm/snorm) attachment that the
// backend actually stores in a floating-point format. GL clamps a read from a // backend actually stores in a floating-point format. GL clamps a read from a
// fixed-point colour buffer to [0,1] (GL_CLAMP_READ_COLOR defaults to // fixed-point color buffer to [0,1] (GL_CLAMP_READ_COLOR defaults to
// GL_FIXED_ONLY); the substituted float storage would not, so the readback path // GL_FIXED_ONLY); the substituted float storage would not, so the readback path
// has to apply the clamp itself. // has to apply the clamp itself.
Bool IsFixedPointFallbackReadAttachment(); Bool IsFixedPointFallbackReadAttachment();
// True when the read buffer names a three-channel attachment the backend actually stores // True when the read buffer names a three-channel attachment the backend actually stores
// in a four-channel format (the colour-renderable widening). A format without alpha reads // in a four-channel format (the color-renderable widening). A format without alpha reads
// back as 1.0, so the readback path has to overwrite the alpha the draw left behind - // back as 1.0, so the readback path has to overwrite the alpha the draw left behind -
// unconditionally, since this is the format's own semantics rather than the // unconditionally, since this is the format's own semantics rather than the
// GL_CLAMP_READ_COLOR rule the clamp above implements. // GL_CLAMP_READ_COLOR rule the clamp above implements.
@@ -2339,19 +2337,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
// driver where no interception is possible. So the stored alpha is kept at 1.0 instead: // driver where no interception is possible. So the stored alpha is kept at 1.0 instead:
// a clear touching a widened buffer writes alpha 1.0, and every draw into it has its // a clear touching a widened buffer writes alpha 1.0, and every draw into it has its
// alpha write mask forced off, so nothing can ever move it again. The application's own // alpha write mask forced off, so nothing can ever move it again. The application's own
// colour mask is untouched - glGet(GL_COLOR_WRITEMASK) still reports what it set. // color mask is untouched - glGet(GL_COLOR_WRITEMASK) still reports what it set.
extern Uint32 g_alphaWidenedDrawBufferMask; extern Uint32 g_alphaWidenedDrawBufferMask;
// Bit i set = DRAW BUFFER i of the framebuffer bound as DRAW resolves to a colour // Bit i set = DRAW BUFFER i of the framebuffer bound as DRAW resolves to a color
// attachment with an INTEGER format. Recomputed beside the mask above and for its sake: // attachment with an INTEGER format. Recomputed beside the mask above and for its sake:
// glClearBufferfv on an integer colour buffer is GL_INVALID_OPERATION, so the // glClearBufferfv on an integer color buffer is GL_INVALID_OPERATION, so the
// per-draw-buffer clear route the widening needs has to stand down when one is present. // per-draw-buffer clear route the widening needs has to stand down when one is present.
// (glClear on an integer colour buffer is left undefined by ES in the first place, and // (glClear on an integer color buffer is left undefined by ES in the first place, and
// an application that wants a defined answer has to call glClearBufferuiv/iv - which does // an application that wants a defined answer has to call glClearBufferuiv/iv - which does
// carry the widened alpha substitution.) // carry the widened alpha substitution.)
extern Uint32 g_integerColorDrawBufferMask; extern Uint32 g_integerColorDrawBufferMask;
// The colour a clear has to hand the driver for one draw buffer: the application's value, // The color a clear has to hand the driver for one draw buffer: the application's value,
// except that a widened attachment's alpha is replaced by the 1.0 its three-channel // except that a widened attachment's alpha is replaced by the 1.0 its three-channel
// format implies. `one` is 1.0 encoded in the clear call's own component type - the // format implies. `one` is 1.0 encoded in the clear call's own component type - the
// integer clears carry the integer 1, the float clear carries 1.0f. // integer clears carry the integer 1, the float clear carries 1.0f.
@@ -2847,6 +2845,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// shader failed to transpile or compile, or the link itself failed). Use() // shader failed to transpile or compile, or the link itself failed). Use()
// must not leave the previously bound program current in that case. // must not leave the previously bound program current in that case.
Bool IsBackendProgramUsable() const { return m_backendProgramUsable; } Bool IsBackendProgramUsable() const { return m_backendProgramUsable; }
NarrowIntegerSeedState& NarrowIntegerSeeds() { return m_narrowIntegerSeeds; }
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; } Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; } Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; } Uint32 GetUnormFallbackClampOutputMask() const { return m_unormFallbackClampOutputMask; }
@@ -2910,7 +2909,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// self-contained would produce a per-draw rebuild: the other eight inputs - the draw // self-contained would produce a per-draw rebuild: the other eight inputs - the draw
// FBO's snorm/unorm clamp masks, the fragColor broadcast count, the storage-block // FBO's snorm/unorm clamp masks, the fragColor broadcast count, the storage-block
// binding signature, the atomic-counter set, the live image formats and the patch // binding signature, the atomic-counter set, the live image formats and the patch
// parameters - are all still specialised at the verb, from state this backend holds. // parameters - are all still specialized at the verb, from state this backend holds.
// 0 means "never stamped", which is a guaranteed miss (applier serials start at 1). // 0 means "never stamped", which is a guaranteed miss (applier serials start at 1).
Uint64 GetSyncedShaderCsoSerial() const { return m_syncedShaderCsoSerial; } Uint64 GetSyncedShaderCsoSerial() const { return m_syncedShaderCsoSerial; }
// P5e (pg): the SECOND server-owned key, and it replaces GetImageUnitVersion() on // P5e (pg): the SECOND server-owned key, and it replaces GetImageUnitVersion() on
@@ -2971,7 +2970,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const std::map<String, String>& inputBlockRenames, const std::map<String, String>& inputBlockRenames,
const std::map<String, String>& outputBlockRenames, Bool stripInputBlockLocations, const std::map<String, String>& outputBlockRenames, Bool stripInputBlockLocations,
Bool stripOutputBlockLocations, Int atomicCounterEsslBindingTop, Bool stripOutputBlockLocations, Int atomicCounterEsslBindingTop,
Bool enableSpirvValidation, String& outSource, Bool narrowIntegerSeedsArmed, Bool enableSpirvValidation, String& outSource,
std::set<String>& outFlattenedXfbBlockNames, std::set<String>& outFlattenedXfbBlockNames,
Vector<Int>& outAtomicCounterGlBindings, String& outError) const; Vector<Int>& outAtomicCounterGlBindings, String& outError) const;
@@ -3030,6 +3029,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Vector<Int> m_formatlessImageUnits; Vector<Int> m_formatlessImageUnits;
Uint64 m_imageUnitFormatSignature = 0; Uint64 m_imageUnitFormatSignature = 0;
SamplerPassMemo m_samplerPassMemo; SamplerPassMemo m_samplerPassMemo;
NarrowIntegerSeedState m_narrowIntegerSeeds;
}; };
extern Uint32 g_snormFallbackClampOutputMask; extern Uint32 g_snormFallbackClampOutputMask;
@@ -3126,7 +3126,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}; };
ImageFormatBakeInputs CollectImageFormatBakeInputs(const ProgramBuildSource& src); ImageFormatBakeInputs CollectImageFormatBakeInputs(const ProgramBuildSource& src);
// ---- P5e SEAM (docs/archive/contracts/CONTRACT-P5E.md §4.2, §5.5; declared by c0e, bodied by // ---- P5e SEAM (the contract §4.2, §5.5; declared by c0e, bodied by
// id/pg) --------------------------------------------------------------------------- // id/pg) ---------------------------------------------------------------------------
// //
// THE PROGRAM TWIN, RESOLVED FROM MGPipeApplierState::DrawProgram / DispatchProgram / // THE PROGRAM TWIN, RESOLVED FROM MGPipeApplierState::DrawProgram / DispatchProgram /
@@ -321,7 +321,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// (Ops_H_ImportExternal below). The twin holds its own reference, and every path that // (Ops_H_ImportExternal below). The twin holds its own reference, and every path that
// ends the store - a respecify, a destroy, a re-import - deletes the GL id and then // ends the store - a respecify, a destroy, a re-import - deletes the GL id and then
// RETIRES the reference here: a fence is inserted after the delete, and the reference is // RETIRES the reference here: a fence is inserted after the delete, and the reference is
// released only once that fence has signalled, i.e. once every command that could still // released only once that fence has signaled, i.e. once every command that could still
// read or write the pages has completed on this context's GPU. The client's own // read or write the pages has completed on this context's GPU. The client's own
// reference ends independently (it releases on respecify/destroy); the pages live until // reference ends independently (it releases on respecify/destroy); the pages live until
// the last of the two. No link watermark is involved (F-L2): the fence is this server's. // the last of the two. No link watermark is involved (F-L2): the fence is this server's.
@@ -963,7 +963,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return; return;
} }
// The kill-switch arm, and it reads the application's REAL flags per call - // The kill-switch arm, and it reads the application's REAL flags per call -
// which is why MGPFlushRange carries them unnormalised. // which is why MGPFlushRange carries them unnormalized.
const Flags<BufferMappingAccessBit> appAccess{ const Flags<BufferMappingAccessBit> appAccess{
static_cast<std::underlying_type_t<BufferMappingAccessBit>>(record.AccessFlags)}; static_cast<std::underlying_type_t<BufferMappingAccessBit>>(record.AccessFlags)};
const Bool invalidate = (appAccess & BufferMappingAccessBit::InvalidateRange) || const Bool invalidate = (appAccess & BufferMappingAccessBit::InvalidateRange) ||
@@ -1046,8 +1046,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// sees the reconciled shadow on return, exactly as before. // sees the reconciled shadow on return, exactly as before.
// //
// THE ORDER FROM HERE IS A CORRECTNESS RULE, NOT A PREFERENCE // THE ORDER FROM HERE IS A CORRECTNESS RULE, NOT A PREFERENCE
// (ARCHITECTURE.md 8.2: "写回的 epoch bump 必须在任何后续读该 handle 的命令之前被 // (ARCHITECTURE.md 8.2: "the writeback's epoch bump must be applied by the server before any
// server 应用;反向通道需要与正向通道相同的有序保证"): // later command that reads that handle; the reverse channel needs the same ordering guarantees as the
// forward channel"):
// 1. writeback - the client's shadow takes the bytes, // 1. writeback - the client's shadow takes the bytes,
// 2. unmap - the read mapping goes away, // 2. unmap - the read mapping goes away,
// 3. serial stamp- syncedChangeSerial catches up with the record, so the next // 3. serial stamp- syncedChangeSerial catches up with the record, so the next
@@ -1727,7 +1728,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Uint16 uploadTarget = MG_Pipe::MGPipeSubDataUploadTargetOf(record.Target); const Uint16 uploadTarget = MG_Pipe::MGPipeSubDataUploadTargetOf(record.Target);
const IntVec3 extent = MG_Record::StagedTextureUploadExtent(stored->Desc, record); const IntVec3 extent = MG_Record::StagedTextureUploadExtent(stored->Desc, record);
const SizeT runBytes = static_cast<SizeT>(record.Blob.Size); const SizeT runBytes = static_cast<SizeT>(record.Blob.Size);
// P8-E (docs/archive/notes/p8/E.md): THE LEVEL HOLDS A COPY THE CLIENT NEVER SAW // P8-E: THE LEVEL HOLDS A COPY THE CLIENT NEVER SAW
// (FollowCopyImageInStagedStore). The run is the client's shadow, stale everywhere it // (FollowCopyImageInStagedStore). The run is the client's shadow, stale everywhere it
// did not write, so only the texels it names land - its regions, or its union box // did not write, so only the texels it names land - its regions, or its union box
// when it carries none (D-D3's "the box is the whole story"). A run the store cannot // when it carries none (D-D3's "the box is the whole story"). A run the store cannot
@@ -1900,7 +1901,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
// The seventh slot table. A process-lifetime global, like the other six, so it links // The seventh slot table. A process-lifetime global, like the other six, so it links
// itself into its own holder list from its own constructor with no initialisation // itself into its own holder list from its own constructor with no initialization
// order question to answer (SlotTables.h). // order question to answer (SlotTables.h).
BackendBufferResourceTable g_backendBufferResources; BackendBufferResourceTable g_backendBufferResources;
@@ -2112,7 +2113,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
#if MOBILEGL_BUILD_DISAGGREGATED #if MOBILEGL_BUILD_DISAGGREGATED
// P11 B2: the T0 half of the deferred-release drain, run right after it at both sync // P11 B2: the T0 half of the deferred-release drain, run right after it at both sync
// points (ProcessDeferredBufferReleases itself moves verbatim, G5). Retired imports whose // points (ProcessDeferredBufferReleases itself moves verbatim, G5). Retired imports whose
// fence signalled let go of their AHardwareBuffer; a deferred T0 destroy is retired once // fence signaled let go of their AHardwareBuffer; a deferred T0 destroy is retired once
// the drain deleted its id (the fence then follows the delete) or its context is gone. // the drain deleted its id (the fence then follows the delete) or its context is gone.
void ProcessDeferredT0Retires() { void ProcessDeferredT0Retires() {
if (!g_t0Retired.empty()) SweepT0Retired(false); if (!g_t0Retired.empty()) SweepT0Retired(false);
@@ -2309,7 +2310,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const SizeT poolSize = ResourceWidthOf(res); const SizeT poolSize = ResourceWidthOf(res);
// The pool DECISION is the legacy one, unchanged: extent and residence only. // The pool DECISION is the legacy one, unchanged: extent and residence only.
// An earlier cut added "and a shadow base was recorded", which silently stopped // An earlier cut added "and a shadow base was recorded", which silently stopped
// a buffer recycling pool ids - a change to D-F's pool behaviour, not to "the // a buffer recycling pool ids - a change to D-F's pool behavior, not to "the
// source of bytes" the conversion is allowed to move, and one that moves // source of bytes" the conversion is allowed to move, and one that moves
// AcquireFromPool call counts StreamedArenaScenario observes. // AcquireFromPool call counts StreamedArenaScenario observes.
const Uint reused = (poolSize > 0 && !resource->persistentMapped) ? AcquireFromPool(poolSize) : 0; const Uint reused = (poolSize > 0 && !resource->persistentMapped) ? AcquireFromPool(poolSize) : 0;
@@ -146,7 +146,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// re-issued is invalidated too - the attachment versions below, and under // re-issued is invalidated too - the attachment versions below, and under
// MOBILEGL_PIPE_PUSH the record memos stamped at the end of this function - so the // MOBILEGL_PIPE_PUSH the record memos stamped at the end of this function - so the
// next sync re-attaches all non-empty attachments at their identity points AND // next sync re-attaches all non-empty attachments at their identity points AND
// (see SyncToBackend's attachment loop) detaches any colour point whose frontend owner // (see SyncToBackend's attachment loop) detaches any color point whose frontend owner
// is empty. Without that detach a stale image would survive under a point the table now // is empty. Without that detach a stale image would survive under a point the table now
// claims for a different, empty attachment. // claims for a different, empty attachment.
for (Uint i = 0; i < MAX_COLOR_ATTACHMENT_SLOTS; ++i) { for (Uint i = 0; i < MAX_COLOR_ATTACHMENT_SLOTS; ++i) {
@@ -250,7 +250,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// kMGPipeSurfaceNoTextureTarget on a texture point is a seam defect - refused loudly and // kMGPipeSurfaceNoTextureTarget on a texture point is a seam defect - refused loudly and
// answered false, never guessed. False is the safe direction for all four: the mask // answered false, never guessed. False is the safe direction for all four: the mask
// turns an emulation ON, and an emulation that does not run leaves the driver's own // turns an emulation ON, and an emulation that does not run leaves the driver's own
// (correct-for-the-real-format) behaviour, while one that runs on the wrong buffer // (correct-for-the-real-format) behavior, while one that runs on the wrong buffer
// clamps or overwrites texels the application wrote. // clamps or overwrites texels the application wrote.
static Bool PushedSurfaceTextureTarget(const MG_Pipe::MGPSurface& surface, TextureTarget* out) { static Bool PushedSurfaceTextureTarget(const MG_Pipe::MGPSurface& surface, TextureTarget* out) {
if (surface.TextureTarget == MG_Pipe::kMGPipeSurfaceNoTextureTarget) { if (surface.TextureTarget == MG_Pipe::kMGPipeSurfaceNoTextureTarget) {
@@ -314,7 +314,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The record's surface for an attachment POINT, or null when this record does not // The record's surface for an attachment POINT, or null when this record does not
// describe that point at all. MGPFramebufferState carries Color[8] + Depth + Stencil // describe that point at all. MGPFramebufferState carries Color[8] + Depth + Stencil
// (D-C1), which is every point a framebuffer can hold on the handle arm: D-C3 refuses // (D-C1), which is every point a framebuffer can hold on the handle arm: D-C3 refuses
// bit 9 outright on a driver reporting more than 8 colour attachments, and the // bit 9 outright on a driver reporting more than 8 color attachments, and the
// FRONT/BACK points belong to the DEFAULT framebuffer, which has no twin at all. // FRONT/BACK points belong to the DEFAULT framebuffer, which has no twin at all.
const MG_Pipe::MGPSurface* PushedSurfaceForAttachment(const MG_Pipe::MGPFramebufferState& record, const MG_Pipe::MGPSurface* PushedSurfaceForAttachment(const MG_Pipe::MGPFramebufferState& record,
FramebufferAttachmentType point) { FramebufferAttachmentType point) {
@@ -465,7 +465,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// an object the apply thread was handed. // an object the apply thread was handed.
// //
// P5e (fix1, ID-81): arm-selected for the texture arm's reason, and here it is a // P5e (fix1, ID-81): arm-selected for the texture arm's reason, and here it is a
// BEHAVIOUR difference even though the by-handle body dereferences nothing: the // BEHAVIOR difference even though the by-handle body dereferences nothing: the
// object form reports a refused allocation to the application through the live // object form reports a refused allocation to the application through the live
// GLContext (RecordError) and the by-handle form deliberately does not, because // GLContext (RecordError) and the by-handle form deliberately does not, because
// under a transport there is no application on this side to report it to. On the // under a transport there is no application on this side to report it to. On the
@@ -483,7 +483,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
// The read attachment's storage carries an alpha channel its frontend format does not // The read attachment's storage carries an alpha channel its frontend format does not
// (the three-channel colour-renderable widening). GL answers such a read with 1.0, but // (the three-channel color-renderable widening). GL answers such a read with 1.0, but
// the storage holds whatever the draw wrote there, so the readback has to overwrite it. // the storage holds whatever the draw wrote there, so the readback has to overwrite it.
Bool IsAlphaWidenedFallbackReadAttachment() { Bool IsAlphaWidenedFallbackReadAttachment() {
const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer(); const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer();
@@ -552,7 +552,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
void BackendFramebufferObject::ApplyReadBufferFromRecord(const MG_Pipe::MGPFramebufferState& record, void BackendFramebufferObject::ApplyReadBufferFromRecord(const MG_Pipe::MGPFramebufferState& record,
Uint glNameForDiag) { Uint glNameForDiag) {
// D-C1 makes the NULL Res the spelling of "no attachment" and c0c's // D-C1 makes the NULL Res the spelling of "no attachment" and c0c's
// kMGPipeSurfaceKindNone makes Kind agree with it on a zero-initialised record. // kMGPipeSurfaceKindNone makes Kind agree with it on a zero-initialized record.
// Res stays the test - it is the one D-C1 fixes - and Kind is CROSS-CHECKED // Res stays the test - it is the one D-C1 fixes - and Kind is CROSS-CHECKED
// rather than consulted: the two are one statement the emitter has to make // rather than consulted: the two are one statement the emitter has to make
// twice, and a record where they disagree is a seam defect, not an empty point. // twice, and a record where they disagree is a seam defect, not an empty point.
@@ -569,7 +569,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
FramebufferAttachmentType pushedReadBuf = FramebufferAttachmentType::None; FramebufferAttachmentType pushedReadBuf = FramebufferAttachmentType::None;
if (!readSurfaceEmpty) { if (!readSurfaceEmpty) {
// EVERY FIELD THAT IDENTIFIES THE IMAGE, not the three v1 compared (m-4). // EVERY FIELD THAT IDENTIFIES THE IMAGE, not the three v1 compared (m-4).
// The same image legally sits at two colour points - a layered attachment of // The same image legally sits at two color points - a layered attachment of
// one array and a single slice of it are different surfaces of one Res - so a // one array and a single slice of it are different surfaces of one Res - so a
// partial comparison resolves to the lower index and the refusal branch below // partial comparison resolves to the lower index and the refusal branch below
// ("refusing to guess") would then be doing part of the guessing itself. // ("refusing to guess") would then be doing part of the guessing itself.
@@ -590,7 +590,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
if (!matched) { if (!matched) {
MGLOG_E_ONCE("MGPipe: framebuffer %u's resolved read surface {%u, %u} matches no " MGLOG_E_ONCE("MGPipe: framebuffer %u's resolved read surface {%u, %u} matches no "
"colour attachment of its own record - refusing to guess a read " "color attachment of its own record - refusing to guess a read "
"buffer", "buffer",
glNameForDiag, record.ReadSurface.Res.Slot, record.ReadSurface.Res.Gen); glNameForDiag, record.ReadSurface.Res.Slot, record.ReadSurface.Res.Gen);
return; return;
@@ -659,7 +659,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// fire the table would disagree with the glDrawBuffers the caller already issued, // fire the table would disagree with the glDrawBuffers the caller already issued,
// which is the exact non-injectivity this table exists to remove. // which is the exact non-injectivity this table exists to remove.
MOBILEGL_ASSERT(a < slotCount && !assigned[a], MOBILEGL_ASSERT(a < slotCount && !assigned[a],
"Draw buffer %u names colour attachment %u which is out of range or duplicated.", s, a); "Draw buffer %u names color attachment %u which is out of range or duplicated.", s, a);
if (a >= slotCount || assigned[a]) { if (a >= slotCount || assigned[a]) {
continue; continue;
} }
@@ -890,7 +890,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// 3. The attachments. Three gates, and the key is the object's rather than the // 3. The attachments. Three gates, and the key is the object's rather than the
// binding's: idGenerationMoved ("I re-minted a driver id, so a point may hold a // binding's: idGenerationMoved ("I re-minted a driver id, so a point may hold a
// dead one"), attachmentPointsMoved ("the colour permutation moved, so a point // dead one"), attachmentPointsMoved ("the color permutation moved, so a point
// whose record did not change still has to be re-issued") and the record hash. // whose record did not change still has to be re-issued") and the record hash.
// NOT m_syncedRecordHashes: the per-target array is the object arm's re-arm // NOT m_syncedRecordHashes: the per-target array is the object arm's re-arm
// trigger, and the points written below belong to the driver FBO rather than to // trigger, and the points written below belong to the driver FBO rather than to
@@ -910,7 +910,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Bool empty = const Bool empty =
surface.Kind == MG_Pipe::kMGPipeSurfaceKindNone || MG_Pipe::MGPipeHandleIsNull(surface.Res); surface.Kind == MG_Pipe::kMGPipeSurfaceKindNone || MG_Pipe::MGPipeHandleIsNull(surface.Res);
// The detach that keeps m_backendColorSlots a permutation of the PHYSICAL // The detach that keeps m_backendColorSlots a permutation of the PHYSICAL
// layout: a colour point whose record says empty must not keep the previous // layout: a color point whose record says empty must not keep the previous
// owner's image, or glReadBuffer returns it. Bounded by the driver's cap, // owner's image, or glReadBuffer returns it. Bounded by the driver's cap,
// because GL_COLOR_ATTACHMENTn above it is INVALID_ENUM. // because GL_COLOR_ATTACHMENTn above it is INVALID_ENUM.
if (isColorPoint && empty) { if (isColorPoint && empty) {
@@ -979,12 +979,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
GLenum BackendFramebufferObject::GetBackendAttachmentType(FramebufferAttachmentType frontendAtt) const { GLenum BackendFramebufferObject::GetBackendAttachmentType(FramebufferAttachmentType frontendAtt) const {
// Only colour attachments are ever relocated; depth/stencil, the default framebuffer's // Only color attachments are ever relocated; depth/stencil, the default framebuffer's
// FRONT/BACK names and None map straight through. // FRONT/BACK names and None map straight through.
if (frontendAtt < FramebufferAttachmentType::Color0 || frontendAtt > FramebufferAttachmentType::Color31) { if (frontendAtt < FramebufferAttachmentType::Color0 || frontendAtt > FramebufferAttachmentType::Color31) {
return MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(frontendAtt); return MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(frontendAtt);
} }
// The table is a permutation of the backend colour points, so this is the one point that // The table is a permutation of the backend color points, so this is the one point that
// owns this attachment. Searching the draw-buffer array instead returned the identity // owns this attachment. Searching the draw-buffer array instead returned the identity
// point for every attachment that was not a draw buffer - which is exactly the point a // point for every attachment that was not a draw buffer - which is exactly the point a
// relocated draw buffer had just taken over, so COLOR_ATTACHMENT0 read back the image of // relocated draw buffer had just taken over, so COLOR_ATTACHMENT0 read back the image of
@@ -237,7 +237,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The four hostBytes sites read the same in both builds. The non-split expansion is the // The four hostBytes sites read the same in both builds. The non-split expansion is the
// ORIGINAL EXPRESSION, character for character - `raw - offset` - so a push or verify build // ORIGINAL EXPRESSION, character for character - `raw - offset` - so a push or verify build
// compiles exactly what it compiled before R-11 and the split arm is the only new behaviour. // compiles exactly what it compiled before R-11 and the split arm is the only new behavior.
#define MGL_SERVER_STAGED_ADOPT(res, width, bytes, offset, size) \ #define MGL_SERVER_STAGED_ADOPT(res, width, bytes, offset, size) \
ServerStaged().Adopt(&(res), (width), (bytes), (offset), (size)) ServerStaged().Adopt(&(res), (width), (bytes), (offset), (size))
#define MGL_SERVER_STAGED_REQUIRE_PENDING(res, base, frontendSize, site) \ #define MGL_SERVER_STAGED_REQUIRE_PENDING(res, base, frontendSize, site) \
@@ -476,7 +476,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
namespace { namespace {
// One shape for all five, so a kind cannot grow a different liveness rule than its // One shape for all five, so a kind cannot grow a different liveness rule than its
// neighbours: index the applier's own dense table, refuse a slot it has not grown to, // neighbors: index the applier's own dense table, refuse a slot it has not grown to,
// refuse a record that is not Live, and refuse a generation that has moved on under // refuse a record that is not Live, and refuse a generation that has moved on under
// the caller. The last of the three is the one that matters: a stale handle resolving // the caller. The last of the three is the one that matters: a stale handle resolving
// to its successor's record is how a twin ends up describing another object's storage. // to its successor's record is how a twin ends up describing another object's storage.
@@ -417,7 +417,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// every draw of every program with an override would rebuild. Taking the client's number // every draw of every program with an override would rebuild. Taking the client's number
// verbatim removes that coupling: there is one authority, and the twin's stamp and the // verbatim removes that coupling: there is one authority, and the twin's stamp and the
// clean clause read the same field. The monolith arm's source carries the answer of the // clean clause read the same field. The monolith arm's source carries the answer of the
// function this replaces, computed from the frontend map, so its behaviour is unchanged. // function this replaces, computed from the frontend map, so its behavior is unchanged.
Uint64 ComputeShaderStorageBlockBindingSignature(const ProgramBuildSource& src) { Uint64 ComputeShaderStorageBlockBindingSignature(const ProgramBuildSource& src) {
return src.StorageOverrideSignature; return src.StorageOverrideSignature;
} }
@@ -888,8 +888,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const UnorderedMap<String, Int>& storageBlockBindingOverrides, const UnorderedMap<String, Int>& storageBlockBindingOverrides,
const std::map<String, String>& inputBlockRenames, const std::map<String, String>& outputBlockRenames, const std::map<String, String>& inputBlockRenames, const std::map<String, String>& outputBlockRenames,
const Bool stripInputBlockLocations, const Bool stripOutputBlockLocations, const Bool stripInputBlockLocations, const Bool stripOutputBlockLocations,
const Int atomicCounterEsslBindingTop, const Bool enableSpirvValidation, String& outSource, const Int atomicCounterEsslBindingTop, const Bool narrowIntegerSeedsArmed, const Bool enableSpirvValidation,
std::set<String>& outFlattenedXfbBlockNames, Vector<Int>& outAtomicCounterGlBindings, String& outSource, std::set<String>& outFlattenedXfbBlockNames, Vector<Int>& outAtomicCounterGlBindings,
String& outError) const { String& outError) const {
// ESSL cannot express gl_DrawID/gl_BaseInstance/gl_BaseVertex; demote them to // ESSL cannot express gl_DrawID/gl_BaseInstance/gl_BaseVertex; demote them to
// plain globals (mg_*) before handing the module to SPIRV-Cross. // plain globals (mg_*) before handing the module to SPIRV-Cross.
@@ -1022,7 +1022,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// GL 4.6 core table 23.53 requires GL_MAX_SAMPLES >= 4, so every multisample // GL 4.6 core table 23.53 requires GL_MAX_SAMPLES >= 4, so every multisample
// ceiling MobileGL advertises is floored to 4 no matter what the ES driver // ceiling MobileGL advertises is floored to 4 no matter what the ES driver
// reports - but the realised allocation cannot be, and // reports - but the realized allocation cannot be, and
// ClampSamplesToBackendSupport quietly gives an integer or depth multisample // ClampSamplesToBackendSupport quietly gives an integer or depth multisample
// texture the ONE sample Adreno and Mali actually support for it. A shader // texture the ONE sample Adreno and Mali actually support for it. A shader
// written against the advertised ceiling then fetches a sample that storage does // written against the advertised ceiling then fetches a sample that storage does
@@ -1050,24 +1050,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Util::ShaderTranspiler::ShaderCompiler::SplitArrayVertexInputsForEssl( MG_Util::ShaderTranspiler::ShaderCompiler::SplitArrayVertexInputsForEssl(
*effectiveSpirv, splitArrayInputSpirv, enableSpirvValidation) && *effectiveSpirv, splitArrayInputSpirv, enableSpirvValidation) &&
!splitArrayInputSpirv.empty() && splitArrayInputSpirv != *effectiveSpirv) { !splitArrayInputSpirv.empty() && splitArrayInputSpirv != *effectiveSpirv) {
// Only when the pass ACTUALLY split something. The optimizer hands back a // Only when the pass ACTUALLY split something: the optimizer hands back a
// re-serialised copy either way, and adopting that copy for every vertex // re-serialized copy either way, and that round trip is not free - it cost the
// shader would put every one of them through a round trip they do not need // minecraft-1.21.1-neoforge-create-indirect-in-world-align1024 retrace 0.15 SSIM.
// - which is not free: it cost the create-indirect retrace 0.15 SSIM the
// first time this gate was missing (the original capture; the case is now
// minecraft-1.21.1-neoforge-create-indirect-in-world-align1024).
effectiveSpirv = &splitArrayInputSpirv; effectiveSpirv = &splitArrayInputSpirv;
} }
// Adopt the rewritten module only when THIS stage actually had one of the // An armed vertex stage's narrow-integer seeds (NarrowIntegerSeeds.h), after the split
// blocks - the optimizer hands back a re-serialised copy either way, and taking // and adopted only when the pass rewrote the module. Keyed by narrowIntegerSeedsArmed.
// that copy for a module it did not rewrite is not free (it cost the Vector<unsigned int> narrowSeededSpirv;
// create-indirect retrace 0.15 SSIM when the array-input split first missed effectiveSpirv =
// this gate; the case is now SeedNarrowIntegerVertexInputsForEssl(narrowIntegerSeedsArmed && glShaderType == GL_VERTEX_SHADER,
// minecraft-1.21.1-neoforge-create-indirect-in-world-align1024). The report has to be *effectiveSpirv, narrowSeededSpirv, enableSpirvValidation);
// per stage, not cumulative: a fragment shader consuming the same block reports a
// name the vertex stage already reported, // Adopt the rewritten module only when THIS stage actually had one of the blocks, for
// and its own rewrite must still be taken or the two stages stop matching. // the array-input split's reason above. The report is per stage, not cumulative: a
// fragment shader consuming a block the vertex stage reported must still take its own
// rewrite or the two stages stop matching.
Vector<unsigned int> flattenedXfbSpirv; Vector<unsigned int> flattenedXfbSpirv;
if (!xfbCaptureBlockNames.empty()) { if (!xfbCaptureBlockNames.empty()) {
// Reported into a local first, and published only if the module is really // Reported into a local first, and published only if the module is really
@@ -1096,11 +1095,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Left outside, a second program sharing this stage's key would be served ESSL // Left outside, a second program sharing this stage's key would be served ESSL
// with the blocks un-renamed and the repair would silently stop working - the // with the blocks un-renamed and the repair would silently stop working - the
// same trap SetAtomicCounterBlockBindings sets one screen down. // same trap SetAtomicCounterBlockBindings sets one screen down.
// * AT THIS POSITION, because moving a SPIR-V pass in a chain is a behavioural // * AT THIS POSITION, because moving a SPIR-V pass in a chain is a behavioral
// change, and this one arrived device-verified on Mali. Hoisting it above the // change, and this one arrived device-verified on Mali. Hoisting it above the
// cache probe instead would have needed no key material at all (the rename would // cache probe instead would have needed no key material at all (the rename would
// already be in the module bytes the key hashes) and was rejected for that // already be in the module bytes the key hashes) and was rejected for that
// reason: it reorders the chain, and it would re-serialise the module on every // reason: it reorders the chain, and it would re-serialize the module on every
// build including the ones the memo is there to make free. // build including the ones the memo is there to make free.
// Its two rename maps are therefore KEY MATERIAL - see the caller. // Its two rename maps are therefore KEY MATERIAL - see the caller.
Vector<unsigned int> uniquifiedIoBlockSpirv; Vector<unsigned int> uniquifiedIoBlockSpirv;
@@ -1334,7 +1333,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The caller arms the two directions; both are false unless the driver POST // The caller arms the two directions; both are false unless the driver POST
// measured the defect AND this program has a stage that can hit it. Adopted only // measured the defect AND this program has a stage that can hit it. Adopted only
// when this stage really had a located block, for the reason the array-input split // when this stage really had a located block, for the reason the array-input split
// documents: the optimizer hands back a re-serialised copy either way. // documents: the optimizer hands back a re-serialized copy either way.
// //
// LAST IN THE CHAIN, and that position is load-bearing. Vulkan SPIR-V REQUIRES a // LAST IN THE CHAIN, and that position is load-bearing. Vulkan SPIR-V REQUIRES a
// Location on every user-defined Input/Output variable // Location on every user-defined Input/Output variable
@@ -1386,7 +1385,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// ES fixes a storage block's binding at link from its layout(binding=) qualifier // ES fixes a storage block's binding at link from its layout(binding=) qualifier
// and has no glShaderStorageBlockBinding to move it afterwards, so a rebinding // and has no glShaderStorageBlockBinding to move it afterwards, so a rebinding
// can only be honoured by printing it INTO the qualifier. Rewriting the Binding // can only be honored by printing it INTO the qualifier. Rewriting the Binding
// decoration before SPIRV-Cross emits is what does that; RemoveLayoutBinding // decoration before SPIRV-Cross emits is what does that; RemoveLayoutBinding
// then deliberately preserves the qualifier for `buffer` declarations. // then deliberately preserves the qualifier for `buffer` declarations.
if (!storageBlockBindingOverrides.empty()) { // empty for almost every program if (!storageBlockBindingOverrides.empty()) { // empty for almost every program
@@ -1517,12 +1516,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
return; return;
} }
// Mirrored from the neighbours rather than fixed: whether SPIRV-Cross redeclares // Mirrored from the neighbors rather than fixed: whether SPIRV-Cross redeclares
// gl_PerVertex, and with which members, depends on what the application's shaders // gl_PerVertex, and with which members, depends on what the application's shaders
// touched, and a synthesized stage that redeclares a DIFFERENT shape than the stage // touched, and a synthesized stage that redeclares a DIFFERENT shape than the stage
// it feeds is an ES link error against a program with no other problem. gl_in copies // it feeds is an ES link error against a program with no other problem. gl_in copies
// the vertex stage's OUT block (that is what arrives) and gl_out the evaluation // the vertex stage's OUT block (that is what arrives) and gl_out the evaluation
// stage's IN block (that is what is expected). A neighbour that redeclared nothing // stage's IN block (that is what is expected). A neighbor that redeclared nothing
// yields an empty list, which leaves the driver's own built-in declaration in place - // yields an empty list, which leaves the driver's own built-in declaration in place -
// which is exactly what matching it requires. // which is exactly what matching it requires.
const String inMembers = ExtractPerVertexBlockMembers(vertexStageEssl, /*input=*/false).value_or(String()); const String inMembers = ExtractPerVertexBlockMembers(vertexStageEssl, /*input=*/false).value_or(String());
@@ -1532,7 +1531,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
String source = String source =
BuildPassthroughTessControlEssl(ResolveBackendEsslVersion(), patchVertices, inMembers, outMembers, BuildPassthroughTessControlEssl(ResolveBackendEsslVersion(), patchVertices, inMembers, outMembers,
m_passthroughTessControlOuterLevel, m_passthroughTessControlInnerLevel); m_passthroughTessControlOuterLevel, m_passthroughTessControlInnerLevel);
// The mirrored member lists can carry gl_PointSize - the neighbour stage declared it, // The mirrored member lists can carry gl_PointSize - the neighbor stage declared it,
// so matching it is the whole point - and a redeclaration is exactly as illegal as a // so matching it is the whole point - and a redeclaration is exactly as illegal as a
// reference in ESSL without the extension. Same directive, same never-speculative // reference in ESSL without the extension. Same directive, same never-speculative
// rule as the per-stage loop; a driver with neither spelling gets nothing added and // rule as the per-stage loop; a driver with neither spelling gets nothing added and
@@ -1544,7 +1543,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
: nullptr; : nullptr;
source = RequestPointSizeExtension(Move(source), passthroughPointSizeExtension); source = RequestPointSizeExtension(Move(source), passthroughPointSizeExtension);
// Same for gl_ClipDistance/gl_CullDistance mirrored out of a vertex stage that // Same for gl_ClipDistance/gl_CullDistance mirrored out of a vertex stage that
// writes them: the neighbour could only compile with the extension, so it exists. // writes them: the neighbor could only compile with the extension, so it exists.
source = RequestClipCullDistanceExtension(Move(source)); source = RequestClipCullDistanceExtension(Move(source));
const GLuint backendShaderId = g_GLESFuncs.glCreateShader(GL_TESS_CONTROL_SHADER); const GLuint backendShaderId = g_GLESFuncs.glCreateShader(GL_TESS_CONTROL_SHADER);
@@ -1605,12 +1604,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
#ifdef TRACY_ENABLE #ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND); ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif #endif
// Recorded before either early return below, so Use() can always name the GL // Before either early return below, so Use() can name the GL program of a no-op draw.
// program a no-op draw belongs to - including the "linked but not drawable" exit.
m_frontendProgramId = src.GetExternalIndex(); m_frontendProgramId = src.GetExternalIndex();
// GetSpirvStatus() as well as GetLinkStatus(): a program whose phase-B job was // GetSpirvStatus() as well as GetLinkStatus(): a program whose phase-B job was
// cancelled (teardown) or whose optimizer run failed is fully linked and fully // canceled (teardown) or whose optimizer run failed is fully linked and fully
// queryable, but has no SPIR-V to build a driver program out of. GL cannot retract // queryable, but has no SPIR-V to build a driver program out of. GL cannot retract
// a LINK_STATUS it already reported true, so "linked but not drawable" is the // a LINK_STATUS it already reported true, so "linked but not drawable" is the
// answer, and this is where the ES backend expresses it. // answer, and this is where the ES backend expresses it.
@@ -1620,6 +1618,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
src.GetExternalIndex()); src.GetExternalIndex());
return; return;
} }
// After the return above, which keeps the previous driver program and the widths it holds.
BeginNarrowIntegerSeedBuild(m_narrowIntegerSeeds);
MGLOG_D("Syncing program to backend. State program ID: %u, Backend ID: %u", src.GetExternalIndex(), MGLOG_D("Syncing program to backend. State program ID: %u, Backend ID: %u", src.GetExternalIndex(),
m_backendProgramId); m_backendProgramId);
@@ -1636,9 +1636,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// this build current - the draw path compares the signature and rebuilds on a change. // this build current - the draw path compares the signature and rebuilds on a change.
const auto& storageBlockBindingOverrides = src.GetShaderStorageBlockBindingOverrides(); const auto& storageBlockBindingOverrides = src.GetShaderStorageBlockBindingOverrides();
m_shaderStorageBlockBindingSignature = ComputeShaderStorageBlockBindingSignature(src); m_shaderStorageBlockBindingSignature = ComputeShaderStorageBlockBindingSignature(src);
// Rebuilt by the transpile loop below, one entry per atomic-counter block it finds. // Rebuilt by the transpile loop below, one entry per atomic-counter block; the top is
// The top is snapshotted here so every stage of this program - and the draw path // snapshotted so every stage and the draw path resolve one slot per GL binding.
// reading it afterwards - resolves the same slot for the same GL binding.
m_atomicCounterGlBindings.clear(); m_atomicCounterGlBindings.clear();
m_atomicCounterEsslBindingTop = AtomicCounterEsslBindingTop(); m_atomicCounterEsslBindingTop = AtomicCounterEsslBindingTop();
// Re-established by AttachPassthroughTessControlStage below when this program needs // Re-established by AttachPassthroughTessControlStage below when this program needs
@@ -1774,7 +1773,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// //
// The repair is a rename keyed on the PRODUCING stage, planned here and applied // The repair is a rename keyed on the PRODUCING stage, planned here and applied
// per stage below so a producer and its consumer keep naming the same block. // per stage below so a producer and its consumer keep naming the same block.
// Gated twice over, because a re-serialised module is not free (it cost the // Gated twice over, because a re-serialized module is not free (it cost the
// create-indirect retrace 0.15 SSIM the first time the array-input split missed // create-indirect retrace 0.15 SSIM the first time the array-input split missed
// its gate; the case is now // its gate; the case is now
// minecraft-1.21.1-neoforge-create-indirect-in-world-align1024): only a tessellation // minecraft-1.21.1-neoforge-create-indirect-in-world-align1024): only a tessellation
@@ -1891,7 +1890,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// an output nothing can read. That is not merely useless: in a separable program // an output nothing can read. That is not merely useless: in a separable program
// pipeline the fragment stage lives in a DIFFERENT program, which never saw this // pipeline the fragment stage lives in a DIFFERENT program, which never saw this
// build and cannot be given a gate, so promoting there would hang an unmatched // build and cannot be given a gate, so promoting there would hang an unmatched
// varying off a program to buy nothing. Both cases keep the pre-emulation behaviour, // varying off a program to buy nothing. Both cases keep the pre-emulation behavior,
// which is what a program with no fragment stage had anyway. // which is what a program with no fragment stage had anyway.
const Bool programHasFragmentStage = const Bool programHasFragmentStage =
std::find(linkedStages.begin(), linkedStages.end(), ShaderStage::Fragment) != linkedStages.end(); std::find(linkedStages.begin(), linkedStages.end(), ShaderStage::Fragment) != linkedStages.end();
@@ -1975,7 +1974,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// KEYING ON THE RESOLVED MAPS rather than on what they were derived from // KEYING ON THE RESOLVED MAPS rather than on what they were derived from
// (collidingIoBlockNames, declaredIoBlockNames, stagePipelineIndices, this // (collidingIoBlockNames, declaredIoBlockNames, stagePipelineIndices, this
// stage's index) is deliberate: the maps ARE the pass's arguments, so they are // stage's index) is deliberate: the maps ARE the pass's arguments, so they are
// exactly as fine as the pass's behaviour and no finer. Two programs whose // exactly as fine as the pass's behavior and no finer. Two programs whose
// collision plans differ but whose maps for THIS stage come out identical really // collision plans differ but whose maps for THIS stage come out identical really
// do produce the same ESSL and should share the entry. // do produce the same ESSL and should share the entry.
// //
@@ -2045,6 +2044,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
esslKeyInputs.stripInputBlockLocations = stripInputBlockLocations; esslKeyInputs.stripInputBlockLocations = stripInputBlockLocations;
esslKeyInputs.stripOutputBlockLocations = stripOutputBlockLocations; esslKeyInputs.stripOutputBlockLocations = stripOutputBlockLocations;
esslKeyInputs.enableSpirvValidation = enableSpirvValidation; esslKeyInputs.enableSpirvValidation = enableSpirvValidation;
esslKeyInputs.narrowIntegerSeedsArmed = glShaderType == GL_VERTEX_SHADER && m_narrowIntegerSeeds.built;
auto& esslCache = MG_Util::ShaderTranspiler::GetEsslTranslationCache(); auto& esslCache = MG_Util::ShaderTranspiler::GetEsslTranslationCache();
MG_Util::ShaderTranspiler::TranslationCacheKey esslCacheKey; MG_Util::ShaderTranspiler::TranslationCacheKey esslCacheKey;
@@ -2070,9 +2070,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!TranspileSpirvToEssl(spirvCode, glShaderType, xfbCaptureBlockNames, imageFormatBake, if (!TranspileSpirvToEssl(spirvCode, glShaderType, xfbCaptureBlockNames, imageFormatBake,
storageBlockBindingOverrides, inputBlockRenames, outputBlockRenames, storageBlockBindingOverrides, inputBlockRenames, outputBlockRenames,
stripInputBlockLocations, stripOutputBlockLocations, stripInputBlockLocations, stripOutputBlockLocations,
m_atomicCounterEsslBindingTop, enableSpirvValidation, source, m_atomicCounterEsslBindingTop, esslKeyInputs.narrowIntegerSeedsArmed,
stageFlattenedXfbBlockNames, stageAtomicCounterGlBindings, enableSpirvValidation, source, stageFlattenedXfbBlockNames,
transpileError)) { stageAtomicCounterGlBindings, transpileError)) {
// MGLOG_E, unlatched, like the compile- and link-failure diagnostics // MGLOG_E, unlatched, like the compile- and link-failure diagnostics
// below: one line per failing stage is bounded by program count and // below: one line per failing stage is bounded by program count and
// naming the stage is the entire diagnostic value. A stage that never // naming the stage is the entire diagnostic value. A stage that never
@@ -2225,7 +2225,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// * AFTER RebindImageUniformsToFrontendUnits, so the binding it copies onto // * AFTER RebindImageUniformsToFrontendUnits, so the binding it copies onto
// both halves of a split image is already the frontend texture unit (and so // both halves of a split image is already the frontend texture unit (and so
// that pass never has to reason about the alias it introduces); // that pass never has to reason about the alias it introduces);
// * BEFORE RemoveLayoutBinding, whose keepBindingRegex recognises an image // * BEFORE RemoveLayoutBinding, whose keepBindingRegex recognizes an image
// declaration and preserves its binding - an image unit cannot be set from // declaration and preserves its binding - an image unit cannot be set from
// the API in ES, so the qualifier is the only binding mechanism there is, // the API in ES, so the qualifier is the only binding mechanism there is,
// and both halves of the pair have to still be carrying theirs when it runs. // and both halves of the pair have to still be carrying theirs when it runs.
@@ -2286,14 +2286,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
pos = source.find(findStr, pos); pos = source.find(findStr, pos);
} }
NoteNarrowIntegerSeedSource(m_narrowIntegerSeeds, source, glShaderType);
const char* sourceCStr = source.c_str(); const char* sourceCStr = source.c_str();
MGLOG_D("Setting shader source for backend shader ID: %u\nsrc:\n%s", backendShaderId, sourceCStr); MGLOG_D("Setting shader source for backend shader ID: %u\nsrc:\n%s", backendShaderId, sourceCStr);
g_GLESFuncs.glShaderSource(backendShaderId, 1, &sourceCStr, nullptr); g_GLESFuncs.glShaderSource(backendShaderId, 1, &sourceCStr, nullptr);
g_GLESFuncs.glCompileShader(backendShaderId); g_GLESFuncs.glCompileShader(backendShaderId);
// GL_FALSE, not GL_TRUE: an unwritten out-param must read as "compile failed" // GL_FALSE, not GL_TRUE: an unwritten out-param must read as "compile failed", never
// and take the diagnostic path, never as a silent success that attaches an // as a silent success that attaches an uncompiled shader.
// uncompiled shader.
GLint compileStatus = GL_FALSE; GLint compileStatus = GL_FALSE;
g_GLESFuncs.glGetShaderiv(backendShaderId, GL_COMPILE_STATUS, &compileStatus); g_GLESFuncs.glGetShaderiv(backendShaderId, GL_COMPILE_STATUS, &compileStatus);
if (compileStatus == GL_FALSE) { if (compileStatus == GL_FALSE) {
@@ -2639,7 +2639,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// arm - and building from an empty reflection would produce a program with no // arm - and building from an empty reflection would produce a program with no
// uniforms and no modules, i.e. a black screen with no marker anywhere. The // uniforms and no modules, i.e. a black screen with no marker anywhere. The
// create's own trip wire already refuses a record that declares no blobs AND // create's own trip wire already refuses a record that declares no blobs AND
// carries no artefacts (PipeApply.cpp); this is the same statement one level later, // carries no artifacts (PipeApply.cpp); this is the same statement one level later,
// where the twin can name the handle. // where the twin can name the handle.
if (!record->Archive) { if (!record->Archive) {
MGLOG_E_ONCE("MGPipe: shader CSO {%u, %u} carries no server-owned archive; its " MGLOG_E_ONCE("MGPipe: shader CSO {%u, %u} carries no server-owned archive; its "
@@ -2649,6 +2649,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_backendProgramUsable = false; m_backendProgramUsable = false;
return; return;
} }
// A capture program with integer inputs is armed at its first build, so arming never
// relinks a program in the middle of a capture (NarrowIntegerSeeds.h).
PrepareNarrowIntegerSeedBuild(m_narrowIntegerSeeds, *record);
SyncToBackendFromSource(ProgramArchiveSource::FromRecord(cso, *record)); SyncToBackendFromSource(ProgramArchiveSource::FromRecord(cso, *record));
// The two server-owned keys the draw path's clean condition reads instead of // The two server-owned keys the draw path's clean condition reads instead of
// GetLinkVersion()/GetImageUnitVersion(). BindingsSerial is the second because a // GetLinkVersion()/GetImageUnitVersion(). BindingsSerial is the second because a
@@ -2665,7 +2668,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// one location per ELEMENT, so a caller walking locations sees the same name // one location per ELEMENT, so a caller walking locations sees the same name
// repeatedly; this turns it back into "goku[k]". Anything that is not an array // repeatedly; this turns it back into "goku[k]". Anything that is not an array
// (or whose base location cannot be resolved) comes back unchanged, so the only // (or whose base location cannot be resolved) comes back unchanged, so the only
// behaviour that moves is the array case. // behavior that moves is the array case.
String SubscriptUniformNameForElement(const ProgramBuildSource& program, const String& name, String SubscriptUniformNameForElement(const ProgramBuildSource& program, const String& name,
Uint location) { Uint location) {
if (name.size() < 3 || name.compare(name.size() - 3, 3, "[0]") != 0) return name; if (name.size() < 3 || name.compare(name.size() - 3, 3, "[0]") != 0) return name;
@@ -2769,6 +2772,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
binding.lastAssignedMsSampleMax = 0; binding.lastAssignedMsSampleMax = 0;
m_samplerUniformBindings.push_back(binding); m_samplerUniformBindings.push_back(binding);
} }
FinishNarrowIntegerSeedBuild(m_narrowIntegerSeeds, m_backendProgramId, m_backendProgramUsable);
} }
void BackendProgramObjectImpl::Use() { void BackendProgramObjectImpl::Use() {
@@ -2790,7 +2794,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!m_backendProgramUsable) { if (!m_backendProgramUsable) {
// Every draw made with this program renders nothing and raises no GL error, so // Every draw made with this program renders nothing and raises no GL error, so
// without this line the only symptom is a framebuffer that kept its clear // without this line the only symptom is a framebuffer that kept its clear
// colour. Latched: the early return above only dedupes CONSECUTIVE binds, so an // color. Latched: the early return above only dedupes CONSECUTIVE binds, so an
// app alternating a healthy and a broken program would otherwise log every // app alternating a healthy and a broken program would otherwise log every
// single draw. Parked at MGLOG_I until the level ordering was fixed. // single draw. Parked at MGLOG_I until the level ordering was fixed.
MGLOG_E_ONCE("Backend program for GL program %u is unusable (a shader failed to transpile, " MGLOG_E_ONCE("Backend program for GL program %u is unusable (a shader failed to transpile, "
@@ -168,8 +168,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_cacheSamplerParameters.borderColorI != samplerParams.borderColorI || m_cacheSamplerParameters.borderColorI != samplerParams.borderColorI ||
m_cacheSamplerParameters.borderColorUI != samplerParams.borderColorUI || m_cacheSamplerParameters.borderColorUI != samplerParams.borderColorUI ||
m_cacheSamplerParameters.borderColorForm != samplerParams.borderColorForm) { m_cacheSamplerParameters.borderColorForm != samplerParams.borderColorForm) {
// Same gate as the texture-side border colour above, and the same reason for // Same gate as the texture-side border color above, and the same reason for
// branching on the form: an integer border colour must reach the driver through // branching on the form: an integer border color must reach the driver through
// glSamplerParameterIiv/Iuiv or an integer sampler reads the float's bit pattern // glSamplerParameterIiv/Iuiv or an integer sampler reads the float's bit pattern
// back instead of the value. // back instead of the value.
if (g_GLESCapabilities.SupportsTextureBorderClamp) { if (g_GLESCapabilities.SupportsTextureBorderClamp) {
@@ -212,7 +212,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
} }
// A three-channel format widened to four to keep a colour attachment renderable (see // A three-channel format widened to four to keep a color attachment renderable (see
// NormalizePixelFormat) is described to the driver as a four-component transfer, so the // NormalizePixelFormat) is described to the driver as a four-component transfer, so the
// three-component client data has to be repacked with an alpha of 1.0 - otherwise the // three-component client data has to be repacked with an alpha of 1.0 - otherwise the
// driver walks three texels' worth of data per four-texel row and the image shears. // driver walks three texels' worth of data per four-texel row and the image shears.
@@ -467,7 +467,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The transfer half of the image-format widening: an image-bindable texture whose ES // The transfer half of the image-format widening: an image-bindable texture whose ES
// storage was widened to a core carrier is described to the driver as a four-component // storage was widened to a core carrier is described to the driver as a four-component
// transfer, so its narrower client data has to be repacked the same way the three-channel // transfer, so its narrower client data has to be repacked the same way the three-channel
// colour-renderable widening repacks its own. // color-renderable widening repacks its own.
// //
// Three shapes, because the carriers come in three kinds. Most of them keep the frontend // Three shapes, because the carriers come in three kinds. Most of them keep the frontend
// format's component TYPE and only add channels, so padding the shadow out to four // format's component TYPE and only add channels, so padding the shadow out to four
@@ -836,7 +836,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// consume is a swap-and-pop - so every one of them is taken, used and dropped // consume is a swap-and-pop - so every one of them is taken, used and dropped
// inside one level's iteration, and never held across a consume. // inside one level's iteration, and never held across a consume.
if (pushedStorage != nullptr) { if (pushedStorage != nullptr) {
// THE METADATA RESPECIFY, HONOURED (ID-18 M4, review M-2). BindMask and // THE METADATA RESPECIFY, HONORED (ID-18 M4, review M-2). BindMask and
// ImageBindableHint are STICKY facts the client discovers AFTER allocation - a // ImageBindableHint are STICKY facts the client discovers AFTER allocation - a
// texture first bound as a shader image - and an IMMUTABLE texture never has a // texture first bound as a shader image - and an IMMUTABLE texture never has a
// later redefinition to carry them, so they arrive on a respecify that restates // later redefinition to carry them, so they arrive on a respecify that restates
@@ -868,7 +868,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// ONE compare replaces the whole cheap-gate trio AND the content version. The // ONE compare replaces the whole cheap-gate trio AND the content version. The
// applier bumps Serial on every respecify and every sub-data it applies to this // applier bumps Serial on every respecify and every sub-data it applies to this
// resource, which is exactly the union those four keys covered - without the // resource, which is exactly the union those four keys covered - without the
// coarse behaviour the sampling-resolution generation had, where any texture's // coarse behavior the sampling-resolution generation had, where any texture's
// shape churn re-opened every other texture's gate. // shape churn re-opened every other texture's gate.
// //
// The pending set is the second half and it is not redundant: a previous sync // The pending set is the second half and it is not redundant: a previous sync
@@ -2086,7 +2086,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// transport-gated - safe by a chain rather than by its own statement, which // transport-gated - safe by a chain rather than by its own statement, which
// is exactly the shape that cost the monolith arm 137 scenarios. Under a // is exactly the shape that cost the monolith arm 137 scenarios. Under a
// transport the conjunct is already implied by `pushedStorage != nullptr`, so // transport the conjunct is already implied by `pushedStorage != nullptr`, so
// this changes no behaviour and closes no door; it says the thing instead of // this changes no behavior and closes no door; it says the thing instead of
// depending on it. // depending on it.
const Bool texBufferByRecord = pushedStorage != nullptr && MGB_TEXTURE_HANDLE_ARM_OFF(*this) == false; const Bool texBufferByRecord = pushedStorage != nullptr && MGB_TEXTURE_HANDLE_ARM_OFF(*this) == false;
static const SharedPtr<MG_State::GLState::BufferObject> kNoFrontendBuffer{}; static const SharedPtr<MG_State::GLState::BufferObject> kNoFrontendBuffer{};
@@ -124,7 +124,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_forceTextureParamsResync = true; m_forceTextureParamsResync = true;
return; return;
} }
// P9 W2 (docs/archive/notes/p9/W2-REMINT.md): EVERY BYTE THIS RE-MINT REPLAYS IS ALREADY ON THE // P9 W2: EVERY BYTE THIS RE-MINT REPLAYS IS ALREADY ON THE
// SERVER. The GPU image holds what the GPU wrote (render targets, clears, copies, // SERVER. The GPU image holds what the GPU wrote (render targets, clears, copies,
// generated mips, image stores) and the staged store holds what the client uploaded; // generated mips, image stores) and the staged store holds what the client uploaded;
// the client is never asked, so no pull protocol exists. Reading only the store would // the client is never asked, so no pull protocol exists. Reading only the store would
@@ -461,7 +461,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// otherwise still claim those values are in force and SyncTextureParamsToBackend // otherwise still claim those values are in force and SyncTextureParamsToBackend
// would skip the whole pass on the unchanged params version, leaving the driver // would skip the whole pass on the unchanged params version, leaving the driver
// texture at defaults for the rest of its life. Latent for swizzle, LOD range and // texture at defaults for the rest of its life. Latent for swizzle, LOD range and
// border colour long before GL_DEPTH_STENCIL_TEXTURE_MODE joined them; the mode // border color long before GL_DEPTH_STENCIL_TEXTURE_MODE joined them; the mode
// makes it visible because falling back to the default silently samples the wrong // makes it visible because falling back to the default silently samples the wrong
// aspect rather than merely mis-filtering. // aspect rather than merely mis-filtering.
m_cacheLodRange = {0, 1000}; m_cacheLodRange = {0, 1000};
@@ -650,7 +650,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
#if MOBILEGL_BUILD_DISAGGREGATED #if MOBILEGL_BUILD_DISAGGREGATED
// SHARED IMAGES (docs/archive/notes/anland/plan-ahb-dmabuf.md). The client defined // SHARED IMAGES. The client defined
// level 0 as WxH RGBA8 with no data and then attached the image; the record's descriptor // level 0 as WxH RGBA8 with no data and then attached the image; the record's descriptor
// still says so, but the texels are the image's, so nothing below may allocate or upload. // still says so, but the texels are the image's, so nothing below may allocate or upload.
Bool BackendTextureObject::SyncSharedImageStorage(const MG_Pipe::MGPipeResourceRecord& record) { Bool BackendTextureObject::SyncSharedImageStorage(const MG_Pipe::MGPipeResourceRecord& record) {
@@ -835,7 +835,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// NO set_texture_params HAS BEEN APPLIED FOR THIS TEXTURE YET, which is not the same // NO set_texture_params HAS BEEN APPLIED FOR THIS TEXTURE YET, which is not the same
// thing as a malformed one and must not be read as one. ParamsSerial is the // thing as a malformed one and must not be read as one. ParamsSerial is the
// applier's own "have I ever stored a params record here" (it is bumped on every // applier's own "have I ever stored a params record here" (it is bumped on every
// set_texture_params and starts at 0), and Params is value-initialised beside it - // set_texture_params and starts at 0), and Params is value-initialized beside it -
// so on a texture the application has never given a parameter to, BuiltinSampler is // so on a texture the application has never given a parameter to, BuiltinSampler is
// null because the record was never written, not because a client wrote a null. // null because the record was never written, not because a client wrote a null.
// There is nothing to push in that state; SyncTextureParamsToBackend's own gate // There is nothing to push in that state; SyncTextureParamsToBackend's own gate
@@ -912,7 +912,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P4a (D-E1): the fifteen sampler values are SAMPLER state (GL 4.6 table 23.18) and // P4a (D-E1): the fifteen sampler values are SAMPLER state (GL 4.6 table 23.18) and
// Espryt pushes them with glTexParameter* onto the TEXTURE rather than with // Espryt pushes them with glTexParameter* onto the TEXTURE rather than with
// glBindSampler onto the unit - behaviour P4a preserves exactly. What changes is // glBindSampler onto the unit - behavior P4a preserves exactly. What changes is
// where the values and the "did they move" answer come from. // where the values and the "did they move" answer come from.
// //
// The whole arm choice is a preprocessor #if/#else rather than a runtime branch // The whole arm choice is a preprocessor #if/#else rather than a runtime branch
@@ -1055,7 +1055,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// independent of any binding - that is what closes D-E3's gap, where a texture that is // independent of any binding - that is what closes D-E3's gap, where a texture that is
// only a READ-framebuffer attachment reaches SyncMipmapsToBackend and nothing else. The // only a READ-framebuffer attachment reaches SyncMipmapsToBackend and nothing else. The
// FORMAT comes from the descriptor beside them, and the BORDER comes from the built-in // FORMAT comes from the descriptor beside them, and the BORDER comes from the built-in
// sampler's CSO (D-F4), because a border colour is sampler state. // sampler's CSO (D-F4), because a border color is sampler state.
#define MGB_TEXPARAM_LEVEL_RANGE \ #define MGB_TEXPARAM_LEVEL_RANGE \
(pushedRecord != nullptr ? UintVec2{static_cast<Uint>(pushedRecord->Params.BaseLevel), \ (pushedRecord != nullptr ? UintVec2{static_cast<Uint>(pushedRecord->Params.BaseLevel), \
static_cast<Uint>(pushedRecord->Params.MaxLevel)} \ static_cast<Uint>(pushedRecord->Params.MaxLevel)} \
@@ -1117,7 +1117,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool borderColorReadable = true; Bool borderColorReadable = true;
pushedRecord = ResolvePushedTextureParams(stateTextureObject); pushedRecord = ResolvePushedTextureParams(stateTextureObject);
if (pushedRecord == nullptr) return; if (pushedRecord == nullptr) return;
// D-F4: the border colour is SAMPLER state (GL 4.6 table 23.18) and lives on the // D-F4: the border color is SAMPLER state (GL 4.6 table 23.18) and lives on the
// built-in sampler's CSO, not on MGPTextureParams - P4a deliberately does not // built-in sampler's CSO, not on MGPTextureParams - P4a deliberately does not
// put SamplerParameters on the wire twice. All FOUR comparands (float, int, // put SamplerParameters on the wire twice. All FOUR comparands (float, int,
// uint, form) still cross and are still all compared below, because two integer // uint, form) still cross and are still all compared below, because two integer
@@ -1129,7 +1129,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} else { } else {
borderColorReadable = false; borderColorReadable = false;
MGLOG_E_ONCE("MGPipe: texture %u's built-in sampler CSO {%u, %u} has no applier " MGLOG_E_ONCE("MGPipe: texture %u's built-in sampler CSO {%u, %u} has no applier "
"record, so its border colour cannot be pushed", "record, so its border color cannot be pushed",
TextureDiagName(stateTextureObject, pushedRecord), TextureDiagName(stateTextureObject, pushedRecord),
pushedRecord->Params.BuiltinSampler.Slot, pushedRecord->Params.BuiltinSampler.Gen); pushedRecord->Params.BuiltinSampler.Slot, pushedRecord->Params.BuiltinSampler.Gen);
} }
@@ -1185,7 +1185,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str());
}); });
// A three-channel format widened to four to keep the image colour-renderable (see // A three-channel format widened to four to keep the image color-renderable (see
// NormalizePixelFormat) gains an alpha channel the frontend format does not have, and // NormalizePixelFormat) gains an alpha channel the frontend format does not have, and
// whatever the draw that filled it wrote there is not what GL would report: a format // whatever the draw that filled it wrote there is not what GL would report: a format
// without alpha reads back as 1.0. Answer the ALPHA swizzle source with ONE so the // without alpha reads back as 1.0. Answer the ALPHA swizzle source with ONE so the
@@ -1246,7 +1246,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// GL_TEXTURE_BORDER_COLOR needs ES 3.2 or EXT/OES_texture_border_clamp; on a driver // GL_TEXTURE_BORDER_COLOR needs ES 3.2 or EXT/OES_texture_border_clamp; on a driver
// without it every such call is INVALID_ENUM, so the parameter is simply not synced. // without it every such call is INVALID_ENUM, so the parameter is simply not synced.
// //
// The FORM has to be forwarded along with the value. A border colour set through // The FORM has to be forwarded along with the value. A border color set through
// glTexParameterIiv/Iuiv is an integer one, and an isampler2D/usampler2D fetch of the // glTexParameterIiv/Iuiv is an integer one, and an isampler2D/usampler2D fetch of the
// border returns whatever the driver's integer border register holds - so pushing it // border returns whatever the driver's integer border register holds - so pushing it
// through glTexParameterfv handed the driver float 255.0 and the shader read back // through glTexParameterfv handed the driver float 255.0 and the shader read back
@@ -1259,7 +1259,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// leave the driver holding the first value forever. // leave the driver holding the first value forever.
// m-3: GATED, because on the handle arm an unreadable border means the CSO record is // m-3: GATED, because on the handle arm an unreadable border means the CSO record is
// missing and the block below is skipped with the refusal already named - so v1's // missing and the block below is skipped with the refusal already named - so v1's
// unconditional initialiser was a live frontend read on a switched-over path whose // unconditional initializer was a live frontend read on a switched-over path whose
// value nothing then used. The value a skipped block sees is the cache's own, which // value nothing then used. The value a skipped block sees is the cache's own, which
// cannot make the condition true. // cannot make the condition true.
const auto borderColorForm = borderColorReadable ? MGB_TEXPARAM_BORDER_FORM : m_cacheBorderColorForm; const auto borderColorForm = borderColorReadable ? MGB_TEXPARAM_BORDER_FORM : m_cacheBorderColorForm;
@@ -1438,7 +1438,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// ===================================================================================== // =====================================================================================
// P5e SEAMS: DECLARED AND REFUSED HERE, BODIED BY THE FAMILY PACKAGES // P5e SEAMS: DECLARED AND REFUSED HERE, BODIED BY THE FAMILY PACKAGES
// (docs/archive/contracts/CONTRACT-P5E.md §4.2; BRIEF-P5E §1's "each such seam is a declared signature // (the contract §4.2; BRIEF-P5E §1's "each such seam is a declared signature
// in c0e so both sides compile from day one") // in c0e so both sides compile from day one")
// ===================================================================================== // =====================================================================================
// //
@@ -422,7 +422,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// SyncZeroStrideAttribute on the wire views: the one spelling that can carry a resolved // SyncZeroStrideAttribute on the wire views: the one spelling that can carry a resolved
// stride of zero, which glVertexAttribPointer's zero means the opposite of. // stride of zero, which glVertexAttribPointer's zero means the opposite of.
inline Bool SyncZeroStrideAttributeByHandle(Uint attribIndex, const MGPVertexAttribWire& attrib, inline Bool SyncZeroStrideAttributeByHandle(Uint attribIndex, const MGPVertexAttribWire& attrib,
const MG_Pipe::MGPVertexBuffer& binding) { const MG_Pipe::MGPVertexBuffer& binding, GLenum issuedType) {
if (MG_Pipe::MGPipeHandleIsNull(binding.Res)) { if (MG_Pipe::MGPipeHandleIsNull(binding.Res)) {
MGLOG_W_ONCE("Zero-stride attribute %u has no bound buffer, skipping.", attribIndex); MGLOG_W_ONCE("Zero-stride attribute %u has no bound buffer, skipping.", attribIndex);
return false; return false;
@@ -438,9 +438,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)), MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)),
attrib.Normalized ? GL_TRUE : GL_FALSE, 0); attrib.Normalized ? GL_TRUE : GL_FALSE, 0);
} else { } else {
g_GLESFuncs.glVertexAttribIFormat(attribIndex, static_cast<GLint>(attrib.Size), g_GLESFuncs.glVertexAttribIFormat(attribIndex, static_cast<GLint>(attrib.Size), issuedType, 0);
MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)),
0);
} }
g_GLESFuncs.glVertexAttribBinding(attribIndex, attribIndex); g_GLESFuncs.glVertexAttribBinding(attribIndex, attribIndex);
// The resolved offset goes on the binding point, not into a relative offset (the // The resolved offset goes on the binding point, not into a relative offset (the
@@ -506,7 +504,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// as the record of what was last // as the record of what was last
// EMITTED, which is what the next // EMITTED, which is what the next
// sync has to correct // sync has to correct
void BackendVertexArrayObject::SyncToBackendFromApplier() { void BackendVertexArrayObject::SyncToBackendFromApplier(const IntegerAttributePlan& plan) {
#ifdef TRACY_ENABLE #ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND); ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif #endif
@@ -545,7 +543,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// same whichever side resolved it first - it is idempotent. // same whichever side resolved it first - it is idempotent.
const Uint32 fetchBaseInstance = BackendUsesNativeBaseInstance() ? 0u : st.VertexFetchBaseInstance; const Uint32 fetchBaseInstance = BackendUsesNativeBaseInstance() ? 0u : st.VertexFetchBaseInstance;
const Bool baseInstanceDirty = m_syncedFetchBaseInstance != fetchBaseInstance; const Bool baseInstanceDirty = m_syncedFetchBaseInstance != fetchBaseInstance;
const Bool emitAttributes = attributesDirty || baseInstanceDirty || m_hasConvertedAttribute; // The integer arrays to issue with the other signedness; only a pointer whose type flips
// is re-issued, through the shadow below.
const Uint32 retypeHeld = HeldIntegerRetype(m_syncedRetypeMask, plan);
const Bool emitAttributes =
attributesDirty || baseInstanceDirty || m_hasConvertedAttribute || retypeHeld != m_syncedRetypeMask;
if (!emitAttributes && !indexBufferDirty) { if (!emitAttributes && !indexBufferDirty) {
return; return;
} }
@@ -595,7 +597,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// already returned. A buffers-only walk leaves the flags as they are (above). // already returned. A buffers-only walk leaves the flags as they are (above).
if (!buffersOnly) HostSetEnabled(attribIndex, attrib.Enabled != 0); if (!buffersOnly) HostSetEnabled(attribIndex, attrib.Enabled != 0);
// The fp64 narrowing, verbatim in behaviour including the Adreno workaround: // The fp64 narrowing, verbatim in behavior including the Adreno workaround:
// when no float32 stream can be built the array is DISABLED rather than left // when no float32 stream can be built the array is DISABLED rather than left
// enabled with no pointer, which is what the driver turns into a SIGSEGV inside // enabled with no pointer, which is what the driver turns into a SIGSEGV inside
// the next draw (KHR-GL43.vertex_attrib_binding.basic-input-case4). IsLong is // the next draw (KHR-GL43.vertex_attrib_binding.basic-input-case4). IsLong is
@@ -649,7 +651,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The binding API rewrote this attribute's format and binding: whatever the // The binding API rewrote this attribute's format and binding: whatever the
// shadow said about its pointer no longer holds. // shadow said about its pointer no longer holds.
m_hostPointer[attribIndex].valid = false; m_hostPointer[attribIndex].valid = false;
if (!SyncZeroStrideAttributeByHandle(attribIndex, attrib, *binding)) { const GLenum issuedType =
IntegerAttribTypeForInput(MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)),
attrib.IsInteger && ((retypeHeld >> attribIndex) & 1u));
if (!SyncZeroStrideAttributeByHandle(attribIndex, attrib, *binding, issuedType)) {
continue; continue;
} }
// No shift here on purpose: a zero stride never advances. Binding index == // No shift here on purpose: a zero stride never advances. Binding index ==
@@ -674,7 +679,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
const SizeT fetchOffset = static_cast<SizeT>(attrib.Offset) + const SizeT fetchOffset = static_cast<SizeT>(attrib.Offset) +
BaseInstanceByteShiftWire(attrib.Stride, divisor, fetchBaseInstance); BaseInstanceByteShiftWire(attrib.Stride, divisor, fetchBaseInstance);
const GLenum glType = MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)); const GLenum glType =
IntegerAttribTypeForInput(MG_Util::ConvertDataTypeToGLEnum(static_cast<DataType>(attrib.Type)),
attrib.IsInteger && ((retypeHeld >> attribIndex) & 1u));
const GLint glSize = static_cast<GLint>(attrib.Size); const GLint glSize = static_cast<GLint>(attrib.Size);
// The pointer the driver VAO already holds for this attribute - the buffer it // The pointer the driver VAO already holds for this attribute - the buffer it
@@ -759,6 +766,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
if (emitAttributes) { if (emitAttributes) {
m_syncedFetchBaseInstance = fetchBaseInstance; m_syncedFetchBaseInstance = fetchBaseInstance;
m_syncedRetypeMask = retypeHeld;
m_lastWalkWasPlain = walkWasPlain; m_lastWalkWasPlain = walkWasPlain;
} }
m_syncedBufferIdGeneration = currentBufferIdGeneration; m_syncedBufferIdGeneration = currentBufferIdGeneration;
@@ -923,7 +931,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
reinterpret_cast<const void*>(BaseInstanceByteShift(attrib, fetchBaseInstance))); reinterpret_cast<const void*>(BaseInstanceByteShift(attrib, fetchBaseInstance)));
} else { } else {
g_GLESFuncs.glVertexAttribIPointer( g_GLESFuncs.glVertexAttribIPointer(
attribIndex, attrib.Size, MG_Util::ConvertDataTypeToGLEnum(attrib.Type), attribIndex, attrib.Size,
IntegerAttribTypeForInput(MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
((m_syncedRetypeMask >> attribIndex) & 1u) != 0),
static_cast<GLsizei>(stride), static_cast<GLsizei>(stride),
// See the pointer call above: the divisor'd // See the pointer call above: the divisor'd
// arrays' baseInstance shift is the pointer. // arrays' baseInstance shift is the pointer.
@@ -971,7 +981,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// object - that absence is the design, not a gap in it (ARCHITECTURE.md 4.2). The // object - that absence is the design, not a gap in it (ARCHITECTURE.md 4.2). The
// two ways to keep the site here would each break something D-N or D-J protects: a // two ways to keep the site here would each break something D-N or D-J protects: a
// handle -> object map is the very thing the split removes, and pulling the bytes // handle -> object map is the very thing the split removes, and pulling the bytes
// eagerly on the client at every draw is new behaviour and new cost. // eagerly on the client at every draw is new behavior and new cost.
// //
// SO THE DEVIATION IS DECLARED, WITH ITS BLAST RADIUS. Under the default mask a // SO THE DEVIATION IS DECLARED, WITH ITS BLAST RADIUS. Under the default mask a
// 64-bit vertex array whose SOURCE buffer was written by a shader and not yet pulled // 64-bit vertex array whose SOURCE buffer was written by a shader and not yet pulled
@@ -1094,7 +1104,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// source type and `normalized` (GL accepts BGRA only normalized, and a packed type's // source type and `normalized` (GL accepts BGRA only normalized, and a packed type's
// signedness is the type's own). The narrowed stream is float32, and `normalized` is // signedness is the type's own). The narrowed stream is float32, and `normalized` is
// deliberately GL_FALSE there rather than attrib.Normalized: GL ignores it for // deliberately GL_FALSE there rather than attrib.Normalized: GL ignores it for
// floating-point array types, and honouring it would scale the fetched values. // floating-point array types, and honoring it would scale the fetched values.
const GLint convertedSize = bgra ? 4 : static_cast<GLint>(attrib.Size); const GLint convertedSize = bgra ? 4 : static_cast<GLint>(attrib.Size);
const GLenum convertedType = bgra ? MG_Util::ConvertDataTypeToGLEnum(sourceType) : GL_FLOAT; const GLenum convertedType = bgra ? MG_Util::ConvertDataTypeToGLEnum(sourceType) : GL_FLOAT;
const GLboolean convertedNormalized = (bgra && attrib.Normalized) ? GL_TRUE : GL_FALSE; const GLboolean convertedNormalized = (bgra && attrib.Normalized) ? GL_TRUE : GL_FALSE;
+5 -5
View File
@@ -87,7 +87,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
// The flattening dispatch is one invocation per output index. ES 3.1 only // The flattening dispatch is one invocation per output index. ES 3.1 only
// guarantees 65535 work groups per dimension, and exceeding it makes // guarantees 65535 work groups per dimension, and exceeding it makes
// glDispatchCompute an INVALID_VALUE no-op - which would leave the draw reading an // glDispatchCompute an INVALID_VALUE no-op - which would leave the draw reading an
// uninitialised index buffer rather than failing visibly. Cap the tier there // uninitialized index buffer rather than failing visibly. Cap the tier there
// instead of querying: 4.19M indices is far past any real multi-draw batch, and // instead of querying: 4.19M indices is far past any real multi-draw batch, and
// beyond it the per-sub-draw tiers are the better answer anyway. // beyond it the per-sub-draw tiers are the better answer anyway.
constexpr SizeT kComputeWorkGroupSize = 64; constexpr SizeT kComputeWorkGroupSize = 64;
@@ -239,7 +239,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
// ---- P5e (ra2), ID-136: THE SEAT THIS FILE'S OWN RULE HAD MISSED ------------------- // ---- P5e (ra2), ID-136: THE SEAT THIS FILE'S OWN RULE HAD MISSED -------------------
// //
// This was the LAST unguarded frontend read on the multi-draw apply path, and it sat // This was the LAST unguarded frontend read on the multi-draw apply path, and it sat
// twenty lines above the function that retired its neighbour. It asked // twenty lines above the function that retired its neighbor. It asked
// `gPipeInputs->GetBufferBindingSlot(BufferTarget::DrawIndirect)` and then // `gPipeInputs->GetBufferBindingSlot(BufferTarget::DrawIndirect)` and then
// `EnsureBufferResource(<frontend object>)` - a registry lookup keyed by the client's // `EnsureBufferResource(<frontend object>)` - a registry lookup keyed by the client's
// identity, which is §4.4's rule and not only the allocator's. Under run-ahead that is // identity, which is §4.4's rule and not only the allocator's. Under run-ahead that is
@@ -524,7 +524,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
std::memcpy(&value, source + i * 4, sizeof(value)); std::memcpy(&value, source + i * 4, sizeof(value));
break; break;
} }
// Unsigned wraparound is the defined behaviour for a negative base vertex. // Unsigned wraparound is the defined behavior for a negative base vertex.
out[i] = (restartActive && value == restartSentinel) ? 0xFFFFFFFFu : value + baseVertexBits; out[i] = (restartActive && value == restartSentinel) ? 0xFFFFFFFFu : value + baseVertexBits;
} }
} }
@@ -789,7 +789,7 @@ void main() {
} }
uint localIndex = outIndex - (low == 0u ? 0u : drawInfo[(low - 1u) * 3u + 2u]); uint localIndex = outIndex - (low == 0u ? 0u : drawInfo[(low - 1u) * 3u + 2u]);
// Unsigned wraparound is the defined behaviour for a negative base vertex. No // Unsigned wraparound is the defined behavior for a negative base vertex. No
// restart sentinel handling: the tier declines outright while restart is enabled. // restart sentinel handling: the tier declines outright while restart is enabled.
flatIndices[outIndex] = ReadSourceIndex(localIndex + drawInfo[low * 3u]) + drawInfo[low * 3u + 1u]; flatIndices[outIndex] = ReadSourceIndex(localIndex + drawInfo[low * 3u]) + drawInfo[low * 3u + 1u];
} }
@@ -1072,7 +1072,7 @@ void main() {
GLsizei drawcount, const GLint* basevertex) { GLsizei drawcount, const GLint* basevertex) {
if (drawcount <= 0 || !count || !indices) return; if (drawcount <= 0 || !count || !indices) return;
// Read before any GL work, because it decides the tier below: a desktop restart index // Read before any GL work, because it decides the tier below: a desktop restart index
// the driver does not know about can only be honoured by the tier that rewrites the // the driver does not know about can only be honored by the tier that rewrites the
// index stream (see ResolveTierForBatch). A restart index this index type cannot hold // index stream (see ResolveTierForBatch). A restart index this index type cannot hold
// needs no rewrite at all - nothing can match it - but it does need the driver's own // needs no rewrite at all - nothing can match it - but it does need the driver's own
// fixed-index restart held off for the batch, which is what the scope below does. // fixed-index restart held off for the batch, which is what the scope below does.
@@ -0,0 +1,189 @@
// MobileGL - MobileGL/MG_Backend/DirectGLES/NarrowIntegerSeeds.cpp
// Copyright (c) 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 "NarrowIntegerSeeds.h"
#include "DirectGLESInternal.h"
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
namespace MobileGL::MG_Backend::DirectGLES {
namespace {
using MG_Util::ShaderTranspiler::kNarrowIntegerWidths16GlobalName;
using MG_Util::ShaderTranspiler::kNarrowIntegerWidths8GlobalName;
// The twin a handle already has, without creating one: a program that was never built has
// no seeds to refuse.
PrgramImpl::BackendProgramObjectImpl* ExistingProgramTwin(MG_Pipe::MGPipeHandle program) {
auto* slot = PrgramImpl::g_backendProgramObjects.FindByHandle(program);
return slot != nullptr ? slot->get() : nullptr;
}
// Whether the program's build for `record`'s link refused the seeds; a link not built yet
// is presumed to take them.
Bool SeedsRefused(MG_Pipe::MGPipeHandle program, const MG_Pipe::MGPipeShaderCsoRecord& record) {
auto* twin = ExistingProgramTwin(program);
return twin != nullptr && twin->NarrowIntegerSeeds().RefusedAt(record.Serial);
}
String UniformDeclaration(const char* name) {
return String("uniform highp uint ") + name + ";";
}
} // namespace
IntegerVertexInputMasks IntegerVertexInputsOf(const MG_Pipe::MGPipeShaderCsoRecord& record) {
auto& memo = record.BackendIntegerInputs;
if (!memo.Valid || memo.Serial != record.Serial) {
IntegerVertexInputMasks inputs;
if (record.Archive) {
// Indexed by location, an empty name being no active input: the same reading
// SyncCurrentVertexAttributeValues makes of these two vectors.
const auto& link = record.Archive->Link;
const SizeT count = std::min<SizeT>(std::min(link.attribs.size(), link.attribTypes.size()), 32);
for (SizeT location = 0; location < count; ++location) {
if (!link.attribs[location].empty())
inputs.Note(static_cast<Uint32>(location), link.attribTypes[location]);
}
}
memo.SignedInputs = inputs.signedInputs;
memo.UnsignedInputs = inputs.unsignedInputs;
memo.Serial = record.Serial;
memo.Valid = true;
}
IntegerVertexInputMasks inputs;
inputs.signedInputs = memo.SignedInputs;
inputs.unsignedInputs = memo.UnsignedInputs;
return inputs;
}
IntegerAttributePlan ResolveIntegerAttributePlanForDraw(MG_Pipe::MGPipeHandle program) {
if (MG_Pipe::MGPipeHandleIsNull(program)) return {};
const MG_Pipe::MGPipeShaderCsoRecord* const record = PipeShaderCsoRecordForHandle(program);
if (record == nullptr || !record->Archive) return {};
const IntegerVertexInputMasks inputs = IntegerVertexInputsOf(*record);
Uint32 remaining = inputs.signedInputs | inputs.unsignedInputs;
if (remaining == 0) return {};
const auto* elements = VertexArrayImpl::BoundVertexElementsRecord(MG_Pipe::MGPipeApplier());
if (elements == nullptr) return {};
IntegerAttributeDataMasks data;
for (; remaining != 0; remaining &= remaining - 1) {
const Uint32 location = static_cast<Uint32>(std::countr_zero(remaining));
const auto& attrib = elements->Attributes[location];
if (attrib.Enabled && attrib.IsInteger && !attrib.IsLong)
data.Note(location, static_cast<DataType>(attrib.Type));
}
// The twin is looked up only for a plan that needs seeds; most integer draws need none.
const IntegerAttributePlan plan = ResolveIntegerAttributePlan(inputs, data, true);
return plan.NeedsSeeds() && SeedsRefused(program, *record) ? plan.WithoutSeeds() : plan;
}
Bool DropRefusedNarrowIntegerSeeds(IntegerAttributePlan& plan, MG_Pipe::MGPipeHandle program) {
if (!plan.NeedsSeeds()) return false;
const MG_Pipe::MGPipeShaderCsoRecord* const record = PipeShaderCsoRecordForHandle(program);
if (record == nullptr || !SeedsRefused(program, *record)) return false;
plan = plan.WithoutSeeds();
return true;
}
void PrepareNarrowIntegerSeedBuild(NarrowIntegerSeedState& state, const MG_Pipe::MGPipeShaderCsoRecord& record) {
const IntegerVertexInputMasks inputs = IntegerVertexInputsOf(record);
state.PrepareBuild(record.Serial, (inputs.signedInputs | inputs.unsignedInputs) != 0,
record.Archive && !record.Archive->Link.xfbVaryings.empty());
}
void PushNarrowIntegerWidthsForDraw(const IntegerAttributePlan& plan, MG_Pipe::MGPipeHandle program) {
// Only the twin this draw's sync stashed, and only while it is the program Use() bound: a
// uniform call with program 0, or on another program, is an error or a wrong write.
if (PrgramImpl::g_currentDrawProgramHandle != program) return;
auto* twin = PrgramImpl::g_currentDrawBackendProgram;
if (twin == nullptr || !twin->IsBackendProgramUsable() ||
PrgramImpl::g_lastUsedBackendProgramId != twin->GetBackendProgramId())
return;
NarrowIntegerSeedState& state = twin->NarrowIntegerSeeds();
if (!state.available) return;
if (state.pushed8 != plan.widths8) {
g_GLESFuncs.glUniform1ui(state.widths8Location, plan.widths8);
state.pushed8 = plan.widths8;
}
if (state.pushed16 != plan.widths16) {
g_GLESFuncs.glUniform1ui(state.widths16Location, plan.widths16);
state.pushed16 = plan.widths16;
}
}
void BeginNarrowIntegerSeedBuild(NarrowIntegerSeedState& state) {
state.built = state.Target();
state.declared = false;
state.available = false;
state.widths8Location = -1;
state.widths16Location = -1;
state.pushed8 = 0;
state.pushed16 = 0;
}
void NoteNarrowIntegerSeedSource(NarrowIntegerSeedState& state, const String& source, GLenum shaderType) {
if (!state.built || shaderType != GL_VERTEX_SHADER) return;
state.declared = source.find(UniformDeclaration(kNarrowIntegerWidths8GlobalName)) != String::npos &&
source.find(UniformDeclaration(kNarrowIntegerWidths16GlobalName)) != String::npos;
}
void FinishNarrowIntegerSeedBuild(NarrowIntegerSeedState& state, GLuint program, Bool programUsable) {
if (!state.built) return;
if (!programUsable) {
// The seeds may be why the build failed; the next build of this link goes without them.
state.disabled = true;
MGLOG_E_ONCE("DirectGLES: a program built with narrow integer seeds did not link; it is rebuilt "
"without them, and its 8/16-bit integer attributes keep the driver's own value");
return;
}
if (state.declared) {
state.widths8Location = g_GLESFuncs.glGetUniformLocation(program, kNarrowIntegerWidths8GlobalName);
state.widths16Location = g_GLESFuncs.glGetUniformLocation(program, kNarrowIntegerWidths16GlobalName);
}
state.available = state.widths8Location >= 0 && state.widths16Location >= 0;
if (!state.available) {
MGLOG_E_ONCE("DirectGLES: a program built with narrow integer seeds has no width uniforms (%s); its "
"8/16-bit integer attributes keep the driver's own value",
state.declared ? "the link dropped them" : "the vertex stage does not declare them");
}
}
const Vector<unsigned int>* SeedNarrowIntegerVertexInputsForEssl(Bool armed, const Vector<unsigned int>& spirv,
Vector<unsigned int>& storage,
Bool enableSpirvValidation) {
if (!armed) return &spirv;
Bool changed = false;
if (!MG_Util::ShaderTranspiler::ShaderCompiler::ExtendNarrowIntegerVertexInputsForEssl(spirv, storage, changed,
enableSpirvValidation)) {
MGLOG_E_ONCE("DirectGLES: the narrow integer seed pass declined a vertex stage; it keeps the driver's "
"own value for 8/16-bit integer attributes");
return &spirv;
}
return changed && !storage.empty() ? &storage : &spirv;
}
String PromoteNarrowIntegerWidthGlobals(String source) {
for (const char* name : {kNarrowIntegerWidths8GlobalName, kNarrowIntegerWidths16GlobalName}) {
// The zero-initialized Private global is printed with or without a precision qualifier;
// only a declaration starting its own line is the global itself.
for (const char* prefix : {"highp uint ", "mediump uint ", "lowp uint ", "uint "}) {
Bool promoted = false;
for (const char* initializer : {" = 0u;", ";"}) {
const String declaration = String(prefix) + name + initializer;
const SizeT pos = source.find(declaration);
if (pos == String::npos || (pos != 0 && source[pos - 1] != '\n')) continue;
source.replace(pos, declaration.size(), UniformDeclaration(name));
promoted = true;
break;
}
if (promoted) break;
}
}
return source;
}
} // namespace MobileGL::MG_Backend::DirectGLES
@@ -0,0 +1,97 @@
// MobileGL - MobileGL/MG_Backend/DirectGLES/NarrowIntegerSeeds.h
// Copyright (c) 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 "IntegerAttributePlan.h"
#include <Includes.h>
#include <MG_Pipe/MGPipeHandles.h>
#include <MG_Pipe/PipeApply.h>
// The program half of IntegerAttributePlan: the two width uniforms a program carries once some
// draw has fed it 8/16-bit data of the other signedness, and the per-draw plan that drives them.
//
// A program is built with the seeds only when it needs them ("armed"), so a program that never
// meets such data keeps its ESSL byte for byte. Arming is sticky for the program object's
// lifetime, so data alternating in signedness rebuilds it at most once and from then on moves two
// uniforms. A program that captures transform feedback and has integer inputs is armed at its
// first build, so arming never relinks a program in the middle of a capture.
//
// Every failure falls back to the values the driver gives today: a build whose ESSL lacks either
// uniform, or whose link dropped one, is "refused" and its draws keep the application's type for
// 8/16-bit data; a build that fails outright disables arming until the application relinks the
// object, and it is rebuilt without the seeds. A relink is presumed to arm until its build says no.
namespace MobileGL::MG_Backend::DirectGLES {
struct NarrowIntegerSeedState {
Bool wanted = false;
Bool disabled = false;
// The record Serial (the application's link) the last build was for.
Uint64 buildSerial = 0;
// What the last build ran with and what it verified.
Bool built = false;
Bool declared = false;
Bool available = false;
Int widths8Location = -1;
Int widths16Location = -1;
// The values the linked program holds; a link resets them to 0.
Uint32 pushed8 = 0;
Uint32 pushed16 = 0;
Bool Target() const { return wanted && !disabled; }
// The program's next build has to change its arming.
Bool Stale() const { return Target() != built; }
// An armed build of the link at `serial` that could not give the program working seeds.
Bool RefusedAt(Uint64 serial) const { return serial == buildSerial && (disabled || (built && !available)); }
// Before a build of the link at `serial`: a failure of an earlier link says nothing about
// this one, a link without integer inputs has nothing to arm, and a capturing one is armed.
void PrepareBuild(Uint64 serial, Bool hasIntegerInputs, Bool captures) {
if (serial != buildSerial) {
disabled = false;
buildSerial = serial;
}
if (!hasIntegerInputs)
wanted = false;
else if (captures)
wanted = true;
}
};
// The program's 32-bit int and uint vertex inputs, memoized on the record.
IntegerVertexInputMasks IntegerVertexInputsOf(const MG_Pipe::MGPipeShaderCsoRecord& record);
// The draw's plan, from the program's record and the bound vertex-elements record. All zero for
// a program without integer inputs, which costs two loads.
IntegerAttributePlan ResolveIntegerAttributePlanForDraw(MG_Pipe::MGPipeHandle program);
// After the program sync: true when this draw's build refused the seeds the plan counted on,
// in which case the plan has dropped them and the vertex array must be synced again.
Bool DropRefusedNarrowIntegerSeeds(IntegerAttributePlan& plan, MG_Pipe::MGPipeHandle program);
// After Use(): the two widths on the program the draw runs, when they differ from what it holds.
void PushNarrowIntegerWidthsForDraw(const IntegerAttributePlan& plan, MG_Pipe::MGPipeHandle program);
// PrepareBuild from the record the build is for.
void PrepareNarrowIntegerSeedBuild(NarrowIntegerSeedState& state, const MG_Pipe::MGPipeShaderCsoRecord& record);
// The build's three steps: reset at its start, note each stage's final ESSL, verify after the link.
void BeginNarrowIntegerSeedBuild(NarrowIntegerSeedState& state);
void NoteNarrowIntegerSeedSource(NarrowIntegerSeedState& state, const String& source, GLenum shaderType);
void FinishNarrowIntegerSeedBuild(NarrowIntegerSeedState& state, GLuint program, Bool programUsable);
// The armed vertex stage's SPIR-V step (after the array-input split): `spirv` itself unless
// the pass rewrote it into `storage`.
const Vector<unsigned int>* SeedNarrowIntegerVertexInputsForEssl(Bool armed, const Vector<unsigned int>& spirv,
Vector<unsigned int>& storage,
Bool enableSpirvValidation);
// The two width globals of a seeded vertex stage, as `uniform highp uint`.
String PromoteNarrowIntegerWidthGlobals(String source);
namespace VertexArrayImpl {
const MG_Pipe::MGPipeVertexElementsRecord* BoundVertexElementsRecord(const MG_Pipe::MGPipeApplierState& st);
} // namespace VertexArrayImpl
} // namespace MobileGL::MG_Backend::DirectGLES
+4 -4
View File
@@ -302,7 +302,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// THE CALLING THREAD'S BUCKET, not every bucket, and that is the semantic answer // THE CALLING THREAD'S BUCKET, not every bucket, and that is the semantic answer
// rather than a shortcut: a caller walking twins is asking "which of the objects I // rather than a shortcut: a caller walking twins is asking "which of the objects I
// can see need this", and what it can see is its own share group. Every bucket in // can see need this", and what it can see is its own share group. Every bucket in
// one process would walk a neighbour session's objects. // one process would walk a neighbor session's objects.
ForCallingThread().ForEachLive(fn); ForCallingThread().ForEachLive(fn);
} }
// Every bucket, in one pass. The caller receives the key with the handle so it can tell // Every bucket, in one pass. The caller receives the key with the handle so it can tell
@@ -479,7 +479,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// THE TWO DIRECTIONS ARE NOT SYMMETRIC. The handle arrived in a payload, so // THE TWO DIRECTIONS ARE NOT SYMMETRIC. The handle arrived in a payload, so
// `handle.Gen < entry.Gen` is a reachable input, and adopting it would destroy the INCUMBENT LIVE twin - a // `handle.Gen < entry.Gen` is a reachable input, and adopting it would destroy the INCUMBENT LIVE twin - a
// driver buffer id, a persistent map, a pooled store, released by a defaulted destructor that issues no // driver buffer id, a persistent map, a pooled store, released by a defaulted destructor that issues no
// glDeleteBuffers and no pool enrolment - and then stamp the slot back to the dead resource's generation, // glDeleteBuffers and no pool enrollment - and then stamp the slot back to the dead resource's generation,
// after which the incumbent's own FindByHandle refuses it and it is silently handed a fresh, empty twin. // after which the incumbent's own FindByHandle refuses it and it is silently handed a fresh, empty twin.
// That is a leak AND a resource that loses its storage with no diagnostic, i.e. the shape commit d7655247 // That is a leak AND a resource that loses its storage with no diagnostic, i.e. the shape commit d7655247
// fixed and the thing MGPipeHandle::Gen exists to prevent. So: forward is a recycle and resets the twin, // fixed and the thing MGPipeHandle::Gen exists to prevent. So: forward is a recycle and resets the twin,
@@ -725,9 +725,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
} }
// The holder list: intrusive and doubly linked, so registering and unregistering are // The holder list: intrusive and doubly linked, so registering and unregistering are
// two pointer writes with no allocation, and its head is a constant-initialised // two pointer writes with no allocation, and its head is a constant-initialized
// static - which is what lets the process-lifetime registry globals in Managers.cpp // static - which is what lets the process-lifetime registry globals in Managers.cpp
// link themselves in from their own constructors with no initialisation-order // link themselves in from their own constructors with no initialization-order
// question to answer. Single-threaded, like every table it links (the tables live and // question to answer. Single-threaded, like every table it links (the tables live and
// die on the context thread, as the notice they answer does). // die on the context thread, as the notice they answer does).
void LinkHolder() { void LinkHolder() {
+7 -7
View File
@@ -163,7 +163,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} // namespace } // namespace
namespace TextureImpl { namespace TextureImpl {
// Every image that can back a colour attachment needs a colour-renderable storage format, // Every image that can back a color attachment needs a color-renderable storage format,
// and ES has no renderable three-channel format at all: a three-channel float fallback is // and ES has no renderable three-channel format at all: a three-channel float fallback is
// a legal ES texture but neither legal multisample storage nor a legal attachment, so // a legal ES texture but neither legal multisample storage nor a legal attachment, so
// GL_RGB8_SNORM / GL_RGB16F / ... have to be widened to four channels for any of them. // GL_RGB8_SNORM / GL_RGB16F / ... have to be widened to four channels for any of them.
@@ -178,7 +178,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// allocating it byte for byte. // allocating it byte for byte.
// //
// Do NOT read that as "nothing changes off-device". Measured on llvmpipe 26.1.6 // Do NOT read that as "nothing changes off-device". Measured on llvmpipe 26.1.6
// (the headless CI driver), an ES 3.2 GL_TEXTURE_2D colour attachment is COMPLETE for // (the headless CI driver), an ES 3.2 GL_TEXTURE_2D color attachment is COMPLETE for
// GL_RGB8 and GL_RGB16F but INCOMPLETE_ATTACHMENT for GL_RGB8_SNORM, GL_SRGB8 and every // GL_RGB8 and GL_RGB16F but INCOMPLETE_ATTACHMENT for GL_RGB8_SNORM, GL_SRGB8 and every
// RGB integer format, and UNSUPPORTED for GL_RGB32F. Those eight formats therefore DO // RGB integer format, and UNSUPPORTED for GL_RGB32F. Those eight formats therefore DO
// take the widened path on llvmpipe, which is where the retrace fixtures and the glcts // take the widened path on llvmpipe, which is where the retrace fixtures and the glcts
@@ -798,7 +798,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// geometry STAGES core but leaves gl_PointSize out of their gl_PerVertex entirely, // geometry STAGES core but leaves gl_PointSize out of their gl_PerVertex entirely,
// and SPIRV-Cross - which only ever sees a SPIR-V BuiltIn PointSize decoration - // and SPIRV-Cross - which only ever sees a SPIR-V BuiltIn PointSize decoration -
// prints the identifier with no directive behind it. Same hard rule as the two // prints the identifier with no directive behind it. Same hard rule as the two
// neighbours: never emitted speculatively, because `#extension` on a name the driver // neighbors: never emitted speculatively, because `#extension` on a name the driver
// does not advertise is a compile error of its own. // does not advertise is a compile error of its own.
if (extensionName == nullptr || glslCode.find(extensionName) != String::npos) { if (extensionName == nullptr || glslCode.find(extensionName) != String::npos) {
return glslCode; return glslCode;
@@ -831,7 +831,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
static const std::regex imageDeclRegex( static const std::regex imageDeclRegex(
R"((layout\s*\(([^)]*)\)\s*)?uniform\s+(?:(?:readonly|writeonly|coherent|volatile|restrict|highp|mediump|lowp)\s+)*[iu]?image[A-Za-z0-9]+\s+([A-Za-z_][A-Za-z0-9_]*)\s*(\[[^\]]*\])?\s*;)"); R"((layout\s*\(([^)]*)\)\s*)?uniform\s+(?:(?:readonly|writeonly|coherent|volatile|restrict|highp|mediump|lowp)\s+)*[iu]?image[A-Za-z0-9]+\s+([A-Za-z_][A-Za-z0-9_]*)\s*(\[[^\]]*\])?\s*;)");
// Every image format spelling GLSL has, so a declaration that already carries one is // Every image format spelling GLSL has, so a declaration that already carries one is
// recognised whatever it says - the caller's map is consulted only for declarations // recognized whatever it says - the caller's map is consulted only for declarations
// with NO format, never to override a written one. // with NO format, never to override a written one.
static const std::regex existingFormatRegex( static const std::regex existingFormatRegex(
R"(\b(rgba32f|rgba16f|rg32f|rg16f|r11f_g11f_b10f|r32f|r16f|rgba16|rgb10_a2|rg16|rg8|r16|r8|rgba16_snorm|rgba8_snorm|rg16_snorm|rg8_snorm|r16_snorm|r8_snorm|rgba32i|rgba16i|rgba8i|rg32i|rg16i|rg8i|r32i|r16i|r8i|rgba32ui|rgba16ui|rgba8ui|rgb10_a2ui|rg32ui|rg16ui|rg8ui|r32ui|r16ui|r8ui)\b)"); R"(\b(rgba32f|rgba16f|rg32f|rg16f|r11f_g11f_b10f|r32f|r16f|rgba16|rgb10_a2|rg16|rg8|r16|r8|rgba16_snorm|rgba8_snorm|rg16_snorm|rg8_snorm|r16_snorm|r8_snorm|rgba32i|rgba16i|rgba8i|rg32i|rg16i|rg8i|r32i|r16i|r8i|rgba32ui|rgba16ui|rgba8ui|rgb10_a2ui|rg32ui|rg16ui|rg8ui|r32ui|r16ui|r8ui)\b)");
@@ -1124,7 +1124,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
source += "precision highp float;\n"; source += "precision highp float;\n";
source += "precision highp int;\n"; source += "precision highp int;\n";
source += "layout(vertices = " + std::to_string(patchVertices) + ") out;\n"; source += "layout(vertices = " + std::to_string(patchVertices) + ") out;\n";
// Mirrored, never invented. An empty member list means the neighbouring stage did not // Mirrored, never invented. An empty member list means the neighboring stage did not
// redeclare the block either, and the driver's own built-in declaration is then what // redeclare the block either, and the driver's own built-in declaration is then what
// both sides agree on - redeclaring here would be the thing that broke the match. // both sides agree on - redeclaring here would be the thing that broke the match.
if (!inPerVertexMembers.empty()) { if (!inPerVertexMembers.empty()) {
@@ -1783,7 +1783,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// //
// Nothing downstream reads these names: the two passes that key on the GL uniform // Nothing downstream reads these names: the two passes that key on the GL uniform
// name (RebindImageUniformsToFrontendUnits, BakeImageFormatQualifiers) both run // name (RebindImageUniformsToFrontendUnits, BakeImageFormatQualifiers) both run
// BEFORE this one, RemoveLayoutBinding recognises an image declaration by its TYPE // BEFORE this one, RemoveLayoutBinding recognizes an image declaration by its TYPE
// token, and CacheResourceLocations skips image uniforms outright because ES image // token, and CacheResourceLocations skips image uniforms outright because ES image
// units come only from layout(binding=N). The declarations this pass LEAVES ALONE - // units come only from layout(binding=N). The declarations this pass LEAVES ALONE -
// already readonly/writeonly in the source, or r32f/r32i/r32ui, which need no // already readonly/writeonly in the source, or r32f/r32i/r32ui, which need no
@@ -1825,7 +1825,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Both halves carry `coherent`; see BuildImageDeclaration. The // Both halves carry `coherent`; see BuildImageDeclaration. The
// single-declaration cases below stay as they were - nothing aliases them, so // single-declaration cases below stay as they were - nothing aliases them, so
// there is no visibility to restore and no reason to pay for the cache // there is no visibility to restore and no reason to pay for the cache
// behaviour. // behavior.
edits.push_back({decl.declStart, decl.declLength, edits.push_back({decl.declStart, decl.declLength,
BuildImageDeclaration(decl, "readonly", decl.aliasName, BuildImageDeclaration(decl, "readonly", decl.aliasName,
/*forceCoherent=*/true) + /*forceCoherent=*/true) +
+11 -11
View File
@@ -47,8 +47,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
} // namespace VertexArrayImpl } // namespace VertexArrayImpl
namespace TextureImpl { namespace TextureImpl {
// Whether images on this format-capability target can back a colour attachment, and so // Whether images on this format-capability target can back a color attachment, and so
// need a colour-renderable storage format even when the frontend asked for a // need a color-renderable storage format even when the frontend asked for a
// three-channel one ES never renders to. Shared by the capability probe (which passes the // three-channel one ES never renders to. Shared by the capability probe (which passes the
// capabilities it has just queried, before the globals are published) and by the // capabilities it has just queried, before the globals are published) and by the
// allocation path (which reads the active backend's), so the format the cache was probed // allocation path (which reads the active backend's), so the format the cache was probed
@@ -74,7 +74,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target); Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target);
// True when the format the image is actually created with has an alpha channel the // True when the format the image is actually created with has an alpha channel the
// frontend format does not (the three-channel colour-renderable widening). GL reads such // frontend format does not (the three-channel color-renderable widening). GL reads such
// a channel back as 1.0, so any swizzle source of ALPHA has to be answered with ONE and // a channel back as 1.0, so any swizzle source of ALPHA has to be answered with ONE and
// any readback of the image has to overwrite the alpha the draw happened to leave there. // any readback of the image has to overwrite the alpha the draw happened to leave there.
Bool BackendTextureFormatAddsAlpha(TextureInternalFormat internalFormat, TextureTarget target); Bool BackendTextureFormatAddsAlpha(TextureInternalFormat internalFormat, TextureTarget target);
@@ -105,7 +105,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// //
// The widened triple REPLACES what GenerateTextureFormatInfo chose, including any // The widened triple REPLACES what GenerateTextureFormatInfo chose, including any
// renderability substitution: an image that cannot be image-bound is useless whatever its // renderability substitution: an image that cannot be image-bound is useless whatever its
// attachment behaviour, so the image constraint wins. In practice that only bites // attachment behavior, so the image constraint wins. In practice that only bites
// RG8_SNORM/R8_SNORM on a driver without EXT_render_snorm, where the storage stays // RG8_SNORM/R8_SNORM on a driver without EXT_render_snorm, where the storage stays
// signed-normalized instead of becoming the half float that fallback would have picked - // signed-normalized instead of becoming the half float that fallback would have picked -
// so an image-bound texture in one of those two formats is no longer attachable, and // so an image-bound texture in one of those two formats is no longer attachable, and
@@ -115,11 +115,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// compiled at all, so nothing that works today is being given up. // compiled at all, so nothing that works today is being given up.
// //
// KNOWN GAP, for the same "all three layers move together" reason: a widened texture that // KNOWN GAP, for the same "all three layers move together" reason: a widened texture that
// is ALSO an FBO colour attachment gains one to three writable channels, and a draw into // is ALSO an FBO color attachment gains one to three writable channels, and a draw into
// it can leave values in channels GL says are 0 and 1. Sampling and imageLoad are covered // it can leave values in channels GL says are 0 and 1. Sampling and imageLoad are covered
// (the swizzle composition in SyncTextureParamsToBackend and the shader-side mask), but a // (the swizzle composition in SyncTextureParamsToBackend and the shader-side mask), but a
// glReadPixels/glGetTexImage that asks for more channels than the frontend format has // glReadPixels/glGetTexImage that asks for more channels than the frontend format has
// would see them. Closing it needs the per-draw-buffer colour mask the three-channel // would see them. Closing it needs the per-draw-buffer color mask the three-channel
// widening already carries (FramebufferImpl::g_alphaWidenedDrawBufferMask) generalized // widening already carries (FramebufferImpl::g_alphaWidenedDrawBufferMask) generalized
// from "alpha" to a channel count, which is its own change. // from "alpha" to a channel count, which is its own change.
// How the FRONTEND's CPU shadow for a widened format is laid out relative to the carrier's // How the FRONTEND's CPU shadow for a widened format is laid out relative to the carrier's
@@ -319,7 +319,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Exists so BuildPassthroughTessControlEssl can MIRROR the stages it has to sit between // Exists so BuildPassthroughTessControlEssl can MIRROR the stages it has to sit between
// rather than guess at them. Whether SPIRV-Cross redeclares the built-in block, and with // rather than guess at them. Whether SPIRV-Cross redeclares the built-in block, and with
// which members, depends on what the application's shader touched; a synthesized stage // which members, depends on what the application's shader touched; a synthesized stage
// that redeclares a different shape than its neighbours is an ES link error against a // that redeclares a different shape than its neighbors is an ES link error against a
// program that has no other problem. // program that has no other problem.
std::optional<String> ExtractPerVertexBlockMembers(const String& essl, Bool input); std::optional<String> ExtractPerVertexBlockMembers(const String& essl, Bool input);
// Moves the clip/cull distance OUTPUTS of a vertex, tessellation evaluation or geometry // Moves the clip/cull distance OUTPUTS of a vertex, tessellation evaluation or geometry
@@ -365,15 +365,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
// bound in its place, and every draw silently renders nothing. // bound in its place, and every draw silently renders nothing.
// //
// `inPerVertexMembers` / `outPerVertexMembers` are the member lists to redeclare gl_in // `inPerVertexMembers` / `outPerVertexMembers` are the member lists to redeclare gl_in
// and gl_out with - normally taken from the neighbouring stages' own emitted ESSL via // and gl_out with - normally taken from the neighboring stages' own emitted ESSL via
// ExtractPerVertexBlockMembers, and empty to leave the driver's built-in declaration // ExtractPerVertexBlockMembers, and empty to leave the driver's built-in declaration
// alone, which is what matching a neighbour that did not redeclare requires. // alone, which is what matching a neighbor that did not redeclare requires.
// //
// All four outer levels and both inner levels are written unconditionally: writing a // All four outer levels and both inner levels are written unconditionally: writing a
// level the evaluation stage's domain does not use is legal and ignored, and it saves // level the evaluation stage's domain does not use is legal and ignored, and it saves
// this from having to know the domain. They are the GL_PATCH_DEFAULT_OUTER_LEVEL / // this from having to know the domain. They are the GL_PATCH_DEFAULT_OUTER_LEVEL /
// GL_PATCH_DEFAULT_INNER_LEVEL state, baked in as literals - ES has no such state and no // GL_PATCH_DEFAULT_INNER_LEVEL state, baked in as literals - ES has no such state and no
// glPatchParameterfv to forward to, so compiling them in is the only way to honour them. // glPatchParameterfv to forward to, so compiling them in is the only way to honor them.
// That makes them part of what a built program is stale against, exactly as PATCH_VERTICES // That makes them part of what a built program is stale against, exactly as PATCH_VERTICES
// is: see the staleness clause in DirectGLES.cpp's SyncCurrentProgram, which compares both. // is: see the staleness clause in DirectGLES.cpp's SyncCurrentProgram, which compares both.
// //
@@ -524,7 +524,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// suffix is the sampler's own name. // suffix is the sampler's own name.
constexpr const char* MS_SAMPLE_MAX_UNIFORM_PREFIX = "mg_msSampleMax_"; constexpr const char* MS_SAMPLE_MAX_UNIFORM_PREFIX = "mg_msSampleMax_";
// The runtime half of ClampMultisampleFetchPass's deferDirectUnsignedFetches. A // The runtime half of ClampMultisampleFetchPass's deferDirectUnsignedFetches. A
// usampler2DMS[Array] reads integer colour textures, which the backend may allocate with // usampler2DMS[Array] reads integer color textures, which the backend may allocate with
// fewer samples than MobileGL advertises, AND the stencil aspect of multisample // fewer samples than MobileGL advertises, AND the stencil aspect of multisample
// depth/stencil textures, which it allocates under the (larger) depth ceiling - so no // depth/stencil textures, which it allocates under the (larger) depth ceiling - so no
// single compile-time bound is right for both. Declares one // single compile-time bound is right for both. Declares one
@@ -878,7 +878,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
E_GL_ARB_texture_rg, E_GL_ARB_texture_rg,
// GL_DEPTH_COMPONENT32F and GL_DEPTH32F_STENCIL8. // GL_DEPTH_COMPONENT32F and GL_DEPTH32F_STENCIL8.
E_GL_ARB_depth_buffer_float, E_GL_ARB_depth_buffer_float,
// The floating-point colour formats. Unlike the rest of this block this string DOES // The floating-point color formats. Unlike the rest of this block this string DOES
// gate CTS cases - KHR-GL4*.internalformat.texture2d.*{16f,32f} is keyed on it with no // gate CTS cases - KHR-GL4*.internalformat.texture2d.*{16f,32f} is keyed on it with no
// core-version fallback, so eight cases per version list were NotSupported on formats // core-version fallback, so eight cases per version list were NotSupported on formats
// the backend has always had. // the backend has always had.
@@ -895,7 +895,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// - multiple capture buffers per stream, gl_SkipComponents / gl_NextBuffer, the // - multiple capture buffers per stream, gl_SkipComponents / gl_NextBuffer, the
// indexed queries and glDrawTransformFeedbackStream (4.0); // indexed queries and glDrawTransformFeedbackStream (4.0);
E_GL_ARB_transform_feedback3, E_GL_ARB_transform_feedback3,
// - GL_RGB10_A2UI (3.3), GL_RGB9_E5 (3.0) and the integer colour formats (3.0); // - GL_RGB10_A2UI (3.3), GL_RGB9_E5 (3.0) and the integer color formats (3.0);
E_GL_ARB_texture_rgb10_a2ui, E_GL_EXT_texture_shared_exponent, E_GL_EXT_texture_integer, E_GL_ARB_texture_rgb10_a2ui, E_GL_EXT_texture_shared_exponent, E_GL_EXT_texture_integer,
// - the eleven pipeline-statistics query targets (4.6). Accepted by glBeginQuery and // - the eleven pipeline-statistics query targets (4.6). Accepted by glBeginQuery and
// answered with GL_QUERY_COUNTER_BITS = 0, the spec's own "this counter is not // answered with GL_QUERY_COUNTER_BITS = 0, the spec's own "this counter is not
@@ -941,7 +941,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// recorded trace can never cover: Iris and Sodium change their SUBMISSION SCHEDULE // recorded trace can never cover: Iris and Sodium change their SUBMISSION SCHEDULE
// the moment they see it - they enqueue whole pipeline batches and poll // the moment they see it - they enqueue whole pipeline batches and poll
// GL_COMPLETION_STATUS_KHR instead of compiling one program at a time - so // GL_COMPLETION_STATUS_KHR instead of compiling one program at a time - so
// MOBILEGL_ASYNC_SHADER_COMPILE=0 has to withdraw the application-visible behaviour // MOBILEGL_ASYNC_SHADER_COMPILE=0 has to withdraw the application-visible behavior
// change as well as the threading, or the kill switch would only be half a switch. // change as well as the threading, or the kill switch would only be half a switch.
if (MG_Util::Async::AsyncShaderCompileEnabled()) { if (MG_Util::Async::AsyncShaderCompileEnabled()) {
extensions.push_back(E_GL_KHR_parallel_shader_compile); extensions.push_back(E_GL_KHR_parallel_shader_compile);
@@ -1117,6 +1117,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
} }
// Without the shaderClipDistance feature the built-in is emulated instead
// (EmulateClipDistancePass: varyings plus a fragment discard, which needs nothing from the
// device), so the frontend can still answer the GL 3.0+ minimum of eight. Every Mali device
// measured so far reports shaderClipDistance = VK_FALSE. MOBILEGL_CLIP_DISTANCE_EMULATION=1
// arms it on a device that has the feature (the pinned integration lane); =0 restores the
// plain zero.
static Bool DecideClipDistanceEmulation(const Bool deviceHasClipDistance) {
switch (MG_Config::Features.ClipDistanceEmulation) {
case MG_Config::QuirkOverride::ForceOn:
MGLOG_I("DirectVulkan: MOBILEGL_CLIP_DISTANCE_EMULATION=1 - emulating gl_ClipDistance even though "
"the device %s shaderClipDistance",
deviceHasClipDistance ? "has" : "lacks");
return true;
case MG_Config::QuirkOverride::ForceOff:
MGLOG_I("DirectVulkan: MOBILEGL_CLIP_DISTANCE_EMULATION=0 - no gl_ClipDistance emulation");
return false;
case MG_Config::QuirkOverride::Auto:
default:
return !deviceHasClipDistance;
}
}
void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() { void BackendObject_DirectVulkan::UpdateDynamicBackendParameters() {
const auto mapShaderStages = [](Uint32 vkStages) { const auto mapShaderStages = [](Uint32 vkStages) {
Uint32 glStages = 0; Uint32 glStages = 0;
@@ -1343,8 +1365,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// by every device, but declaring ClipDistance in a module needs the shaderClipDistance // by every device, but declaring ClipDistance in a module needs the shaderClipDistance
// FEATURE, which VulkanRenderer enables exactly where the physical device has it. Without // FEATURE, which VulkanRenderer enables exactly where the physical device has it. Without
// it the limit describes a capacity no shader may use, so report none. // it the limit describes a capacity no shader may use, so report none.
const Bool emulateClipDistance = DecideClipDistanceEmulation(m_vulkanCaps.SupportsShaderClipDistance);
m_dynamicParameters.EmulatesClipDistance = emulateClipDistance;
// Hosted natively, but Vulkan has no per-distance enable state: apply GL_CLIP_DISTANCEi
// in the producing stage instead (EmulateClipDistanceOptions::nativeBuiltin).
m_dynamicParameters.MasksNativeClipDistances = !emulateClipDistance && m_vulkanCaps.SupportsShaderClipDistance;
m_dynamicParameters.MaxClipDistances = m_dynamicParameters.MaxClipDistances =
m_vulkanCaps.SupportsShaderClipDistance ? std::max(m_vulkanCaps.MaxClipDistances, 0) : 0; emulateClipDistance
? 8
: (m_vulkanCaps.SupportsShaderClipDistance ? std::max(m_vulkanCaps.MaxClipDistances, 0) : 0);
MGLOG_I("DirectVulkan: gl_ClipDistance %s (GL_MAX_CLIP_DISTANCES = %d)",
emulateClipDistance
? "EMULATED (varyings + fragment discard)"
: (m_dynamicParameters.MasksNativeClipDistances ? "native, GL_CLIP_DISTANCEi applied in the shader"
: "unsupported"),
m_dynamicParameters.MaxClipDistances);
// The cull pair, gated on its own feature. shaderCullDistance is separate from // The cull pair, gated on its own feature. shaderCullDistance is separate from
// shaderClipDistance and VulkanRenderer enables it independently, so it gets its own // shaderClipDistance and VulkanRenderer enables it independently, so it gets its own
// gate rather than riding on the clip one. // gate rather than riding on the clip one.
@@ -1357,7 +1392,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
(m_vulkanCaps.SupportsShaderClipDistance || m_vulkanCaps.SupportsShaderCullDistance) (m_vulkanCaps.SupportsShaderClipDistance || m_vulkanCaps.SupportsShaderCullDistance)
? std::max({m_vulkanCaps.MaxCombinedClipAndCullDistances, m_dynamicParameters.MaxClipDistances, ? std::max({m_vulkanCaps.MaxCombinedClipAndCullDistances, m_dynamicParameters.MaxClipDistances,
m_dynamicParameters.MaxCullDistances}) m_dynamicParameters.MaxCullDistances})
: 0; : m_dynamicParameters.MaxClipDistances;
m_dynamicParameters.MaxViewports = m_vulkanCaps.MaxViewports; m_dynamicParameters.MaxViewports = m_vulkanCaps.MaxViewports;
// Assigned explicitly rather than left to the struct's defaults, like every other // Assigned explicitly rather than left to the struct's defaults, like every other
// parameter here, so a second fill cannot inherit a stale value. GL_UNDEFINED_VERTEX is // parameter here, so a second fill cannot inherit a stale value. GL_UNDEFINED_VERTEX is
@@ -73,7 +73,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// (MG_Remote::Client::EmitObjectDeathRecord, CONTRACT-P5C §5.2). // (MG_Remote::Client::EmitObjectDeathRecord, CONTRACT-P5C §5.2).
// //
// WHY THAT MATTERS, measured rather than argued (package L's probe, // WHY THAT MATTERS, measured rather than argued (package L's probe,
// docs/archive/notes/p7/magma-two-process-first-run.md §6): under SPAWN the client process has no // the phase notes §6): under SPAWN the client process has no
// DirectVulkan backend at all, so InstallClientWireTables' own emitter already // DirectVulkan backend at all, so InstallClientWireTables' own emitter already
// answered the notice and CtWireScenario's two death cases PASSED. Under INPROC the // answered the notice and CtWireScenario's two death cases PASSED. Under INPROC the
// server role's backend is a thread of this process, Transport is Split rather than // server role's backend is a thread of this process, Transport is Split rather than
@@ -238,7 +238,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// GetProgramResourceCache does it - over a multi-stage program with an arrayed SSBO and an // GetProgramResourceCache does it - over a multi-stage program with an arrayed SSBO and an
// atomic-counter block, and asserts element-for-element equality with what the archive // atomic-counter block, and asserts element-for-element equality with what the archive
// published. That equivalence is what makes an #if here a build-time SELECTION rather than // published. That equivalence is what makes an #if here a build-time SELECTION rather than
// a behavioural fork, and it is the reason the case exists. // a behavioral fork, and it is the reason the case exists.
// //
// WHY AN #if AT ALL, AND WHAT IT COSTS. LinkArtifacts::storageBlocks is // WHY AN #if AT ALL, AND WHAT IT COSTS. LinkArtifacts::storageBlocks is
// MOBILEGL_BUILD_DISAGGREGATED-only, because one more Vector member changes the struct's // MOBILEGL_BUILD_DISAGGREGATED-only, because one more Vector member changes the struct's
@@ -247,7 +247,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// SPIRV-Reflect rebuild and `ClearProgramResourceCaches`, verbatim; only this build drops // SPIRV-Reflect rebuild and `ClearProgramResourceCaches`, verbatim; only this build drops
// them - and with them the rehash hazard the ordering comment below the block-binding // them - and with them the rehash hazard the ordering comment below the block-binding
// setter documents. Retiring the pull half is the day the member stops needing its guard, // setter documents. Retiring the pull half is the day the member stops needing its guard,
// which is a G1/P13 decision and not a wave-2 one; recorded in docs/archive/notes/p7/magma-c.md. // which is a G1/P13 decision and not a wave-2 one; recorded in the phase notes.
void ClearProgramResourceCaches() { void ClearProgramResourceCaches() {
// Nothing to clear: there is no cache in this build. Kept as an entry point so // Nothing to clear: there is no cache in this build. Kept as an entry point so
// BackendObject_DirectVulkan's two EGL-teardown call sites stay statement for // BackendObject_DirectVulkan's two EGL-teardown call sites stay statement for
@@ -263,7 +263,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}; };
auto it = find(name); auto it = find(name);
if (it == blocks.end()) { if (it == blocks.end()) {
// Archive names are normalised, so an arrayed block that GL enumerates per element // Archive names are normalized, so an arrayed block that GL enumerates per element
// - "B[0]", "B[1]" - is one entry here, spelled "B". Same second chance the cache // - "B[0]", "B[1]" - is one entry here, spelled "B". Same second chance the cache
// gave, for the same callers. // gave, for the same callers.
const auto bracket = name.rfind('['); const auto bracket = name.rfind('[');
@@ -14,7 +14,7 @@
// //
// SessionLocal<T> is that copy. It resolves through the CALLING THREAD's bound MagmaSession, and to // SessionLocal<T> is that copy. It resolves through the CALLING THREAD's bound MagmaSession, and to
// the process-wide default session on every thread nothing bound - which is the monolith, the client // the process-wide default session on every thread nothing bound - which is the monolith, the client
// role and every unit case, i.e. exactly the old behaviour, on whatever thread the app renders from. // role and every unit case, i.e. exactly the old behavior, on whatever thread the app renders from.
// A served session's backend owns its MagmaSession and binds it on its apply thread (the only thread // A served session's backend owns its MagmaSession and binds it on its apply thread (the only thread
// that runs that session's backend), so the two never mix. // that runs that session's backend), so the two never mix.
@@ -203,7 +203,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
// The barrier belongs in the frame's own recording. Bailing out because // The barrier belongs in the frame's own recording. Bailing out because
// something was already recorded (the previous behaviour) dropped the // something was already recorded (the previous behavior) dropped the
// transition entirely for every frame that never ran a default-framebuffer // transition entirely for every frame that never ran a default-framebuffer
// render pass - the only other thing that carries the image to // render pass - the only other thing that carries the image to
// PRESENT_SRC_KHR, via that pass's finalLayout - so the swapchain image was // PRESENT_SRC_KHR, via that pass's finalLayout - so the swapchain image was
@@ -53,24 +53,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// The view has no lifetime of its own, so the memo lives in the server record it views // The view has no lifetime of its own, so the memo lives in the server record it views
// (MGPipeShaderCsoRecord::BackendHashMemo), never in the client's program object: an entry // (MGPipeShaderCsoRecord::BackendHashMemo), never in the client's program object: an entry
// answers only while the record's Serial and BindingsSerial are the ones it was taken at. // answers only while the record's Serial and BindingsSerial are the ones it was taken at.
Bool GetBackendHashMemo(Uint flags, Uint64& hash) const { using BackendHashMemoEntry = MG_Pipe::MGPipeShaderCsoRecord::BackendHashMemoEntry;
for (const auto& entry : m_record->BackendHashMemo) { BackendHashMemoEntry* FindBackendHashMemo(Uint key) const {
if (entry.Valid && entry.Key == flags && entry.Serial == m_record->Serial && for (auto& entry : m_record->BackendHashMemo) {
entry.BindingsSerial == m_record->BindingsSerial) { if (entry.Valid && entry.Key == key && entry.Serial == m_record->Serial &&
hash = entry.Hash; entry.BindingsSerial == m_record->BindingsSerial)
return true; return &entry;
}
} }
return false; return nullptr;
} }
void SetBackendHashMemo(Uint flags, Uint64 hash) const { // Takes the next slot round robin; its LastVariantHash starts as the plain `hash`.
BackendHashMemoEntry& SetBackendHashMemo(Uint key, Uint64 hash) const {
auto& memo = m_record->BackendHashMemo; auto& memo = m_record->BackendHashMemo;
auto& entry = memo[m_record->BackendHashMemoNext++ % memo.size()]; auto& entry = memo[m_record->BackendHashMemoNext++ % memo.size()];
entry.Serial = m_record->Serial; entry.Serial = m_record->Serial;
entry.BindingsSerial = m_record->BindingsSerial; entry.BindingsSerial = m_record->BindingsSerial;
entry.Hash = hash; entry.Hash = hash;
entry.Key = flags; entry.LastVariantHash = hash;
entry.Key = key;
entry.Valid = true; entry.Valid = true;
return entry;
} }
Uint64 GetBackendStateVersion() const { return m_record->Serial; } Uint64 GetBackendStateVersion() const { return m_record->Serial; }
Uint64 GetBlockBindingVersion() const { return m_record->BindingsSerial; } Uint64 GetBlockBindingVersion() const { return m_record->BindingsSerial; }
@@ -163,7 +165,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// wire draw path entirely - this header no longer includes spirv_reflect.h at all. // wire draw path entirely - this header no longer includes spirv_reflect.h at all.
Uint GetShaderStorageBlockIndex(const String& name) const { Uint GetShaderStorageBlockIndex(const String& name) const {
const auto& blocks = Link().storageBlocks; const auto& blocks = Link().storageBlocks;
// The archive carries NORMALISED names (the array subscript stripped), because // The archive carries NORMALIZED names (the array subscript stripped), because
// that is the spelling SPIRV-Reflect's type_name gives and the spelling // that is the spelling SPIRV-Reflect's type_name gives and the spelling
// ProgramFactory::ReflectLayout looks up. A caller that still holds a GL-style // ProgramFactory::ReflectLayout looks up. A caller that still holds a GL-style
// "Foo[2]" gets the same second chance it always had. // "Foo[2]" gets the same second chance it always had.
@@ -0,0 +1,87 @@
// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/NarrowIntegerVertexFormats.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 "../VkIncludes.h"
#include <MG_Util/ShaderTranspiler/NarrowIntegerVertexInputs.h>
#include <Includes.h>
namespace MobileGL::MG_Backend::DirectVulkan {
using MG_Util::ShaderTranspiler::IntegerVertexInputMasks;
using MG_Util::ShaderTranspiler::NarrowIntegerVertexInputs;
using MG_Util::ShaderTranspiler::ResolveNarrowIntegerVertexInputs;
// Which vertex inputs need the ExtendNarrowIntegerVertexInputs program variant, decided from
// two halves that are each computed once and cached with the object they describe.
//
// The pipeline gives an integer attribute the fetch format of the shader input's signedness
// (VUID-VkGraphicsPipelineCreateInfo-Input-08733). For 32-bit data that swap only renames the
// bits; for 8/16-bit data it changes the extension rule, so 240 in GL_UNSIGNED_BYTE data
// reaches an `int` input as -16. These are exactly the (data, input) pairs that need it.
enum class NarrowIntegerVertexFormat : Uint8 {
None = 0,
Unsigned8,
Unsigned16,
Signed8,
Signed16,
};
constexpr NarrowIntegerVertexFormat ClassifyNarrowIntegerVertexFormat(VkFormat format) {
switch (format) {
case VK_FORMAT_R8_UINT:
case VK_FORMAT_R8G8_UINT:
case VK_FORMAT_R8G8B8_UINT:
case VK_FORMAT_R8G8B8A8_UINT:
return NarrowIntegerVertexFormat::Unsigned8;
case VK_FORMAT_R16_UINT:
case VK_FORMAT_R16G16_UINT:
case VK_FORMAT_R16G16B16_UINT:
case VK_FORMAT_R16G16B16A16_UINT:
return NarrowIntegerVertexFormat::Unsigned16;
case VK_FORMAT_R8_SINT:
case VK_FORMAT_R8G8_SINT:
case VK_FORMAT_R8G8B8_SINT:
case VK_FORMAT_R8G8B8A8_SINT:
return NarrowIntegerVertexFormat::Signed8;
case VK_FORMAT_R16_SINT:
case VK_FORMAT_R16G16_SINT:
case VK_FORMAT_R16G16B16_SINT:
case VK_FORMAT_R16G16B16A16_SINT:
return NarrowIntegerVertexFormat::Signed16;
default:
return NarrowIntegerVertexFormat::None;
}
}
// The vertex-input half, filled from the source formats when a vertex input layout is built,
// so a draw reads four words instead of walking its attributes.
struct NarrowIntegerAttributeMasks : MG_Util::ShaderTranspiler::NarrowIntegerAttributeMasks {
constexpr void Note(Uint32 location, VkFormat format) {
if (location >= 32) return;
const Uint32 bit = 1u << location;
switch (ClassifyNarrowIntegerVertexFormat(format)) {
case NarrowIntegerVertexFormat::Unsigned8:
unsigned8 |= bit;
break;
case NarrowIntegerVertexFormat::Unsigned16:
unsigned16 |= bit;
break;
case NarrowIntegerVertexFormat::Signed8:
signed8 |= bit;
break;
case NarrowIntegerVertexFormat::Signed16:
signed16 |= bit;
break;
case NarrowIntegerVertexFormat::None:
break;
}
}
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -20,6 +20,7 @@
#include <cstring> #include <cstring>
#include <format> #include <format>
#include <map> #include <map>
#include <unordered_set>
#include <utility> #include <utility>
#include <spirv-tools/libspirv.h> #include <spirv-tools/libspirv.h>
#include <spirv-tools/optimizer.hpp> #include <spirv-tools/optimizer.hpp>
@@ -451,7 +452,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// call was DEBUG-or-env gated and only logged, so an invalid module produced by a backend // call was DEBUG-or-env gated and only logged, so an invalid module produced by a backend
// transform went straight to vkCreateShaderModule. That is not a survivable outcome on // transform went straight to vkCreateShaderModule. That is not a survivable outcome on
// this hardware - Mali r54 SIGSEGVs building the pipeline instead of returning an error, // this hardware - Mali r54 SIGSEGVs building the pipeline instead of returning an error,
// the same "not a validating entry point" behaviour PipelineFactory already documents for // the same "not a validating entry point" behavior PipelineFactory already documents for
// vkCreateGraphicsPipelines - so the callers that feed the driver now act on it. // vkCreateGraphicsPipelines - so the callers that feed the driver now act on it.
// //
// This function does NOT log the failure at E any more. It used to, unlatched, on the // This function does NOT log the failure at E any more. It used to, unlatched, on the
@@ -914,6 +915,36 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true; return true;
} }
// Where a per-vertex fix-up of `entryPoint`'s outputs goes: in front of every emit a
// geometry shader can execute - EmitVertex and EmitStreamVertex alike, in the entry
// function or in any function it calls, since the front end does not inline helpers - and,
// for the other stages, in front of each return from the entry function itself, as a
// helper's return does not end the invocation. Collected before anything is inserted.
Vector<spvtools::opt::Instruction*> CollectPerVertexFixupSites(spvtools::opt::IRContext* context,
const spvtools::opt::Instruction& entryPoint) {
Vector<spvtools::opt::Instruction*> sites;
const Uint32 entryFunctionId = entryPoint.GetSingleWordInOperand(1);
if (context->GetFunction(entryFunctionId) == nullptr) return sites;
const auto collect = [&sites](spvtools::opt::Function& function, auto isSite) {
function.ForEachInst([&](spvtools::opt::Instruction* inst) {
if (isSite(inst->opcode())) sites.push_back(inst);
});
};
if (static_cast<spv::ExecutionModel>(entryPoint.GetSingleWordInOperand(0)) !=
spv::ExecutionModel::Geometry) {
collect(*context->GetFunction(entryFunctionId), [](spv::Op op) { return op == spv::Op::OpReturn; });
return sites;
}
std::unordered_set<Uint32> callTree;
context->CollectCallTreeFromRoots(entryFunctionId, &callTree);
for (auto& function : *context->module()) {
if (callTree.count(function.result_id()) == 0) continue;
collect(function,
[](spv::Op op) { return op == spv::Op::OpEmitVertex || op == spv::Op::OpEmitStreamVertex; });
}
return sites;
}
// Is `pointerId` the position target itself, or an access chain rooted at it? // Is `pointerId` the position target itself, or an access chain rooted at it?
Bool PointerReachesPositionTarget(spvtools::opt::IRContext* context, Uint32 pointerId, Bool PointerReachesPositionTarget(spvtools::opt::IRContext* context, Uint32 pointerId,
const PositionTargetInfo& target) { const PositionTargetInfo& target) {
@@ -941,7 +972,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// //
// Deliberately conservative - it answers "assume yes" for every shape it cannot read // Deliberately conservative - it answers "assume yes" for every shape it cannot read
// exactly, because a false "no" would silently drop the clip-space fixup from a shader // exactly, because a false "no" would silently drop the clip-space fixup from a shader
// that does write gl_Position, while a false "yes" only reinstates the behaviour this // that does write gl_Position, while a false "yes" only reinstates the behavior this
// pass has always had. Scans every function rather than just the entry point's: a shader // pass has always had. Scans every function rather than just the entry point's: a shader
// that assigns gl_Position inside a helper is still a shader that writes it, and passing // that assigns gl_Position inside a helper is still a shader that writes it, and passing
// the pointer to a call is a write as far as this can tell. // the pointer to a call is a write as far as this can tell.
@@ -1058,7 +1089,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!FindPositionTarget(context(), &target)) return Status::SuccessWithoutChange; if (!FindPositionTarget(context(), &target)) return Status::SuccessWithoutChange;
// Nothing to remap in a Position the shader never writes. Declining is not just // Nothing to remap in a Position the shader never writes. Declining is not just
// an optimisation: the fixup is load-modify-store, so on an unwritten Position it // an optimization: the fixup is load-modify-store, so on an unwritten Position it
// converts "undefined, never written" into "written with whatever the load // converts "undefined, never written" into "written with whatever the load
// returned", and the store is a reference to a variable the link-time sanitize // returned", and the store is a reference to a variable the link-time sanitize
// chain has already delisted from the entry-point interface. glslang emits the // chain has already delisted from the entry-point interface. glslang emits the
@@ -1095,22 +1126,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
continue; continue;
} }
auto* function = context()->GetFunction(entryPoint.GetSingleWordInOperand(1));
if (!function) continue;
Bool modifiedThisEntryPoint = false; Bool modifiedThisEntryPoint = false;
for (auto& bb : *function) { for (auto* site : CollectPerVertexFixupSites(context(), entryPoint)) {
for (auto instIter = bb.begin(); instIter != bb.end(); ++instIter) { modifiedThisEntryPoint |=
auto* inst = &*instIter; InsertPositionFixup(context(), site, target, halfConstId, doYFlip, doZRemap,
const Bool needsFixup = doSurfaceRotate90, doSurfaceRotate180, doSurfaceRotate270);
(model == spv::ExecutionModel::Geometry && inst->opcode() == spv::Op::OpEmitVertex) ||
(model != spv::ExecutionModel::Geometry && inst->opcode() == spv::Op::OpReturn);
if (!needsFixup) continue;
modifiedThisEntryPoint |=
InsertPositionFixup(context(), inst, target, halfConstId, doYFlip, doZRemap,
doSurfaceRotate90, doSurfaceRotate180, doSurfaceRotate270);
}
} }
// Per entry point, and only for one this pass actually injected into: the // Per entry point, and only for one this pass actually injected into: the
// injected load/store is a static use of the position variable, so the // injected load/store is a static use of the position variable, so the
@@ -1145,7 +1165,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// ivec2(gl_FragCoord.xy)) into an image exactly the size of that viewport, so every // ivec2(gl_FragCoord.xy)) into an image exactly the size of that viewport, so every
// store fell outside the image and the test read back zeroes. // store fell outside the image and the test read back zeroes.
// //
// The rewrite redirects every read of the builtin to a Private copy initialised once at // The rewrite redirects every read of the builtin to a Private copy initialized once at
// entry, which is exact for all access forms (whole-vector loads, `.y` access chains, // entry, which is exact for all access forms (whole-vector loads, `.y` access chains,
// OpCopyMemory) and leaves the builtin itself - and its decorations - untouched. // OpCopyMemory) and leaves the builtin itself - and its decorations - untouched.
class GlFragCoordYFlipPass final : public spvtools::opt::Pass { class GlFragCoordYFlipPass final : public spvtools::opt::Pass {
@@ -1213,7 +1233,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
{SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast<Uint32>(spv::StorageClass::Private)}}}); {SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast<Uint32>(spv::StorageClass::Private)}}});
context()->AddGlobalValue(std::move(copyVar)); context()->AddGlobalValue(std::move(copyVar));
// Redirect the reads BEFORE emitting the initialiser, so the initialiser's own // Redirect the reads BEFORE emitting the initializer, so the initializer's own
// load of the builtin is not rewritten into a load of the (still empty) copy. // load of the builtin is not rewritten into a load of the (still empty) copy.
if (!RedirectReads(builtinVarId, copyVarId)) return Status::SuccessWithoutChange; if (!RedirectReads(builtinVarId, copyVarId)) return Status::SuccessWithoutChange;
@@ -1364,8 +1384,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Decorates the module's captured varyings for VK_EXT_transform_feedback: // Decorates the module's captured varyings for VK_EXT_transform_feedback:
// user outputs get XfbBuffer/XfbStride/Offset directly; a captured // user outputs get XfbBuffer/XfbStride/Offset directly; a captured
// gl_Position (a gl_PerVertex member) is mirrored into a dedicated output // gl_Position (a gl_PerVertex member) is mirrored into a dedicated output
// variable copied before every OpReturn, BEFORE the position fixup runs, // variable copied at every CollectPerVertexFixupSites site, BEFORE the position
// so the captured value is the shader's own (pre-remap) gl_Position. // fixup runs, so the captured value is the shader's own (pre-remap) gl_Position.
class XfbCaptureDecoratePass final : public spvtools::opt::Pass { class XfbCaptureDecoratePass final : public spvtools::opt::Pass {
public: public:
struct CapturedVarying { struct CapturedVarying {
@@ -1393,7 +1413,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
auto entryPointIter = get_module()->entry_points().begin(); auto entryPointIter = get_module()->entry_points().begin();
if (entryPointIter == get_module()->entry_points().end()) return Status::SuccessWithoutChange; if (entryPointIter == get_module()->entry_points().end()) return Status::SuccessWithoutChange;
spvtools::opt::Instruction* entryPoint = &*entryPointIter; spvtools::opt::Instruction* entryPoint = &*entryPointIter;
const Uint32 entryFunctionId = entryPoint->GetSingleWordInOperand(1);
// Name -> result id map from the debug section. // Name -> result id map from the debug section.
std::unordered_map<std::string, Uint32> idsByName; std::unordered_map<std::string, Uint32> idsByName;
@@ -1542,14 +1561,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
if (needsPositionMirror) { if (needsPositionMirror) {
modified |= MirrorPerVertexBuiltInForCapture(entryFunctionId, *entryPoint, spv::BuiltIn::Position, modified |= MirrorPerVertexBuiltInForCapture(*entryPoint, spv::BuiltIn::Position, IsVec4Float32,
IsVec4Float32, "gl_Position", positionBufferIndex, "gl_Position", positionBufferIndex, positionOffset,
positionOffset, decorateForXfb); decorateForXfb);
} }
if (needsPointSizeMirror) { if (needsPointSizeMirror) {
modified |= MirrorPerVertexBuiltInForCapture(entryFunctionId, *entryPoint, spv::BuiltIn::PointSize, modified |= MirrorPerVertexBuiltInForCapture(*entryPoint, spv::BuiltIn::PointSize, IsFloat32Scalar,
IsFloat32Scalar, "gl_PointSize", pointSizeBufferIndex, "gl_PointSize", pointSizeBufferIndex, pointSizeOffset,
pointSizeOffset, decorateForXfb); decorateForXfb);
} }
if (!modified) return Status::SuccessWithoutChange; if (!modified) return Status::SuccessWithoutChange;
@@ -1592,11 +1611,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// built-in is looked up and what type it has, so one injector serves both. Anything // built-in is looked up and what type it has, so one injector serves both. Anything
// else in gl_PerVertex would need its own type check before it could be added here. // else in gl_PerVertex would need its own type check before it could be added here.
template <typename DecorateFn> template <typename DecorateFn>
Bool MirrorPerVertexBuiltInForCapture(Uint32 entryFunctionId, spvtools::opt::Instruction& entryPoint, Bool MirrorPerVertexBuiltInForCapture(spvtools::opt::Instruction& entryPoint, spv::BuiltIn builtIn,
spv::BuiltIn builtIn, BuiltInTypeCheckFn typeCheck, BuiltInTypeCheckFn typeCheck, const char* glslName,
const char* glslName, Uint32 bufferIndex, Uint32 offsetBytes, Uint32 bufferIndex, Uint32 offsetBytes,
const DecorateFn& decorateForXfb) { const DecorateFn& decorateForXfb) {
const Uint32 entryPointModel = entryPoint.GetSingleWordInOperand(0);
using namespace spvtools::opt; using namespace spvtools::opt;
PositionTargetInfo target{}; PositionTargetInfo target{};
if (!FindBuiltInTarget(context(), builtIn, typeCheck, &target)) { if (!FindBuiltInTarget(context(), builtIn, typeCheck, &target)) {
@@ -1640,27 +1658,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
decorateForXfb(mirrorVariableId, bufferIndex, offsetBytes); decorateForXfb(mirrorVariableId, bufferIndex, offsetBytes);
entryPoint.AddOperand({SPV_OPERAND_TYPE_ID, {mirrorVariableId}}); entryPoint.AddOperand({SPV_OPERAND_TYPE_ID, {mirrorVariableId}});
auto* function = context()->GetFunction(entryFunctionId); // Geometry stages capture per emitted vertex; other stages at return.
if (function == nullptr) return false;
const auto model = static_cast<spv::ExecutionModel>(entryPointModel);
Bool injected = false; Bool injected = false;
for (auto& block : *function) { for (auto* site : CollectPerVertexFixupSites(context(), entryPoint)) {
for (auto instIter = block.begin(); instIter != block.end(); ++instIter) { InstructionBuilder builder(context(), site, IRContext::kAnalysisNone);
// Geometry stages capture per emitted vertex; other stages at return. const Uint32 memberIndexId = builder.GetUintConstantId(target.memberIndex);
const Bool isInjectionSite = model == spv::ExecutionModel::Geometry auto* access = builder.AddAccessChain(target.vectorPtrTypeId, target.variableId, {memberIndexId});
? instIter->opcode() == spv::Op::OpEmitVertex if (access == nullptr) return injected;
: instIter->opcode() == spv::Op::OpReturn; auto* value = builder.AddLoad(target.vectorTypeId, access->result_id());
if (!isInjectionSite) continue; if (value == nullptr) return injected;
InstructionBuilder builder(context(), &*instIter, IRContext::kAnalysisNone); builder.AddStore(mirrorVariableId, value->result_id());
const Uint32 memberIndexId = builder.GetUintConstantId(target.memberIndex); injected = true;
auto* access =
builder.AddAccessChain(target.vectorPtrTypeId, target.variableId, {memberIndexId});
if (access == nullptr) return injected;
auto* value = builder.AddLoad(target.vectorTypeId, access->result_id());
if (value == nullptr) return injected;
builder.AddStore(mirrorVariableId, value->result_id());
injected = true;
}
} }
// The mirror was listed on the entry point above, but the loop just added a READ // The mirror was listed on the entry point above, but the loop just added a READ
// of the SOURCE block through an access chain, and the interface rule covers // of the SOURCE block through an access chain, and the interface rule covers
@@ -1680,7 +1688,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}; };
// Adreno 650 (driver 512.502) faults the GPU on an implicit-LOD sample of a full-screen // Adreno 650 (driver 512.502) faults the GPU on an implicit-LOD sample of a full-screen
// colour render target: the texture unit's derivative path reads outside the image's // color render target: the texture unit's derivative path reads outside the image's
// allocation even though the sampler clamps LOD to 0 and the mapping is 1:1. MobileGL's // allocation even though the sampler clamps LOD to 0 and the mapping is 1:1. MobileGL's
// own default-framebuffer blit shader works around it with textureLod, but an // own default-framebuffer blit shader works around it with textureLod, but an
// application's shader (Minecraft's blit.fsh is `texture(InSampler, texCoord)`) cannot be // application's shader (Minecraft's blit.fsh is `texture(InSampler, texCoord)`) cannot be
@@ -1951,36 +1959,37 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
return success; return success;
} }
} // namespace
Bool TransformSpirvForVulkanPositionFix(const Vector<Uint>& input, Vector<Uint>& output, Bool TransformSpirvForVulkanPositionFix(const Vector<Uint>& input, Vector<Uint>& output,
ProgramFactory::CompileOptionFlags transformFlags) { ProgramFactory::CompileOptionFlags transformFlags) {
if (input.empty()) { if (input.empty()) {
output.clear(); output.clear();
return true; return true;
}
if (!transformFlags) {
output = input;
return true;
}
spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_3);
spvtools::OptimizerOptions options;
options.set_run_validator(false); // see TransformSpirvForExplicitLod0Sampling
optimizer.SetMessageConsumer(
[](spv_message_level_t, const char*, const spv_position_t&, const char* message) {
MGLOG_E("Vulkan: position fix pass: %s", message != nullptr ? message : "");
});
optimizer.RegisterPass(CreateGlToVulkanPositionFixPass(transformFlags));
const Bool success = optimizer.Run(input.data(), input.size(), &output, options);
if (!success) {
MGLOG_E("Vulkan: failed to run GL->Vulkan position fix pass");
output = input;
}
return success;
} }
if (!transformFlags) {
output = input;
return true;
}
spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_3);
spvtools::OptimizerOptions options;
options.set_run_validator(false); // see TransformSpirvForExplicitLod0Sampling
optimizer.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&, const char* message) {
MGLOG_E("Vulkan: position fix pass: %s", message != nullptr ? message : "");
});
optimizer.RegisterPass(CreateGlToVulkanPositionFixPass(transformFlags));
const Bool success = optimizer.Run(input.data(), input.size(), &output, options);
if (!success) {
MGLOG_E("Vulkan: failed to run GL->Vulkan position fix pass");
output = input;
}
return success;
}
namespace {
ShaderStage PickClipFixupStage(const Vector<ShaderStage>& stages) { ShaderStage PickClipFixupStage(const Vector<ShaderStage>& stages) {
Bool hasGeometry = false; Bool hasGeometry = false;
Bool hasTessEval = false; Bool hasTessEval = false;
@@ -2137,7 +2146,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// GetOrCreateProgram's MOBILEGL_ASSERT(remapOk) as the only complaint, and // GetOrCreateProgram's MOBILEGL_ASSERT(remapOk) as the only complaint, and
// that assert compiles out above DEBUG - so a release build SILENTLY kept // that assert compiles out above DEBUG - so a release build SILENTLY kept
// glslang's per-stage auto-mapped binding numbers, skipping the cross-stage // glslang's per-stage auto-mapped binding numbers, skipping the cross-stage
// unification and the set->0 normalisation this function exists to do. A // unification and the set->0 normalization this function exists to do. A
// program with an image array plus any second descriptor got aliased // program with an image array plus any second descriptor got aliased
// bindings out of that, and a DEBUG build trapped on the same program. // bindings out of that, and a DEBUG build trapped on the same program.
// The refusal below is MGLOG_E and per-program-compile, so it reports every // The refusal below is MGLOG_E and per-program-compile, so it reports every
@@ -2727,6 +2736,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkProgramObject& entry) const { VkProgramObject& entry) const {
entry.activeVertexInputLocationMask = 0; entry.activeVertexInputLocationMask = 0;
entry.vertexInputTypes.fill(0); entry.vertexInputTypes.fill(0);
entry.integerVertexInputs = {};
entry.readsBaseVertexBuiltin = false; entry.readsBaseVertexBuiltin = false;
for (SizeT moduleIndex = 0; moduleIndex < stages.size() && moduleIndex < spirv.size(); ++moduleIndex) { for (SizeT moduleIndex = 0; moduleIndex < stages.size() && moduleIndex < spirv.size(); ++moduleIndex) {
@@ -2793,6 +2803,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
entry.activeVertexInputLocationMask |= (1u << expandedLocation); entry.activeVertexInputLocationMask |= (1u << expandedLocation);
entry.vertexInputTypes[expandedLocation] = locationType; entry.vertexInputTypes[expandedLocation] = locationType;
entry.integerVertexInputs.Note(expandedLocation, locationType);
} }
} }
} }
@@ -2978,7 +2989,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_D("ProgramFactory::ReflectLayout: %s array '%s' at binding %u spans %u descriptors but the " MGLOG_D("ProgramFactory::ReflectLayout: %s array '%s' at binding %u spans %u descriptors but the "
"reflection reserved fewer uniform locations for it (base=%d) - a multi-dimensional array " "reflection reserved fewer uniform locations for it (base=%d) - a multi-dimensional array "
"is the usual cause, and MobileGL declines it rather than resolve elements onto a " "is the usual cause, and MobileGL declines it rather than resolve elements onto a "
"neighbouring uniform", "neighboring uniform",
kindLabel, uniformName.c_str(), binding, count, baseLocation); kindLabel, uniformName.c_str(), binding, count, baseLocation);
outDeclined = true; outDeclined = true;
} }
@@ -3519,7 +3530,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
"ProgramFactory::ReflectLayout, vkCreatePipelineLayout"); "ProgramFactory::ReflectLayout, vkCreatePipelineLayout");
// Built here rather than where bindingKinds is sized: at that point the vector is only // Built here rather than where bindingKinds is sized: at that point the vector is only
// zero-initialised and the kinds are assigned further down, so a list built there would be // zero-initialized and the kinds are assigned further down, so a list built there would be
// empty. Ascending by construction because the index walks upward. // empty. Ascending by construction because the index walks upward.
entry.activeBindings.clear(); entry.activeBindings.clear();
for (Uint32 binding = 0; binding < static_cast<Uint32>(entry.bindingKinds.size()); ++binding) { for (Uint32 binding = 0; binding < static_cast<Uint32>(entry.bindingKinds.size()); ++binding) {
@@ -3544,8 +3555,79 @@ namespace MobileGL::MG_Backend::DirectVulkan {
++m_cacheStructureEpoch; ++m_cacheStructureEpoch;
} }
const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram(const MagmaProgramSource& program, namespace {
CompileOptionFlags flags) { // The vertex stage's own transforms, in the order they have to run: the ZeroBaseVertex
// variant, the unconditional 64-bit input packing, then the narrow-integer variant.
void TransformVertexStageForVulkan(Vector<Uint>& module, ProgramFactory::CompileOptionFlags flags,
const NarrowIntegerVertexInputs& narrowIntegerInputs,
Bool enableSpirvValidation, Uint programIndex) {
// The non-indexed variant of a vertex stage that reads gl_BaseVertex: GL wants zero
// there, Vulkan's builtin would hand it the draw's firstVertex. Requested per draw
// through CompileOptionBit::ZeroBaseVertex, so the indexed variant of the same
// program keeps the native builtin and stays correct for glDrawElementsBaseVertex
// and for the baseVertex word of an indexed indirect command.
if (flags & ProgramFactory::CompileOptionBit::ZeroBaseVertex) {
Vector<Uint> zeroedSpirv;
if (MG_Util::ShaderTranspiler::ShaderCompiler::ZeroBaseVertexForVulkan(module, zeroedSpirv,
enableSpirvValidation)) {
module = std::move(zeroedSpirv);
} else {
// Failing open keeps the native builtin, which is the pre-fix behavior:
// gl_BaseVertex reads firstVertex on a DrawArrays instead of zero.
MGLOG_E("ProgramFactory: failed to zero gl_BaseVertex for program %u; non-indexed "
"draws will read the draw's first vertex from it instead of zero",
programIndex);
}
}
// A 64-bit vertex input has to arrive as its 32-bit word pair: VK_FORMAT_R64*_SFLOAT is
// optional and lavapipe advertises none of them at all. The pass is unconditional so it
// always agrees with the Float64 case in VertexInputStateFactory::ToVkVertexFormat, and
// ReflectVertexInputs then sees an ordinary uvec2/uvec4 input.
//
// Failure here is not recoverable and must not be swallowed: ToVkVertexFormat has already
// committed to R32G32{,B32A32}_UINT for the attribute, so a module still declaring
// `in double` would reconcile to Unknown and build a pipeline with a UINT format under a
// double input - garbage with no diagnostic anywhere.
{
Vector<Uint> packedSpirv;
const Bool packOk = MG_Util::ShaderTranspiler::ShaderCompiler::PackDoubleVertexInputsForVulkan(
module, packedSpirv, enableSpirvValidation);
MOBILEGL_ASSERT(packOk,
"ProgramFactory: 64-bit vertex input packing failed for program %u; the "
"vertex-input format and the shader input type now disagree",
programIndex);
if (packOk) {
module = std::move(packedSpirv);
} else {
MGLOG_E("ProgramFactory: failed to pack 64-bit vertex inputs for program %u; "
"double-typed vertex attributes will be fetched as uint32 words and not "
"reinterpreted",
programIndex);
}
}
// The narrow-integer variant: the pipeline fetches these attributes with the format of
// the input's signedness, and this restores the extension rule of the data's own (see
// ResolveNarrowIntegerVertexInputs). After the 64-bit packing so it sees the inputs in
// the shape the pipeline's vertex input state and ReflectVertexInputs describe.
if (narrowIntegerInputs.Any()) {
Vector<Uint> extendedSpirv;
if (MG_Util::ShaderTranspiler::ShaderCompiler::ExtendNarrowIntegerVertexInputsForVulkan(
module, extendedSpirv, narrowIntegerInputs, enableSpirvValidation)) {
module = std::move(extendedSpirv);
} else {
MGLOG_E("ProgramFactory: failed to extend narrow integer vertex inputs for program %u; "
"8/16-bit integer attributes read through an input of the other signedness keep "
"the fetch format's extension",
programIndex);
}
}
}
} // namespace
MagmaProgramSource::BackendHashMemoEntry& ProgramFactory::ProgramHashMemo(const MagmaProgramSource& program,
CompileOptionFlags flags) const {
// Hashing the full SPIR-V of every stage is far too expensive to repeat per draw; // Hashing the full SPIR-V of every stage is far too expensive to repeat per draw;
// reuse the program's memoized hash while its backend state version is unchanged. // reuse the program's memoized hash while its backend state version is unchanged.
// The memo keys on the flags word, which ComputeHash is no longer a pure function of: // The memo keys on the flags word, which ComputeHash is no longer a pure function of:
@@ -3561,16 +3643,46 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Uint memoKey = (flags & CompileOptionBit::FragCoordYFlip) const Uint memoKey = (flags & CompileOptionBit::FragCoordYFlip)
? (flags.GetRaw() | (m_defaultFramebufferHeight << 16)) ? (flags.GetRaw() | (m_defaultFramebufferHeight << 16))
: flags.GetRaw(); : flags.GetRaw();
HashType hash = 0; if (auto* memo = program.FindBackendHashMemo(memoKey)) return *memo;
if (!program.GetBackendHashMemo(memoKey, hash)) { return program.SetBackendHashMemo(memoKey, ComputeHash(program, flags));
hash = ComputeHash(program, flags); }
program.SetBackendHashMemo(memoKey, hash);
const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram(
const MagmaProgramSource& program, CompileOptionFlags flags,
const NarrowIntegerVertexInputs& narrowIntegerInputs) {
auto& memo = ProgramHashMemo(program, flags);
// Mixed in after the memo, which keys on the flags word alone: the memo keeps answering
// for the plain program, and the variant costs one 16-byte hash on top of it.
HashType hash = memo.Hash;
if (narrowIntegerInputs.Any()) hash = MixNarrowIntegerVertexInputs(hash, narrowIntegerInputs);
memo.LastVariantHash = hash;
return FindOrBuildProgram(program, flags, narrowIntegerInputs, hash);
}
const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateDrawProgram(const MagmaProgramSource& program,
CompileOptionFlags flags) {
auto& memo = ProgramHashMemo(program, flags);
if (memo.LastVariantHash != memo.Hash) {
const auto it = m_cache.find(memo.LastVariantHash);
if (it != m_cache.end()) {
it->second.lastUsedFrame = m_frameCounter;
return it->second;
}
// The variant aged out, and its hash alone cannot rebuild it: start from the plain
// program, and the draw settles on its variant again.
memo.LastVariantHash = memo.Hash;
} }
return FindOrBuildProgram(program, flags, {}, memo.Hash);
}
const ProgramFactory::VkProgramObject& ProgramFactory::FindOrBuildProgram(
const MagmaProgramSource& program, CompileOptionFlags flags,
const NarrowIntegerVertexInputs& narrowIntegerInputs, HashType hash) {
auto it = m_cache.find(hash); auto it = m_cache.find(hash);
if (it != m_cache.end()) { if (it != m_cache.end()) {
// Every draw/dispatch funnels through this lookup (the renderer memos only // Every draw/dispatch funnels through this lookup or GetOrCreateDrawProgram's (the
// skip re-hashing, never the factory lookup), so an actively-used entry is // renderer memos only skip re-hashing, never the factory lookup), so an actively-used
// stamped at least once per frame boundary and can never be aged out while // entry is stamped at least once per frame boundary and can never be aged out while
// any in-flight command buffer still references it. // any in-flight command buffer still references it.
it->second.lastUsedFrame = m_frameCounter; it->second.lastUsedFrame = m_frameCounter;
return it->second; return it->second;
@@ -3581,6 +3693,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
++m_cacheStructureEpoch; ++m_cacheStructureEpoch;
auto& entry = m_cache[hash]; auto& entry = m_cache[hash];
entry.hash = hash; entry.hash = hash;
entry.narrowIntegerInputs = narrowIntegerInputs;
entry.lastUsedFrame = m_frameCounter; entry.lastUsedFrame = m_frameCounter;
// The EXECUTABLE's stage list, not GetAttachedShaders(): `spirv` is a link artifact with // The EXECUTABLE's stage list, not GetAttachedShaders(): `spirv` is a link artifact with
// one module per linked stage, while the attach list is live and grows on // one module per linked stage, while the attach list is live and grows on
@@ -3818,50 +3931,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
} }
// The non-indexed variant of a vertex stage that reads gl_BaseVertex: GL wants zero
// there, Vulkan's builtin would hand it the draw's firstVertex. Requested per draw
// through CompileOptionBit::ZeroBaseVertex, so the indexed variant of the same
// program keeps the native builtin and stays correct for glDrawElementsBaseVertex
// and for the baseVertex word of an indexed indirect command.
if (stages[i] == ShaderStage::Vertex && (flags & CompileOptionBit::ZeroBaseVertex)) {
Vector<Uint> zeroedSpirv;
if (MG_Util::ShaderTranspiler::ShaderCompiler::ZeroBaseVertexForVulkan(moduleSpirvs[i], zeroedSpirv,
enableSpirvValidation)) {
moduleSpirvs[i] = std::move(zeroedSpirv);
} else {
// Failing open keeps the native builtin, which is the pre-fix behavior:
// gl_BaseVertex reads firstVertex on a DrawArrays instead of zero.
MGLOG_E("ProgramFactory: failed to zero gl_BaseVertex for program %u; non-indexed "
"draws will read the draw's first vertex from it instead of zero",
program.GetExternalIndex());
}
}
// A 64-bit vertex input has to arrive as its 32-bit word pair: VK_FORMAT_R64*_SFLOAT is
// optional and lavapipe advertises none of them at all. The pass is unconditional so it
// always agrees with the Float64 case in VertexInputStateFactory::ToVkVertexFormat, and
// ReflectVertexInputs below then sees an ordinary uvec2/uvec4 input.
//
// Failure here is not recoverable and must not be swallowed: ToVkVertexFormat has already
// committed to R32G32{,B32A32}_UINT for the attribute, so a module still declaring
// `in double` would reconcile to Unknown and build a pipeline with a UINT format under a
// double input - garbage with no diagnostic anywhere.
if (stages[i] == ShaderStage::Vertex) { if (stages[i] == ShaderStage::Vertex) {
Vector<Uint> packedSpirv; TransformVertexStageForVulkan(moduleSpirvs[i], flags, narrowIntegerInputs, enableSpirvValidation,
const Bool packOk = MG_Util::ShaderTranspiler::ShaderCompiler::PackDoubleVertexInputsForVulkan( program.GetExternalIndex());
moduleSpirvs[i], packedSpirv, enableSpirvValidation);
MOBILEGL_ASSERT(packOk,
"ProgramFactory: 64-bit vertex input packing failed for program %u; the "
"vertex-input format and the shader input type now disagree",
program.GetExternalIndex());
if (packOk) {
moduleSpirvs[i] = std::move(packedSpirv);
} else {
MGLOG_E("ProgramFactory: failed to pack 64-bit vertex inputs for program %u; "
"double-typed vertex attributes will be fetched as uint32 words and not "
"reinterpreted",
program.GetExternalIndex());
}
} }
// When Vulkan can legally access storage images without a statically declared // When Vulkan can legally access storage images without a statically declared
@@ -4056,7 +4128,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
ReflectPassthroughTessControlNeed(stages, moduleSpirvs, entry); ReflectPassthroughTessControlNeed(stages, moduleSpirvs, entry);
ReflectLayout(program, moduleSpirvs, entry); ReflectLayout(program, moduleSpirvs, entry);
// A failed remap means the modules kept glslang's per-stage auto-mapped binding numbers - // A failed remap means the modules kept glslang's per-stage auto-mapped binding numbers -
// no cross-stage unification, no set->0 normalisation - so the bindings this layout // no cross-stage unification, no set->0 normalization - so the bindings this layout
// describes are not the bindings the shader reads. That has to stop the program from // describes are not the bindings the shader reads. That has to stop the program from
// drawing, and until now nothing did: the MOBILEGL_ASSERT above compiles out of every // drawing, and until now nothing did: the MOBILEGL_ASSERT above compiles out of every
// build past DEBUG, and RemapDescriptorBindingsForVulkan's own refusal message said so at // build past DEBUG, and RemapDescriptorBindingsForVulkan's own refusal message said so at
@@ -4256,7 +4328,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// PATCH_DEFAULT_OUTER_LEVEL / PATCH_DEFAULT_INNER_LEVEL state. // PATCH_DEFAULT_OUTER_LEVEL / PATCH_DEFAULT_INNER_LEVEL state.
// //
// Those two levels are baked in as literals - Vulkan has no equivalent dynamic state, so // Those two levels are baked in as literals - Vulkan has no equivalent dynamic state, so
// compiling them in is the only way to honour glPatchParameterfv. That makes them part of // compiling them in is the only way to honor glPatchParameterfv. That makes them part of
// this module's identity: GetOrCreatePassthroughTessControlStage keys its cache on them, // this module's identity: GetOrCreatePassthroughTessControlStage keys its cache on them,
// and PipelineFactory hashes them into the pipeline key. The three must move together. // and PipelineFactory hashes them into the pipeline key. The three must move together.
// //
@@ -4272,7 +4344,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// this from having to know the domain. // this from having to know the domain.
String source = "#version 450 core\n"; String source = "#version 450 core\n";
source += "layout(vertices = " + std::to_string(patchVertices) + ") out;\n"; source += "layout(vertices = " + std::to_string(patchVertices) + ") out;\n";
// gl_in and gl_out are redeclared to the exact gl_PerVertex the NEIGHBOURING EVALUATION // gl_in and gl_out are redeclared to the exact gl_PerVertex the NEIGHBORING EVALUATION
// STAGE carries, because Vulkan matches built-in interface blocks by their whole shape, // STAGE carries, because Vulkan matches built-in interface blocks by their whole shape,
// and the two obvious spellings are both wrong: // and the two obvious spellings are both wrong:
// * narrowing the block to gl_Position alone makes the evaluation stage read a patch of // * narrowing the block to gl_Position alone makes the evaluation stage read a patch of
@@ -12,6 +12,7 @@
#include "../MagmaSession.h" #include "../MagmaSession.h"
#include "PipelineFactory.h" #include "PipelineFactory.h"
#include "MagmaProgramSource.h" #include "MagmaProgramSource.h"
#include "NarrowIntegerVertexFormats.h"
#include "MG_State/GLState/ProgramState/ShaderObject.h" #include "MG_State/GLState/ProgramState/ShaderObject.h"
#include "MG_State/GLState/TextureState/TextureEnum.h" #include "MG_State/GLState/TextureState/TextureEnum.h"
@@ -91,7 +92,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// The gl_PerVertex members a pass-through tessellation control stage may have to carry, // The gl_PerVertex members a pass-through tessellation control stage may have to carry,
// in the order glslang declares them - which is the order a redeclaration must use. // in the order glslang declares them - which is the order a redeclaration must use.
// Which of them exist is a function of the neighbouring stage's GLSL VERSION // Which of them exist is a function of the neighboring stage's GLSL VERSION
// (gl_CullDistance joins the block at #version 450), so the mask is read off that // (gl_CullDistance joins the block at #version 450), so the mask is read off that
// stage's SPIR-V rather than assumed. See ReflectPerVertexInputMembers. // stage's SPIR-V rather than assumed. See ReflectPerVertexInputMembers.
enum class PerVertexMemberBit : Uint32 { enum class PerVertexMemberBit : Uint32 {
@@ -200,6 +201,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Int globalUboBinding = -1; Int globalUboBinding = -1;
Uint32 activeVertexInputLocationMask = 0; Uint32 activeVertexInputLocationMask = 0;
Array<GLenum, kMaxVertexInputLocations> vertexInputTypes{}; Array<GLenum, kMaxVertexInputLocations> vertexInputTypes{};
// The locations of vertexInputTypes that are int / uint, for the per-draw narrow-integer
// variant decision (ResolveNarrowIntegerVertexInputs). Identical in every variant.
IntegerVertexInputMasks integerVertexInputs;
// The narrow-integer correction this entry's vertex stage was built with (none for the
// plain program): what a draw compares its own resolved set against.
NarrowIntegerVertexInputs narrowIntegerInputs;
Uint32 activeFragmentOutputLocationMask = 0; Uint32 activeFragmentOutputLocationMask = 0;
Array<GLenum, kMaxVertexInputLocations> fragmentOutputTypes{}; Array<GLenum, kMaxVertexInputLocations> fragmentOutputTypes{};
ShaderStage rasterizationProducerStage = ShaderStage::Unknown; ShaderStage rasterizationProducerStage = ShaderStage::Unknown;
@@ -328,6 +335,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
globalUboBinding = other.globalUboBinding; globalUboBinding = other.globalUboBinding;
activeVertexInputLocationMask = other.activeVertexInputLocationMask; activeVertexInputLocationMask = other.activeVertexInputLocationMask;
vertexInputTypes = other.vertexInputTypes; vertexInputTypes = other.vertexInputTypes;
integerVertexInputs = other.integerVertexInputs;
narrowIntegerInputs = other.narrowIntegerInputs;
activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask; activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask;
fragmentOutputTypes = other.fragmentOutputTypes; fragmentOutputTypes = other.fragmentOutputTypes;
rasterizationProducerStage = other.rasterizationProducerStage; rasterizationProducerStage = other.rasterizationProducerStage;
@@ -351,6 +360,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
other.pointSizeCapabilityUnsupported = false; other.pointSizeCapabilityUnsupported = false;
other.globalUboBinding = -1; other.globalUboBinding = -1;
other.activeVertexInputLocationMask = 0; other.activeVertexInputLocationMask = 0;
other.integerVertexInputs = {};
other.narrowIntegerInputs = {};
other.activeFragmentOutputLocationMask = 0; other.activeFragmentOutputLocationMask = 0;
other.rasterizationProducerStage = ShaderStage::Unknown; other.rasterizationProducerStage = ShaderStage::Unknown;
other.producerOutputComponentCount = 0; other.producerOutputComponentCount = 0;
@@ -402,6 +413,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
globalUboBinding = other.globalUboBinding; globalUboBinding = other.globalUboBinding;
activeVertexInputLocationMask = other.activeVertexInputLocationMask; activeVertexInputLocationMask = other.activeVertexInputLocationMask;
vertexInputTypes = other.vertexInputTypes; vertexInputTypes = other.vertexInputTypes;
integerVertexInputs = other.integerVertexInputs;
narrowIntegerInputs = other.narrowIntegerInputs;
activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask; activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask;
fragmentOutputTypes = other.fragmentOutputTypes; fragmentOutputTypes = other.fragmentOutputTypes;
rasterizationProducerStage = other.rasterizationProducerStage; rasterizationProducerStage = other.rasterizationProducerStage;
@@ -425,6 +438,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
other.pointSizeCapabilityUnsupported = false; other.pointSizeCapabilityUnsupported = false;
other.globalUboBinding = -1; other.globalUboBinding = -1;
other.activeVertexInputLocationMask = 0; other.activeVertexInputLocationMask = 0;
other.integerVertexInputs = {};
other.narrowIntegerInputs = {};
other.activeFragmentOutputLocationMask = 0; other.activeFragmentOutputLocationMask = 0;
other.rasterizationProducerStage = ShaderStage::Unknown; other.rasterizationProducerStage = ShaderStage::Unknown;
other.producerOutputComponentCount = 0; other.producerOutputComponentCount = 0;
@@ -518,7 +533,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
ProgramFactory(const ProgramFactory&) = delete; ProgramFactory(const ProgramFactory&) = delete;
HashType ComputeHash(const MagmaProgramSource& program, CompileOptionFlags flags) const; HashType ComputeHash(const MagmaProgramSource& program, CompileOptionFlags flags) const;
const VkProgramObject& GetOrCreateProgram(const MagmaProgramSource& program, CompileOptionFlags flags); // `narrowIntegerInputs` selects the variant whose vertex stage widens narrow integer
// attribute data GL's way (ExtendNarrowIntegerVertexInputsPass). It is draw-time state like
// the flags, and the draw path asks for it only when ResolveNarrowIntegerVertexInputs finds
// a location that needs it, so every other program keeps a single entry. The variant
// returned is remembered per flags for GetOrCreateDrawProgram.
const VkProgramObject& GetOrCreateProgram(const MagmaProgramSource& program, CompileOptionFlags flags,
const NarrowIntegerVertexInputs& narrowIntegerInputs = {});
// A draw's first lookup: the variant GetOrCreateProgram last returned for these flags, plain
// or narrow-integer. Every variant reflects the same vertex inputs and builtins, which is all
// a draw reads before it knows the variant it needs, so a program whose draws keep needing
// one variant costs a single lookup and its plain entry, never drawn with, ages out.
const VkProgramObject& GetOrCreateDrawProgram(const MagmaProgramSource& program, CompileOptionFlags flags);
// The cache identity of a narrow-integer variant: the hash of the same program and flags
// without it, mixed with the corrected locations. Content in, content out, so the variant's
// pipelines key apart from the plain program's through programHash alone.
static HashType MixNarrowIntegerVertexInputs(HashType hash, const NarrowIntegerVertexInputs& inputs) {
return XXH64(&inputs, sizeof(inputs), hash);
}
// The default framebuffer's current image height, baked as a literal into every // The default framebuffer's current image height, baked as a literal into every
// FragCoordYFlip variant (there is no push-constant or specialization channel here, and // FragCoordYFlip variant (there is no push-constant or specialization channel here, and
@@ -615,7 +647,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// member set, because all three decide what the generator emits. The size comes from // member set, because all three decide what the generator emits. The size comes from
// PATCH_VERTICES and the levels from PATCH_DEFAULT_OUTER_LEVEL / PATCH_DEFAULT_INNER_LEVEL // PATCH_VERTICES and the levels from PATCH_DEFAULT_OUTER_LEVEL / PATCH_DEFAULT_INNER_LEVEL
// - draw state rather than link state, and the CTS case that motivated this links at the // - draw state rather than link state, and the CTS case that motivated this links at the
// default 3 and draws at 4. The member set comes from the neighbouring evaluation stage's // default 3 and draws at 4. The member set comes from the neighboring evaluation stage's
// own SPIR-V, so two programs at different GLSL versions need different modules. The // own SPIR-V, so two programs at different GLSL versions need different modules. The
// pipeline cache re-keys on the same inputs, so the module a pipeline was built with is // pipeline cache re-keys on the same inputs, so the module a pipeline was built with is
// part of that pipeline's identity. Compiling is bounded by the number of distinct // part of that pipeline's identity. Compiling is bounded by the number of distinct
@@ -654,6 +686,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
HashType hash = 0; HashType hash = 0;
}; };
// The program's memo slot for `flags`, its plain hash computed on a miss.
MagmaProgramSource::BackendHashMemoEntry& ProgramHashMemo(const MagmaProgramSource& program,
CompileOptionFlags flags) const;
// The cache entry under `hash`, built as that variant of `program` when absent.
const VkProgramObject& FindOrBuildProgram(const MagmaProgramSource& program, CompileOptionFlags flags,
const NarrowIntegerVertexInputs& narrowIntegerInputs, HashType hash);
static TextureTarget UniformTypeToTextureTarget(GLenum glType); static TextureTarget UniformTypeToTextureTarget(GLenum glType);
// `stages` is ALWAYS ProgramObject::GetLinkedShaderStages() - one entry per module of // `stages` is ALWAYS ProgramObject::GetLinkedShaderStages() - one entry per module of
// `spirv`, at the same index. Taking the stages rather than the shader objects is what // `spirv`, at the same index. Taking the stages rather than the shader objects is what
@@ -726,4 +765,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Private copy whose y is (framebufferHeight - y). Fails open (output = input, false). Exposed // Private copy whose y is (framebufferHeight - y). Fails open (output = input, false). Exposed
// so a unit case can validate the bytes it hands the driver. // so a unit case can validate the bytes it hands the driver.
Bool TransformSpirvForFragCoordYFlip(const Vector<Uint>& input, Vector<Uint>& output, Uint32 framebufferHeight); Bool TransformSpirvForFragCoordYFlip(const Vector<Uint>& input, Vector<Uint>& output, Uint32 framebufferHeight);
// The clip-space fix-up of the last pre-rasterization stage: gl_Position is rewritten in place
// per `transformFlags` before every vertex the stage produces. Fails open (output = input,
// false). Exposed, like the transform above, so a unit case can validate its bytes.
Bool TransformSpirvForVulkanPositionFix(const Vector<Uint>& input, Vector<Uint>& output,
ProgramFactory::CompileOptionFlags transformFlags);
} // namespace MobileGL::MG_Backend::DirectVulkan } // namespace MobileGL::MG_Backend::DirectVulkan
@@ -66,9 +66,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// EGL content-validity tracking for the default framebuffer. A color // EGL content-validity tracking for the default framebuffer. A color
// buffer's content is undefined once its image has been presented // buffer's content is undefined once its image has been presented
// (EGL_BUFFER_DESTROYED swap behaviour, the implementation default), // (EGL_BUFFER_DESTROYED swap behavior, the implementation default),
// and every ancillary (depth/stencil) buffer's content is undefined // and every ancillary (depth/stencil) buffer's content is undefined
// after ANY swap regardless of swap behaviour (EGL 1.5 §3.10.1). The // after ANY swap regardless of swap behavior (EGL 1.5 §3.10.1). The
// render-pass manager turns an undefined attachment's tile load into // render-pass manager turns an undefined attachment's tile load into
// LOAD_OP_DONT_CARE. Flags start false (a fresh swapchain image holds // LOAD_OP_DONT_CARE. Flags start false (a fresh swapchain image holds
// garbage) and a render pass storing into an attachment sets it back // garbage) and a render pass storing into an attachment sets it back
@@ -844,7 +844,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// is not the answer either: the transient arena carries no // is not the answer either: the transient arena carries no
// {UNIFORM,STORAGE}_TEXEL_BUFFER usage (VkBufferManager::InitializeTransientArenas), // {UNIFORM,STORAGE}_TEXEL_BUFFER usage (VkBufferManager::InitializeTransientArenas),
// so no VkBufferView can be made over it, and widening a shared per-frame // so no VkBufferView can be made over it, and widening a shared per-frame
// allocation to buy an unreachable path is the wrong trade. See docs/archive/notes/p7/magma-a.md. // allocation to buy an unreachable path is the wrong trade. See the phase notes.
return writable ? false : placeholder(); return writable ? false : placeholder();
} }
VkBufferViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO}; VkBufferViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO};
@@ -973,7 +973,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// record that is the draw/dispatch tail in VulkanRenderer.cpp / WireDraw.cpp, // record that is the draw/dispatch tail in VulkanRenderer.cpp / WireDraw.cpp,
// which belong to wave-2 packages B and C, not to this file. Losing the // which belong to wave-2 packages B and C, not to this file. Losing the
// dispatch with a named line in the role log beats returning stale counters // dispatch with a named line in the role log beats returning stale counters
// with no error at all. docs/archive/notes/p7/magma-a.md carries the recipe. // with no error at all. The phase notes carry the recipe.
MGLOG_E_ONCE("ResolveStorageBufferDescriptor: block '%s' is bound at offset %llu, which is not a " MGLOG_E_ONCE("ResolveStorageBufferDescriptor: block '%s' is bound at offset %llu, which is not a "
"multiple of this device's minStorageBufferOffsetAlignment (%llu); %s ranges cannot be " "multiple of this device's minStorageBufferOffsetAlignment (%llu); %s ranges cannot be "
"expressed as a descriptor and the shader's writes would be dropped by a read-only " "expressed as a descriptor and the shader's writes would be dropped by a read-only "
@@ -1146,7 +1146,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, and that is a multiple of four on every // GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, and that is a multiple of four on every
// device - but GL 4.6 core 6.1.1 puts no such rule on the SIZE, so `copied` is free // device - but GL 4.6 core 6.1.1 puts no such rule on the SIZE, so `copied` is free
// to end mid-word and the Redmi Minecraft run lands there // to end mid-word and the Redmi Minecraft run lands there
// (docs/archive/notes/p5f/magma-inproc-fix.md #5). An unaligned start is not reachable through // An unaligned start is not reachable through
// the public API and only a protocol-level offset can produce one; the sub-word arm // the public API and only a protocol-level offset can produce one; the sub-word arm
// handles it anyway rather than leaving a second shape to trip over later. Both arms // handles it anyway rather than leaving a second shape to trip over later. Both arms
// copy the same bytes to the same place: the sub-word one pays one extra transient // copy the same bytes to the same place: the sub-word one pays one extra transient
@@ -116,7 +116,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// attribute whose base offsets cannot be added is one Vulkan cannot fetch, the // attribute whose base offsets cannot be added is one Vulkan cannot fetch, the
// monolith arm never computes that sum at all, and masking is strictly safer than // monolith arm never computes that sum at all, and masking is strictly safer than
// a Fatal for a number no application can reach on purpose. Recorded in // a Fatal for a number no application can reach on purpose. Recorded in
// docs/archive/notes/p7/magma-c.md rather than silently reconciled. // the phase notes rather than silently reconciled.
const auto describe = [&](const char* reason, VkFormat format) { const auto describe = [&](const char* reason, VkFormat format) {
MGLOG_E_ONCE("Magma wire vertex layout: %s location=%u type=%u size=%u normalized=%u integer=%u " MGLOG_E_ONCE("Magma wire vertex layout: %s location=%u type=%u size=%u normalized=%u integer=%u "
"long=%u bgra=%u stride=%d offset=%llu format=%d bufferWindow=%u+%u activeMask=0x%x", "long=%u bgra=%u stride=%d offset=%llu format=%d bufferWindow=%u+%u activeMask=0x%x",
@@ -214,6 +214,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
out.bindings.push_back( out.bindings.push_back(
{binding, stride, buffer.Divisor ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX}); {binding, stride, buffer.Divisor ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX});
out.attributes.push_back({location, binding, format, 0}); out.attributes.push_back({location, binding, format, 0});
out.narrowIntegerAttributes.Note(location, format);
out.bindingAttributeLocations.push_back(location); out.bindingAttributeLocations.push_back(location);
out.bindingBaseOffsets.push_back(static_cast<SizeT>(offset)); out.bindingBaseOffsets.push_back(static_cast<SizeT>(offset));
out.bindingConversions.push_back(conversion); out.bindingConversions.push_back(conversion);
@@ -14,6 +14,7 @@
#include "Config.h" #include "Config.h"
#include "MagmaPipeArms.h" #include "MagmaPipeArms.h"
#include "NarrowIntegerVertexFormats.h"
#include "VertexInputStateBuilder.h" #include "VertexInputStateBuilder.h"
#include "MG_State/GLState/VertexArrayState/VertexArrayObject.h" #include "MG_State/GLState/VertexArrayState/VertexArrayObject.h"
#include <Includes.h> #include <Includes.h>
@@ -63,6 +64,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Bitmask of `attributes[i].location` - the draw path needs it up to // Bitmask of `attributes[i].location` - the draw path needs it up to
// three times per draw, so it is baked once at build time. // three times per draw, so it is baked once at build time.
Uint32 attributeLocationMask = 0; Uint32 attributeLocationMask = 0;
// The locations of `attributes` whose source data is 8/16-bit integer, by width and
// signedness - the vertex-input half of the narrow-integer program variant decision
// (NarrowIntegerVertexFormats.h). A pure function of `attributes`, so `layoutHash`
// already covers it.
NarrowIntegerAttributeMasks narrowIntegerAttributes;
// Per-binding glVertexAttribDivisor values other than 1. Vulkan's instance input // Per-binding glVertexAttribDivisor values other than 1. Vulkan's instance input
// rate advances once per instance and nothing else, so anything else has to be // rate advances once per instance and nothing else, so anything else has to be
// stated through VK_EXT_vertex_attribute_divisor. Empty when every instanced // stated through VK_EXT_vertex_attribute_divisor. Empty when every instanced
@@ -27,19 +27,6 @@
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
namespace { namespace {
// P7 wave 2 package B3, INVESTIGATION PROBE (split-only, read once, not in
// ConfigLoader's accepted-env table - package B's MGITEST_MAGMA_FORCE_SHADER_MIPMAP
// shape). MOBILEGL_MAGMA_WIREBUF_PROBE=1 prints one line per streamed-buffer event so
// the `glBufferSubData -> draw` ordering of a trace can be read off a log instead of
// inferred. Costs a branch on a cached bool when unset.
Bool WireBufProbeEnabled() {
static const Bool enabled = [] {
const char* value = std::getenv("MOBILEGL_MAGMA_WIREBUF_PROBE");
return value && value[0] == '1';
}();
return enabled;
}
// P7 wave 2 package B3, FORCING KNOB (split-only, read once, not in ConfigLoader's // P7 wave 2 package B3, FORCING KNOB (split-only, read once, not in ConfigLoader's
// accepted-env table - package B's MGITEST_MAGMA_FORCE_SHADER_MIPMAP shape). // accepted-env table - package B's MGITEST_MAGMA_FORCE_SHADER_MIPMAP shape).
// //
@@ -424,14 +411,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
resource->contentSerial = ++m_wireContentSerial; resource->contentSerial = ++m_wireContentSerial;
if (!resource->shadow.empty()) Memcpy(resource->shadow.data() + offset, bytes, static_cast<SizeT>(size)); if (!resource->shadow.empty()) Memcpy(resource->shadow.data() + offset, bytes, static_cast<SizeT>(size));
Bool uploaded = false; Bool uploaded = false;
const Uint64 probeLastUse = resource->lastUseSerial;
const Uint64 probeCompleted = GetCompletedSerial();
// ---- P7 wave 2 package B3: WHAT EACH TERM OF THIS PREDICATE IS FOR ----------------- // ---- P7 wave 2 package B3: WHAT EACH TERM OF THIS PREDICATE IS FOR -----------------
// //
// A streamed `glBufferSubData -> draw` pair is only correct if the write is ordered // A streamed `glBufferSubData -> draw` pair is only correct if the write is ordered
// against every draw that still reads the bytes it overwrites; a write this predicate // against every draw that still reads the bytes it overwrites; a write this predicate
// answers "idle" is applied as an unsynchronised host memcpy, and a draw still reading // answers "idle" is applied as an unsynchronized host memcpy, and a draw still reading
// the range then renders bytes that are not its own - silently, with no log line, no GL // the range then renders bytes that are not its own - silently, with no log line, no GL
// error and no Fatal. That was the OpenRA device divergence (magma-b3.md §2). // error and no Fatal. That was the OpenRA device divergence (magma-b3.md §2).
// //
@@ -445,16 +430,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// submission still carries, and run_trace_case.cmake reds a split retrace on any // submission still carries, and run_trace_case.cmake reds a split retrace on any
// "MGWIRE-FLOOR unsound-serial-complete" line, which is that fix's lane. // "MGWIRE-FLOOR unsound-serial-complete" line, which is that fix's lane.
// //
// THE SUBMISSION TERM IS A PROBE AND A DEFENCE, NOT A SECOND GUARANTEE. It is not sound // THE SUBMISSION TERM IS A PROBE AND A DEFENSE, NOT A SECOND GUARANTEE. It is not sound
// on its own: lastUseSubmitIndex is stamped at AcquireWireSlice with the next submission // on its own: lastUseSubmitIndex is stamped at AcquireWireSlice with the next submission
// index, but the draw that acquired the slice can still be pushed into a LATER // index, but the draw that acquired the slice can still be pushed into a LATER
// submission - BindProgramUniformBuffers -> SyncWireTextureShape's preserve path -> // submission - BindProgramUniformBuffers -> SyncWireTextureShape's preserve path ->
// FlushWirePendingCommandsForTextureUpdate submits the stamped index without the draw - // FlushWirePendingCommandsForTextureUpdate submits the stamped index without the draw -
// so the stamp can name S1 while the draw rides S2. What it does do: with the floor // so the stamp can name S1 while the draw rides S2. What it does do: with the floor
// sound it should never be the term that says "busy" (the WBUF probe reports bySubmit // sound it should never be the term that says "busy" (a write it alone orders points to a
// separately, so a device that shows one has found a floor hole), and it is the only // floor hole), and it is the only thing the MGITEST_MAGMA_FORCE_STALE_BUFFER_SERIAL knob
// thing the MGITEST_MAGMA_FORCE_STALE_BUFFER_SERIAL knob leaves standing, which is what // leaves standing, which is what the StaleSerial. entries pin.
// the StaleSerial. entries pin.
Bool busyBySerial = resource->lastUseSerial > GetCompletedSerial(); Bool busyBySerial = resource->lastUseSerial > GetCompletedSerial();
if (busyBySerial && ForceStaleSerialSelector() != 0) { if (busyBySerial && ForceStaleSerialSelector() != 0) {
const Uint64 index = ++g_orderedWireWriteCounter; const Uint64 index = ++g_orderedWireWriteCounter;
@@ -474,7 +458,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} else if (busyBySerial || busyBySubmission) { } else if (busyBySerial || busyBySubmission) {
// Vulkan buffer copies require four-byte aligned ranges. An odd GL // Vulkan buffer copies require four-byte aligned ranges. An odd GL
// byte update waits, then writes only the exact range; rounding it // byte update waits, then writes only the exact range; rounding it
// from a CPU shadow could overwrite neighbouring GPU-written bytes. // from a CPU shadow could overwrite neighboring GPU-written bytes.
if (((offset | size) & 3u) == 0) { if (((offset | size) & 3u) == 0) {
uploaded = StagedWireRangeCopy(*resource, bytes, static_cast<SizeT>(offset), static_cast<SizeT>(size)); uploaded = StagedWireRangeCopy(*resource, bytes, static_cast<SizeT>(offset), static_cast<SizeT>(size));
} }
@@ -494,16 +478,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-upload\"}"); MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-upload\"}");
std::abort(); std::abort();
} }
if (WireBufProbeEnabled()) {
MGLOG_I("WBUF write slot=%u gen=%u off=%llu size=%llu branch=%s bySerial=%d bySubmit=%d "
"lastUse=%llu completed=%llu frameSerial=%llu floor=%llu lastUseSubmit=%llu",
res.Slot, res.Gen, static_cast<unsigned long long>(offset), static_cast<unsigned long long>(size),
(busyBySerial || busyBySubmission) ? "STAGED-or-WAIT" : "HOST-IMMEDIATE", busyBySerial ? 1 : 0,
busyBySubmission ? 1 : 0, static_cast<unsigned long long>(probeLastUse),
static_cast<unsigned long long>(probeCompleted), static_cast<unsigned long long>(m_frameSerial),
static_cast<unsigned long long>(m_completedSerialFloor),
static_cast<unsigned long long>(resource->lastUseSubmitIndex));
}
MG_Record::StagedShadowStore::CoverageAdd(resource->stagedCoverage, static_cast<SizeT>(offset), MG_Record::StagedShadowStore::CoverageAdd(resource->stagedCoverage, static_cast<SizeT>(offset),
static_cast<SizeT>(offset + size)); static_cast<SizeT>(offset + size));
} }
@@ -615,7 +589,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
// B3: the draw this slice is being acquired for is normally recorded into the NEXT // B3: the draw this slice is being acquired for is normally recorded into the NEXT
// submission - normally, not always: a mid-draw flush (SyncWireTextureShape's preserve // submission - normally, not always: a mid-draw flush (SyncWireTextureShape's preserve
// path) can submit this index without the draw. This stamp is the defence term's input // path) can submit this index without the draw. This stamp is the defense term's input
// (see WriteWireBuffer); the floor behind lastUseSerial is the guarantee. Read off the // (see WriteWireBuffer); the floor behind lastUseSerial is the guarantee. Read off the
// owning renderer's counter (its GetWireNextSubmitIndex()) when it was handed one. // owning renderer's counter (its GetWireNextSubmitIndex()) when it was handed one.
if (m_submitCounter != nullptr) if (m_submitCounter != nullptr)
@@ -624,10 +598,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
resource->lastUseSubmitIndex = pVulkanRenderer->GetWireNextSubmitIndex(); resource->lastUseSubmitIndex = pVulkanRenderer->GetWireNextSubmitIndex();
outSlice = resource->buffer.GetSlice(0, resource->size); outSlice = resource->buffer.GetSlice(0, resource->size);
outSlice.mapped = nullptr; outSlice.mapped = nullptr;
if (WireBufProbeEnabled()) {
MGLOG_I("WBUF bind slot=%u gen=%u frameSerial=%llu", res.Slot, res.Gen,
static_cast<unsigned long long>(m_frameSerial));
}
return true; return true;
} }
@@ -1422,11 +1392,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
"VkBufferManager::BeginFrame frame index out of range"); "VkBufferManager::BeginFrame frame index out of range");
m_currentFrameIndex = frameIndex; m_currentFrameIndex = frameIndex;
++m_frameSerial; ++m_frameSerial;
if (WireBufProbeEnabled()) {
MGLOG_I("WBUF BEGINFRAME idx=%u newSerial=%llu floor=%llu (arena slot rewound)", frameIndex,
static_cast<unsigned long long>(m_frameSerial),
static_cast<unsigned long long>(m_completedSerialFloor));
}
CollectDeferredReleases(frameIndex); CollectDeferredReleases(frameIndex);
m_transientUploadArena.BeginFrame(frameIndex); m_transientUploadArena.BeginFrame(frameIndex);
} }
@@ -192,7 +192,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint64 size = 0; Uint64 size = 0;
Uint64 lastUseSerial = 0; Uint64 lastUseSerial = 0;
// P7 wave 2 package B3: the submission expected to carry the most recent GPU use // P7 wave 2 package B3: the submission expected to carry the most recent GPU use
// of this buffer - the busy predicate's DEFENCE term, not its guarantee (see // of this buffer - the busy predicate's DEFENSE term, not its guarantee (see
// WriteWireBuffer). IsSubmitIndexComplete polls the real fence and reports an // WriteWireBuffer). IsSubmitIndexComplete polls the real fence and reports an
// unsubmitted index as incomplete, but the stamp is taken before the draw is // unsubmitted index as incomplete, but the stamp is taken before the draw is
// recorded and a mid-draw flush can submit it without the draw. // recorded and a mid-draw flush can submit it without the draw.
@@ -57,7 +57,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (MG_Pipe::gPipeInputs->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb)) return; if (MG_Pipe::gPipeInputs->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb)) return;
if (ResolveSrgbAttachmentWriteFormat(destinationFormat, false) == destinationFormat) return; if (ResolveSrgbAttachmentWriteFormat(destinationFormat, false) == destinationFormat) return;
// sRGB -> linear (GL 4.6 core 8.24), applied to the colour channels only: alpha is stored // sRGB -> linear (GL 4.6 core 8.24), applied to the color channels only: alpha is stored
// linearly in an sRGB format and must pass through untouched. // linearly in an sRGB format and must pass through untouched.
const auto toLinear = [](Float encoded) { const auto toLinear = [](Float encoded) {
const Float value = std::clamp(encoded, 0.0f, 1.0f); const Float value = std::clamp(encoded, 0.0f, 1.0f);
@@ -24,7 +24,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 stencil{}; Uint32 stencil{};
}; };
// A colour clear reaches us from one of glClear/ClearBufferfv, ClearBufferiv or // A color clear reaches us from one of glClear/ClearBufferfv, ClearBufferiv or
// ClearBufferuiv, and Vulkan reads VkClearColorValue's union according to the destination // ClearBufferuiv, and Vulkan reads VkClearColorValue's union according to the destination
// image's format rather than converting between the members - a float written where an // image's format rather than converting between the members - a float written where an
// integer format is expected is reinterpreted bit for bit, not rounded. Remember which entry // integer format is expected is reinterpreted bit for bit, not rounded. Remember which entry
@@ -53,7 +53,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// vkCmdClearColorImage names the image, so the driver applies the destination format's transfer // vkCmdClearColorImage names the image, so the driver applies the destination format's transfer
// function to whatever value it is handed. Every other write path in this backend goes through // function to whatever value it is handed. Every other write path in this backend goes through
// the UNORM twin view while GL_FRAMEBUFFER_SRGB is off (ResolveSrgbAttachmentWriteFormat) and // the UNORM twin view while GL_FRAMEBUFFER_SRGB is off (ResolveSrgbAttachmentWriteFormat) and
// therefore stores the raw value GL asked for. Rewrites `payload` to the linear colour whose // therefore stores the raw value GL asked for. Rewrites `payload` to the linear color whose
// encoding is that raw value, so a direct image clear of an sRGB destination agrees with them. // encoding is that raw value, so a direct image clear of an sRGB destination agrees with them.
// A no-op for every other format, for integer clear encodings, and when GL is doing the // A no-op for every other format, for integer clear encodings, and when GL is doing the
// encoding itself. // encoding itself.
@@ -28,7 +28,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// ResolveAttachmentLayerCount lives in VkTextureManager.h, beside ToVulkanLevelExtent, because // ResolveAttachmentLayerCount lives in VkTextureManager.h, beside ToVulkanLevelExtent, because
// VkClearManager needs the SAME answer: its pending-clear key's layerCount becomes a real // VkClearManager needs the SAME answer: its pending-clear key's layerCount becomes a real
// VkImageSubresourceRange when a clear is materialised outside a render pass. See the header. // VkImageSubresourceRange when a clear is materialized outside a render pass. See the header.
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(VkImageLayout trackedLayout, Bool clearDepth, DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(VkImageLayout trackedLayout, Bool clearDepth,
Bool clearStencil) { Bool clearStencil) {
@@ -81,7 +81,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// which std::swap(pair&, pair&) requires. That was invisible while UnorderedMap // which std::swap(pair&, pair&) requires. That was invisible while UnorderedMap
// only ever move-CONSTRUCTED an element into a fresh slot. ska::flat_hash_map // only ever move-CONSTRUCTED an element into a fresh slot. ska::flat_hash_map
// probes robin-hood: inserting swaps the entry being placed against the one // probes robin-hood: inserting swaps the entry being placed against the one
// already sitting in the slot whenever it has travelled further from its desired // already sitting in the slot whenever it has traveled further from its desired
// position, so the mapped type has to be swappable or the table fails to // position, so the mapped type has to be swappable or the table fails to
// instantiate at all. // instantiate at all.
// //
@@ -139,7 +139,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Unsigned packed floats: no sign bit at all. 65024 is the largest 11-bit float; the // Unsigned packed floats: no sign bit at all. 65024 is the largest 11-bit float; the
// 10-bit blue channel tops out lower (64512) and RGB9E5 higher (65408), but the bound // 10-bit blue channel tops out lower (64512) and RGB9E5 higher (65408), but the bound
// that matters for correctness is the lower one, and a single conservative upper bound // that matters for correctness is the lower one, and a single conservative upper bound
// costs nothing a real border colour will ever notice. // costs nothing a real border color will ever notice.
case TextureInternalFormat::R11FG11FB10F: case TextureInternalFormat::R11FG11FB10F:
return {true, 0.0f, 64512.0f}; return {true, 0.0f, 64512.0f};
case TextureInternalFormat::RGB9E5: case TextureInternalFormat::RGB9E5:
@@ -152,7 +152,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Per-component representable range of an integer texture format, as Int64 so that the whole // Per-component representable range of an integer texture format, as Int64 so that the whole
// signed and unsigned 32-bit ranges are expressible in one type and the clamp can be written // signed and unsigned 32-bit ranges are expressible in one type and the clamp can be written
// once for both domains. Alpha is carried separately because RGB10_A2UI is the one format // once for both domains. Alpha is carried separately because RGB10_A2UI is the one format
// whose alpha is narrower than its colour channels. // whose alpha is narrower than its color channels.
struct IntegerBorderRange { struct IntegerBorderRange {
Int64 rgbMin = 0; Int64 rgbMin = 0;
Int64 rgbMax = 0; Int64 rgbMax = 0;
@@ -360,7 +360,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const auto compareFunc = sampler.GetSamplerCompareFunc(); const auto compareFunc = sampler.GetSamplerCompareFunc();
XXHASH_VERIFY(XXH64_update(hashState, &compareFunc, sizeof(compareFunc))); XXHASH_VERIFY(XXH64_update(hashState, &compareFunc, sizeof(compareFunc)));
// The resolved enum AND, when it is one of the *_CUSTOM_EXT values, the sixteen bytes of the // The resolved enum AND, when it is one of the *_CUSTOM_EXT values, the sixteen bytes of the
// colour itself: two samplers that differ only in a custom border colour carry the same enum // color itself: two samplers that differ only in a custom border color carry the same enum
// and would otherwise collide onto whichever one was created first. // and would otherwise collide onto whichever one was created first.
XXHASH_VERIFY(XXH64_update(hashState, &borderColor.color, sizeof(borderColor.color))); XXHASH_VERIFY(XXH64_update(hashState, &borderColor.color, sizeof(borderColor.color)));
if (borderColor.isCustom) { if (borderColor.isCustom) {
@@ -512,14 +512,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return resolved; // FLOAT_TRANSPARENT_BLACK, never sampled return resolved; // FLOAT_TRANSPARENT_BLACK, never sampled
} }
// Border colour is sampler state: a bound sampler object supplies its own, and a texture // Border color is sampler state: a bound sampler object supplies its own, and a texture
// with none reaches the very same value through the sampler object it owns. // with none reaches the very same value through the sampler object it owns.
const auto domain = ResolveBorderColorDomain(format); const auto domain = ResolveBorderColorDomain(format);
const Bool canUseCustom = m_customBorderColorSupported && m_maxCustomBorderColorSamplers > 0 && const Bool canUseCustom = m_customBorderColorSupported && m_maxCustomBorderColorSamplers > 0 &&
m_customBorderColorSamplerCount < m_maxCustomBorderColorSamplers; m_customBorderColorSamplerCount < m_maxCustomBorderColorSamplers;
if (domain != BorderColorDomain::Float) { if (domain != BorderColorDomain::Float) {
// An integer image view REQUIRES an integer border colour, whatever the value is - even // An integer image view REQUIRES an integer border color, whatever the value is - even
// (0,0,0,1). The value itself is whichever integer form the application wrote; a float // (0,0,0,1). The value itself is whichever integer form the application wrote; a float
// border on an integer texture is nonsense GL leaves undefined, so the derived integer // border on an integer texture is nonsense GL leaves undefined, so the derived integer
// representation (a plain cast) is as good an answer as any. // representation (a plain cast) is as good an answer as any.
@@ -528,7 +528,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// through Int64 so the whole signed and unsigned 32-bit ranges are expressible at once. // through Int64 so the whole signed and unsigned 32-bit ranges are expressible at once.
// //
// Which representation to start from is the TEXTURE's domain, not the entry-point form // Which representation to start from is the TEXTURE's domain, not the entry-point form
// the application used. GL 4.6 core 8.10 stores an "I"-form border colour unmodified with // the application used. GL 4.6 core 8.10 stores an "I"-form border color unmodified with
// an integer internal data type and does not define a sign conversion between the two // an integer internal data type and does not define a sign conversion between the two
// integer forms, so the stored bits are reinterpreted in the sampled format's own // integer forms, so the stored bits are reinterpreted in the sampled format's own
// signedness. Measured, not assumed: a border of -1 written with glTexParameterIiv // signedness. Measured, not assumed: a border of -1 written with glTexParameterIiv
@@ -578,7 +578,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
return resolved; return resolved;
} }
// No custom colour available: pick the nearest of the three integer palette entries // No custom color available: pick the nearest of the three integer palette entries
// rather than always answering transparent black, which is what turned an integer border // rather than always answering transparent black, which is what turned an integer border
// of (-1,-1,-1,-1) into 0 and broke the CTS's clamped-texel detection outright. // of (-1,-1,-1,-1) into 0 and broke the CTS's clamped-texel detection outright.
const Bool opaque = clamped[3] != 0; const Bool opaque = clamped[3] != 0;
@@ -588,7 +588,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return resolved; return resolved;
} }
// Float domain. GL 4.6 core 8.14.2/8.23: the border colour is interpreted in the texture's // Float domain. GL 4.6 core 8.14.2/8.23: the border color is interpreted in the texture's
// format, so it is clamped to that format's representable range first. Without the clamp the // format, so it is clamped to that format's representable range first. Without the clamp the
// CTS's border of (255,255,255,255) on a GL_RGBA8 texture matched none of the palette entries // CTS's border of (255,255,255,255) on a GL_RGBA8 texture matched none of the palette entries
// and fell through to transparent black - every border texel sampled 0 where the test wanted // and fell through to transparent black - every border texel sampled 0 where the test wanted
@@ -32,7 +32,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// customBorderColorWithoutFormat; see VulkanRenderer::m_customBorderColorFeatureEnabled. // customBorderColorWithoutFormat; see VulkanRenderer::m_customBorderColorFeatureEnabled.
Bool customBorderColorSupported = false; Bool customBorderColorSupported = false;
// VkPhysicalDeviceCustomBorderColorPropertiesEXT::maxCustomBorderColorSamplers. A hard device // VkPhysicalDeviceCustomBorderColorPropertiesEXT::maxCustomBorderColorSamplers. A hard device
// limit on how many LIVE samplers may carry a custom border colour, so the cache counts them // limit on how many LIVE samplers may carry a custom border color, so the cache counts them
// and falls back to the snapped predefined value once it is reached. // and falls back to the snapped predefined value once it is reached.
Uint32 maxCustomBorderColorSamplers = 0; Uint32 maxCustomBorderColorSamplers = 0;
}; };
@@ -70,7 +70,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Resolved ONCE per GetOrCreateSampler call and threaded into both the cache key and the // Resolved ONCE per GetOrCreateSampler call and threaded into both the cache key and the
// create-info, so the two cannot disagree - the same discipline the resolved anisotropy needs, // create-info, so the two cannot disagree - the same discipline the resolved anisotropy needs,
// and here it also makes the maxCustomBorderColorSamplers fallback deterministic: whether a // and here it also makes the maxCustomBorderColorSamplers fallback deterministic: whether a
// custom colour was affordable is decided before the key is built, not twice with a budget // custom color was affordable is decided before the key is built, not twice with a budget
// change in between. // change in between.
struct ResolvedBorderColor { struct ResolvedBorderColor {
VkBorderColor color = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK; VkBorderColor color = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
@@ -115,7 +115,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Float m_maxSamplerAnisotropy = 1.0f; Float m_maxSamplerAnisotropy = 1.0f;
Bool m_customBorderColorSupported = false; Bool m_customBorderColorSupported = false;
Uint32 m_maxCustomBorderColorSamplers = 0; Uint32 m_maxCustomBorderColorSamplers = 0;
// Live cache entries carrying a custom border colour. Kept in step with the entries themselves // Live cache entries carrying a custom border color. Kept in step with the entries themselves
// in exactly the three places one can appear or disappear: creation, the OnFrameBoundary sweep, // in exactly the three places one can appear or disappear: creation, the OnFrameBoundary sweep,
// and Shutdown. // and Shutdown.
Uint32 m_customBorderColorSamplerCount = 0; Uint32 m_customBorderColorSamplerCount = 0;
@@ -732,7 +732,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// TryResolveTextureShapeInfo's answers derived from the wire descriptor instead of the // TryResolveTextureShapeInfo's answers derived from the wire descriptor instead of the
// frontend object. The descriptor's Width/Height/Depth are the BASE level's GL extent and // frontend object. The descriptor's Width/Height/Depth are the BASE level's GL extent and
// ArrayLayers the layer count the emitter already normalised (MGPipeTextureExtentOf: a 1D // ArrayLayers the layer count the emitter already normalized (MGPipeTextureExtentOf: a 1D
// array's layers are already out of its height, a cube map reports 6). // array's layers are already out of its height, a cube map reports 6).
static Bool ResolveWireTextureShapeInfo(const MG_Pipe::MGPResourceDesc& desc, TextureShapeInfo& outShape) { static Bool ResolveWireTextureShapeInfo(const MG_Pipe::MGPResourceDesc& desc, TextureShapeInfo& outShape) {
using MG_Pipe::MGPipeResourceTarget; using MG_Pipe::MGPipeResourceTarget;
@@ -768,7 +768,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
outShape.arrayLayers = 6; outShape.arrayLayers = 6;
return desc.Width > 0 && desc.Width == desc.Height; return desc.Width > 0 && desc.Width == desc.Height;
case MGPipeResourceTarget::TexCubeArray: case MGPipeResourceTarget::TexCubeArray:
// glTexStorage3D hands 6n through as the depth; the emitter normalised it into // glTexStorage3D hands 6n through as the depth; the emitter normalized it into
// ArrayLayers. A non-whole cube count has no Vulkan shape - declined like every // ArrayLayers. A non-whole cube count has no Vulkan shape - declined like every
// other unrepresentable target. // other unrepresentable target.
outShape.viewType = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; outShape.viewType = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
@@ -1706,7 +1706,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false; return false;
} }
// The RGBA8/RGBX8 buffers the registry allocates have a Vulkan format; an external-only // The RGBA8/RGBX8 buffers the registry allocates have a Vulkan format; an external-only
// format would need a YCbCr conversion and is not a colour buffer this path can write. // format would need a YCbCr conversion and is not a color buffer this path can write.
if (formatProperties.format == VK_FORMAT_UNDEFINED) { if (formatProperties.format == VK_FORMAT_UNDEFINED) {
why = "the buffer has no Vulkan format (external format only)"; why = "the buffer has no Vulkan format (external format only)";
return false; return false;
@@ -84,9 +84,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// again in VkClearManager.cpp - and the two are not independent: the render pass builds the // again in VkClearManager.cpp - and the two are not independent: the render pass builds the
// attachment view and VkFramebufferCreateInfo::layers from one, while the CLEAR key built from the // attachment view and VkFramebufferCreateInfo::layers from one, while the CLEAR key built from the
// other is written verbatim into VkImageSubresourceRange::layerCount when a queued glClear is // other is written verbatim into VkImageSubresourceRange::layerCount when a queued glClear is
// materialised outside a render pass (MaterializePendingClearForTexture). They are two consumers // materialized outside a render pass (MaterializePendingClearForTexture). They are two consumers
// of the same GL clear, so any disagreement means the same glClear produces two different pictures // of the same GL clear, so any disagreement means the same glClear produces two different pictures
// depending only on which path happens to consume it first - and the materialise path then POPS // depending only on which path happens to consume it first - and the materialize path then POPS
// the entry, so the other one never runs. Fixing one copy and leaving the other is exactly how // the entry, so the other one never runs. Fixing one copy and leaving the other is exactly how
// that split gets introduced; keep them the same function. // that split gets introduced; keep them the same function.
// //
@@ -132,6 +132,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
private: private:
VkTextureManager& m_manager; VkTextureManager& m_manager;
}; };
// True inside a PendingClearHold: the image being resolved is an attachment of the pass
// being prepared, which takes pending clears as its load op.
Bool PendingClearsHeld() const { return m_pendingClearHold != 0; }
// A whole-subresource clear that was recorded but has not run yet (VulkanRenderer::DeferWireClear). // A whole-subresource clear that was recorded but has not run yet (VulkanRenderer::DeferWireClear).
struct WirePendingClear { struct WirePendingClear {
Uint32 level = 0, layer = 0, layers = 1; Uint32 level = 0, layer = 0, layers = 1;
@@ -585,8 +588,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void MarkWireTextureGpuWritten(MG_Pipe::MGPipeHandle handle, Uint32 mipLevel, Uint32 baseArrayLayer = 0, void MarkWireTextureGpuWritten(MG_Pipe::MGPipeHandle handle, Uint32 mipLevel, Uint32 baseArrayLayer = 0,
Uint32 layerCount = 1); Uint32 layerCount = 1);
// SHARED IMAGES (docs/archive/notes/anland/plan-ahb-dmabuf.md): a server shared image's // SHARED IMAGES: a server shared image's
// AHardwareBuffer bound as a single-level 2D colour image on THIS session's device, on dedicated // AHardwareBuffer bound as a single-level 2D color image on THIS session's device, on dedicated
// memory imported from the buffer. Android only (false with `why` elsewhere, or when the device // memory imported from the buffer. Android only (false with `why` elsewhere, or when the device
// did not take the AHardwareBuffer extension). The image is born UNDEFINED; whoever uses it // did not take the AHardwareBuffer extension). The image is born UNDEFINED; whoever uses it
// acquires it from the foreign queue family first, and a writer releases it back after. // acquires it from the foreign queue family first, and a writer releases it back after.
@@ -296,7 +296,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// `defaultFramebufferOrientation` is set only when the source is the swapchain's // `defaultFramebufferOrientation` is set only when the source is the swapchain's
// depth/stencil image, which this renderer stores display-side-up: the copy rect then // depth/stencil image, which this renderer stores display-side-up: the copy rect then
// has to be mapped out of GL's bottom-origin space and the copied rows re-oriented on // has to be mapped out of GL's bottom-origin space and the copied rows re-oriented on
// the way back, exactly as the colour ReadPixels path does. // the way back, exactly as the color ReadPixels path does.
// `sourceLayerCount` above 1 says the `height` rows the client is owed are stored as that // `sourceLayerCount` above 1 says the `height` rows the client is owed are stored as that
// many ARRAY LAYERS of a one-row image rather than as rows of one layer - the shape a GL // many ARRAY LAYERS of a one-row image rather than as rows of one layer - the shape a GL
// 1D array has in Vulkan. The two produce byte-identical tightly-packed readbacks, so // 1D array has in Vulkan. The two produce byte-identical tightly-packed readbacks, so
@@ -620,7 +620,35 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 level = 0, layer = 0, layers = 1; Uint32 level = 0, layer = 0, layers = 1;
}; };
Vector<WireFoldClear> m_wireFoldClears; Vector<WireFoldClear> m_wireFoldClears;
// Attachments of the pass being prepared whose content is undefined: a default-framebuffer
// image first used since its present. Their load op becomes DONT_CARE (WireClearVariant).
Vector<Uint32> m_wireDiscardLoads;
static Bool WireImageCanDeferClear(const WireImage& image); static Bool WireImageCanDeferClear(const WireImage& image);
// A whole-image clear of the default framebuffer, held for the next pass that draws into
// the window (its load op) the way WirePendingClear holds a texture's. Any other use of the
// window images, and the present, run it first (MaterializeDefaultWirePendingClear).
struct DefaultWirePendingClear {
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
Uint32 swapchainImageIndex = 0;
Bool color = false;
VkImageAspectFlags depthStencil = 0;
ClearAttachmentPayload colorPayload{};
// colorPayload against the window format and GL_FRAMEBUFFER_SRGB as they were at the
// glClear (PreCompensateSrgbClearColor): the standalone clear a present or another
// use of the window records later must not read that state again, after the verb.
ClearAttachmentPayload colorPayloadCompensated{};
ClearAttachmentPayload depthStencilPayload{};
Bool Pending() const { return color || depthStencil != 0; }
};
DefaultWirePendingClear m_defaultWirePendingClear;
GLbitfield DeferDefaultWireClear(const MG_Pipe::MGPFramebufferState& fbo, const ClearAttachmentPayload& payload,
GLint drawbuffer);
void MaterializeDefaultWirePendingClear();
void FoldDefaultWirePendingClear(RenderPassEntry& begun, Uint64& variantKey, VkClearValue* merged);
// Records a clear of `area` of one wire image in a single-attachment pass of its own.
// `colorPreCompensated`: the payload already went through PreCompensateSrgbClearColor.
void RecordWireImageClear(WireImage& image, VkImageAspectFlags aspect, const ClearAttachmentPayload& payload,
const VkRect2D& area, Bool textureSurface, Bool colorPreCompensated = false);
static void NoteWirePendingClear(const WireImage& image, VkImageAspectFlags aspects, const VkClearValue& value); static void NoteWirePendingClear(const WireImage& image, VkImageAspectFlags aspects, const VkClearValue& value);
VkRenderPass WireClearVariant(RenderPassEntry& begun, Uint64 variantKey); VkRenderPass WireClearVariant(RenderPassEntry& begun, Uint64 variantKey);
Bool BeginWireDrawPassFolding(FrameContext::FrameData& frame, RenderPassEntry& begun, Uint64 variantKey, Bool BeginWireDrawPassFolding(FrameContext::FrameData& frame, RenderPassEntry& begun, Uint64 variantKey,
@@ -651,7 +679,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// and the default draw framebuffer alike. The CAPABILITY decline inside // and the default draw framebuffer alike. The CAPABILITY decline inside
// ResolveWireDepthStencil (a stencil-only resolve without VK_EXT_shader_stencil_export // ResolveWireDepthStencil (a stencil-only resolve without VK_EXT_shader_stencil_export
// on a device without VK_KHR_depth_stencil_resolve) is still decided per aspect, after // on a device without VK_KHR_depth_stencil_resolve) is still decided per aspect, after
// colour ran; rule I (a) accepts that (docs/archive/notes/p7/magma-b2.md §2.10). // color ran; rule I (a) accepts that (§2.10).
Bool DeclineWireDepthStencilResolveShape(const WireImage& source, const WireImage& destination, GLint sx0, Bool DeclineWireDepthStencilResolveShape(const WireImage& source, const WireImage& destination, GLint sx0,
GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1,
GLint dy1); GLint dy1);
@@ -682,7 +710,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool m_wireAhbImport = false; Bool m_wireAhbImport = false;
void* m_wireGetAhbProperties = nullptr; void* m_wireGetAhbProperties = nullptr;
// YUV shared images (WireYuvImage.cpp): the device enabled samplerYcbcrConversion, and the // YUV shared images (WireYuvImage.cpp): the device enabled samplerYcbcrConversion, and the
// conversion pipelines made so far (one per buffer format and colour conversion). // conversion pipelines made so far (one per buffer format and color conversion).
Bool m_samplerYcbcrConversion = false; Bool m_samplerYcbcrConversion = false;
struct WireYuvResources; struct WireYuvResources;
WireYuvResources* m_wireYuvResources = nullptr; WireYuvResources* m_wireYuvResources = nullptr;
@@ -704,7 +732,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
UnorderedMap<Uint64, SharedImagePresentTarget> m_sharedImagePresentTargets; UnorderedMap<Uint64, SharedImagePresentTarget> m_sharedImagePresentTargets;
void DestroySharedImagePresentTargets(Bool onlyDead); void DestroySharedImagePresentTargets(Bool onlyDead);
// SHARED-IMAGE SYNCHRONISATION (WireSharedImage.cpp). The sync_file fences other sessions // SHARED-IMAGE SYNCHRONIZATION (WireSharedImage.cpp). The sync_file fences other sessions
// publish become binary semaphores: a temporary SYNC_FD import is a WAIT, an exportable // publish become binary semaphores: a temporary SYNC_FD import is a WAIT, an exportable
// semaphore a SIGNAL, of the next frame submission - whichever of SubmitPendingCommandBuffer // semaphore a SIGNAL, of the next frame submission - whichever of SubmitPendingCommandBuffer
// and Present's submit comes first, which is the one carrying what was recorded since. // and Present's submit comes first, which is the one carrying what was recorded since.
@@ -1185,9 +1213,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// VK_EXT_custom_border_color. Vulkan's four predefined VkBorderColor values cover only // VK_EXT_custom_border_color. Vulkan's four predefined VkBorderColor values cover only
// transparent/opaque black and opaque white; GL_TEXTURE_BORDER_COLOR is an arbitrary vec4 (or // transparent/opaque black and opaque white; GL_TEXTURE_BORDER_COLOR is an arbitrary vec4 (or
// an arbitrary ivec4/uvec4 through the "I" entry points). Without this extension a border // an arbitrary ivec4/uvec4 through the "I" entry points). Without this extension a border
// colour outside the palette has to be snapped to the nearest predefined one. Both features // color outside the palette has to be snapped to the nearest predefined one. Both features
// are required together: customBorderColorWithoutFormat is what lets a sampler carry a custom // are required together: customBorderColorWithoutFormat is what lets a sampler carry a custom
// colour without naming the image format it will be paired with, which GL's sampler objects // color without naming the image format it will be paired with, which GL's sampler objects
// cannot know. maxCustomBorderColorSamplers is a real device limit, so the sampler cache has // cannot know. maxCustomBorderColorSamplers is a real device limit, so the sampler cache has
// to be able to fall back to the snapped value once it is reached. // to be able to fall back to the snapped value once it is reached.
Bool m_customBorderColorFeatureEnabled = false; Bool m_customBorderColorFeatureEnabled = false;
@@ -1249,7 +1277,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// pipeline from asking for LAST on a device that cannot preserve a fan's provoking vertex. // pipeline from asking for LAST on a device that cannot preserve a fan's provoking vertex.
Bool m_provokingVertexXfbPreserveEnabled = false; Bool m_provokingVertexXfbPreserveEnabled = false;
// provokingVertexModePerPipeline: when VK_FALSE every pipeline in one render pass instance // provokingVertexModePerPipeline: when VK_FALSE every pipeline in one render pass instance
// must agree on the mode, so glProvokingVertex(GL_FIRST_VERTEX_CONVENTION) cannot be honoured // must agree on the mode, so glProvokingVertex(GL_FIRST_VERTEX_CONVENTION) cannot be honored
// per draw and every pipeline takes GL's default (LAST) instead. // per draw and every pipeline takes GL's default (LAST) instead.
Bool m_provokingVertexModePerPipeline = false; Bool m_provokingVertexModePerPipeline = false;
// transformFeedbackPreservesTriangleFanProvokingVertex. // transformFeedbackPreservesTriangleFanProvokingVertex.
@@ -726,11 +726,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Nothing else on this path asks: the three checks around it cover the mip range, the region // Nothing else on this path asks: the three checks around it cover the mip range, the region
// bounds and the slice range, and none of them ever looked at a format. // bounds and the slice range, and none of them ever looked at a format.
// //
// A decline rather than a MOBILEGL_ASSERT, for the reason the neighbouring guards spell out: // A decline rather than a MOBILEGL_ASSERT, for the reason the neighboring guards spell out:
// assertions compile out of the release build that the CTS and shipping both run, which is // assertions compile out of the release build that the CTS and shipping both run, which is
// exactly where the corruption was observed. // exactly where the corruption was observed.
if (srcImage.format != dstImage.format) { if (srcImage.format != dstImage.format) {
// Size-compatibility is the COLOUR rule. Vulkan makes each depth/stencil format compatible // Size-compatibility is the COLOR rule. Vulkan makes each depth/stencil format compatible
// only with ITSELF, and the texel block sizes cannot tell them apart: X8_D24_UNORM_PACK32, // only with ITSELF, and the texel block sizes cannot tell them apart: X8_D24_UNORM_PACK32,
// D32_SFLOAT and D24_UNORM_S8_UINT are all 4 bytes and all in different compatibility // D32_SFLOAT and D24_UNORM_S8_UINT are all 4 bytes and all in different compatibility
// classes, so a raw block-size test waves through exactly the pairs Vulkan forbids. The // classes, so a raw block-size test waves through exactly the pairs Vulkan forbids. The
@@ -1419,9 +1419,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, imageAspect, mipLevel, 1); VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, imageAspect, mipLevel, 1);
MOBILEGL_ASSERT(ok, "%s: failed to transition depth-stencil source image", __func__); MOBILEGL_ASSERT(ok, "%s: failed to transition depth-stencil source image", __func__);
// The swapchain's depth/stencil image is stored display-side-up like its colour twin, so // The swapchain's depth/stencil image is stored display-side-up like its color twin, so
// the GL rect has to be mapped into that space before the copy and the copied rows // the GL rect has to be mapped into that space before the copy and the copied rows
// re-oriented afterwards - the same two halves the colour ReadPixels path applies. // re-oriented afterwards - the same two halves the color ReadPixels path applies.
VkOffset2D copyOffset{x, y}; VkOffset2D copyOffset{x, y};
VkExtent2D copyExtent{static_cast<Uint32>(width), static_cast<Uint32>(height)}; VkExtent2D copyExtent{static_cast<Uint32>(width), static_cast<Uint32>(height)};
if (defaultFramebufferOrientation) { if (defaultFramebufferOrientation) {
@@ -755,7 +755,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// P11 B2 (T0): a client's AHardwareBuffer imported as a wire buffer store. Wire arms, on // P11 B2 (T0): a client's AHardwareBuffer imported as a wire buffer store. Wire arms, on
// Android, whenever the device advertises the extension - not per session: the device is // Android, whenever the device advertises the extension - not per session: the device is
// created once per server and a later session asking T0 must find it able. Enabling it // created once per server and a later session asking T0 must find it able. Enabling it
// changes no T2 behaviour; only an import uses it. The 1.0-era dependencies are core at the // changes no T2 behavior; only an import uses it. The 1.0-era dependencies are core at the
// 1.1 this device is created at, and are enabled too when advertised (the spike's set). // 1.1 this device is created at, and are enabled too when advertised (the spike's set).
m_wireAhbImport = false; m_wireAhbImport = false;
m_wireGetAhbProperties = nullptr; m_wireGetAhbProperties = nullptr;
@@ -943,7 +943,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// an integer border of -1 come back as 0. // an integer border of -1 come back as 0.
// //
// customBorderColorWithoutFormat is required alongside customBorderColors, not merely // customBorderColorWithoutFormat is required alongside customBorderColors, not merely
// preferred: a GL sampler object carries a border colour with no idea which texture it will // preferred: a GL sampler object carries a border color with no idea which texture it will
// be paired with, so the VkSamplerCustomBorderColorCreateInfoEXT this backend builds has to // be paired with, so the VkSamplerCustomBorderColorCreateInfoEXT this backend builds has to
// leave `format` VK_FORMAT_UNDEFINED. // leave `format` VK_FORMAT_UNDEFINED.
m_customBorderColorFeatureEnabled = false; m_customBorderColorFeatureEnabled = false;
@@ -987,7 +987,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Native subgroup topology, and VK_EXT_subgroup_size_control's // Native subgroup topology, and VK_EXT_subgroup_size_control's
// computeFullSubgroups feature. REQUIRE_FULL_SUBGROUPS on a compute stage is what // computeFullSubgroups feature. REQUIRE_FULL_SUBGROUPS on a compute stage is what
// turns the derived gl_NumSubgroups (DeriveNumSubgroupsPass) from // turns the derived gl_NumSubgroups (DeriveNumSubgroupsPass) from
// encouraged-but-unspecified driver behaviour into a spec guarantee: with the bit // encouraged-but-unspecified driver behavior into a spec guarantee: with the bit
// set and local_size_x a multiple of the subgroup size, every subgroup launches // set and local_size_x a multiple of the subgroup size, every subgroup launches
// full, so the subgroup count is exactly invocations / size ("Full Subgroups", // full, so the subgroup count is exactly invocations / size ("Full Subgroups",
// VUID-VkPipelineShaderStageCreateInfo-flags-02759/-02785). // VUID-VkPipelineShaderStageCreateInfo-flags-02759/-02785).
@@ -1657,7 +1657,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MakeWireDepthResolveDeviceIdentity(m_physicalDevice.properties, renderPassArmAvailable, knob); MakeWireDepthResolveDeviceIdentity(m_physicalDevice.properties, renderPassArmAvailable, knob);
static WireDepthResolveArmCache s_choices; static WireDepthResolveArmCache s_choices;
const WireDepthResolveArmChoice resolved = s_choices.Resolve(identity, [&]() { const WireDepthResolveArmChoice resolved = s_choices.Resolve(identity, [&]() {
if (knob == WireDepthResolveProbeKnob::Unrecognised) if (knob == WireDepthResolveProbeKnob::Unrecognized)
MGLOG_W("DirectVulkan: MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=%s is not `bug`, `clean` or " MGLOG_W("DirectVulkan: MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=%s is not `bug`, `clean` or "
"`elide-subject`; the resolve probe runs as if it were unset", "`elide-subject`; the resolve probe runs as if it were unset",
knobValue); knobValue);
@@ -90,11 +90,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// end up with a module carrying no Xfb mode - the clip/XFB validation backstop rewinding // end up with a module carrying no Xfb mode - the clip/XFB validation backstop rewinding
// past the decoration, or XfbCaptureDecoratePass resolving none of the requested varyings // past the decoration, or XfbCaptureDecoratePass resolving none of the requested varyings
// and changing nothing. Declining the span leaves the capture buffers untouched, which is // and changing nothing. Declining the span leaves the capture buffers untouched, which is
// the same nothing the driver would have written, without the undefined behaviour. // the same nothing the driver would have written, without the undefined behavior.
if (m_currentDrawXfbCaptureDeclined) { if (m_currentDrawXfbCaptureDeclined) {
MGLOG_E_ONCE("BeginXfbCaptureForDraw: declining the capture span - the bound program's last " MGLOG_E_ONCE("BeginXfbCaptureForDraw: declining the capture span - the bound program's last "
"pre-rasterization stage carries no Xfb execution mode, so recording one would be " "pre-rasterization stage carries no Xfb execution mode, so recording one would be "
"undefined behaviour rather than a capture"); "undefined behavior rather than a capture");
return false; return false;
} }
return BeginWireXfbCaptureForDraw(frame); return BeginWireXfbCaptureForDraw(frame);
@@ -74,7 +74,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// //
// Rectangles were never converted. The viewport, the scissor and the ReadPixels copy offset // Rectangles were never converted. The viewport, the scissor and the ReadPixels copy offset
// all used the GL bottom-origin Y verbatim as a Vulkan top-origin Y, which is correct only // all used the GL bottom-origin Y verbatim as a Vulkan top-origin Y, which is correct only
// when y == H - y - h (full height, or vertically centred) - and full height is the only case // when y == H - y - h (full height, or vertically centered) - and full height is the only case
// any test ever exercised. In the conformance suite the errors CANCEL in placement (the draw // any test ever exercised. In the conformance suite the errors CANCEL in placement (the draw
// lands in Vulkan rows [y, y+h) and the readback copies the same rows back) and compose into // lands in Vulkan rows [y, y+h) and the readback copies the same rows back) and compose into
// an exact vertical flip: 1,759 of Magma's 1,793 non-passing cases, 861 vertical flips and // an exact vertical flip: 1,759 of Magma's 1,793 non-passing cases, 861 vertical flips and
@@ -155,7 +155,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// The GL viewport rect, expressed against the default framebuffer's stored orientation. // The GL viewport rect, expressed against the default framebuffer's stored orientation.
// A full-height viewport is unchanged by this, which is why every existing scenario keeps // A full-height viewport is unchanged by this, which is why every existing scenario keeps
// its exact behaviour. // its exact behavior.
if (isDefaultFramebuffer) { if (isDefaultFramebuffer) {
const DefaultFramebufferRectMapping mapping = GetDefaultFramebufferRectMapping(preTransform); const DefaultFramebufferRectMapping mapping = GetDefaultFramebufferRectMapping(preTransform);
viewportX = MapDefaultFramebufferRectAxis(viewportX, viewportWidth, framebufferExtent.x(), mapping.mirrorX); viewportX = MapDefaultFramebufferRectAxis(viewportX, viewportWidth, framebufferExtent.x(), mapping.mirrorX);
@@ -338,7 +338,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static_cast<Uint32>(std::max<Int>(0, rawY1 - rawY0)), static_cast<Uint32>(std::max<Int>(0, rawY1 - rawY0)),
}; };
// A quarter turn maps to {false, false}, so this is a no-op today; it is here so the // A quarter turn maps to {false, false}, so this is a no-op today; it is here so the
// branch cannot drift away from the identity/180 one when quarter turns are modelled. // branch cannot drift away from the identity/180 one when quarter turns are modeled.
return MapScissorRectToDefaultFramebuffer(scissor, framebufferExtent, preTransform); return MapScissorRectToDefaultFramebuffer(scissor, framebufferExtent, preTransform);
} }
@@ -670,8 +670,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// - patch vertices, polygon mode, cull face mode, depth func, logic op, // - patch vertices, polygon mode, cull face mode, depth func, logic op,
// min sample shading, the glSampleMaski word // min sample shading, the glSampleMaski word
// - front/back stencil ops + compare funcs (ref/mask are dynamic state) // - front/back stencil ops + compare funcs (ref/mask are dynamic state)
// - per draw buffer up to the render pass's colour span: indexed blend enable, // - per draw buffer up to the render pass's color span: indexed blend enable,
// blend factors/equations, indexed colour write mask (broadcast from index 0 // blend factors/equations, indexed color write mask (broadcast from index 0
// when the device lacks independentBlend - the same read the payload does) // when the device lacks independentBlend - the same read the payload does)
// FBO-derived payload inputs (attachment presence/formats/draw-buffer gating) are // FBO-derived payload inputs (attachment presence/formats/draw-buffer gating) are
// pinned by the render-pass hash key, exactly as the version-keyed memo relied on. // pinned by the render-pass hash key, exactly as the version-keyed memo relied on.
@@ -918,6 +918,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Uint32 vertexInputAttribMask = vis.attributeLocationMask; const Uint32 vertexInputAttribMask = vis.attributeLocationMask;
const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask; const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask;
const Uint32 missingAttribMask = activeAttribMask & ~vertexInputAttribMask; const Uint32 missingAttribMask = activeAttribMask & ~vertexInputAttribMask;
// The fetch format takes the shader input's signedness (VUID-VkGraphicsPipelineCreateInfo-
// Input-08733). That renames the bits of 32-bit data, but extends 8/16-bit data by the
// input's rule instead of the data's; the program variant SetupWireDraw picks for exactly
// those attributes (ResolveNarrowIntegerVertexInputs) puts GL's value back in the shader.
auto& patchedAttributes = m_patchedAttributesScratch; auto& patchedAttributes = m_patchedAttributesScratch;
patchedAttributes.assign(vis.attributes.begin(), vis.attributes.end()); patchedAttributes.assign(vis.attributes.begin(), vis.attributes.end());
Bool hasPatchedVertexAttributes = false; Bool hasPatchedVertexAttributes = false;
@@ -1074,7 +1078,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// is an OVER-count, which breaks the lower-bound contract every other tally keeps. // is an OVER-count, which breaks the lower-bound contract every other tally keeps.
// //
// The 15 are: the six capability reads (cull face, depth test, polygon offset // The 15 are: the six capability reads (cull face, depth test, polygon offset
// fill, rasterizer discard, colour logic op, stencil test), the draw-FBO slot // fill, rasterizer discard, color logic op, stencil test), the draw-FBO slot
// read that gates depth/stencil, the two stencil face states, the polygon mode, // read that gates depth/stencil, the two stencil face states, the polygon mode,
// the min sample shading value, the patch vertex count, the depth mask, the depth // the min sample shading value, the patch vertex count, the depth mask, the depth
// func, and the second draw-FBO slot read below. The sample-shading CAPABILITY // func, and the second draw-FBO slot read below. The sample-shading CAPABILITY
@@ -1099,7 +1103,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.sampleShadingEnable = m_sampleRateShadingFeatureEnabled && .sampleShadingEnable = m_sampleRateShadingFeatureEnabled &&
MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::SampleShading), MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::SampleShading),
.minSampleShading = MG_Pipe::gPipeInputs->GetMinSampleShadingValue(), .minSampleShading = MG_Pipe::gPipeInputs->GetMinSampleShadingValue(),
// Word 1 keeps its all-ones initialiser: GL has no state for samples 32..63. // Word 1 keeps its all-ones initializer: GL has no state for samples 32..63.
.sampleMask = {ResolveEffectiveSampleMask(renderPassEntry.sampleCount), 0xffffffffu}, .sampleMask = {ResolveEffectiveSampleMask(renderPassEntry.sampleCount), 0xffffffffu},
.subpass = 0, .subpass = 0,
.topology = vkTopology, .topology = vkTopology,
@@ -1117,7 +1121,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// over the same rows), and a Y-negated position - the default framebuffer's flip or // over the same rows), and a Y-negated position - the default framebuffer's flip or
// an upper-left clip origin - reverses the winding once more. Expressed as the front // an upper-left clip origin - reverses the winding once more. Expressed as the front
// face rather than as swapped cull bits so gl_FrontFacing agrees with culling, and // face rather than as swapped cull bits so gl_FrontFacing agrees with culling, and
// so glFrontFace(GL_CW) is honoured. // so glFrontFace(GL_CW) is honored.
.frontFace = ((MG_Pipe::gPipeInputs->GetRenderStateParameters().FrontFaceModeSetting == .frontFace = ((MG_Pipe::gPipeInputs->GetRenderStateParameters().FrontFaceModeSetting ==
FrontFaceMode::Clockwise) != invertClockwise) FrontFaceMode::Clockwise) != invertClockwise)
? VK_FRONT_FACE_COUNTER_CLOCKWISE ? VK_FRONT_FACE_COUNTER_CLOCKWISE
@@ -1160,7 +1164,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// attached and CreatePipeline refuses the pipeline, which skips the draw. // attached and CreatePipeline refuses the pipeline, which skips the draw.
// //
// Gated on the PATCH topology as well, and that gate is load-bearing rather than an // Gated on the PATCH topology as well, and that gate is load-bearing rather than an
// optimisation: patchControlPoints is only meaningful for a patch draw, and a pipeline // optimization: patchControlPoints is only meaningful for a patch draw, and a pipeline
// that carries tessellation stages while its topology is anything else violates // that carries tessellation stages while its topology is anything else violates
// VUID-VkGraphicsPipelineCreateInfo-topology-00737 - the same class of invalid input as // VUID-VkGraphicsPipelineCreateInfo-topology-00737 - the same class of invalid input as
// the missing control stage, on the same driver. Such a draw is illegal in GL too (a // the missing control stage, on the same driver. Such a draw is illegal in GL too (a
@@ -1213,7 +1217,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
payload.backStencilCompareOp = VK_COMPARE_OP_ALWAYS; payload.backStencilCompareOp = VK_COMPARE_OP_ALWAYS;
} }
const Uint32 fragmentOutputMask = programObj.activeFragmentOutputLocationMask; const Uint32 fragmentOutputMask = programObj.activeFragmentOutputLocationMask;
// Outputs at locations past the render pass's trimmed colour span are // Outputs at locations past the render pass's trimmed color span are
// simply discarded - GL's semantic for a fragment output whose draw // simply discarded - GL's semantic for a fragment output whose draw
// buffer is GL_NONE (the trailing UNUSED slots no longer occupy // buffer is GL_NONE (the trailing UNUSED slots no longer occupy
// references, see GetOrCreateRenderPass). // references, see GetOrCreateRenderPass).
@@ -1336,7 +1340,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// VUID-VkPipelineColorBlendAttachmentState-srcColorBlendFactor-00608 and its three // VUID-VkPipelineColorBlendAttachmentState-srcColorBlendFactor-00608 and its three
// siblings forbid a VK_BLEND_FACTOR_SRC1_* in the struct without the feature whatever // siblings forbid a VK_BLEND_FACTOR_SRC1_* in the struct without the feature whatever
// blendEnable says, so clearing only the enable would still be invalid pipeline state. // blendEnable says, so clearing only the enable would still be invalid pipeline state.
// The previous behaviour, throwing, took the whole process down over one unsupported // The previous behavior, throwing, took the whole process down over one unsupported
// blend factor; this is defined, survivable and visible in the log, and it matches what // blend factor; this is defined, survivable and visible in the log, and it matches what
// the non-blendable-format arm above already does. // the non-blendable-format arm above already does.
if (!m_dualSrcBlendFeatureEnabled && if (!m_dualSrcBlendFeatureEnabled &&
@@ -16,6 +16,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Present has its own submit path. Tag its last wire pass before that // Present has its own submit path. Tag its last wire pass before that
// submission, rather than retaining it until a draw in the next frame. // submission, rather than retaining it until a draw in the next frame.
RetireWireDrawPass(); RetireWireDrawPass();
// A window color clear no pass drew after still has to reach the presented image. Its
// depth/stencil half is dropped: a swap leaves those undefined (see the content flags
// reset below). A frame that cannot be presented drops the whole clear.
if (m_swapchainObject.GetHandle() == VK_NULL_HANDLE || m_presentSuspended) {
m_defaultWirePendingClear = {};
} else {
m_defaultWirePendingClear.depthStencil = 0;
MaterializeDefaultWirePendingClear();
}
#endif #endif
if (m_swapchainObject.GetHandle() == VK_NULL_HANDLE || m_presentSuspended) { if (m_swapchainObject.GetHandle() == VK_NULL_HANDLE || m_presentSuspended) {
// No usable swapchain: the window was zero-area at initialization, or // No usable swapchain: the window was zero-area at initialization, or
@@ -190,7 +199,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VK_VERIFY(result, "Present, vkQueuePresentKHR"); VK_VERIFY(result, "Present, vkQueuePresentKHR");
// EGL swap semantics: the presented color buffer's content is undefined the // EGL swap semantics: the presented color buffer's content is undefined the
// next time this image is acquired (EGL_BUFFER_DESTROYED, the default swap // next time this image is acquired (EGL_BUFFER_DESTROYED, the default swap
// behaviour), and EVERY ancillary depth/stencil buffer's content is // behavior), and EVERY ancillary depth/stencil buffer's content is
// undefined after any swap. The render-pass manager turns the undefined // undefined after any swap. The render-pass manager turns the undefined
// attachments' next tile loads into LOAD_OP_DONT_CARE. // attachments' next tile loads into LOAD_OP_DONT_CARE.
// //
@@ -212,7 +212,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// The floor now advances only to a serial NO remaining in-flight submission still // The floor now advances only to a serial NO remaining in-flight submission still
// carries. NotifyFrameSerialComplete is monotone and already refuses the still- // carries. NotifyFrameSerialComplete is monotone and already refuses the still-
// recording serial, so this can only ever advance the floor later, never further. // recording serial, so this can only ever advance the floor later, never further.
// On the disaggregated build's MONOLITH arm this is behaviour-identical (one // On the disaggregated build's MONOLITH arm this is behavior-identical (one
// submission per serial). The #else branch below is the pull build's statement, // submission per serial). The #else branch below is the pull build's statement,
// unchanged, which is what keeps G1's .text byte-identical. // unchanged, which is what keeps G1's .text byte-identical.
Bool retiredAny = false; Bool retiredAny = false;
@@ -242,10 +242,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
for (const auto& remaining : m_inFlightSubmits) { for (const auto& remaining : m_inFlightSubmits) {
if (remaining.frameSerial <= advanceTo) { if (remaining.frameSerial <= advanceTo) {
WireDeclineTally::CountUnsoundSerialComplete(); WireDeclineTally::CountUnsoundSerialComplete();
// Logged per event, not once: this is the red-once's reading and the log is // Logged per event, not once: this line is the red-once's reading and the only
// the only place it can be read from: the renderer this trace builds is // place the count is printed. A line at teardown would not do: the renderer
// never Shutdown() (the process exits), so a teardown dump reports the // this trace builds is never Shutdown() (the process exits).
// start-up context's zero instead of the replay's count.
MGLOG_W("MGWIRE-FLOOR unsound-serial-complete #%llu: advancing the completed " MGLOG_W("MGWIRE-FLOOR unsound-serial-complete #%llu: advancing the completed "
"frame-serial floor to %llu, which submission %llu (serial %llu) still carries", "frame-serial floor to %llu, which submission %llu (serial %llu) still carries",
static_cast<unsigned long long>(WireDeclineTally::UnsoundSerialCompleteEvents()), static_cast<unsigned long long>(WireDeclineTally::UnsoundSerialCompleteEvents()),
@@ -255,23 +254,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
break; break;
} }
} }
{
static const Bool probe = [] {
const char* value = std::getenv("MOBILEGL_MAGMA_WIREBUF_PROBE");
return value && value[0] == '1';
}();
if (probe) {
Uint64 minRemaining = ~Uint64{0};
for (const auto& r : m_inFlightSubmits)
minRemaining = std::min(minRemaining, r.frameSerial);
MGLOG_I("FLOOR retire upTo=%llu advanceTo=%llu remaining=%zu minRemainingSerial=%lld "
"frameSerial=%llu",
static_cast<unsigned long long>(submitIndex), static_cast<unsigned long long>(advanceTo),
m_inFlightSubmits.size(),
m_inFlightSubmits.empty() ? -1LL : static_cast<long long>(minRemaining),
static_cast<unsigned long long>(m_bufferManager.GetFrameSerial()));
}
}
m_bufferManager.NotifyFrameSerialComplete(advanceTo); m_bufferManager.NotifyFrameSerialComplete(advanceTo);
} }
// Mid-frame-flushed command buffers whose submission just completed can // Mid-frame-flushed command buffers whose submission just completed can
@@ -338,23 +320,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// loops still rewind the arena and age their caches every 8 iterations - // loops still rewind the arena and age their caches every 8 iterations -
// bounded by 8 iterations' transient usage. // bounded by 8 iterations' transient usage.
++m_drainsSinceLastPresent; ++m_drainsSinceLastPresent;
// B3 probe: a drain that reaches here found the GPU caught up and nothing recording.
// Every 8th one is treated as a FRAME BOUNDARY - mid-frame. This probe is what REFUTED
// that as the OpenRA mechanism (one successful drain, zero boundary works across the
// replay; magma-b3.md §2.4): the cause was the floor in OnSubmitsCompletedUpTo.
{
static const Bool probe = [] {
const char* value = std::getenv("MOBILEGL_MAGMA_WIREBUF_PROBE");
return value && value[0] == '1';
}();
if (probe) {
MGLOG_I("WBUF drain#%llu%s submit=%llu completed=%llu",
static_cast<unsigned long long>(m_drainsSinceLastPresent),
(m_drainsSinceLastPresent % 8) == 0 ? " ***FRAME-BOUNDARY-WORK***" : "",
static_cast<unsigned long long>(m_submitCounter),
static_cast<unsigned long long>(m_completedSubmitCounter));
}
}
if ((m_drainsSinceLastPresent % 8) != 0) { if ((m_drainsSinceLastPresent % 8) != 0) {
return true; return true;
} }
@@ -742,7 +707,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
// Only provokingVertexModePerPipeline lets modes differ inside one render pass instance; // Only provokingVertexModePerPipeline lets modes differ inside one render pass instance;
// elsewhere every pipeline takes GL's default so the render pass stays self-consistent, and // elsewhere every pipeline takes GL's default so the render pass stays self-consistent, and
// glProvokingVertex(GL_FIRST_VERTEX_CONVENTION) goes unhonoured. Honouring it there would // glProvokingVertex(GL_FIRST_VERTEX_CONVENTION) goes unhonored. Honoring it there would
// mean ending the render pass on every glProvokingVertex change; not worth it until a target // mean ending the render pass on every glProvokingVertex change; not worth it until a target
// device actually lacks the property. // device actually lacks the property.
if (!m_provokingVertexModePerPipeline) { if (!m_provokingVertexModePerPipeline) {
@@ -139,7 +139,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// P7 wave 2-B (CONTRACT-P7 §3.2, `default-color-blit-shape`). The shader blit below can only // P7 wave 2-B (CONTRACT-P7 §3.2, `default-color-blit-shape`). The shader blit below can only
// SAMPLE its source: sampler2D, a float result, and one of two 2D view types. A framebuffer // SAMPLE its source: sampler2D, a float result, and one of two 2D view types. A framebuffer
// whose colour attachment is RGBA8UI, or a layer of a 3D texture, is none of those, and on a // whose color attachment is RGBA8UI, or a layer of a 3D texture, is none of those, and on a
// rotated Android surface there is no other arm - an axis-aligned vkCmdBlitImage cannot // rotated Android surface there is no other arm - an axis-aligned vkCmdBlitImage cannot
// express a quarter turn. So the source region becomes an owned 2D image first, through // express a quarter turn. So the source region becomes an owned 2D image first, through
// vkCmdCopyImage, which cares about the texel BLOCK SIZE and not about how the block is // vkCmdCopyImage, which cares about the texel BLOCK SIZE and not about how the block is
@@ -163,7 +163,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static_cast<Uint64>(std::max(sx0, sx1)) > source.extent.width || static_cast<Uint64>(std::max(sx0, sx1)) > source.extent.width ||
static_cast<Uint64>(std::max(sy0, sy1)) > source.extent.height) static_cast<Uint64>(std::max(sy0, sy1)) > source.extent.height)
return false; return false;
// A compressed source cannot be a colour attachment, so it cannot be a framebuffer blit's // A compressed source cannot be a color attachment, so it cannot be a framebuffer blit's
// source either - but the block arithmetic below would silently produce nonsense for one, // source either - but the block arithmetic below would silently produce nonsense for one,
// and "cannot happen" is the reason the guards this package retires were written as // and "cannot happen" is the reason the guards this package retires were written as
// asserts. The size-compatibility classes are Vulkan's own (VkFormat texel block size); // asserts. The size-compatibility classes are Vulkan's own (VkFormat texel block size);
@@ -10,28 +10,28 @@
#include <Includes.h> #include <Includes.h>
#include "../VkIncludes.h" #include "../VkIncludes.h"
// P7 g3-blit (g3det, gate 3): WHICH SAMPLER THE WIRE ARM'S SHADER COLOUR BLIT READS ITS SOURCE // P7 g3-blit (g3det, gate 3): WHICH SAMPLER THE WIRE ARM'S SHADER COLOR BLIT READS ITS SOURCE
// THROUGH. // THROUGH.
// //
// GL applies a blit's `filter` only where the image is STRETCHED (GL 4.6 core 18.3.1). A blit whose // GL applies a blit's `filter` only where the image is STRETCHED (GL 4.6 core 18.3.1). A blit whose
// source and destination rectangles are the same size is a copy, whatever `filter` says, and so is // source and destination rectangles are the same size is a copy, whatever `filter` says, and so is
// a mirrored one. The wire arm's shader blit (WireColorBlit.cpp, BlitWireColorImage) draws a quad // a mirrored one. The wire arm's shader blit (WireColorBlit.cpp, BlitWireColorImage) draws a quad
// and samples `textureLod(sourceImage, texCoord, 0)` at interpolated coordinates, and it used the // and samples `textureLod(sourceImage, texCoord, 0)` at interpolated coordinates, and it used the
// LINEAR sampler for every GL_LINEAR blit. At 1:1 every fragment centre maps to a texel centre, so // LINEAR sampler for every GL_LINEAR blit. At 1:1 every fragment center maps to a texel center, so
// in exact arithmetic linear filtering reads the same single texel. Adreno 830 quantises the // in exact arithmetic linear filtering reads the same single texel. Adreno 830 quantises the
// coordinate to sub-texel precision: a fragment that lands a fraction off the centre blends in a // coordinate to sub-texel precision: a fragment that lands a fraction off the center blends in a
// small weight of a neighbour. minecraft-1.21.1-neoforge-create-instancing-in-world ends with a // small weight of a neighbor. minecraft-1.21.1-neoforge-create-instancing-in-world ends with a
// 1:1 GL_LINEAR glBlitFramebuffer into FB 0, and on the wire arm 712 high-contrast pixels moved by // 1:1 GL_LINEAR glBlitFramebuffer into FB 0, and on the wire arm 712 high-contrast pixels moved by
// 1 LSB toward a neighbour. The monolith's vkCmdBlitImage copied them exactly // 1 LSB toward a neighbor. The monolith's vkCmdBlitImage copied them exactly
// (~/w7/logs/g3det/VERDICT.md section 3). NEAREST within half a texel of the centre is exact, so an // (~/w7/logs/g3det/VERDICT.md section 3). NEAREST within half a texel of the center is exact, so an
// unscaled blit takes NEAREST. Lavapipe samples texel centres exactly, so no host picture can see // unscaled blit takes NEAREST. Lavapipe samples texel centers exactly, so no host picture can see
// the difference; this pure half is what the unit test pins // the difference; this pure half is what the unit test pins
// (MG_Test/Pipeline/PipelineQuirkTest.cpp, WireColorBlitSamples*). // (MG_Test/Pipeline/PipelineQuirkTest.cpp, WireColorBlitSamples*).
// //
// THE SURFACE TRANSFORM DOES NOT TURN A COPY INTO A STRETCH. BlitWireColorImage applies the // THE SURFACE TRANSFORM DOES NOT TURN A COPY INTO A STRETCH. BlitWireColorImage applies the
// quarter and half turns in NDC, over a viewport of the native image, and for a quarter turn it // quarter and half turns in NDC, over a viewport of the native image, and for a quarter turn it
// takes the logical extent as the native extent swapped. A native pixel then has exactly the pitch // takes the logical extent as the native extent swapped. A native pixel then has exactly the pitch
// of a logical one, and the rotation maps pixel centres onto pixel centres. So the scale test is on // of a logical one, and the rotation maps pixel centers onto pixel centers. So the scale test is on
// GL's own (logical) rectangles for identity and all three rotations. BlitWireColorImage declines // GL's own (logical) rectangles for identity and all three rotations. BlitWireColorImage declines
// any other transform before it chooses a sampler; for one, this answers with the filter's own // any other transform before it chooses a sampler; for one, this answers with the filter's own
// mapping, as before this package. // mapping, as before this package.
@@ -129,7 +129,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkPhysicalDeviceMemoryProperties memoryProperties{}; VkPhysicalDeviceMemoryProperties memoryProperties{};
vkGetPhysicalDeviceMemoryProperties(context.physicalDevice, &memoryProperties); vkGetPhysicalDeviceMemoryProperties(context.physicalDevice, &memoryProperties);
// The wire's images (VkTextureManager): MUTABLE_FORMAT (a storage-compatible colour class), // The wire's images (VkTextureManager): MUTABLE_FORMAT (a storage-compatible color class),
// SAMPLED | TRANSFER_SRC | TRANSFER_DST | COLOR_ATTACHMENT, a 3D one 2D_ARRAY_COMPATIBLE. // SAMPLED | TRANSFER_SRC | TRANSFER_DST | COLOR_ATTACHMENT, a 3D one 2D_ARRAY_COMPATIBLE.
const auto makeImage = [&](Bool volume, VkFormat format, VkImage& out) -> Bool { const auto makeImage = [&](Bool volume, VkFormat format, VkImage& out) -> Bool {
VkFormatProperties formatProperties{}; VkFormatProperties formatProperties{};
@@ -19,8 +19,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// P7 wave 2 package B3: the per-site tally behind CONTRACT-P7 §0 rule I. // P7 wave 2 package B3: the per-site tally behind CONTRACT-P7 §0 rule I.
// //
// Shape is PipeApplier.h's ServerVerbSink tallies, and for the same reason (R-16: a probe may // Shape is RecordVerbSink.h's per-verb tallies, and for the same reason (R-16: a probe may not
// not arm against a stub). "The retrace passed" and "the retrace silently dropped nine draws" // arm against a stub). "The retrace passed" and "the retrace silently dropped nine draws"
// produce the same exit code and the same log, so the lane has to assert the number that only // produce the same exit code and the same log, so the lane has to assert the number that only
// this path can move. // this path can move.
// //
@@ -100,10 +100,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// from the forced-condition harness; silent when nothing declined, so a green lane stays // from the forced-condition harness; silent when nothing declined, so a green lane stays
// quiet and a lane that dropped a draw cannot. // quiet and a lane that dropped a draw cannot.
static void Dump(const char* tag) { static void Dump(const char* tag) {
// The floor counter prints unconditionally: "zero unsound advances" is the reading the
// red-once needs, and an absent line cannot be told from a lane that never ran.
MGLOG_I("MGWIRE-FLOOR[%s] unsoundSerialComplete=%llu", tag,
static_cast<unsigned long long>(UnsoundSerialCompleteEvents()));
if (TotalDeclines() == 0 && PeakPendingUploadBytes() == 0) return; if (TotalDeclines() == 0 && PeakPendingUploadBytes() == 0) return;
MGLOG_I("MGWIRE-DECLINES[%s] total=%llu pendingEntries=%llu pendingBytes=%llu peakPendingBytes=%llu", tag, MGLOG_I("MGWIRE-DECLINES[%s] total=%llu pendingEntries=%llu pendingBytes=%llu peakPendingBytes=%llu", tag,
static_cast<unsigned long long>(TotalDeclines()), static_cast<unsigned long long>(TotalDeclines()),
@@ -218,7 +218,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
depthStencil.depthCompareOp = VK_COMPARE_OP_ALWAYS; depthStencil.depthCompareOp = VK_COMPARE_OP_ALWAYS;
depthStencil.minDepthBounds = 0.0f; depthStencil.minDepthBounds = 0.0f;
depthStencil.maxDepthBounds = 1.0f; depthStencil.maxDepthBounds = 1.0f;
// No colour attachment in this render pass, so no attachment states either. // No color attachment in this render pass, so no attachment states either.
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
const VkDynamicState dynamicStates[]{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; const VkDynamicState dynamicStates[]{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
VkPipelineDynamicStateCreateInfo dynamic{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; VkPipelineDynamicStateCreateInfo dynamic{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
@@ -269,7 +269,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
framebufferInfo.layers = 1; framebufferInfo.layers = 1;
VkFramebuffer framebuffer = VK_NULL_HANDLE; VkFramebuffer framebuffer = VK_NULL_HANDLE;
VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &framebuffer)); VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &framebuffer));
// Each in-flight pass owns its descriptor set, for the colour blit's reason: updating // Each in-flight pass owns its descriptor set, for the color blit's reason: updating
// a cached set would rewrite the source view of an older queued pass. // a cached set would rewrite the source view of an older queued pass.
VkDescriptorPool descriptorPool = VK_NULL_HANDLE; VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
VkDescriptorSet descriptorSet = VK_NULL_HANDLE; VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
@@ -10,9 +10,9 @@
// Native Vulkan depth mip generation: no GL program, registry entry, or runtime transpilation. // Native Vulkan depth mip generation: no GL program, registry entry, or runtime transpilation.
// //
// The geometry is WireColorBlit.vert's, unchanged - the same quad, the same push-constant // The geometry is WireColorBlit.vert's, unchanged - the same quad, the same push-constant
// rect, the same GL-bottom-left to native-image mapping - so a depth mip and a colour blit // rect, the same GL-bottom-left to native-image mapping - so a depth mip and a color blit
// place their fragments identically and only the fragment stage differs. The block carries // place their fragments identically and only the fragment stage differs. The block carries
// one member more than the colour blit's, and the fragment stage is the member's only // one member more than the color blit's, and the fragment stage is the member's only
// reader: a push-constant block must be declared identically in every stage of one pipeline, // reader: a push-constant block must be declared identically in every stage of one pipeline,
// so it is declared here too rather than split into a second range. // so it is declared here too rather than split into a second range.
layout(push_constant) uniform DepthMipmapRect { layout(push_constant) uniform DepthMipmapRect {
@@ -163,7 +163,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
ForceBug, ForceBug,
ForceClean, ForceClean,
ElideSubject, ElideSubject,
Unrecognised Unrecognized
}; };
inline WireDepthResolveProbeKnob ParseWireDepthResolveProbeKnob(const char* value) { inline WireDepthResolveProbeKnob ParseWireDepthResolveProbeKnob(const char* value) {
@@ -171,7 +171,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (std::strcmp(value, "bug") == 0) return WireDepthResolveProbeKnob::ForceBug; if (std::strcmp(value, "bug") == 0) return WireDepthResolveProbeKnob::ForceBug;
if (std::strcmp(value, "clean") == 0) return WireDepthResolveProbeKnob::ForceClean; if (std::strcmp(value, "clean") == 0) return WireDepthResolveProbeKnob::ForceClean;
if (std::strcmp(value, "elide-subject") == 0) return WireDepthResolveProbeKnob::ElideSubject; if (std::strcmp(value, "elide-subject") == 0) return WireDepthResolveProbeKnob::ElideSubject;
return WireDepthResolveProbeKnob::Unrecognised; return WireDepthResolveProbeKnob::Unrecognized;
} }
// One packed format's worth of readings: the control resolved both aspects; the render pass // One packed format's worth of readings: the control resolved both aspects; the render pass
@@ -95,7 +95,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (result != VK_SUCCESS) return result; if (result != VK_SUCCESS) return result;
// THE STENCIL MODULE IS ONLY CREATED WHERE IT CAN RUN: its SPIR-V declares the // THE STENCIL MODULE IS ONLY CREATED WHERE IT CAN RUN: its SPIR-V declares the
// StencilExportEXT capability, and handing a module with an unsupported capability to // StencilExportEXT capability, and handing a module with an unsupported capability to
// vkCreateShaderModule is undefined behaviour - not an error this could catch. // vkCreateShaderModule is undefined behavior - not an error this could catch.
if (shaderStencilExport) { if (shaderStencilExport) {
result = shader(kWireMultisampleStencilResolveFragmentSpirv, result = shader(kWireMultisampleStencilResolveFragmentSpirv,
sizeof(kWireMultisampleStencilResolveFragmentSpirv), kit.stencilFragment); sizeof(kWireMultisampleStencilResolveFragmentSpirv), kit.stencilFragment);
@@ -206,7 +206,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
op.reference = 0; op.reference = 0;
depthStencilState.front = depthStencilState.back = op; depthStencilState.front = depthStencilState.back = op;
} }
// No colour attachment in this render pass, so no attachment states either. // No color attachment in this render pass, so no attachment states either.
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO}; VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
const VkDynamicState dynamicStates[]{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; const VkDynamicState dynamicStates[]{VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR};
VkPipelineDynamicStateCreateInfo dynamic{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO}; VkPipelineDynamicStateCreateInfo dynamic{VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO};
@@ -9,6 +9,14 @@
#include "VulkanRendererInternal.h" #include "VulkanRendererInternal.h"
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
namespace {
// A wire pass variant key holds two clear bits per attachment from bit 0 and, from this
// bit on, one bit per attachment whose content is undefined (WireClearVariant).
constexpr Uint32 kWireDiscardBit = 32;
static_assert(2 * (MG_Pipe::kMGPipeMaxColorAttachments + 1) <= kWireDiscardBit,
"the clear bits of a wire pass variant key overlap its discard bits");
} // namespace
// GL_EXT_fragment_shading_rate's pipeline rate and combiner ops for the draw about to be // GL_EXT_fragment_shading_rate's pipeline rate and combiner ops for the draw about to be
// recorded. The packed rate is log2(width) << 2 | log2(height), which is exactly the extent // recorded. The packed rate is log2(width) << 2 | log2(height), which is exactly the extent
// VK_KHR_fragment_shading_rate takes, and the combiner op indices follow // VK_KHR_fragment_shading_rate takes, and the combiner op indices follow
@@ -445,6 +453,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
WireDrawScratch& scratch, Bool passContinues, Bool& continuing) { WireDrawScratch& scratch, Bool passContinues, Bool& continuing) {
continuing = true; continuing = true;
m_wireFoldClears.clear(); m_wireFoldClears.clear();
m_wireDiscardLoads.clear();
if (passContinues) { if (passContinues) {
auto& store = MG_Record::ServerStagedTexture(); auto& store = MG_Record::ServerStagedTexture();
if (!m_wirePassMarksValid || m_wirePassMarksClearGeneration != store.GpuDirtyClearGeneration()) { if (!m_wirePassMarksValid || m_wirePassMarksClearGeneration != store.GpuDirtyClearGeneration()) {
@@ -536,6 +545,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!image.isDefault && surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture) if (!image.isDefault && surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture)
scratch.gpuWriteMarks.push_back({image.storage, image.level, image.layer, image.layers}); scratch.gpuWriteMarks.push_back({image.storage, image.level, image.layer, image.layers});
if (image.isDefault) { if (image.isDefault) {
// A present leaves the default framebuffer undefined (VulkanRenderer::Present):
// the first pass after it has nothing to load.
const Bool defined =
aspect == VK_IMAGE_ASPECT_COLOR_BIT
? m_swapchainObject.IsImageContentDefined(image.swapchainImageIndex)
: m_swapchainObject.IsDepthStencilContentDefined(image.swapchainImageIndex);
if (!defined) m_wireDiscardLoads.push_back(static_cast<Uint32>(attachments.size() - 1));
if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) if (aspect == VK_IMAGE_ASPECT_COLOR_BIT)
m_swapchainObject.SetImageContentDefined(image.swapchainImageIndex, true); m_swapchainObject.SetImageContentDefined(image.swapchainImageIndex, true);
else else
@@ -734,11 +750,51 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (m_transformFeedbackFeatureEnabled && MG_Pipe::gPipeInputs->IsTransformFeedbackActive() && if (m_transformFeedbackFeatureEnabled && MG_Pipe::gPipeInputs->IsTransformFeedbackActive() &&
program.GetTransformFeedbackVaryingCount() > 0) program.GetTransformFeedbackVaryingCount() > 0)
flags |= ProgramFactory::CompileOptionBit::XfbCapture; flags |= ProgramFactory::CompileOptionBit::XfbCapture;
const auto* programObject = &m_programFactory->GetOrCreateProgram(program, flags); // The variant this program's last draw with these flags settled on: what the draw reads
// from it before the narrow-integer pick below is the same in every variant.
const auto* programObject = &m_programFactory->GetOrCreateDrawProgram(program, flags);
if (!(aspects & DrawSetupAspect::IndexBuffer) && programObject->readsBaseVertexBuiltin) { if (!(aspects & DrawSetupAspect::IndexBuffer) && programObject->readsBaseVertexBuiltin) {
flags |= ProgramFactory::CompileOptionBit::ZeroBaseVertex; flags |= ProgramFactory::CompileOptionBit::ZeroBaseVertex;
programObject = &m_programFactory->GetOrCreateProgram(program, flags); programObject = &m_programFactory->GetOrCreateDrawProgram(program, flags);
} }
const auto vaoHandle = state.BoundVertexElements;
if (MG_Pipe::MGPipeHandleIsNull(vaoHandle)) MagmaWireFatal("vertex-elements-record");
if (vaoHandle.Slot >= state.VertexElementsCsos.size()) MagmaWireFatal("vertex-elements-record");
const auto& vao = state.VertexElementsCsos[vaoHandle.Slot];
if (!vao.Live || vao.Gen != vaoHandle.Gen) MagmaWireFatal("vertex-elements-generation");
// P7 wave 2 package C, CONTRACT-P7 §3.2: THE `vertex-layout` ROW IS RETIRED AND SPLIT.
//
// A false is now ONE reason and only one - `buffer-window`, an enabled attribute the
// program reads whose slot lies outside the window set_vertex_buffers published. That
// is a disagreement about what crossed, not about what this device can express, so it
// keeps a named Fatal through the hook (MGPipeSessionFail -> Session::Fail, which is
// what rule I asks of a refusal that must stay a refusal). It is the check that caught
// the Redmi VAO hash collision fixed in 376c04be, which is why it is not softened.
// The P7 marker is gone from the detail string with the abort: this is a permanent
// protocol error, not a shape the split path has yet to migrate, so it must not keep
// counting against the census of unmigrated refusals (scripts/ci/fatal_census.py
// counts the marker literally, which is also why no comment in this package spells it).
// P14: resolved through the record's own memo (ResolveWireVertexInput), except in a nested
// preparation, which builds into its own scratch rather than under the outer draw's feet.
const VertexInputStateFactory::BackendVertexInputState* resolvedInput = nullptr;
if (&scratch == &m_wireDrawScratch) {
resolvedInput = m_vertexInputStateFactory->ResolveWireVertexInput(
vao, state, programObject->activeVertexInputLocationMask);
} else if (m_vertexInputStateFactory->BuildWireVertexInput(
vao, state, programObject->activeVertexInputLocationMask, scratch.input)) {
resolvedInput = &scratch.input;
}
if (resolvedInput == nullptr) MagmaWireFatal("vertex-layout-buffer-window");
const auto& input = *resolvedInput;
// The narrow-integer variant (NarrowIntegerVertexFormats.h), chosen from two masks cached on
// the program entry and on the resolved layout: a program without integer vertex inputs pays
// four ANDs and a compare here. Every variant reflects the same inputs, so the layout
// resolved above with the entry's active mask is the chosen variant's too. A second lookup
// only when this draw needs another variant than the last one with these flags.
const NarrowIntegerVertexInputs narrowIntegerInputs =
ResolveNarrowIntegerVertexInputs(programObject->integerVertexInputs, input.narrowIntegerAttributes);
if (narrowIntegerInputs != programObject->narrowIntegerInputs)
programObject = &m_programFactory->GetOrCreateProgram(program, flags, narrowIntegerInputs);
if (programObject->invalidSpirvDeclined) { if (programObject->invalidSpirvDeclined) {
MGL_WIRE_DECLINE_AT(ProgramInvalidSpirv, MGL_WIRE_DECLINE_AT(ProgramInvalidSpirv,
"a module of the program fails SPIR-V validation and was never built"); "a module of the program fails SPIR-V validation and was never built");
@@ -776,35 +832,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
(void)ResolveWireImage(*fbo, fbo->Stencil, VK_IMAGE_ASPECT_STENCIL_BIT); (void)ResolveWireImage(*fbo, fbo->Stencil, VK_IMAGE_ASPECT_STENCIL_BIT);
} }
m_textureManager->FlushPendingUploads(); m_textureManager->FlushPendingUploads();
const auto vaoHandle = state.BoundVertexElements;
if (MG_Pipe::MGPipeHandleIsNull(vaoHandle)) MagmaWireFatal("vertex-elements-record");
if (vaoHandle.Slot >= state.VertexElementsCsos.size()) MagmaWireFatal("vertex-elements-record");
const auto& vao = state.VertexElementsCsos[vaoHandle.Slot];
if (!vao.Live || vao.Gen != vaoHandle.Gen) MagmaWireFatal("vertex-elements-generation");
// P7 wave 2 package C, CONTRACT-P7 §3.2: THE `vertex-layout` ROW IS RETIRED AND SPLIT.
//
// A false is now ONE reason and only one - `buffer-window`, an enabled attribute the
// program reads whose slot lies outside the window set_vertex_buffers published. That
// is a disagreement about what crossed, not about what this device can express, so it
// keeps a named Fatal through the hook (MGPipeSessionFail -> Session::Fail, which is
// what rule I asks of a refusal that must stay a refusal). It is the check that caught
// the Redmi VAO hash collision fixed in 376c04be, which is why it is not softened.
// The P7 marker is gone from the detail string with the abort: this is a permanent
// protocol error, not a shape the split path has yet to migrate, so it must not keep
// counting against the census of unmigrated refusals (scripts/ci/fatal_census.py
// counts the marker literally, which is also why no comment in this package spells it).
// P14: resolved through the record's own memo (ResolveWireVertexInput), except in a nested
// preparation, which builds into its own scratch rather than under the outer draw's feet.
const VertexInputStateFactory::BackendVertexInputState* resolvedInput = nullptr;
if (&scratch == &m_wireDrawScratch) {
resolvedInput =
m_vertexInputStateFactory->ResolveWireVertexInput(vao, state, programObj.activeVertexInputLocationMask);
} else if (m_vertexInputStateFactory->BuildWireVertexInput(vao, state, programObj.activeVertexInputLocationMask,
scratch.input)) {
resolvedInput = &scratch.input;
}
if (resolvedInput == nullptr) MagmaWireFatal("vertex-layout-buffer-window");
const auto& input = *resolvedInput;
// The seven device/format reasons are now MASKED OUT of the vertex input state, and // The seven device/format reasons are now MASKED OUT of the vertex input state, and
// this is the monolith arm's pre-flight for them, statement for statement // this is the monolith arm's pre-flight for them, statement for statement
// (VulkanRenderer.cpp's SetupDraw: "Fail loudly rather than render wrong pixels"). An // (VulkanRenderer.cpp's SetupDraw: "Fail loudly rather than render wrong pixels"). An
@@ -1164,7 +1191,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Every write goes through the pass's attachment views (the UNORM twin of an sRGB image while // Every write goes through the pass's attachment views (the UNORM twin of an sRGB image while
// GL_FRAMEBUFFER_SRGB is off, exactly GL's raw clear - no pre-compensation), and wire attachments // GL_FRAMEBUFFER_SRGB is off, exactly GL's raw clear - no pre-compensation), and wire attachments
// live in GENERAL. What none of this expresses goes back to the standalone path, per GL buffer // live in GENERAL. What none of this expresses goes back to the standalone path, per GL buffer
// bit: a partial colour or stencil write mask (that path warns and skips, as before), an // bit: a partial color or stencil write mask (that path warns and skips, as before), an
// attachment whose extent or layer count differs from the pass's (the pass covers the common area // attachment whose extent or layer count differs from the pass's (the pass covers the common area
// only), and a stencil clear the pass's depth attachment does not carry. // only), and a stencil clear the pass's depth attachment does not carry.
GLbitfield VulkanRenderer::ClearWireFramebufferInPass(const MG_Pipe::MGPFramebufferState& fbo, GLbitfield VulkanRenderer::ClearWireFramebufferInPass(const MG_Pipe::MGPFramebufferState& fbo,
@@ -1174,7 +1201,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Nested inside a draw's preparation the scratch and the pass belong to that draw. // Nested inside a draw's preparation the scratch and the pass belong to that draw.
if (m_wirePreparationDepth != 0) return remaining; if (m_wirePreparationDepth != 0) return remaining;
// NOT THE DEFAULT FRAMEBUFFER (measured, not understood): on Adreno 750 the openra retrace // NOT THE DEFAULT FRAMEBUFFER (measured, not understood): on Adreno 750 the openra retrace
// loses its palette-sampled sprites (SSIM 0.956) when a colour+depth clear of the default // loses its palette-sampled sprites (SSIM 0.956) when a color+depth clear of the default
// framebuffer becomes part of its draw pass - with LOAD_OP_CLEAR as well as with // framebuffer becomes part of its draw pass - with LOAD_OP_CLEAR as well as with
// vkCmdClearAttachments - while the same clear split across the two paths, or left // vkCmdClearAttachments - while the same clear split across the two paths, or left
// standalone, renders correctly. Default-framebuffer clears keep the standalone path until // standalone, renders correctly. Default-framebuffer clears keep the standalone path until
@@ -1343,16 +1370,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
// The draw pass `begun` with LOAD_OP_CLEAR for the attachments `variantKey` names (bit 2a: // The draw pass `begun` with LOAD_OP_CLEAR for the attachments `variantKey` names (bit 2a:
// attachment a's colour or depth, bit 2a+1: its stencil). Built once per key and kept on the // attachment a's color or depth, bit 2a+1: its stencil). Built once per key and kept on the
// pass, like the pass itself. // pass, like the pass itself.
VkRenderPass VulkanRenderer::WireClearVariant(RenderPassEntry& begun, Uint64 variantKey) { VkRenderPass VulkanRenderer::WireClearVariant(RenderPassEntry& begun, Uint64 variantKey) {
for (const auto& entry : begun.clearVariants) for (const auto& entry : begun.clearVariants)
if (entry.first == variantKey) return entry.second; if (entry.first == variantKey) return entry.second;
const Uint32 attachmentCount = static_cast<Uint32>(begun.wireAttachments.size()); const Uint32 attachmentCount = static_cast<Uint32>(begun.wireAttachments.size());
auto descriptions = begun.wireAttachments; auto descriptions = begun.wireAttachments;
// Bits 2a and 2a+1 clear attachment a (color or depth, and stencil); bit kWireDiscardBit + a
// marks its content undefined, so whatever is not cleared is not loaded either.
for (Uint32 a = 0; a < attachmentCount; ++a) { for (Uint32 a = 0; a < attachmentCount; ++a) {
if (variantKey >> (2 * a) & 1) descriptions[a].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; const Bool discard = variantKey >> (kWireDiscardBit + a) & 1;
if (variantKey >> (2 * a + 1) & 1) descriptions[a].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; if (variantKey >> (2 * a) & 1)
descriptions[a].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
else if (discard)
descriptions[a].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
if (variantKey >> (2 * a + 1) & 1)
descriptions[a].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
else if (discard)
descriptions[a].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
} }
VkSubpassDescription subpass{}; VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
@@ -1371,6 +1407,142 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return variant; return variant;
} }
// The default framebuffer's half of the deferred clears: a whole-window glClear waits for the
// next pass that draws into the window, which loads it with LOAD_OP_CLEAR
// (BeginWireDrawPassFolding) instead of storing a cleared window and loading it back. A pass
// open on the window ends first, since the in-pass clear does not take the window
// (MagmaInPassClearDefault); a present drops the depth/stencil half (Present). Returns the
// buffers it did not take.
GLbitfield VulkanRenderer::DeferDefaultWireClear(const MG_Pipe::MGPFramebufferState& fbo,
const ClearAttachmentPayload& payload, GLint drawbuffer) {
GLbitfield remaining = payload.mask & (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
if (!remaining || !fbo.IsDefault || drawbuffer >= 0 || m_wirePreparationDepth != 0) return remaining;
if (GLScissorTestClips()) return remaining;
GLbitfield handled = 0;
Bool color = false;
VkImageAspectFlags depthStencil = 0;
if (remaining & GL_COLOR_BUFFER_BIT) {
const Int32 slot = fbo.DrawBuffers[0];
const auto mask = MG_Pipe::gPipeInputs->GetColorMaskIndexed(0);
const Bool none = slot < 0 || (!mask.r() && !mask.g() && !mask.b() && !mask.a());
if (none)
handled |= GL_COLOR_BUFFER_BIT;
else if (mask.r() && mask.g() && mask.b() && mask.a() &&
payload.colorEncoding == ClearColorEncoding::Float) {
handled |= GL_COLOR_BUFFER_BIT;
color = true;
}
}
// A window without a depth/stencil buffer clears nothing there.
const VkFormat depthStencilFormat = m_swapchainObject.GetDepthStencilFormat();
const Bool hasDepth = depthStencilFormat != VK_FORMAT_UNDEFINED;
const Bool packed = vkuFormatIsDepthAndStencil(depthStencilFormat);
if (remaining & GL_DEPTH_BUFFER_BIT) {
handled |= GL_DEPTH_BUFFER_BIT;
if (hasDepth && MG_Pipe::gPipeInputs->GetDepthMask()) depthStencil |= VK_IMAGE_ASPECT_DEPTH_BIT;
}
if (remaining & GL_STENCIL_BUFFER_BIT) {
const Uint32 mask = MG_Pipe::gPipeInputs->GetStencilState(StencilFace::Front).WriteMask & 255u;
if (mask == 0 || !packed)
handled |= GL_STENCIL_BUFFER_BIT;
else if (mask == 255u) {
handled |= GL_STENCIL_BUFFER_BIT;
depthStencil |= VK_IMAGE_ASPECT_STENCIL_BIT;
}
}
if (!color && depthStencil == 0) return remaining & ~handled;
if (VkRenderPassManager::GetActiveRenderPass() != nullptr && m_wireDrawPass && m_wireDrawPassKey.isDefault)
RetireWireDrawPass();
// The index the next drawing pass on the window will address (WireDraw.cpp's pass key).
const Uint32 index = DefaultFramebufferWriteIndex(true);
auto& pending = m_defaultWirePendingClear;
if (pending.Pending() &&
(pending.swapchain != m_swapchainObject.GetHandle() || pending.swapchainImageIndex != index))
MaterializeDefaultWirePendingClear();
pending.swapchain = m_swapchainObject.GetHandle();
pending.swapchainImageIndex = index;
if (color) {
pending.color = true;
pending.colorPayload = payload;
pending.colorPayloadCompensated = payload;
PreCompensateSrgbClearColor(pending.colorPayloadCompensated, m_swapchainObject.GetSurfaceFormat().format);
m_swapchainObject.SetImageContentDefined(index, true);
}
if (depthStencil & VK_IMAGE_ASPECT_DEPTH_BIT) pending.depthStencilPayload.depth = payload.depth;
if (depthStencil & VK_IMAGE_ASPECT_STENCIL_BIT) pending.depthStencilPayload.stencil = payload.stencil;
if (depthStencil) {
pending.depthStencil |= depthStencil;
m_swapchainObject.SetDepthStencilContentDefined(index, true);
}
return remaining & ~handled;
}
void VulkanRenderer::MaterializeDefaultWirePendingClear() {
if (!m_defaultWirePendingClear.Pending()) return;
const DefaultWirePendingClear pending = m_defaultWirePendingClear;
m_defaultWirePendingClear = {};
// A recreated swapchain took the images the clear was for.
if (pending.swapchain != m_swapchainObject.GetHandle() ||
pending.swapchainImageIndex >= m_swapchainObject.GetImageCount())
return;
const VkExtent2D extent = m_swapchainObject.GetExtent();
const VkRect2D area{{0, 0}, extent};
WireImage image{};
image.isDefault = true;
image.extent = extent;
image.swapchainImageIndex = pending.swapchainImageIndex;
if (pending.color) {
image.aspect = VK_IMAGE_ASPECT_COLOR_BIT;
image.image = m_swapchainObject.GetImage(pending.swapchainImageIndex);
image.format = m_swapchainObject.GetSurfaceFormat().format;
image.layout = m_swapchainObject.GetImageLayout(pending.swapchainImageIndex);
RecordWireImageClear(image, VK_IMAGE_ASPECT_COLOR_BIT, pending.colorPayloadCompensated, area, false,
/*colorPreCompensated=*/true);
}
if (pending.depthStencil && m_swapchainObject.GetDepthStencilImage(pending.swapchainImageIndex)) {
image.aspect = pending.depthStencil;
image.image = m_swapchainObject.GetDepthStencilImage(pending.swapchainImageIndex);
image.format = m_swapchainObject.GetDepthStencilFormat();
image.layout = m_swapchainObject.GetDepthStencilImageLayout(pending.swapchainImageIndex);
RecordWireImageClear(image, pending.depthStencil, pending.depthStencilPayload, area, false);
}
}
// A pending window clear (DeferDefaultWireClear) becomes the load op of the window pass that
// begins: its attachments are matched by image, since a continued pass key is not walked again.
// A clear the caller already put in `variantKey` is newer and wins.
void VulkanRenderer::FoldDefaultWirePendingClear(RenderPassEntry& begun, Uint64& variantKey, VkClearValue* merged) {
auto& pending = m_defaultWirePendingClear;
if (!pending.Pending() || !m_wireDrawPassKey.isDefault || m_wireDrawPassKey.swapchain != pending.swapchain ||
m_wireDrawPassKey.swapchainImageIndex != pending.swapchainImageIndex)
return;
const Uint32 attachmentCount =
static_cast<Uint32>(std::min<SizeT>(begun.wireAttachments.size(), m_wireDrawPassKey.attachments.size()));
const VkImage colorImage = m_swapchainObject.GetImage(pending.swapchainImageIndex);
const VkImage depthStencilImage = m_swapchainObject.GetDepthStencilImage(pending.swapchainImageIndex);
for (Uint32 a = 0; a < attachmentCount && a < MG_Pipe::kMGPipeMaxColorAttachments + 1; ++a) {
const auto& attachment = m_wireDrawPassKey.attachments[a];
if (pending.color && attachment.image == colorImage) {
if (!(variantKey >> (2 * a) & 1)) {
variantKey |= 1ull << (2 * a);
merged[a].color = MakeVkClearColorValue(pending.colorPayload, false);
}
pending.color = false;
} else if (pending.depthStencil && attachment.image == depthStencilImage) {
if ((pending.depthStencil & VK_IMAGE_ASPECT_DEPTH_BIT) && !(variantKey >> (2 * a) & 1)) {
variantKey |= 1ull << (2 * a);
merged[a].depthStencil.depth = pending.depthStencilPayload.depth;
}
if ((pending.depthStencil & VK_IMAGE_ASPECT_STENCIL_BIT) && !(variantKey >> (2 * a + 1) & 1)) {
variantKey |= 1ull << (2 * a + 1);
merged[a].depthStencil.stencil = pending.depthStencilPayload.stencil;
}
pending.depthStencil = 0;
}
}
// An aspect the pass does not attach stays pending: none of its draws write it.
}
// P15: EVERY DRAW PASS BEGINS HERE. The pending clears of its attachments // P15: EVERY DRAW PASS BEGINS HERE. The pending clears of its attachments
// (m_wireFoldClears, gathered by PrepareWireDrawPass) become their load op and leave the // (m_wireFoldClears, gathered by PrepareWireDrawPass) become their load op and leave the
// images; a clear the caller already put in `variantKey` (its own glClear, newer than any // images; a clear the caller already put in `variantKey` (its own glClear, newer than any
@@ -1415,6 +1587,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
} }
m_wireFoldClears.clear(); m_wireFoldClears.clear();
FoldDefaultWirePendingClear(begun, variantKey, merged);
for (const Uint32 a : m_wireDiscardLoads)
if (a < attachmentCount) variantKey |= 1ull << (kWireDiscardBit + a);
m_wireDiscardLoads.clear();
if (variantKey == 0) return VkRenderPassManager::BeginRenderPass(frame.commandBuffer, begun); if (variantKey == 0) return VkRenderPassManager::BeginRenderPass(frame.commandBuffer, begun);
return VkRenderPassManager::BeginRenderPass(frame.commandBuffer, begun, WireClearVariant(begun, variantKey), return VkRenderPassManager::BeginRenderPass(frame.commandBuffer, begun, WireClearVariant(begun, variantKey),
merged, attachmentCount); merged, attachmentCount);
@@ -25,7 +25,7 @@
// level return every non-canonical word canonicalized, while a 16x16 or 64x64 level reads back verbatim. // level return every non-canonical word canonicalized, while a 16x16 or 64x64 level reads back verbatim.
// The stored words are intact - a shader reading the same image through an R32_UINT view sees // The stored words are intact - a shader reading the same image through an R32_UINT view sees
// every one of them - and so are transfer WRITES into it. Measured with format lists, without // every one of them - and so are transfer WRITES into it. Measured with format lists, without
// the colour-attachment usage and with storage usage: none of them changes the answer, and the // the color-attachment usage and with storage usage: none of them changes the answer, and the
// device has no VK_EXT_image_compression_control to ask with. // device has no VK_EXT_image_compression_control to ask with.
// //
// The route therefore reads through a shader: a compute pass fetches the source through an // The route therefore reads through a shader: a compute pass fetches the source through an
@@ -109,7 +109,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// comparison or the route itself is what failed, and nothing is concluded. // comparison or the route itself is what failed, and nothing is concluded.
Uint32 controlMismatches = 0; Uint32 controlMismatches = 0;
// Reported, not judged: the same words in a 16x16 level read back by plain transfer. Clean // Reported, not judged: the same words in a 16x16 level read back by plain transfer. Clean
// on the device this was characterised on (power-of-two extents read back verbatim, 28, 30 // on the device this was characterized on (power-of-two extents read back verbatim, 28, 30
// and 31 do not), which is why the subject is 30x30. // and 31 do not), which is why the subject is 30x30.
Uint32 smallTexels = 0; Uint32 smallTexels = 0;
Uint32 smallMismatches = 0; Uint32 smallMismatches = 0;
@@ -615,7 +615,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} }
// THE HOST GATE CANNOT REACH THE SHADER MIP ARM ON ITS OWN. lavapipe reports // THE HOST GATE CANNOT REACH THE SHADER MIP ARM ON ITS OWN. lavapipe reports
// BLIT_SRC|BLIT_DST for every colour format this lane can create, so `nativeBlit` is // BLIT_SRC|BLIT_DST for every color format this lane can create, so `nativeBlit` is
// always true there and both shader shapes below would only ever run on the phone - // always true there and both shader shapes below would only ever run on the phone -
// which is how the Adreno refusal reached a release in the first place. The knob makes // which is how the Adreno refusal reached a release in the first place. The knob makes
// them reachable from a host lane and is read exactly once: // them reachable from a host lane and is read exactly once:
@@ -658,6 +658,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
WireImage out{}; WireImage out{};
out.aspect = aspect; out.aspect = aspect;
if (fbo.IsDefault) { if (fbo.IsDefault) {
// Only an attachment of the pass being prepared takes a pending window clear as its
// load op; every other use runs it first.
if (m_defaultWirePendingClear.Pending() && !m_textureManager->PendingClearsHeld())
MaterializeDefaultWirePendingClear();
out.isDefault = true; out.isDefault = true;
out.extent = m_swapchainObject.GetExtent(); out.extent = m_swapchainObject.GetExtent();
out.swapchainImageIndex = out.swapchainImageIndex =
@@ -789,7 +793,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// ---- P15: WIRE DEFERRED CLEARS ------------------------------------------------------------- // ---- P15: WIRE DEFERRED CLEARS -------------------------------------------------------------
// //
// Minecraft (1.21.5) clears its targets through framebuffers of their own - a depth-only one // Minecraft (1.21.5) clears its targets through framebuffers of their own - a depth-only one
// for the shared depth texture, a colour+depth one per target - and then draws through the // for the shared depth texture, a color+depth one per target - and then draws through the
// main framebuffer. A clear that begins a LOAD_OP_CLEAR pass of its own made every such clear a // main framebuffer. A clear that begins a LOAD_OP_CLEAR pass of its own made every such clear a
// pass that did nothing but clear, closed by the very next draw: 11.5 passes a frame against // pass that did nothing but clear, closed by the very next draw: 11.5 passes a frame against
// dev's 4.1 (whose clear manager folded the same clears into the next pass's load op), each // dev's 4.1 (whose clear manager folded the same clears into the next pass's load op), each
@@ -805,7 +809,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// ClearWireFramebufferInPass), and it is presented, not sampled. // ClearWireFramebufferInPass), and it is presented, not sampled.
// Whether `image` can carry a deferred clear: a plain single-sample 2D(-array) image of this // Whether `image` can carry a deferred clear: a plain single-sample 2D(-array) image of this
// session with a float colour or a depth/stencil format, clearable by a transfer command. // session with a float color or a depth/stencil format, clearable by a transfer command.
Bool VulkanRenderer::WireImageCanDeferClear(const WireImage& image) { Bool VulkanRenderer::WireImageCanDeferClear(const WireImage& image) {
const auto* resource = image.resource; const auto* resource = image.resource;
if (resource == nullptr || image.isDefault || image.is3D || image.samples != VK_SAMPLE_COUNT_1_BIT) if (resource == nullptr || image.isDefault || image.is3D || image.samples != VK_SAMPLE_COUNT_1_BIT)
@@ -1109,6 +1113,87 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return remaining & ~handled; return remaining & ~handled;
} }
void VulkanRenderer::RecordWireImageClear(WireImage& image, VkImageAspectFlags aspect,
const ClearAttachmentPayload& payload, const VkRect2D& area,
Bool textureSurface, Bool colorPreCompensated) {
const Bool color = aspect == VK_IMAGE_ASPECT_COLOR_BIT;
const auto layout =
color ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
TransitionWireImage(image, layout);
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
viewInfo.image = image.image;
viewInfo.viewType = image.viewType;
viewInfo.format = image.format;
viewInfo.subresourceRange = {
color ? VK_IMAGE_ASPECT_COLOR_BIT
: (image.format == VK_FORMAT_D24_UNORM_S8_UINT || image.format == VK_FORMAT_D32_SFLOAT_S8_UINT
? VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT
: image.aspect),
image.level, 1, image.layer, image.layers};
VkImageView view = VK_NULL_HANDLE;
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &view));
VkAttachmentDescription attachment{};
attachment.format = image.format;
attachment.samples = image.samples;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.initialLayout = layout;
attachment.finalLayout = layout;
VkAttachmentReference ref{0, layout};
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = color ? 1 : 0;
subpass.pColorAttachments = color ? &ref : nullptr;
subpass.pDepthStencilAttachment = color ? nullptr : &ref;
VkRenderPassCreateInfo passInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
passInfo.attachmentCount = 1;
passInfo.pAttachments = &attachment;
passInfo.subpassCount = 1;
passInfo.pSubpasses = &subpass;
VkRenderPass pass = VK_NULL_HANDLE;
VK_VERIFY(vkCreateRenderPass(m_device, &passInfo, nullptr, &pass));
VkFramebufferCreateInfo fbInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
fbInfo.renderPass = pass;
fbInfo.attachmentCount = 1;
fbInfo.pAttachments = &view;
fbInfo.width = image.extent.width;
fbInfo.height = image.extent.height;
fbInfo.layers = image.layers;
VkFramebuffer fb = VK_NULL_HANDLE;
VK_VERIFY(vkCreateFramebuffer(m_device, &fbInfo, nullptr, &fb));
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
begin.renderPass = pass;
begin.framebuffer = fb;
begin.renderArea = area;
auto clearPayload = payload;
if (color && !colorPreCompensated) PreCompensateSrgbClearColor(clearPayload, image.format);
VkClearAttachment clear{};
clear.aspectMask = aspect;
clear.clearValue.color = MakeVkClearColorValue(clearPayload, false);
if (!color) clear.clearValue.depthStencil = {payload.depth, payload.stencil};
VkClearRect rect{area, 0, image.layers};
auto& frame = m_frameContext.GetCurrent();
EndActiveRenderPassOn(frame.commandBuffer);
vkCmdBeginRenderPass(frame.commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE);
vkCmdClearAttachments(frame.commandBuffer, 1, &clear, 1, &rect);
vkCmdEndRenderPass(frame.commandBuffer);
WireRetiredObjects retired{};
retired.framebuffers.push_back(fb);
retired.renderPasses.push_back(pass);
retired.imageViews.push_back(view);
RetireWireObjects(Move(retired));
if (!image.isDefault && textureSurface)
m_textureManager->MarkWireTextureGpuWritten(image.storage, image.level, image.layer, image.layers);
if (image.isDefault) {
if (color)
m_swapchainObject.SetImageContentDefined(image.swapchainImageIndex, true);
else
m_swapchainObject.SetDepthStencilContentDefined(image.swapchainImageIndex, true);
}
}
void VulkanRenderer::ClearWireFramebuffer(const MG_Pipe::MGPFramebufferState& fbo, void VulkanRenderer::ClearWireFramebuffer(const MG_Pipe::MGPFramebufferState& fbo,
const ClearAttachmentPayload& clearRequest, GLint drawbuffer) { const ClearAttachmentPayload& clearRequest, GLint drawbuffer) {
if (MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard)) return; if (MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard)) return;
@@ -1126,6 +1211,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
payload.mask = ClearWireFramebufferInPass(fbo, payload, drawbuffer); payload.mask = ClearWireFramebufferInPass(fbo, payload, drawbuffer);
if (payload.mask) payload.mask = ClearWireAttachmentsOfOpenPass(fbo, payload, drawbuffer); if (payload.mask) payload.mask = ClearWireAttachmentsOfOpenPass(fbo, payload, drawbuffer);
if (payload.mask) payload.mask = DeferWireClear(fbo, payload, drawbuffer); if (payload.mask) payload.mask = DeferWireClear(fbo, payload, drawbuffer);
if (payload.mask) payload.mask = DeferDefaultWireClear(fbo, payload, drawbuffer);
if (payload.mask) payload.mask = ClearWireFramebufferInPass(fbo, payload, drawbuffer); if (payload.mask) payload.mask = ClearWireFramebufferInPass(fbo, payload, drawbuffer);
if (!payload.mask) return; if (!payload.mask) return;
RetireWireDrawPass(); RetireWireDrawPass();
@@ -1177,82 +1263,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_swapchainObject.GetPreTransform(), &area.offset, &area.extent)) m_swapchainObject.GetPreTransform(), &area.offset, &area.extent))
MagmaWireFatal("clear-surface-transform"); MagmaWireFatal("clear-surface-transform");
} }
const Bool color = aspect == VK_IMAGE_ASPECT_COLOR_BIT; RecordWireImageClear(image, aspect, payload, area, surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture);
const auto layout =
color ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
TransitionWireImage(image, layout);
VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
viewInfo.image = image.image;
viewInfo.viewType = image.viewType;
viewInfo.format = image.format;
viewInfo.subresourceRange = {
color ? VK_IMAGE_ASPECT_COLOR_BIT
: (image.format == VK_FORMAT_D24_UNORM_S8_UINT || image.format == VK_FORMAT_D32_SFLOAT_S8_UINT
? VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT
: image.aspect),
image.level, 1, image.layer, image.layers};
VkImageView view = VK_NULL_HANDLE;
VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &view));
VkAttachmentDescription attachment{};
attachment.format = image.format;
attachment.samples = image.samples;
attachment.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.initialLayout = layout;
attachment.finalLayout = layout;
VkAttachmentReference ref{0, layout};
VkSubpassDescription subpass{};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = color ? 1 : 0;
subpass.pColorAttachments = color ? &ref : nullptr;
subpass.pDepthStencilAttachment = color ? nullptr : &ref;
VkRenderPassCreateInfo passInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
passInfo.attachmentCount = 1;
passInfo.pAttachments = &attachment;
passInfo.subpassCount = 1;
passInfo.pSubpasses = &subpass;
VkRenderPass pass = VK_NULL_HANDLE;
VK_VERIFY(vkCreateRenderPass(m_device, &passInfo, nullptr, &pass));
VkFramebufferCreateInfo fbInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
fbInfo.renderPass = pass;
fbInfo.attachmentCount = 1;
fbInfo.pAttachments = &view;
fbInfo.width = image.extent.width;
fbInfo.height = image.extent.height;
fbInfo.layers = image.layers;
VkFramebuffer fb = VK_NULL_HANDLE;
VK_VERIFY(vkCreateFramebuffer(m_device, &fbInfo, nullptr, &fb));
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
begin.renderPass = pass;
begin.framebuffer = fb;
begin.renderArea = area;
auto clearPayload = payload;
if (color) PreCompensateSrgbClearColor(clearPayload, image.format);
VkClearAttachment clear{};
clear.aspectMask = aspect;
clear.clearValue.color = MakeVkClearColorValue(clearPayload, false);
if (!color) clear.clearValue.depthStencil = {payload.depth, payload.stencil};
VkClearRect rect{area, 0, image.layers};
auto& frame = m_frameContext.GetCurrent();
EndActiveRenderPassOn(frame.commandBuffer);
vkCmdBeginRenderPass(frame.commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE);
vkCmdClearAttachments(frame.commandBuffer, 1, &clear, 1, &rect);
vkCmdEndRenderPass(frame.commandBuffer);
WireRetiredObjects retired{};
retired.framebuffers.push_back(fb);
retired.renderPasses.push_back(pass);
retired.imageViews.push_back(view);
RetireWireObjects(Move(retired));
if (!image.isDefault && surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture)
m_textureManager->MarkWireTextureGpuWritten(image.storage, image.level, image.layer, image.layers);
if (image.isDefault) {
if (color)
m_swapchainObject.SetImageContentDefined(image.swapchainImageIndex, true);
else
m_swapchainObject.SetDepthStencilContentDefined(image.swapchainImageIndex, true);
}
}; };
if (payload.mask & GL_COLOR_BUFFER_BIT) { if (payload.mask & GL_COLOR_BUFFER_BIT) {
for (Uint32 i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i) { for (Uint32 i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i) {
@@ -1328,7 +1339,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
!m_swapchainObject.IsImageContentDefined(source.swapchainImageIndex)) { !m_swapchainObject.IsImageContentDefined(source.swapchainImageIndex)) {
// Named rather than silent: the readback hands back whatever the image holds. The // Named rather than silent: the readback hands back whatever the image holds. The
// ordinary cause is a readback of the default framebuffer applied after the Present // ordinary cause is a readback of the default framebuffer applied after the Present
// that consumed it, where the app's own colour is still what is expected (see // that consumed it, where the app's own color is still what is expected (see
// m_defaultFramebufferImageIndex). // m_defaultFramebufferImageIndex).
MGLOG_W_ONCE("DirectVulkan::ReadPixels: no write is recorded for swapchain image %u of the " MGLOG_W_ONCE("DirectVulkan::ReadPixels: no write is recorded for swapchain image %u of the "
"default framebuffer; the readback returns its raw pixels", "default framebuffer; the readback returns its raw pixels",
@@ -1416,7 +1427,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Int64 dstHeight = dstSpanY < 0 ? -dstSpanY : dstSpanY; const Int64 dstHeight = dstSpanY < 0 ? -dstSpanY : dstSpanY;
// A SCALE has no arm here and is not supposed to: 18.3.1 makes a multisample blit whose // A SCALE has no arm here and is not supposed to: 18.3.1 makes a multisample blit whose
// rectangles differ in size INVALID_OPERATION. Rule I (a), the same three lines the // rectangles differ in size INVALID_OPERATION. Rule I (a), the same three lines the
// colour arm's multisample-destination decline uses. A ZERO span on one side only is a // color arm's multisample-destination decline uses. A ZERO span on one side only is a
// size mismatch too (review round 2): it used to return silently with the other side // size mismatch too (review round 2): it used to return silently with the other side
// non-empty, where GL raises the error. Only when BOTH sides are empty on an axis is // non-empty, where GL raises the error. Only when BOTH sides are empty on an axis is
// there nothing to do and nothing to say - that is the caller's early return. // there nothing to do and nothing to say - that is the caller's early return.
@@ -1439,7 +1450,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// (BlitDepthAcrossFormats, shared with the monolith arm) has no flip either. Both // (BlitDepthAcrossFormats, shared with the monolith arm) has no flip either. Both
// decline by name, each under its OWN once-site (review round 2: one site covered both, // decline by name, each under its OWN once-site (review round 2: one site covered both,
// so after the first X decline a cross-format Y decline logged nothing); §6 in // so after the first X decline a cross-format Y decline logged nothing); §6 in
// docs/archive/notes/p7/magma-b2.md carries them. These are declines of a LEGAL blit - GL would // the phase notes carries them. These are declines of a LEGAL blit - GL would
// raise no error here - so the INVALID_OPERATION is rule I (a)'s named refusal, not // raise no error here - so the INVALID_OPERATION is rule I (a)'s named refusal, not
// GL's answer. // GL's answer.
const Bool mirrorX = (spanX < 0) != (dstSpanX < 0); const Bool mirrorX = (spanX < 0) != (dstSpanX < 0);
@@ -1602,7 +1613,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// P7 wave 2-B2 (review round): A REVERSED RECTANGLE IS NOT A MALFORMED ONE. This guard // P7 wave 2-B2 (review round): A REVERSED RECTANGLE IS NOT A MALFORMED ONE. This guard
// used to require `sx1 > sx0` and `sy1 > sy0` and take the session with a named Fatal // used to require `sx1 > sx0` and `sy1 > sy0` and take the session with a named Fatal
// otherwise - so `glBlitFramebuffer(0, h, w, 0, 0, 0, w, h, COLOR|DEPTH)` out of a // otherwise - so `glBlitFramebuffer(0, h, w, 0, 0, 0, w, h, COLOR|DEPTH)` out of a
// multisample framebuffer blitted its COLOUR correctly (the colour arm has handled a // multisample framebuffer blitted its COLOR correctly (the color arm has handled a
// flip since P5f and now handles a scale too) and then died on its DEPTH. GL 4.6 core // flip since P5f and now handles a scale too) and then died on its DEPTH. GL 4.6 core
// 18.3.1 asks for identical DIMENSIONS, not identical corners - a conforming check compares absolute // 18.3.1 asks for identical DIMENSIONS, not identical corners - a conforming check compares absolute
// spans - so a flip is a legal blit that has to produce a mirrored picture. // spans - so a flip is a legal blit that has to produce a mirrored picture.
@@ -1610,10 +1621,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Spans are therefore signed and everything downstream works off the min corner. // Spans are therefore signed and everything downstream works off the min corner.
// //
// THE SHAPE DECLINES ARE DECIDED ONCE PER CALL (review round 2). BlitWireFramebuffers // THE SHAPE DECLINES ARE DECIDED ONCE PER CALL (review round 2). BlitWireFramebuffers
// asks DeclineWireDepthStencilResolveShape before ANY aspect runs, because the colour // asks DeclineWireDepthStencilResolveShape before ANY aspect runs, because the color
// lambda runs first: asked only here, a scaled COLOR|DEPTH blit scaled and wrote its // lambda runs first: asked only here, a scaled COLOR|DEPTH blit scaled and wrote its
// colour and THEN recorded INVALID_OPERATION on its depth - a partial effect GL 18.3.1 // color and THEN recorded INVALID_OPERATION on its depth - a partial effect GL 18.3.1
// forbids (the whole call errors) - and an X-mirrored one mirrored its colour and left // forbids (the whole call errors) - and an X-mirrored one mirrored its color and left
// its depth alone. The call below is the same question for a caller that did not ask; // its depth alone. The call below is the same question for a caller that did not ask;
// on the BlitWireFramebuffers path it has already answered false. // on the BlitWireFramebuffers path it has already answered false.
if (DeclineWireDepthStencilResolveShape(source, destination, sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1)) return; if (DeclineWireDepthStencilResolveShape(source, destination, sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1)) return;
@@ -1715,7 +1726,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// so a shader decline (a stencil aspect without VK_EXT_shader_stencil_export, a format // so a shader decline (a stencil aspect without VK_EXT_shader_stencil_export, a format
// that is not SAMPLED_IMAGE + DEPTH_STENCIL_ATTACHMENT, a failed pipeline create - // that is not SAMPLED_IMAGE + DEPTH_STENCIL_ATTACHMENT, a failed pipeline create -
// WireMultisampleResolve.cpp) that lands on the render pass is the resolve most likely // WireMultisampleResolve.cpp) that lands on the render pass is the resolve most likely
// to read back as zeroes with no GL error. A recorded debt (docs/archive/notes/p7/g5-msrbo.md §4). // to read back as zeroes with no GL error. A recorded debt (§4).
if (resolvedByShader) { if (resolvedByShader) {
MGLOG_I_ONCE("DirectVulkan::ResolveWireDepthStencil: resolved by the shader pass " MGLOG_I_ONCE("DirectVulkan::ResolveWireDepthStencil: resolved by the shader pass "
"(prefer-shader=%d, render-pass arm available=%d)", "(prefer-shader=%d, render-pass arm available=%d)",
@@ -1874,8 +1885,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!read || !draw) MagmaWireFatal("blit-framebuffer-record"); if (!read || !draw) MagmaWireFatal("blit-framebuffer-record");
state.VerbBlitNamedConsumed = named; state.VerbBlitNamedConsumed = named;
// P7 wave 2-B2 (review round 2): ONE ANSWER FOR THE WHOLE CALL. The aspects below run // P7 wave 2-B2 (review round 2): ONE ANSWER FOR THE WHOLE CALL. The aspects below run
// colour first, so a multisample depth/stencil shape that declines (a scale, an X // color first, so a multisample depth/stencil shape that declines (a scale, an X
// mirror, a Y mirror across two formats) used to be discovered only after the colour // mirror, a Y mirror across two formats) used to be discovered only after the color
// attachment had already been scaled or mirrored and written - the call then recorded // attachment had already been scaled or mirrored and written - the call then recorded
// INVALID_OPERATION with half its effect in place, which GL 18.3.1 does not allow (an // INVALID_OPERATION with half its effect in place, which GL 18.3.1 does not allow (an
// erroring blit writes nothing). The shape is a fact about the rectangles and the two // erroring blit writes nothing). The shape is a fact about the rectangles and the two
@@ -1887,7 +1898,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// on the belief that a default draw behind a multisample read was ResolveWireDepthStencil's // on the belief that a default draw behind a multisample read was ResolveWireDepthStencil's
// region Fatal whatever the shape, but that arm asks DeclineWireDepthStencilResolveShape // region Fatal whatever the shape, but that arm asks DeclineWireDepthStencilResolveShape
// BEFORE its region check - so a scaled (or X-mirrored) COLOR|DEPTH blit out of a // BEFORE its region check - so a scaled (or X-mirrored) COLOR|DEPTH blit out of a
// multisample framebuffer onto the window scaled and wrote its colour through // multisample framebuffer onto the window scaled and wrote its color through
// BlitWireColorToDefault and then recorded INVALID_OPERATION on its depth, the very // BlitWireColorToDefault and then recorded INVALID_OPERATION on its depth, the very
// partial effect this block exists to close. Both sides are resolved as READS // partial effect this block exists to close. Both sides are resolved as READS
// (isWriteTarget=false): only their format and samples are consulted here, and a write // (isWriteTarget=false): only their format and samples are consulted here, and a write
@@ -2005,7 +2016,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// The filter only started MATTERING with the scale above: at 1:1 LINEAR and // The filter only started MATTERING with the scale above: at 1:1 LINEAR and
// NEAREST read the same texel, so nothing here ever asked whether the format // NEAREST read the same texel, so nothing here ever asked whether the format
// could be filtered. VK_FILTER_LINEAR on a source whose optimalTilingFeatures // could be filtered. VK_FILTER_LINEAR on a source whose optimalTilingFeatures
// lack SAMPLED_IMAGE_FILTER_LINEAR is undefined behaviour, and GL says filter // lack SAMPLED_IMAGE_FILTER_LINEAR is undefined behavior, and GL says filter
// is ignored for a multisample resolve in the first place - so a format that // is ignored for a multisample resolve in the first place - so a format that
// cannot be filtered takes NEAREST rather than the command being illegal. // cannot be filtered takes NEAREST rather than the command being illegal.
VkFormatProperties scratchProperties{}; VkFormatProperties scratchProperties{};
@@ -2077,7 +2088,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// express a quarter turn. It is still TAKEN FIRST on every transform it can // express a quarter turn. It is still TAKEN FIRST on every transform it can
// express, identity included. That has been the routing since 38919d453: every // express, identity included. That has been the routing since 38919d453: every
// host lane (all identity) and the Redmi pbuffer run their default-framebuffer // host lane (all identity) and the Redmi pbuffer run their default-framebuffer
// colour blits through it, F1WireScenario.ColorBlitToDefaultHonorsScissorAndReversedRect // color blits through it, F1WireScenario.ColorBlitToDefaultHonorsScissorAndReversedRect
// among them. The ordinary vkCmdBlitImage arm below is reached on identity only // among them. The ordinary vkCmdBlitImage arm below is reached on identity only
// when this pass declines the source. Because identity blits run here, the pass // when this pass declines the source. Because identity blits run here, the pass
// must copy exactly wherever GL says the blit is a copy: an unscaled blit samples // must copy exactly wherever GL says the blit is a copy: an unscaled blit samples
@@ -2179,7 +2190,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// P7 wave 2-B2, CONTRACT-P7 §3.2: WHAT IS LEFT HERE IS A MULTISAMPLE DESTINATION. // P7 wave 2-B2, CONTRACT-P7 §3.2: WHAT IS LEFT HERE IS A MULTISAMPLE DESTINATION.
// Every blit that resolves a multisample SOURCE into a single-sample destination // Every blit that resolves a multisample SOURCE into a single-sample destination
// returned above - the depth/stencil arm at the top of this lambda through // returned above - the depth/stencil arm at the top of this lambda through
// ResolveWireDepthStencil, the colour arm below it through its resolve scratch - // ResolveWireDepthStencil, the color arm below it through its resolve scratch -
// and both of those now carry their own scale and flip. So the samples of the two // and both of those now carry their own scale and flip. So the samples of the two
// sides can only differ here by the DESTINATION being multisampled, which is the // sides can only differ here by the DESTINATION being multisampled, which is the
// shape this arm declines rather than the one it performs. // shape this arm declines rather than the one it performs.
@@ -2423,8 +2434,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
const Bool depthArmTakesShape = const Bool depthArmTakesShape =
planar && layersInRange && (props.optimalTilingFeatures & depthPassFeatures) == depthPassFeatures; planar && layersInRange && (props.optimalTilingFeatures & depthPassFeatures) == depthPassFeatures;
// The colour arm's shape (the two shader shapes are explained at the colour arm below). // The color arm's shape (the two shader shapes are explained at the color arm below).
const Bool colour = resource->aspect == VK_IMAGE_ASPECT_COLOR_BIT; const Bool color = resource->aspect == VK_IMAGE_ASPECT_COLOR_BIT;
// The shader arm samples through a sampler2D and writes a vec4, which is what // The shader arm samples through a sampler2D and writes a vec4, which is what
// BlitWireColorImage's own float check enforces; naming it here rather than letting the // BlitWireColorImage's own float check enforces; naming it here rather than letting the
// blit return false is what puts the reason in the decline line. // blit return false is what puts the reason in the decline line.
@@ -2434,15 +2445,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
forcedShaderTier < 2 && (resource->usageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0; forcedShaderTier < 2 && (resource->usageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0;
const Bool viaScratch = const Bool viaScratch =
!attachable && renderableFormat && (resource->usageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0; !attachable && renderableFormat && (resource->usageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0;
const Bool colourArmTakesShape = const Bool colorArmTakesShape = color && planar && layersInRange && sampledFloat && (attachable || viaScratch);
colour && planar && layersInRange && sampledFloat && (attachable || viaScratch);
// P8-F: `mipmap-shader-format-or-shape` IS A NAMED DECLINE, NOT A FATAL (rule I (a)). // P8-F: `mipmap-shader-format-or-shape` IS A NAMED DECLINE, NOT A FATAL (rule I (a)).
// The monolith arm has always answered this chain with a decline (VulkanRenderer.cpp's // The monolith arm has always answered this chain with a decline (VulkanRenderer.cpp's
// GenerateMipmap: a format without blit support is skipped and the chain left unwritten), // GenerateMipmap: a format without blit support is skipped and the chain left unwritten),
// and a shape no shader arm takes is a coverage gap in this backend, not a broken record. // and a shape no shader arm takes is a coverage gap in this backend, not a broken record.
// The leading word stays, so every device-log grep keyed on it still finds the line. // The leading word stays, so every device-log grep keyed on it still finds the line.
const Bool shaderArmTakesShape = const Bool shaderArmTakesShape =
resource->aspect == VK_IMAGE_ASPECT_DEPTH_BIT ? depthArmTakesShape : colourArmTakesShape; resource->aspect == VK_IMAGE_ASPECT_DEPTH_BIT ? depthArmTakesShape : colorArmTakesShape;
if (!nativeBlit && !shaderArmTakesShape) { if (!nativeBlit && !shaderArmTakesShape) {
const String detail = const String detail =
MagmaWireShapeDetail("mipmap-shader-format-or-shape", format, resource->format, props, resource->aspect, MagmaWireShapeDetail("mipmap-shader-format-or-shape", format, resource->format, props, resource->aspect,
@@ -2459,7 +2469,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// P7 wave 2-B, CONTRACT-P7 §5.1 (A): THE DEPTH ARM, and it is first because a // P7 wave 2-B, CONTRACT-P7 §5.1 (A): THE DEPTH ARM, and it is first because a
// depth format without BLIT_DST is the ordinary case, not the exotic one - // depth format without BLIT_DST is the ordinary case, not the exotic one -
// VK_FORMAT_FEATURE_BLIT_DST is optional for depth and plenty of devices decline // VK_FORMAT_FEATURE_BLIT_DST is optional for depth and plenty of devices decline
// it. Before this package the whole branch below was colour-only, so such a // it. Before this package the whole branch below was color-only, so such a
// texture reached `mipmap-shader-format-or-shape` on its very first disjunction // texture reached `mipmap-shader-format-or-shape` on its very first disjunction
// while the monolith arm quietly generated the chain with a shader. // while the monolith arm quietly generated the chain with a shader.
// //
@@ -2511,7 +2521,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// TWO SHADER SHAPES, not one. The arm that was here rendered STRAIGHT INTO the // TWO SHADER SHAPES, not one. The arm that was here rendered STRAIGHT INTO the
// next level, which needs the image to have been created with // next level, which needs the image to have been created with
// COLOR_ATTACHMENT usage - and a texture whose format the device refuses as a // COLOR_ATTACHMENT usage - and a texture whose format the device refuses as a
// colour attachment never gets that bit (VkTextureManager.cpp:2156-2160), so // color attachment never gets that bit (VkTextureManager.cpp:2156-2160), so
// every sampled-only texture without BLIT support fell into the refusal rather // every sampled-only texture without BLIT support fell into the refusal rather
// than into a fallback. When the FORMAT can be rendered and only the IMAGE // than into a fallback. When the FORMAT can be rendered and only the IMAGE
// cannot, an owned scratch attachment of the destination level's size renders // cannot, an owned scratch attachment of the destination level's size renders
@@ -2519,7 +2529,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the texels still come from GPU contents rather than from a staged shadow. // the texels still come from GPU contents rather than from a staged shadow.
// //
// Which of the two (`attachable` or `viaScratch`), and whether either takes the // Which of the two (`attachable` or `viaScratch`), and whether either takes the
// shape at all, was decided before the growth (colourArmTakesShape, P8-F). // shape at all, was decided before the growth (colorArmTakesShape, P8-F).
for (Uint32 level = base + 1; level < base + count; ++level) { for (Uint32 level = base + 1; level < base + count; ++level) {
for (Uint32 layer = baseLayer; layer < baseLayer + layerCount; ++layer) { for (Uint32 layer = baseLayer; layer < baseLayer + layerCount; ++layer) {
WireImage source{}; WireImage source{};
@@ -13,7 +13,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// GL shader compiler. It is WireDepthMipmap.cpp's pass with the box filter taken out and a // GL shader compiler. It is WireDepthMipmap.cpp's pass with the box filter taken out and a
// sample index put in. // sample index put in.
// //
// WHY THIS FILE EXISTS. vkCmdResolveImage is COLOUR ONLY. The one way Vulkan resolves a depth // WHY THIS FILE EXISTS. vkCmdResolveImage is COLOR ONLY. The one way Vulkan resolves a depth
// or stencil aspect without a shader is a render pass with a depth/stencil resolve attachment, // or stencil aspect without a shader is a render pass with a depth/stencil resolve attachment,
// which needs VK_KHR_create_renderpass2 and VK_KHR_depth_stencil_resolve - and until this // which needs VK_KHR_create_renderpass2 and VK_KHR_depth_stencil_resolve - and until this
// package a device without either killed the session at // package a device without either killed the session at
@@ -138,7 +138,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkFramebuffer framebuffer = VK_NULL_HANDLE; VkFramebuffer framebuffer = VK_NULL_HANDLE;
VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &framebuffer)); VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &framebuffer));
// Each in-flight pass owns its descriptor set, for the colour blit's reason: updating a // Each in-flight pass owns its descriptor set, for the color blit's reason: updating a
// cached set would rewrite the source view of an older queued pass. // cached set would rewrite the source view of an older queued pass.
VkDescriptorPool descriptorPool = VK_NULL_HANDLE; VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
VkDescriptorSet descriptorSet = VK_NULL_HANDLE; VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
@@ -37,7 +37,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Every private placeholder is its own vkCreateImage + vkAllocateMemory, freed only at Shutdown. // Every private placeholder is its own vkCreateImage + vkAllocateMemory, freed only at Shutdown.
// (shape, unit) bounds them by shapes x kMGPipeMaxImageUnits, and a peer rotating formats and // (shape, unit) bounds them by shapes x kMGPipeMaxImageUnits, and a peer rotating formats and
// targets across the units could still walk that towards maxMemoryAllocationCount (4096 is the // targets across the units could still walk that toward maxMemoryAllocationCount (4096 is the
// spec minimum). So past this many, a NEW (shape, unit) takes its shape's SHARED placeholder - the // spec minimum). So past this many, a NEW (shape, unit) takes its shape's SHARED placeholder - the
// pre-fix aliasing, logged once by the caller - instead of another allocation; a unit that already // pre-fix aliasing, logged once by the caller - instead of another allocation; a unit that already
// has its private placeholder keeps it. Two per unit is far past any real program. // has its private placeholder keeps it. Two per unit is far past any real program.
@@ -9,7 +9,7 @@
#include "VulkanRendererInternal.h" #include "VulkanRendererInternal.h"
namespace MobileGL::MG_Backend::DirectVulkan { namespace MobileGL::MG_Backend::DirectVulkan {
// SHARED IMAGES (docs/archive/notes/anland/plan-ahb-dmabuf.md). The present side's copy is // SHARED IMAGES. The present side's copy is
// WireFramebuffer.cpp's readback, into an image: the same flush, the same write-visibility // WireFramebuffer.cpp's readback, into an image: the same flush, the same write-visibility
// barrier, the same transition of the default framebuffer's image. // barrier, the same transition of the default framebuffer's image.
// //
@@ -15,9 +15,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// texture's image: the texture keeps its own RGBA8 level 0 (VkTextureManager::SyncWireTextureShape) // texture's image: the texture keeps its own RGBA8 level 0 (VkTextureManager::SyncWireTextureShape)
// and, at its first use in each frame, the image is CONVERTED into it here - the image's // and, at its first use in each frame, the image is CONVERTED into it here - the image's
// AHardwareBuffer imported with its external format, sampled through an immutable sampler that // AHardwareBuffer imported with its external format, sampled through an immutable sampler that
// carries a VkSamplerYcbcrConversion built from the import's colour hints, one triangle over the // carries a VkSamplerYcbcrConversion built from the import's color hints, one triangle over the
// target. Zero CPU copies for an image this server allocated; a foreign one (a video decoder's // target. Zero CPU copies for an image this server allocated; a foreign one (a video decoder's
// buffer) is first brought up to date by the registry's labelled CPU copy (RefreshForeign). // buffer) is first brought up to date by the registry's labeled CPU copy (RefreshForeign).
// //
// Why converted rather than sampled in place: a Y'CbCr conversion is an immutable sampler, which // Why converted rather than sampled in place: a Y'CbCr conversion is an immutable sampler, which
// lives in the descriptor set layout and therefore in every application pipeline that samples the // lives in the descriptor set layout and therefore in every application pipeline that samples the
@@ -154,7 +154,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// ---- the texture's import of the buffer, and the pipeline its conversion needs ---- // ---- the texture's import of the buffer, and the pipeline its conversion needs ----
auto* source = static_cast<WireYuvSource*>(resource.yuvSource.get()); auto* source = static_cast<WireYuvSource*>(resource.yuvSource.get());
if (source != nullptr && !(source->hints == hints)) { if (source != nullptr && !(source->hints == hints)) {
// Re-imported with other colour hints: the conversion (and so the view) changes. // Re-imported with other color hints: the conversion (and so the view) changes.
auto retired = MakeUnique<VkTextureManager::TextureResource>(); auto retired = MakeUnique<VkTextureManager::TextureResource>();
retired->yuvSource = std::move(resource.yuvSource); retired->yuvSource = std::move(resource.yuvSource);
WireRetiredObjects objects{}; WireRetiredObjects objects{};
+2 -2
View File
@@ -85,7 +85,7 @@ namespace MobileGL::MG_Backend {
// (VkBufferManager::RegisterWireResourceOps, VkBufferManager.cpp): under a transport it // (VkBufferManager::RegisterWireResourceOps, VkBufferManager.cpp): under a transport it
// owns buffer stores and claims bit 7 (the P4a mask, see ConsumedSubsystemsFor below); // owns buffer stores and claims bit 7 (the P4a mask, see ConsumedSubsystemsFor below);
// under monolith it registers none and its buffers keep the legacy BufferBackendOps path. // under monolith it registers none and its buffers keep the legacy BufferBackendOps path.
// P5e (docs/archive/contracts/CONTRACT-P5E.md §6.2). THE ONE CONSTANT THE WHOLE PHASE HANGS ON. // P5e (§6.2). THE ONE CONSTANT THE WHOLE PHASE HANGS ON.
// //
// kCapRunAheadApply says "this server applies an unbarriered record without reading // kCapRunAheadApply says "this server applies an unbarriered record without reading
// client memory". That is only true once EVERY per-draw family reads records instead // client memory". That is only true once EVERY per-draw family reads records instead
@@ -240,7 +240,7 @@ namespace MobileGL::MG_Backend {
// a bit derived from `ActiveBackendType == ...` would keep claiming the capability // a bit derived from `ActiveBackendType == ...` would keep claiming the capability
// for a build, a flavour or a future backend whose table does not carry the arm - // for a build, a flavour or a future backend whose table does not carry the arm -
// which is exactly how 66 uploads were lost to a consumer bit that nobody could // which is exactly how 66 uploads were lost to a consumer bit that nobody could
// have honoured. `g_resourceOpsAtStep2` is the pointer this function just pinned, // have honored. `g_resourceOpsAtStep2` is the pointer this function just pinned,
// and step 5 already refuses a run where that pointer was swapped underneath us, so // and step 5 already refuses a run where that pointer was swapped underneath us, so
// reading the arm off it means the published bit and the table that has to answer // reading the arm off it means the published bit and the table that has to answer
// for it cannot come apart. // for it cannot come apart.
+45 -62
View File
@@ -6,13 +6,14 @@
// SPDX-License-Identifier: LGPL-3.0-only // SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header // End of Source File Header
// The backend-side half of the PipeInputs block: the poison Fatal with its verb name, the // The backend-side half of the PipeInputs block: the poison Fatal with its verb name and -
// name lookups the runtime knobs need, and - in a verify build - the per-field equality, // in a verify build - the per-field equality, the entry comparator, the corruption injector,
// the entry comparator and the corruption injector. Compiled only under MOBILEGL_PIPE_PUSH // the name lookups its knob and the poison omission need, and the omission's server half.
// (CMakeLists.txt appends it to SOURCE_FILES there), so the pull build never sees it. Spells // Spells no MG_State global: everything that reads the live context lives in
// no MG_State global: everything that reads the live context lives in MG_Impl/Pipe/PipeFill.cpp. // MG_Impl/Pipe/PipeFill.cpp.
#include <MG_Backend/MGPipe/PipeInputs.h> #include <MG_Backend/MGPipe/PipeInputs.h>
#include <atomic>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
@@ -29,22 +30,6 @@ namespace MobileGL::MG_Pipe {
MGPipeInputPoisonFatal(field, MGPipeVerbName(verb)); MGPipeInputPoisonFatal(field, MGPipeVerbName(verb));
} }
Optional<MGPipeInputField> MGPipeFindInputField(const char* name) {
if (name == nullptr) return std::nullopt;
for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) {
if (std::strcmp(kMGPipeInputFieldNames[i], name) == 0) return static_cast<MGPipeInputField>(i);
}
return std::nullopt;
}
Optional<MGPipeVerb> MGPipeFindVerb(const char* name) {
if (name == nullptr) return std::nullopt;
for (SizeT i = 0; i < kMGPipeVerbCount; ++i) {
if (std::strcmp(kMGPipeVerbNames[i], name) == 0) return static_cast<MGPipeVerb>(i);
}
return std::nullopt;
}
// The stamp is the poison's (m_filled, the generations): a build without MOBILEGL_PIPE_POISON // The stamp is the poison's (m_filled, the generations): a build without MOBILEGL_PIPE_POISON
// compiles no read check that could ask any of this. Every transport build has the poison. // compiles no read check that could ask any of this. Every transport build has the poison.
#if MOBILEGL_PIPE_POISON #if MOBILEGL_PIPE_POISON
@@ -55,46 +40,24 @@ namespace MobileGL::MG_Pipe {
Uint64 g_residualPulls = 0; Uint64 g_residualPulls = 0;
#if MOBILEGL_PIPE_VERIFY #if MOBILEGL_PIPE_VERIFY
// ---- MOBILEGL_PIPE_POISON_OMIT, server half ---- // ---- negative control B, server half (MGPipeServerSetPoisonOmission) ----
// //
// The knob withholds the stamp of one (verb, field) pair, a negative control for the // The omission withholds the stamp of one (verb, field) pair, a negative control for the
// poison check. Under a transport the backend reads the server's stamp, which re-stamps // poison check. Under a transport the backend reads the server's stamp, which re-stamps
// every field the verb's class can answer, so the server must withhold the pair too or // every field the verb's class can answer, so the server must withhold the pair too or
// the control cannot go red. Parsed here because this file is in the server library and // the control cannot go red. The harness arms it while the apply thread is already
// MG_Impl is not; a malformed value is the client's Fatal{PipeVerifyBadKnob}, and this // running, so the pair is ONE atomic word: the verb in the high half, the field in the
// side simply does not arm. Verify builds only, so the shipped stamp stays branch-free. // low one, and no verb at all (0xffff) until something is armed. Verify builds only, so
struct ServerPoisonOmission { // the shipped stamp stays branch-free.
Bool Parsed = false; constexpr Uint32 kNoServerOmission = ~Uint32{0};
String Value; std::atomic<Uint32> g_serverOmission{kNoServerOmission};
Bool Armed = false;
MGPipeVerb Verb = MGPipeVerb::kVerbCount;
MGPipeInputField Field = MGPipeInputField::kFieldCount;
};
ServerPoisonOmission g_serverOmission;
const ServerPoisonOmission& ServerOmission() { constexpr Uint32 PackServerOmission(MGPipeVerb verb, MGPipeInputField field) {
const String& knob = MG_Config::Features.PipePoisonOmit; return (static_cast<Uint32>(verb) << 16) | static_cast<Uint32>(field);
if (g_serverOmission.Parsed && knob.size() == g_serverOmission.Value.size() &&
(knob.empty() || knob == g_serverOmission.Value)) {
return g_serverOmission;
}
g_serverOmission = ServerPoisonOmission{};
g_serverOmission.Parsed = true;
g_serverOmission.Value = knob;
const auto colon = knob.find(':');
if (colon == String::npos || colon == 0 || colon + 1 >= knob.size()) return g_serverOmission;
const auto verb = MGPipeFindVerb(knob.substr(0, colon).c_str());
const auto field = MGPipeFindInputField(knob.substr(colon + 1).c_str());
if (!verb || !field) return g_serverOmission;
g_serverOmission.Armed = true;
g_serverOmission.Verb = *verb;
g_serverOmission.Field = *field;
return g_serverOmission;
} }
Bool ServerOmitsStamp(MGPipeVerb verb, MGPipeInputField field) { Bool ServerOmitsStamp(MGPipeVerb verb, MGPipeInputField field) {
const ServerPoisonOmission& omission = ServerOmission(); return g_serverOmission.load(std::memory_order_acquire) == PackServerOmission(verb, field);
return omission.Armed && omission.Verb == verb && omission.Field == field;
} }
#endif // MOBILEGL_PIPE_VERIFY #endif // MOBILEGL_PIPE_VERIFY
@@ -390,11 +353,11 @@ namespace MobileGL::MG_Pipe {
void MGPipeServerBlockNoteIdentity() { void MGPipeServerBlockNoteIdentity() {
// D10: THE WIDER PREDICATE, and the narrow one was a latent crash rather than a missing // D10: THE WIDER PREDICATE, and the narrow one was a latent crash rather than a missing
// optimisation. Gated on the REHEARSAL, this early-returned for the whole life of a // optimization. Gated on the REHEARSAL, this early-returned for the whole life of a
// spawn server - because that knob is off by default and nothing in the spawn shape // spawn server - because that knob is off by default and nothing in the spawn shape
// turns it on - so the server's gPipeInputs->ContextIdentity() stayed nullptr forever. // turns it on - so the server's gPipeInputs->ContextIdentity() stayed nullptr forever.
// DirectGLES' fb-slot memo compares that identity FIRST and with no generation, so a // DirectGLES' fb-slot memo compares that identity FIRST and with no generation, so a
// nullptr against its own nullptr initialiser reads as a CACHE HIT and hands out a slot // nullptr against its own nullptr initializer reads as a CACHE HIT and hands out a slot
// that was never filled. `st` lands the named Fatal at that site; this is the gate that // that was never filled. `st` lands the named Fatal at that site; this is the gate that
// stops it being reachable in the first place. // stops it being reachable in the first place.
if (!MGPipeBlocksAreDistinct()) return; if (!MGPipeBlocksAreDistinct()) return;
@@ -403,7 +366,7 @@ namespace MobileGL::MG_Pipe {
// served-context clock: MGPipeApplierContextSerial moves exactly when the served // served-context clock: MGPipeApplierContextSerial moves exactly when the served
// context does (MGPipeApplierReset), which is all the backends' per-context memo keys // context does (MGPipeApplierReset), which is all the backends' per-context memo keys
// ask of an identity. The odd-encoding keeps the token non-null at serial 0: a null // ask of an identity. The odd-encoding keeps the token non-null at serial 0: a null
// identity reads as a hit against DirectGLES' zero-initialised fb-slot memo cache and // identity reads as a hit against DirectGLES' zero-initialized fb-slot memo cache and
// hands out a null slot - an unnamed crash where the rehearsal exists to produce a // hands out a null slot - an unnamed crash where the rehearsal exists to produce a
// named one. // named one.
const Uint64 serial = MGPipeApplierContextSerial(); const Uint64 serial = MGPipeApplierContextSerial();
@@ -453,12 +416,10 @@ namespace MobileGL::MG_Pipe {
filled.FilledGen[i] = serial & (Uint64{0} - bit); filled.FilledGen[i] = serial & (Uint64{0} - bit);
} }
#if MOBILEGL_PIPE_VERIFY #if MOBILEGL_PIPE_VERIFY
// Negative control B's server half (ServerPoisonOmission above): the one withheld pair. // Negative control B's server half (g_serverOmission above): the one withheld pair.
{ {
const ServerPoisonOmission& omission = ServerOmission(); const Uint32 omission = g_serverOmission.load(std::memory_order_acquire);
if (omission.Armed && omission.Verb == verb) { if ((omission >> 16) == static_cast<Uint32>(verb)) filled.FilledGen[omission & 0xffffu] = 0;
filled.FilledGen[static_cast<SizeT>(omission.Field)] = 0;
}
} }
#endif #endif
MGPipeStampAccess::SetServerStamped(inputs, true); MGPipeStampAccess::SetServerStamped(inputs, true);
@@ -475,6 +436,12 @@ namespace MobileGL::MG_Pipe {
MGPipeStampAccess::SetServerStamped(gPipeInputs, false); MGPipeStampAccess::SetServerStamped(gPipeInputs, false);
} }
#if MOBILEGL_PIPE_VERIFY
void MGPipeServerSetPoisonOmission(MGPipeVerb verb, MGPipeInputField field) {
g_serverOmission.store(PackServerOmission(verb, field), std::memory_order_release);
}
#endif
Uint64 MGPipeResidualPullCount() { Uint64 MGPipeResidualPullCount() {
return g_residualPulls; return g_residualPulls;
} }
@@ -691,5 +658,21 @@ namespace MobileGL::MG_Pipe {
return true; return true;
}); });
} }
Optional<MGPipeInputField> MGPipeFindInputField(const char* name) {
if (name == nullptr) return std::nullopt;
for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) {
if (std::strcmp(kMGPipeInputFieldNames[i], name) == 0) return static_cast<MGPipeInputField>(i);
}
return std::nullopt;
}
Optional<MGPipeVerb> MGPipeFindVerb(const char* name) {
if (name == nullptr) return std::nullopt;
for (SizeT i = 0; i < kMGPipeVerbCount; ++i) {
if (std::strcmp(kMGPipeVerbNames[i], name) == 0) return static_cast<MGPipeVerb>(i);
}
return std::nullopt;
}
#endif // MOBILEGL_PIPE_VERIFY #endif // MOBILEGL_PIPE_VERIFY
} // namespace MobileGL::MG_Pipe } // namespace MobileGL::MG_Pipe
+12 -11
View File
@@ -46,10 +46,6 @@ namespace MobileGL::MG_Pipe {
[[noreturn]] void MGPipeInputPoisonFatalForVerb(MGPipeInputField field, MGPipeVerb verb); [[noreturn]] void MGPipeInputPoisonFatalForVerb(MGPipeInputField field, MGPipeVerb verb);
// kMGPipeVerbNames[verb], or "<none>" for kVerbCount (no verb has been filled yet). // kMGPipeVerbNames[verb], or "<none>" for kVerbCount (no verb has been filled yet).
const char* MGPipeVerbName(MGPipeVerb verb); const char* MGPipeVerbName(MGPipeVerb verb);
// Name lookups for the runtime knobs (MOBILEGL_PIPE_VERIFY_CORRUPT names a field,
// MOBILEGL_PIPE_POISON_OMIT a Verb:Field pair). Empty on an unknown name.
Optional<MGPipeInputField> MGPipeFindInputField(const char* name);
Optional<MGPipeVerb> MGPipeFindVerb(const char* name);
// The server-side read check (ARCHITECTURE.md §9.2.1). Inside a server-stamped verb // The server-side read check (ARCHITECTURE.md §9.2.1). Inside a server-stamped verb
// (PipeInputs::ServerStampedVerb) a stale read is judged by the field's ownership class: // (PipeInputs::ServerStampedVerb) a stale read is judged by the field's ownership class:
@@ -231,7 +227,7 @@ namespace MobileGL::MG_Pipe {
// such a verb is a BARRIER_PULLED read counted rather than Fatal, and only there is a // such a verb is a BARRIER_PULLED read counted rather than Fatal, and only there is a
// sticky forward a residual pull. PipeApplier::LeaveApplier must clear it after every // sticky forward a residual pull. PipeApplier::LeaveApplier must clear it after every
// record: a server process has no client half to do it, so the flag would stay set and // record: a server process has no client half to do it, so the flag would stay set and
// later reads (InvalidateCompileEnv during another context's backend initialisation) // later reads (InvalidateCompileEnv during another context's backend initialization)
// would be judged against the last verb's mask. // would be judged against the last verb's mask.
Bool ServerStampedVerb() const { return m_serverStampedVerb; } Bool ServerStampedVerb() const { return m_serverStampedVerb; }
@@ -626,7 +622,7 @@ namespace MobileGL::MG_Pipe {
// them with handle tables and callbacks. // them with handle tables and callbacks.
// They carry no MGP_INPUT_CHECK / MGP_INPUT_VERIFY_READ (the declared exception to // They carry no MGP_INPUT_CHECK / MGP_INPUT_VERIFY_READ (the declared exception to
// P1 brief D4's "every accessor body"): a forward is a live call, not a stored value, // P1 brief D4's "every accessor body"): a forward is a live call, not a stored value,
// and InvalidateCompileEnv is reached from backend initialisation before any verb has // and InvalidateCompileEnv is reached from backend initialization before any verb has
// filled, where a check would be Fatal{...@<none>} on every start. Their sticky stamp // filled, where a check would be Fatal{...@<none>} on every start. Their sticky stamp
// is therefore consulted by no accessor; the tests pin it through // is therefore consulted by no accessor; the tests pin it through
// MGPipeInputFieldIsFresh directly. // MGPipeInputFieldIsFresh directly.
@@ -693,7 +689,7 @@ namespace MobileGL::MG_Pipe {
// MGPipeServerSetContextLive's flag. In the block, not in a process global, because the // MGPipeServerSetContextLive's flag. In the block, not in a process global, because the
// in-process display server runs several sessions at once and each has its own served // in-process display server runs several sessions at once and each has its own served
// context: one session's teardown or eglMakeCurrent(NO_CONTEXT) must not mark a // context: one session's teardown or eglMakeCurrent(NO_CONTEXT) must not mark a
// neighbour's context dead mid-frame (P14; the per-thread block resolution below is // neighbor's context dead mid-frame (P14; the per-thread block resolution below is
// what makes this the calling session's). // what makes this the calling session's).
Bool m_serverContextLive = false; Bool m_serverContextLive = false;
MGPipeVerb m_currentVerb = MGPipeVerb::kVerbCount; MGPipeVerb m_currentVerb = MGPipeVerb::kVerbCount;
@@ -900,7 +896,7 @@ namespace MobileGL::MG_Pipe {
// from MGPipeApplierContextSerial() - the server's own served-context clock, which is what // from MGPipeApplierContextSerial() - the server's own served-context clock, which is what
// moves when the served context does. Without it the server block's ContextIdentity() would // moves when the served context does. Without it the server block's ContextIdentity() would
// stay nullptr, and DirectGLES' fb-slot memo cache compares identity FIRST (a nullptr // stay nullptr, and DirectGLES' fb-slot memo cache compares identity FIRST (a nullptr
// against its own nullptr initialiser reads as a hit and hands out a null slot): an // against its own nullptr initializer reads as a hit and hands out a null slot): an
// unnamed crash where the rehearsal exists to produce a named one. A no-op with the // unnamed crash where the rehearsal exists to produce a named one. A no-op with the
// rehearsal off, so the client's per-verb SetIdentity keeps owning the shared block there. // rehearsal off, so the client's per-verb SetIdentity keeps owning the shared block there.
void MGPipeServerBlockNoteIdentity(); void MGPipeServerBlockNoteIdentity();
@@ -965,9 +961,7 @@ namespace MobileGL::MG_Pipe {
// PLAIN, NOT ATOMIC, AND THAT IS THE SAME RULING TABLE 3 MAKES FOR gPipeInputS ITSELF // PLAIN, NOT ATOMIC, AND THAT IS THE SAME RULING TABLE 3 MAKES FOR gPipeInputS ITSELF
// (CONTRACT-P5.md section 4): the verb barrier leaves at most one of {GL thread, apply // (CONTRACT-P5.md section 4): the verb barrier leaves at most one of {GL thread, apply
// thread} runnable, so there is one writer at any instant. This counter, m_serverStampedVerb // thread} runnable, so there is one writer at any instant. This counter, m_serverStampedVerb
// and FilledGen[] all rest on that and on nothing else - so MOBILEGL_IPC_VERB_BARRIER=0, // and FilledGen[] all rest on that and on nothing else.
// R-1's negative control, is a data race on all three as well as the correctness failure it
// is there to show. It is expected to be red; it is not expected to be meaningful.
Uint64 MGPipeResidualPullCount(); Uint64 MGPipeResidualPullCount();
void MGPipeResetResidualPullCountForTesting(); void MGPipeResetResidualPullCountForTesting();
@@ -998,6 +992,13 @@ namespace MobileGL::MG_Pipe {
// dereferenced, the snapshot is only ever compared). Returns false for a forwarded field, // dereferenced, the snapshot is only ever compared). Returns false for a forwarded field,
// which has nothing to corrupt. // which has nothing to corrupt.
Bool MGPipeApplyVerifyCorruption(PipeInputs& snapshot, MGPipeInputField field); Bool MGPipeApplyVerifyCorruption(PipeInputs& snapshot, MGPipeInputField field);
// PipeInputs.cpp. Name lookups for MOBILEGL_PIPE_VERIFY_CORRUPT (a field) and
// MGPipeSetPoisonOmission (a verb and a field). Empty on an unknown name.
Optional<MGPipeInputField> MGPipeFindInputField(const char* name);
Optional<MGPipeVerb> MGPipeFindVerb(const char* name);
// PipeInputs.cpp. Negative control B's server half, armed by MGPipeSetPoisonOmission: the
// server's verb stamp withholds the stamp of `field` at `verb` too.
void MGPipeServerSetPoisonOmission(MGPipeVerb verb, MGPipeInputField field);
// PipeFill.cpp. Writes the MOBILEGL_PIPE_VERIFY_FATAL=0 summary line ("N divergence(s) // PipeFill.cpp. Writes the MOBILEGL_PIPE_VERIFY_FATAL=0 summary line ("N divergence(s)
// survived") NOW, while the role's log file is still open. MobileGL::Destroy calls it right // survived") NOW, while the role's log file is still open. MobileGL::Destroy calls it right
// before MG_Util::Debug::Close(): the line used to come from a namespace-scope static's // before MG_Util::Debug::Close(): the line used to come from a namespace-scope static's
@@ -98,9 +98,9 @@ namespace MobileGL::MG_Record {
break; break;
default: default:
// A value class outside the three is a wire fault, not a fallback: all three // A value class outside the three is a wire fault, not a fallback: all three
// representations of a clear colour are numerically populated by the frontend // representations of a clear color are numerically populated by the frontend
// and only this field says which one the backend must use, so guessing renders // and only this field says which one the backend must use, so guessing renders
// a plausible wrong colour. // a plausible wrong color.
MG_Pipe::MGPipeProtocolFatalAt("MGPClear::ValueClass", clear.ValueClass, 3); MG_Pipe::MGPipeProtocolFatalAt("MGPClear::ValueClass", clear.ValueClass, 3);
} }
break; break;
@@ -227,7 +227,7 @@ namespace MobileGL::MG_Record {
// scatters it per its own GL_PACK_* state, so a DstSize that disagrees with the tight // scatters it per its own GL_PACK_* state, so a DstSize that disagrees with the tight
// extent (a client that sized its destination with pack padding) is logged and the tight // extent (a client that sized its destination with pack padding) is logged and the tight
// extent this side owns is read and posted - never the client's number, so a wrong // extent this side owns is read and posted - never the client's number, so a wrong
// DstSize cannot make this a short read into uninitialised scratch. F2's first PH-3 draft // DstSize cannot make this a short read into uninitialized scratch. F2's first PH-3 draft
// refused the mismatch instead, which broke the ID-49 control // refused the mismatch instead, which broke the ID-49 control
// (ServerLoopEglTest.AReadPixelsReplyIsTheTightExtentWhateverDstSizeTheClientSent); the // (ServerLoopEglTest.AReadPixelsReplyIsTheTightExtentWhateverDstSizeTheClientSent); the
// bound above is what PH-3 needs and it does not depend on DstSize. // bound above is what PH-3 needs and it does not depend on DstSize.
@@ -301,7 +301,7 @@ namespace MobileGL::MG_Record {
return false; return false;
} }
// ---- P9 (W1, docs/archive/contracts/CONTRACT-P9.md §1): the pack-buffer readbacks ------------------------ // ---- P9 (W1 §1): the pack-buffer readbacks ------------------------
// //
// WHY THE ROWS GO THROUGH THE APPLIER'S BUFFER-WRITE GATE AND NOT THROUGH THE DRIVER'S OWN // WHY THE ROWS GO THROUGH THE APPLIER'S BUFFER-WRITE GATE AND NOT THROUGH THE DRIVER'S OWN
// PBO READ. Monolith binds the driver buffer as GL_PIXEL_PACK_BUFFER and lets glReadPixels // PBO READ. Monolith binds the driver buffer as GL_PIXEL_PACK_BUFFER and lets glReadPixels
@@ -504,7 +504,7 @@ namespace MobileGL::MG_Record {
slot); slot);
} }
// P5b d1 (docs/archive/contracts/CONTRACT-P5B.md §2 d1): draw_vbo's whole cross product. The record // P5b d1 (§2 d1): draw_vbo's whole cross product. The record
// carries the GL call verbatim (rule D) and this body reproduces the backend call the // carries the GL call verbatim (rule D) and this body reproduces the backend call the
// monolith makes for that shape, reading only the record and the backend's own // monolith makes for that shape, reading only the record and the backend's own
// barrier-pulled state; the twenty GL entry points collapse onto the arms below: // barrier-pulled state; the twenty GL entry points collapse onto the arms below:
@@ -815,7 +815,7 @@ namespace MobileGL::MG_Record {
} }
// ----------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------
// P5b: the stubs the four migration packages replace (docs/archive/contracts/CONTRACT-P5B.md). // P5b: the stubs the four migration packages replace.
// //
// Each dies by the GL slot's own name. The record has crossed and been validated by the // Each dies by the GL slot's own name. The record has crossed and been validated by the
// codec by the time one of these runs, so the only thing missing is the backend call, and // codec by the time one of these runs, so the only thing missing is the backend call, and
@@ -824,7 +824,7 @@ namespace MobileGL::MG_Record {
// in the same words), the call, and a tally the lane can assert moved. // in the same words), the call, and a tally the lane can assert moved.
// ----------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------
// ---- i1 ---- (docs/archive/contracts/CONTRACT-P5B.md §2 i1; landed by package p5b/i1) // ---- i1 ---- (§2 i1; landed by package p5b/i1)
// //
// RULE D IN FIVE BODIES. Each reproduces the backend call the monolith makes, from the // RULE D IN FIVE BODIES. Each reproduces the backend call the monolith makes, from the
// record and from server state, and NOTHING ELSE: the backend goes on reading the frontend // record and from server state, and NOTHING ELSE: the backend goes on reading the frontend
@@ -974,7 +974,7 @@ namespace MobileGL::MG_Record {
return true; return true;
} }
// ---- t2 ---- (docs/archive/contracts/CONTRACT-P5B.md §2 t2) // ---- t2 ---- (§2 t2)
// //
// SIX BODIES, SIX BACKEND CALLS, NO STATE OF THEIR OWN. Rule D: the record IS the call, and // SIX BODIES, SIX BACKEND CALLS, NO STATE OF THEIR OWN. Rule D: the record IS the call, and
// everything the backend reads around it - the capture program, the capture-buffer // everything the backend reads around it - the capture program, the capture-buffer
+1 -1
View File
@@ -43,7 +43,7 @@ namespace MobileGL::MG_Record {
Bool OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges, Bool OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges,
const MG_Pipe::MGHostSpan* userIndices, const MG_Pipe::MGPDrawIndirect* indirect) override; const MG_Pipe::MGHostSpan* userIndices, const MG_Pipe::MGPDrawIndirect* indirect) override;
// ---- P5b (docs/archive/contracts/CONTRACT-P5B.md): one override per row a migration package owns. // ---- P5b: one override per row a migration package owns.
// At the contract commit EVERY BODY BELOW IS A STUB that dies // At the contract commit EVERY BODY BELOW IS A STUB that dies
// Fatal{UnmigratedVerb, "<GL slot>"} by the slot's own name - the same line the client's // Fatal{UnmigratedVerb, "<GL slot>"} by the slot's own name - the same line the client's
// class-C table raises and the census greps - so a client flipped ahead of its server // class-C table raises and the census greps - so a client flipped ahead of its server
+2 -2
View File
@@ -22,7 +22,7 @@
// INVALIDATE_RANGE clobbered GPU-written data - an SSBO counter beside the app's SubData - with // INVALIDATE_RANGE clobbered GPU-written data - an SSBO counter beside the app's SubData - with
// stale shadow bytes (FlushPendingRangesFrom, ManagersBuffers.cpp). Tier 1's INVALIDATE_RANGE is an ASSERTION that // stale shadow bytes (FlushPendingRangesFrom, ManagersBuffers.cpp). Tier 1's INVALIDATE_RANGE is an ASSERTION that
// the old bytes are dead, so declaring bytes covered that nothing staged is not a missing // the old bytes are dead, so declaring bytes covered that nothing staged is not a missing
// optimisation, it is a silent data loss. The coverage set below is therefore exact, and a // optimization, it is a silent data loss. The coverage set below is therefore exact, and a
// drain that reaches outside it is Fatal rather than a re-read of whatever happens to be there. // drain that reaches outside it is Fatal rather than a re-read of whatever happens to be there.
// //
// WHY IT IS A HEADER AND NOT A BLOCK INSIDE Managers.cpp. Two reasons, and the second is the // WHY IT IS A HEADER AND NOT A BLOCK INSIDE Managers.cpp. Two reasons, and the second is the
@@ -55,7 +55,7 @@ namespace MobileGL::MG_Record {
// P14: WHOSE STAGED BYTES ARE THESE? The two server stores below are one per process, but the // P14: WHOSE STAGED BYTES ARE THESE? The two server stores below are one per process, but the
// in-process display server serves several sessions in that process at once, and a session's // in-process display server serves several sessions in that process at once, and a session's
// end (DestroyEGLContext's OnBackendContextDestroyed) used to DropAll() them - every live // end (DestroyEGLContext's OnBackendContextDestroyed) used to DropAll() them - every live
// neighbour's declared texture levels and buffer shadows with it. The neighbour's next upload // neighbor's declared texture levels and buffer shadows with it. The neighbor's next upload
// into a texture it had declared long ago then read as "resource_subdata arrived before the // into a texture it had declared long ago then read as "resource_subdata arrived before the
// server declared texture level", a Fatal that took the whole display process (and so every // server declared texture level", a Fatal that took the whole display process (and so every
// session in it) down. The texture store is also keyed by the client's {slot, gen}, which two // session in it) down. The texture store is also keyed by the client's {slot, gen}, which two
@@ -61,7 +61,7 @@
// that lets the sync read - the set is complete exactly when every piece has landed, and a level // that lets the sync read - the set is complete exactly when every piece has landed, and a level
// with a hole in it is Fatal{StageSnapshotTooNarrow}: the same words as the buffer half, for the // with a hole in it is Fatal{StageSnapshotTooNarrow}: the same words as the buffer half, for the
// same reason, because those bytes have never existed on this side and inventing them is silent // same reason, because those bytes have never existed on this side and inventing them is silent
// data loss rather than a missing optimisation. THE QUESTION IS "IS THE RANGE THE READER ASKS // data loss rather than a missing optimization. THE QUESTION IS "IS THE RANGE THE READER ASKS
// FOR COVERED", not "is the level the size the client meant": no format crosses the wire, so a // FOR COVERED", not "is the level the size the client meant": no format crosses the wire, so a
// leading run that already reaches the image's high-water mark reads as complete when later // leading run that already reaches the image's high-water mark reads as complete when later
// pieces are still missing - unreachable, because nothing reads between the pieces of one verb, // pieces are still missing - unreachable, because nothing reads between the pieces of one verb,
@@ -502,7 +502,7 @@ namespace MobileGL::MG_Record {
// The calling thread's SESSION's levels (every group of it - the callers are a session's // The calling thread's SESSION's levels (every group of it - the callers are a session's
// context teardown and its applier release), or every level on a thread that is no // context teardown and its applier release), or every level on a thread that is no
// session's. A live neighbour session's declared levels are not this caller's to drop: // session's. A live neighbor session's declared levels are not this caller's to drop:
// that is the P14 Fatal StagedShadow.h's StagedBucket describes. // that is the P14 Fatal StagedShadow.h's StagedBucket describes.
void DropAll() { void DropAll() {
if (!m_any.load(std::memory_order_acquire)) return; if (!m_any.load(std::memory_order_acquire)) return;
@@ -519,7 +519,7 @@ namespace MobileGL::MG_Record {
EraseSession(m_driverWritten, session); EraseSession(m_driverWritten, session);
} }
// ---- P8-E (docs/archive/notes/p8/E.md): WHEN THESE BYTES ARE THE LEVEL'S CONTENT ---- // ---- P8-E: WHEN THESE BYTES ARE THE LEVEL'S CONTENT ----
// //
// The store holds what the client uploaded. It is also the only CPU copy of a level the // The store holds what the client uploaded. It is also the only CPU copy of a level the
// server has, so a readback the driver refuses (a format the ES driver will not attach - // server has, so a readback the driver refuses (a format the ES driver will not attach -

Some files were not shown because too many files have changed in this diff Show More