Repository navigation
Large pools held once in the residency set: segments take covering chunks' buffers, one buffer per pool past its size, a grown pool keeps it (stacked on #1407; PRAGMATA on an 8 GB Mac) - #1614
Draft
JioChoi wants to merge 88 commits into
Conversation
Originally c8f2ca85. profile_guess's Switch 2 SDK version (our dc63c283) is now on main as SDK_VERSION, so it is no longer part of this commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…key Kong Bananza One commit for the Ampere translator work that shares InstEmitAmpere*.cs (split out it would not build step by step): - FRND.F16, MUFU.TANH, LDGSTS.E.BYPASS.128 / LDGDEPBAR, F2FP.MERGE_C .H1, TXQ with a UR handle (0xf6f), CSMTEST (0x80d) never rejects; - NVN2 per-stage storage buffer banks 25..29 (mapped to c0 0x110 + 0x100 stage), bindless constant buffers (ABindlessLoad, NVK use_bindless_cbuf), UBMSK (main's BMSK in its uniform form), TXD.B; - TEX / TLD / TLD4 / TXD with the handle in a uniform register or a register, SUATOM with the handle in another bank or a register, SUST.B, bindless surfaces, images whose handle is in a uniform buffer (img_ubo, NVN_RB_IMGU), 1D integer fetches as 2D rows (tbuf_as_2d on the host); - texture handles that are words of other constant buffers: when main's rules (a c[2] word, or a texture word ORed with a sampler word of its bank, 4a0551e) find none, a second pass takes such a word alone or ORed with a sampler word of another bank (Orbitals); c[2]'s separate sampler words as main has them (either order; a sampler word alone is no candidate). Tests for the second pass in test_dmsh_ampere_mem.py; - ATOMS, ATOMS.POPC.INC, ATOMG.CAS, SURED of any operation and bank (forms with bits 80..83 or 59..60 set refused, as main's SURED refused them); main's UP2UR kept; - TXD derivative order, I2F / I2FP .RZ, FSWZADD sign, SHFL.UP clamp, the c[1] GlobalToStorage rule, BREV / UBREV, IMAD.WIDE carry out, MEMBAR scope, PIXLD.MY_INDEX (through main's 0x925 handler, which keeps its covered-pixel read), LDG reads its address before the predicated zero fill, HADD2 / HMUL2 second source, LDL / STL without the shared stride, Ampere results kept on predicated paths, NVN2 bound handles as words of the compute uniform slot (Program.cs). Originally 13ba049c, a4161f25, f62e6ebd, 0eadd2cd, 778241be, c61ec3d0, 38d05a86, 6a498a2b, 86688336, 6c567f20, 90bb7c35, a81d156d, 8aa7be8b, 53e01d73, 0a64b42e, de264f7e, d30f550c, cd8d9b74, ad44b3af, b298b203, 87aa564d, 5c825028, a91a2276. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n it; loop guard Two GPU hang defences (a Metal command buffer that never completes is not reclaimed when the process exits, and the Mac's WindowServer stops minutes later): - BarrierExits.cs: a compute thread that returns before a threadgroup_barrier keeps arriving at the barriers instead (Metal requires every thread at a barrier). - dmsh_build.py loop_guard: every loop also ends once its function has run 2^18 iterations in all (PRAGMATA's longest bounded loop: 255). Originally 19300ea8 and the shader part of 1b5c30f5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sbuf_scalar_index: a scalar load of a storage buffer whose offset is computed and bounds-checked but indexed at element 0 gets its index back (Orbitals' GPUScene upload shaders scattered every primitive to slot 0: black 3D backgrounds). Originally be8a1b4a. Our vs_layer point-size throwaway (3d04e320) is left out: main's drop_point_size (f9b438f, from PR 1261) does the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uffer addresses Main now reads the separate texture and sampler counts (+0xb4, +0xb5) and maps them to NVN's c2 tables (67f1d00, Star Fox). What this adds on top: - the push region is two 16-byte aligned blocks (+0xa9 and +0xaa words): PRAGMATA's 6,856 compute layouts add up to the size at +0xba only this way. A layout that does not add up, images (+0xb6) included, is reported with a [RED] line; - a storage buffer whose address the shader keeps in NVN2's push constants (c[24]) is read from the pushed bytes on the host. Originally 2bcc8d4b. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Symptom: in Orbitals, Niagara's sorted particle list was wrong every frame (broken beams), and single views of the split screen flashed for a frame. Cause: Ampere compilers rely on lockstep warps for small shared-memory scans with no barrier. Unreal's GPU radix sort downsweep (d032ce5090303e01) does STS, then LDS of the neighbouring lane's word, then STS. In MSL the load and the thread's own earlier store do not alias, so Metal moves the load above the store and reads another lane's old value: a Metal reproduction sorted 12 of 100 keys. Fix: dmsh_build.py tg_fence puts a simdgroup-scope memory fence (not an execution barrier, so safe in divergent code) after each store to a threadgroup array; kernels without one are unchanged (about 7.5% of Orbitals' shaders are touched). tg_spill runs after it and widens the fence to device memory for an array it moves to the spill buffer. Its declaration regex is TG_FENCE_DECL, separate from tg_spill's TG_DECL. Measured: 100 of 100 keys sorted with the fence. With only the fenced downsweep in the library the beams were right; with the fence in every shader, three 80-frame samples of stage 2 had 0 flash views (22 in 80 on the build before, which also lacked the split texture / sampler handle fix). Test: test_dmsh_tg_fence.py (CPU: the rewrite and the fence's scope after tg_spill; with the Metal compiler, both compile). Reference entry: shaders.md, Warp-synchronous shared memory without a barrier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…amed A command buffer not completed within DM_NVN_GPU_WATCHDOG seconds (default 4) ends the process before more work queues behind it (a hung command buffer is not reclaimed at exit and the Mac stops minutes later). A ring of (fence, kind, key) records every draw's and dispatch's shaders (wk_note); the watchdog and a failed command buffer print the shaders of that submission (wk_dump): a page fault names none. Originally e0440f65, 118d0f13, 544392ce. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GPU-written indirect dispatch counts are clamped (65535 per dimension, 2^22 threadgroups in all; DM_NVN_DISPATCH_CLAMP=0 off): garbage counts launched billions of threadgroups. Originally bc5cf916. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With more controllers connected than the applet's maximum, games re-open the applet forever (Orbitals' lobby). The host disconnects the extras when the applet returns. Originally 7a2ba5df. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A fence sync covers its own queue's submissions, not the other queues' (Mario Kart World submits on three; its main thread waited out each one's work). - An acquire sync waits for the texture's own last present, not everything submitted (the frame-time controller settled at 30 fps). NVN API 200 and up only. Originally faddc0b5 and ec0972a4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…age at creation Symptom: in Orbitals "texture upgraded to allow ... view format" repeated all session, about 8,000 upgrades (44,000 in a long play): each a copy into a new texture before the next pass, with the draw binding the texture as it was until then. Cause: shaders read and write R32 images in the other signedness (img_fit's r32 view), so a 32-bit integer texture needs MTLTextureUsagePixelFormatView. tex_desc gives it to R32 textures at creation, but buffer textures (textureBufferDescriptorWithPixelFormat) and linear textures (tex_linear) are made elsewhere, without it. Niagara's 128-element R32I free-ID buffer, an image buffer texture, is re-created every frame, so it was upgraded every frame. Fix: R32Sint / R32Uint buffer and linear textures get the format-view usage at creation. DM_NVN_TBUF_PFV=0 and DM_NVN_LINEAR_PFV=0 turn each off for an A/B. Measured: upgrades in an Orbitals session went from about 8,000 to 0. Test: t_tbuf_pfv (Metal validation; R32I / R32UI buffer and linear textures have the usage, R32F do not; 3 of 7 checks fail with both switches at 0). Reference entry: rendering.md, 32-bit integer buffer and linear textures re-created every frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"[RED] SyncWait timed out on fence N: committed M, completed K" after a wait of a second or more: with fence <= committed the GPU did not finish (PRAGMATA aborts on it). Originally the host part of 1b5c30f5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unreal Engine 5.6's Switch 2 platform layer calls it at start (Orbitals). The function returns a Result; upstream's hook was void, which left x0 as it was. Replaces our 14f5ee7e / 2e12bc79 / e65aa5ee, which upstream's GameChat hooks cover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…napshots, buffer rows, attributes, passes) The NVN / Metal host changes of the Orbitals (Unreal Engine 5.6) and Donkey Kong Bananza ports that share nvn_mtl.m too closely to build one by one: - same-pass feedback: a draw rendering a level or layer of a view it samples reads a snapshot of the whole view, the levels below when its sampler's LOD clamp ends below the attachment's level, or the view directly when the clamp starts above it (Donkey Kong Bananza's environment-cube prefilter read garbage at tile edges, NaN into the bloom); it replaces upstream's pinned-level views (fb_pinned_level), which it covers; uniform-held handles (TEXU) are checked for feedback too (Orbitals' smoke flicker); - a 3D texture in a 2D slot gets the zero texture; deferred zero fills drained; once-logs that stay once; a 3D texture the GPU has not written is taken from its storage even with IMAGE (Orbitals' volumetric lightmap indirection volume); - bindless depth reads resolved per draw (DM_NVN_DRAW_DSPLIT experiment); - finalized buffer textures as 2D rows keyed by storage and view, a two-way 16K rows cache, rows in the shader's pixel class, content-keyed copy caches, uniform-buffer handles read in place (DM_NVN_TEXUKEY logs them); - vertex attributes 16..30 through to the pipeline (UE 5.6 binds 17); - Unreal's deferred lights kept in one render pass (DM_NVN_RT_DSAMPLE, DM_NVN_RT_DMASK); - screenshots encoded off the present thread, the command allocator cap 8 -> 32, a 'barrier' profile key for draw-pass ordering, DM_NVN_C0DIM_NVN2=0 (measurement). Originally 9bf8cad7, 89ee018a, 5aa0fb38, e2028088 (all but its switches), 99455d13, 68a4cd14, f9036bc8, f9263147, 6c7c343a, 3c547966, 56efa046, dd370e17, 5bbb9f9b, 20ee51ed, a83f839f. The profile key for draw-pass ordering is main's draw_barriers (671c68e), not a second key named barrier: barrier_game is main's, and the README row notes the Unreal case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… scaled mode; DM_WINDOW_SMALL - A rescue's display link is made only once the window is visible (one made while macOS reported the window covered stayed near 30 Hz). - Fullscreen on a scaled mode offered only at a rate the game's does not divide takes the same size at the panel's own pixels (a 4K 75 Hz screen shows 60 fps at 60 Hz). - DM_WINDOW_SMALL=<width>: a small test window in the bottom-right corner. Originally 136705e0, 14278cc5, 6842be3b. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…id entries, DM_CTL players 2-8 - NVN window builder getters, queue builder memory-size getters, window texture counts, float blend clamp and provoking vertex (PRAGMATA, Orbitals); - vi::ListFrameHistory reports the latest presented frame; - H.264 parameter sets from Initialize (t_codec_video passes them); DM_CODEC_DUMP; - hid InitializeGesture(int, int); DM_CTL "p<n>:" and "both:" drive players 2-8. Originally c56689a7 (NVN part), 24dc4535, 3eab35cf, b36fc2cb and the hid part of 5aa0fb38. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…S pool is read back into its storage Some games encode BC blocks by compute into an RGBA32Uint texture, copy them with CopyTextureToTexture into an integer texture in a CPU_NO_ACCESS pool, and sample a BC3 texture made over the same storage. On NVN both are the same bytes; here they are two Metal textures, and a copy into a texture in such a pool was not written into its storage, so the BC texture, filled from that storage at its first use, read zeros. tex_block_alias_dst() lets copy_t2t read such a copy back into the storage: a single-sample 2D destination with RG32Uint, RGBA16Uint or RGBA32Uint texels in a CPU_NO_ACCESS pool. tex_init_fill then gives the BC texture the blocks at its first use. Limit: the readback must land before that first use. DM_NVN_BLOCK_ALIAS=0 turns it off for an A/B (on by default). t_block_alias checks the copy reaches the storage, the BC3 texture holds the blocks, an RGBA8 copy is left alone, and the switch turns the readback off. Ported from PR seedds#1187 (wtfsayo, 2aef49e). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…not specialized from a base Symptom: Orbitals' player ship went black while engaging, and loaded dark then popped in. Cause: its base pass shader (e97d65cf4844ecdb, M_Cel_Base_PlayerShip) writes the GBufferB blue channel (BGRA8Unorm) from an integer register (as_type<float>(int), one of the outputs its mixed entry point returns as integers, mask_m). On the Metal 4 path, where its pipeline was specialized from a base with open colour state, GBufferB, GBufferC and GBufferD came out zero over the ship (per-pass snapshots, stage 2): no base colour, so the lights left it black. The same pipeline built whole, or through the MTLDevice (DM_PSO_MTL4=0), writes the values; so does the specialized one when that channel is written as 0.0. A small probe does not reproduce it, so the trigger inside Metal's specialization is not known. Fix: pso_intbits_whole names such pipelines (an integer-bit output into a non-integer target, merged passes through lmap) and pso_desc labels them whole, as for [[primitive_id]]. DM_PSO_INTBITS_WHOLE=0 turns the rule off for A/B. DM_PSO_WHOLE=<fragment key prefixes>|all (diagnostic) builds chosen pipelines whole; it is how this was bisected (0-7 and 8-b still dark, e fixed, e97d65cf alone fixed). Measured: the ship dark in 3 of 24 screenshots of an engagement with the rule, 21 of 24 with DM_PSO_INTBITS_WHOLE=0. In stage 2, 22 base-pass fragment shaders have such outputs, so they lose the specialization's compile time. The prebuild still makes their unused bases. Test: t_pso_intbits (10 checks: the decision, and that a named pipeline builds whole and writes the bits' unorm value with its other outputs intact; 3 fail with the rule off). Both switches are in docs/env_switches.tsv. Reference entry: rendering.md, Integer bits in a float colour output in a specialized Metal 4 pipeline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dmsh_xlate.dll and Ryujinx.Graphics.Shader.dll rebuilt from this tree (dotnet publish dm/gfx/tools/dmsh_xlate/dmsh_xlate.csproj -c Release -r osx-arm64 --self-contained false), tool_lock.py --update-lock, dmsh_chain.py --header. The chain takes in this series' translator and shader build changes: the Ampere instructions, the compute barrier exits and loop guard, scalar storage-buffer indices, the NVN2 compute c0 push blocks and the threadgroup store fence. VOTEU's uniform predicate is main's (95f9a6a). Rebuilt on main 671c68e. Checked: every dm/gfx/tests test_dmsh_*.py and test_dmsh_pool_arrays.py pass with this translator (DMSH_TEST_XLATE), tool_lock.py --check-tools passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mes table and gallery Orbitals is a Switch 2 co-op game on Unreal Engine 5.6.1 (SDK 21.4.2, Ounce; NVN2; Wwise 2024.1), played split-screen by two players. Its profile is the one the NSP script guessed from the dump, checked by hand: Unreal's 64 GB alias region (as Octopath Traveler 0), 20 MB of system resource, the two IoStore shader libraries as the global and game groups, encoding on its own thread, and the game's own 30 fps; `barrier game`, as a UE title orders its own passes. Shader tables: 23,432 shaders (3,415 compute, 3,556 vertex, 16,459 pixel, 2 geometry), all of them translating on chain 85c751370308. The prewarm seed list is the Mac's device list of the play sessions of 2026-11-04 (1,344 shaders, 1,523 render and 211 compute pipelines). Frame rate: the Switch 2 holds an even 30 here (173 of 182 frames 33.3 ms apart in a 60 fps capture); on an M4 Pro at 30, 1,198 of 1,199 presents are two refreshes apart. Unlocked to 60 it gave 55-59 fps with missed refreshes, so the profile keeps 30. Checked: a scripted run on the Mac from boot to stage 2 with both players (gameplay at 65 s, 13 screenshots, 30.0 fps, GPU about 21 ms, one [RED] line: a vertex attribute format converted on the CPU). The gallery picture is stage 2, both views. The note says how to drive both players, and what is open (the darker engagement scene, an IPA). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mechanisms the series fixes, each with its symptom, cause, fix and the game it was found on, and a line under the Orbitals note's "Found on this game": - shaders.md: Ampere instructions of Unreal Engine 5.6's Switch 2 shaders; compute threads that return before a barrier (BarrierExits, the loop guard); NVN2 compute c0 with two push blocks; a scalar storage-buffer load that lost its index; - nvn-objects.md: the GPU watchdog and GPU-written dispatch counts (with the draw cap and the SyncWait report); NVN2 fence and acquire syncs cover their own queue; - services.md: more controllers than the controller applet's maximum; Unreal Engine 5.6's Switch 2 platform layer at start (nn::chat::Initialize's Result, vi::ListFrameHistory, the builder getters). Main's "VOTEU writes the uniform predicate" (95f9a6a) gets Orbitals' case: the wrong picture it made there (Niagara mesh blobs from Unreal's instance culling) in its symptom, its symptom index line and its Found on. The note links SDK 21's audio entries, which came with Star Fox (seedds#1259) and which the game needs (without them: Result 0xe15 at boot). docs_symptoms.py run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make -C dm/gfx/tests GAME=040004F027560000 run stopped at check_t_thermal ("no frame step: thermal
state is not read: FAIL"): the test reads nvn_game_fps, which starts from the profile's fps_target,
and Orbitals is the first profile at 30, so every check saw 30 where it expects 60. The test is about
the thermal and movie caps, so it sets DM_FPS_TARGET=60 itself before the first read.
Checked: check_t_thermal and thermal-tsan pass with GAME=040004F027560000 (21 and 3 checks) and with
GAME=010015100B514000, as CI builds them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
….IS test On top of upstream c94ce65 (LDC .IS reads the bank chosen at run time): - Ra = RZ reads as 0 instead of R255; - c0 stays out of the run-time banks also when the instruction names it (every LDC .IS in PRAGMATA, 3 vertex and 78 compute shaders, has bank field 0 with 0x10000 in Ra); - FMNMX.NAN (source only); - test: PRAGMATA's LDC.64.IS c[0x0][R16+0x8] reads c1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pending write's value DeferAmpereWrites gave a register written again while its earlier deferred write was still pending a shadow that started from the register, which that write had not reached yet. An instruction that reads the register and writes it again saw the value from before the earlier write. PRAGMATA's LDC.64.IS into R12:R13 followed by a SUATOM that reads R12 and writes it again lost the c1 read as dead code. The new shadow now starts from the latest pending entry of the register. Main's guard rules (9507c22: the guard kept at issue, an earlier guarded entry staying pending) are unchanged. Checked: test_dmsh_ampere_mem.py LdcIndexedBank passes (test_pragmata_reads_c1_at_the_immediate_offset failed on this series without it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nter loop_guard runs before dmsh_meshstage.to_stage and puts "uint dm_lg = 0u; (void)dm_lg;" as the first line of every function body, while _KERNEL wanted the stage memory array right after the brace: every task and mesh kernel failed (PRAGMATA: all 539 mesh shaders). Plain declarations before the array are now matched and kept at the top of dm_stage_body. Ported onto upstream's dmsh_meshstage.py; the test moved into dm/gfx/tests/test_dmsh_meshstage.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esh shader compiled in that slot Main puts the mesh stage's storage buffer table at c0 0x770. PRAGMATA's 539 mesh shaders sit in the SDF hull slot (control +0x56 = 7) and read their storage buffer addresses and sizes at c0 0x210..0x280 (bank 26 through AMapC0), the hull stage's table: read at the mesh stage's own place, 538 of 539 came out with no slot on every storage buffer and the host bound slot 0 for each. The table is where the shader's compiler put it, by the slot it was compiled for, so the choice is per shader: reflect takes DMSH_C0_SSBO_MS_HULL when all of a mesh shader's storage reads fall in the hull table, else main's DMSH_C0_SSBO_MS; unfillable accepts both; the host fills the mesh stage's table at both places (the mesh stage's c0 is its own, nothing else lives at 0x210 there). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…initions (PRAGMATA) PAK version 4.2 sets feature flags and keeps its data in chunks (eigeen/ree-pak-rs); the Switch 2 SDF form has 0x110-byte entries whose records hold an NVN2 control section at +0x58. A program with task and mesh shaders keeps them in the hull and domain slots: the control section's stage byte (+0x56: 6 task, 7 mesh) names them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PRAGMATA's visibility buffer (336 shaders) does SURED.D.2D.MAX.U64 on an RG32UI image. The translator emits a 64-bit image atomic max / min, dmsh_atomic.py rewrites it to Metal's ulong texture atomics (MSL 3.1, with the bounds check), and the host binds such an image as a 64-bit view (nvn_refl atom 2) on Apple GPU family 9. ADD and SUATOM with a result are refused (Metal has no 64-bit texture add nor a returning 64-bit texture atomic). t_image_atomic64.m is the GPU fixture (DMSH_TEST_ATOMIC64; not run by default). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guest's integer add, subtract and multiply wrap modulo 2^32. The SPIR-V backend emitted them as signed OpIAdd / OpISub / OpIMul, and SPIRV-Cross writes those as int arithmetic. In MSL, as in C++, signed overflow is undefined, so the Metal compiler may assume it never happens: it folded the signed byte extract (x * 256) >> 24, which IMAD.SHL.U32 followed by SHF.R.S32.HI becomes, into x >> 16. PRAGMATA 50e310abbb05e838 read byte 2 of its snorm8 words with byte 3 mixed in (0xffffc05d instead of 0x5d for 0xc05d0000), so -127 came out where +127 belonged. GenerateBinary takes a wrapping flag; Add, Subtract and Multiply set it and compute on the unsigned type. Division, min and max keep their signed forms. Found by an offline differential check against a SASS reference model (outside this tree), and reproduced on the GPU. WrappingMultiply in test_dmsh_ampere_alu.py checks the multiply is unsigned and fails on the previous translator. Two ampere_mem tests now accept the unsigned spelling of their constants (4000u, 1u, 0u + 416u). The translator binaries and the chain bump are left to the rebuild. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since 5eba12cf the translator writes integer add, subtract and multiply on uint, so SPIRV-Cross spells `x + 1568` as `int(as_type<uint>(x) + 1568u)` and `b + 1` as `int(uint(b) + 1u)`. Several MSL pattern matchers in the build only knew the signed spelling and silently stopped matching: - drv_reads sample_table (the c0 + 0x620 sample position table): 65 PRAGMATA pixel shaders lost `sample_table: True`, so their dynamic c0 read was reported unfillable (likely behind the watchdog hang with all 1,617 overrides loaded; a37ad609 and 4677fd9a were in the hung submission). - SBUF_SCALAR_INDEX (the Orbitals GPUScene element-0 repair): `X * 4`. - FIRST_STORE / FIRST_STORE_2D (the first-thread store test): `b + 1`. - dmsh_wide's sums of a common value (`_ADD`), now also with `- Ku` and two's-complement constants. - dmsh_geometry.lean's record offsets and their rewrites (`+ K`, `* 144`). Each now accepts both spellings and keeps the one it found when it rewrites. test_dmsh_wrapping_forms.py checks every matcher on both; 6 of its 7 tests fail on the previous tools. Rebuilt with the 5eba12cf translator, the 65 shaders' drv_reads and drv_unfillable equal the pre-fix (6cc4) json, and no other json field differs but the chain. (test_dmsh_abi's ratchet fails on nvn_mtl.m before and after this change; not touched here.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
lint_python.py --against origin/main found 31 in the two vendored files: the parser's and Func's methods take `self` (they took `s`), one startswith takes a tuple, the closures bind their loop variables as defaults, and the loop record's dict is cast where pyright read it as possibly None. No change in what they compute: data_loops gives the same result on 59 PRAGMATA compute libraries, and test_dmsh_loopdiag.py passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebuilt from this tree (dotnet publish Release osx-arm64, not self-contained; dmsh_xlate.dll and Ryujinx.Graphics.Shader.dll copied), tool_lock.py --update-lock, dmsh_chain.py --header, --check-tools OK. The chain was 7bdd33b7caf2: the 0x80d fix, the Ampere ALU fixes, the optimizer's IsUnused, PIXLD.COVMASK, and dmsh_loopdiag.py / dmsh_dsl.py joining the chain's files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A library <dir>/<key>.metallib takes the place of the cache's, and the shader keeps the cache's reflection json, so a fix to a few shaders (PRAGMATA's mesh-stage AoS writes, 6adf14e9 / 4fcffc73 / c0e44513) can be tried without a library rebuild. The cache may be a folder or a packed image (lib_from_file reads either); the override folder is plain. Host only, no chain change. env_switches.tsv row added. (cherry picked from commit 4682bfdb, hangguard; applied by hand at r5's shader load) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…write the primitive count: PRAGMATA's cockpit window loses its white streaks A mesh shader may write each vertex twice: in the SoA blocks (ISBEWR.O.ATTR.SKEW.32, mode 0xf8) and as a record of its n enabled components with non-skew stores (ISBEWR.O.ATTR.32, mode 0xb8), at the end of its own SoA area + 4n * v + 4c, with the same values. The translator took mode 0xb8 as a payload word at the outputs' base, so the records ran past the SoA area over the primitive count and the index bytes. PRAGMATA's 6adf14e9a4100082 then drew up to 64 garbage triangles a workgroup with its Gaussian splat pixel shader: white streaks over the cockpit window. dmsh_meshstage.memory passes the records' start (the shader's own SoA size), n and size as stage_memory[4..6], and in a mesh shader AStageOut and AIsberd send a mode 0xb8 address inside them to the SoA word of the same vertex and component, so the two writes coincide. A task shader's mode 0xb8 is still its payload. Tests: test_dmsh_meshstage.py (records, the w / n arithmetic), test_dmsh_ampere_mem.py StageMemory. Binaries and chain in the next commit. (Source of upstream branch mesh-aos-attr e58cd1d5, adapted to this tree's memory(): its SoA area is prim - out here; the same fix as flash-dbg a53f953c, validated in play on the earlier chain with a 3-key side library.) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main's test_chain_reuse_and_direct_a (bd9f422) looks for R2 = R0 * 16 as `int _N = as_type<int>(x) * 16;`. With this branch's wrapping integer arithmetic (integer multiply on uint) spirv-cross writes `int _N = int(as_type<uint>(x) * 16u);`. The test now accepts both spellings; the translation is the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebuilt from this tree after the rebase onto main bd9f422 (dotnet publish Release osx-arm64, not self-contained; dmsh_xlate.dll and Ryujinx.Graphics.Shader.dll copied; main's Ryujinx.Common.dll and Spv.Generator.dll kept), tool_lock.py --update-lock, dmsh_chain.py --header, --check-tools OK. The chain was 42d6026a1aa5 (main 23a1ea1db092). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…before Metal's fast sin / cos: Bananza's SSAO at full size had no occlusion past x*y about 659,000 Ampere's MUFU.SIN / COS take the angle in turns and use only its fraction. The translator multiplied the turns by 2*pi in float and called Metal's sin / cos: the product rounds the fraction away (a step of 0.4 rad at 2^20 turns), and fast sin / cos return 0 from about 2^20 turns on. Donkey Kong Bananza's SSAO (a6f04605183d9915) rotates its kernel per pixel by an angle that grows with x*y; past x*y of about 659,000 the rotation became (0, 0) and the lower right of the picture had no ambient occlusion. The SSAO runs at half the scene's size: at the docked top of dynamic resolution (1152x648) that is a wedge in the corner (5,425 pixels), with the effects at full size (1676x943) a fifth of the picture; at 838x471 there is no hole, but angles off by up to 0.23 rad. Any Ampere shader passing sin / cos an angle that large had the same fault; only Bananza was checked. AMufu now lowers both as sin / cos((t - rint(t)) * 2*pi): t - rint(t) is exact and in [-0.5, 0.5], where the fast functions are accurate. Maxwell code is unchanged. Checked: t_mufu_turns compares the lowering on the GPU with a double reference over the SSAO's angles at 838x471, 1152x648, 1676x943 and 2560x1440 (largest error 2e-7; the old lowering collapsed 0, 5,425, 345,255 and 1,893,254 rotations). regress.py check on Bananza: 23,638 of 23,638 shaders build on both sides, 0 newly failing, 8,258 with changed MSL, 0 interface changes. (cherry picked from commit 135804e; upstream PR seedds#1234; its translator binaries, lock and chain header are left for the integration's one bump) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit 380a37cf2bb8f331e8144f5337a9831fcd3cf4e6)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…that covers it seedds#1407's pool table reaches a large pool through 64 MB segments of its own, all resident, beside the lookup chunks over the same pages: PRAGMATA's title load on an 8 GB Mac held 3,295 MB of shared buffers over 2,506 MB of distinct pages, and the GPU memory guard stopped the game. A segment now takes a covering chunk's buffer (the table entry's delta from that buffer's base), and as segments are made before most chunks, each table build looks again at the segments that own theirs. Retiring the chunk clears the segments on it for the next table build. DM_NVN_SEG_SHARE=0 keeps own buffers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DeVTtdCXD7t64Ek6e1py6A
…nts would hold more Lookups of unknown length (BIG_UNKNOWN) make chunks over the same pages, lengthened in steps, each resident: on an 8 GB Mac PRAGMATA's opening load held 5,501 MB of shared buffers over 2,738 MB of distinct pages, and the GPU memory guard stopped the game. When a pool's chunks and own table segments plus the new chunk would exceed the pool, big_lookup makes the whole-pool entry, retires the chunks, and the table's segments take that buffer. A sparse pool with holes is refused by the page check and keeps its chunks. DM_NVN_BIG_WHOLE=0 turns it off. t_sparse_safe case 5 checks it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DeVTtdCXD7t64Ek6e1py6A
… for the old range A sparse run grows in 4 MB steps (mtl_pool_extend), and each step retired the whole-pool buffer, so the next lookup made a new one: PRAGMATA's title load made 13, two at once over the same 700 MB. The old buffer now stays as the pool's wide entry, which lookups in its range take first; the pool turns whole again only once its chunks and segments pass 1.5 times its size. t_sparse_safe case 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DeVTtdCXD7t64Ek6e1py6A
…TA's note and the symptom index Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DeVTtdCXD7t64Ek6e1py6A
3 tasks done
3 tasks done
1 task done
This was referenced Nov 11, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1407 (PRAGMATA bring-up, head 1fd3301, chain 916e8d674d87): review only the last 4 commits. These change #1407's large-pool code (pool-table segments, sparse runs), so they would go into #1407 or land after it.
What and why
On an 8 GB Mac (M1 MacBook Air), PRAGMATA (
0400C76024710000v196608) could not get through its title load or the opening load. With #1391's GPU memory guard on, the guard stopped every run. The residency set's report showed shared buffers far larger than the guest pages they cover: at the opening load, 5,501 MB over 2,738 MB of distinct pages.A large pool reaches the GPU through three kinds of no-copy buffer over the same pages, each resident whole:
BIG_UNKNOWN: storage buffer addresses read from push constants). These are the 85–109 MB buffers in the report;A 24 GB Mac holds that; an 8 GB one cannot.
The change has three parts, all in
nvn_mtl.m:segbase).DM_NVN_SEG_SHARE=0turns this off.big_drop_chunks) and the segments take that buffer. A sparse pool with a hole is refused by the page check (DM_NVN_SPARSE_SAFE) and keeps its chunks.DM_NVN_BIG_WHOLE=0turns this off.mtl_pool_extend), instead of retiring it. Lookups in that range take it first. The pool turns whole again only once its chunks and segments pass 1.5 times its size.Whoever made a buffer releases it (
big_chunk_retire). Retiring a chunk whose buffer a segment took clears that segment for the next table build.Reference entry: Large pools held twice in the residency set, linked from PRAGMATA's note.
Checked
GAME=010015100B514000, as CI):t_sparse_safe120 checks, 0 failed (case 5: a segment takes a covering chunk's buffer, retiring the chunk clears it, the whole-pool buffer and its takeover by the segments, a grown pool's wide entry);t_cpu_tex,t_vbufpass;test_docs.pyOK;docs_symptoms.pyandenv_switches.pyrun.--seen-shaders) library. Built together with A GPU memory guard before each residency commit stops the game instead of freezing the Mac #1391/A game that stops submitting no longer piles up retired pool buffers, the likely cause of the freeze after a window switch #1392 and the guard-count fixes in GPU memory guard: measure the residency set, and take pending removals off it (stacked on #1392; PRAGMATA on an 8 GB Mac) #1615, PRAGMATA's opening load fits an 8 GB Mac's GPU working set: large-pool chunks retire, pools from 128 MB are chunked, and DM_NVN_TEX_MIP_SKIP #1319, Shared textures no live object resolves to are retired when GPU memory runs short: Ocarina of Time no longer freezes after the talk with Saria #1455, A GPU-only pool's render targets share one placement heap: Ocarina of Time's 3 GB of render targets fit in 0.7 GB #1453, Allow up to three compressed texture mip levels to be omitted #1418, guest memory in files,DM_NVN_GMEM_RES=0 DM_NVN_TEX_MIP_SKIP=1 DM_NVN_TEX_MIP_LEVELS=2.scripts/regress.py check --games. There are no other game builds on this machine. The change reaches every game with pools of 128 MB or more under#1407's large-pool code (Ocarina of Time, Kirby Air Riders, Mario Kart World and Donkey Kong Bananza are likely). Not tested on iOS.Docs
docs/reference/memory.mdentry and PRAGMATA's note, andpython3 dm/tools/docs_symptoms.pywas run (the symptom index)🤖 Generated with Claude Code
https://claude.ai/code/session_01DeVTtdCXD7t64Ek6e1py6A