Skip to content

Same-pass feedback: a read-only image load of a colour attachment takes the snapshot too (#1009) - #1035

Closed
Haseenesss wants to merge 2 commits into
seedds:mainfrom
Haseenesss:fb-image-feedback
Closed

Haseenesss wants to merge 2 commits into
seedds:mainfrom
Haseenesss:fb-image-feedback

Conversation

@Haseenesss

Copy link
Copy Markdown
Contributor

A fragment shader that reads its own colour attachment through a read-only image now gets the same-pass feedback snapshot that a sampled texture already got.

Closes #1009.

Cause

Xenoblade Chronicles 3's lighting-zone shader 15816d836842bfb5 (shd_lgt.wishp) reads its own R32Uint colour attachment through image slot 0 between the game's barriers, to skip zones it has already marked. On Apple's tiled GPUs the attachment stays in tile memory until the pass ends, so the image load read the previous frame's stored content instead of this pass's clear and earlier draws. Each zone bit inverted every frame, and rendered cutscenes alternated brightness with blocky light/shadow bands.

fb_prepare handled this for NVN_RB_TEX slots only; read-only NVN_RB_IMG slots read the attachment directly.

Change

  • fb_prepare (nvn_mtl.m): a read-only, non-atomic fragment image (reg < 32) whose binding is one level and layer of a colour attachment takes the same snapshot as a texture sample, including the direct-read mask (a later writer then starts a new pass).
  • encode_args, NVN_RB_IMG branch: binds the snapshot for that draw, declared MTLResourceUsageRead.
  • Both sides compare the object img_binding returns. A RegisterImage view already names one level and layer, and a swizzle-only binding is img_fit's identity view of the same range. A slot whose pixel class needs a reinterpreting view is left as is.
  • The translated slot is texture2d<uint> with no access qualifier (access::sample; reflection rw = 0), so the shader-read snapshot is a legal binding. The chain is unchanged.
  • Docs: a new reference entry, "A read-only image load of a colour attachment" (docs/reference/rendering.md), the symptom index, and the finding in Xenoblade Chronicles 3's note.

This builds on the patch attached to the issue; the image is resolved through img_binding on both sides so level/layer views match.

Checks

Apple M2 (8 GB), macOS 27.2, Xcode 27.2.

  • make -C dm/gfx/tests GAME=010074F013262000 run: exit 0. check_t_feedback gains an image case under MTL_DEBUG_LAYER=1: default 102 checks, DM_NVN_FB_SNAP=all --image 23, DM_NVN_FB_SNAP=0 --image 17, 0 failed. It covers a level-1 view of a two-level R32UI against the level-1 attachment (and not level 0), a SINT class on R32Uint left as is, the value after a clear and an earlier draw (0x33, not the pre-pass 0x11), and an image read of an unwritten attachment making a later writer end the pass.
  • make -C dm/host GAME=010074F013262000 builds. lint_c.py tidy (LLVM 23.1.4) on nvn_mtl.m: 0 file/check pairs above the baseline. test_docs.py OK.
  • In game, Xenoblade Chronicles 3 2.2.0, two hosts that differ only by this change (built at 5474c6f, the chain of the shader libraries built for the game), Events Theater chapter 1, second scene (the battle with Colony Sigma), 48 consecutive distinct presents per burst, crop luminance:
    • before: 8 of 10 bursts reverse every frame (sign-flip fraction 0.83–1.00), e.g. 38.3 / 40.6 alternating through 48 frames, or 64 / 58 after a cut; the difference image shows whole regions of lighting switching;
    • after: highest flip fraction 0.28 over 11 bursts; the same quiet stretch drifts by about 0.1 level per frame;
    • GPU p50 50.4 → 53.0 ms, 18.3 → 17.2 fps (the extra snapshots). No new [RED] lines.
    • The chapter 1 prologue does not show the two-level step on either host.
    • After CONTINUE, the field of the first save renders and walks normally.

regress.py

regress.py impact origin/main..HEAD: 3 games use what the change touches (Breath of the Wild, Pokémon Scarlet, Tears of the Kingdom, through fb_prepare / encode_args → nvnQueueSubmitCommands), 128 profiles have no usage manifest. None of those three is on this machine, and no local game has an NSP folder with a --platform mac build in the layout check needs, so check --games was not run. The change does not touch the chain, so chain has nothing to compare. The Xenoblade Chronicles 3 A/B above is the runtime check; games that sample their own attachments through textures (BotW's decals, agl's post effects) take an unchanged path, since only read-only NVN_RB_IMG slots are added.

🤖 Generated with Claude Code

seedds and others added 2 commits November 3, 2026 12:22
…es the snapshot too (seedds#1009)

Xenoblade Chronicles 3's lighting-zone shader 15816d836842bfb5 (shd_lgt.wishp)
reads its own R32Uint colour attachment through a read-only image between the
game's barriers, to skip zones it has already marked. On Apple's tiled GPUs
the attachment stays in tile memory until the pass ends, so the image load
read the previous frame's stored content instead of this pass's clear and
draws; each zone bit inverted every frame and rendered cutscenes flickered.

fb_prepare took the same-pass feedback snapshot only for sampled textures. A
read-only, non-atomic fragment image whose binding is one level and layer of a
colour attachment now takes the same path, and encode_args binds the snapshot
for that draw, declared read-only. Both sides compare the object img_binding
returns; a slot whose pixel class needs a reinterpreting view is left as is.
The translated slot has no access qualifier (access::sample), so the
shader-read snapshot is a legal binding.

t_feedback gains an image case for modes 0, 1 and 2 under the Metal debug
layer. On an M2, Events Theater chapter 1's second scene alternated every
frame before (sign-flip fraction 0.83-1.00 in 8 of 10 bursts) and holds one
level after (at most 0.28); GPU p50 50.4 -> 53.0 ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) docs Documentation game: 010074F013262000 Xenoblade Chronicles 3 labels Nov 3, 2026
@seedds

seedds commented Nov 3, 2026

Copy link
Copy Markdown
Owner

Thank you. #1025 and this PR fixed #1009 the same way. #1025 landed (f5d5327) because it was built on current main, where the tile-read path (fb_tile_plan) now sits in the same loop, and it has a few extra guards (single-level 2D view, registered pixel class). Your symptom wording went into the reference entry, and your Colony Sigma measurements into the game note. You are credited as a co-author.

@seedds seedds closed this Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation game: 010074F013262000 Xenoblade Chronicles 3 nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Xenoblade Chronicles 3: rendered cutscenes flicker every frame (lighting shader reads its own attachment as a read-only image)

2 participants