Skip to content

Store lookahead: a draw that may read a texture through a handle needs every candidate - #1340

Open
Copak731 wants to merge 1 commit into
seedds:mainfrom
Copak731:lookahead-bindless-readers
Open

Copak731 wants to merge 1 commit into
seedds:mainfrom
Copak731:lookahead-bindless-readers

Conversation

@Copak731

@Copak731 Copak731 commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

What and why

The TBDR optimizations are off by default because the store lookahead "would miss bindless readers" (docs/agents/04-graphics.md §4, tbdr_on()). The cause is in sdx_walk: a draw's reads are noted only from its bound slots (NVN_RB_TEX, TBUF, IMG), so a handle read from a uniform buffer (TEXU), an array of handles (TEXA / TEXD), or a shader the reflection calls bindless or dynamic gives no read event, and sdx_decide can meet the attachment's next full clear first and drop a store that a later bindless draw needs.

Such a draw (and one with no program) is now noted as needing every candidate texture known at that point (SDX_NEED): the store is kept, the ledger counts it as a read, and no pass merge starts on it (fz_prefilter looks for SDX_READ). Slot reads and the rest of the decision are unchanged. This makes DM_NVN_TBDR=1 correct for bindless readers; it does not turn the lookahead on by default (its other costs — deferred uniform updates and lazy clears off in TBDR mode — and its gain in a game are still to be measured). A step toward #159.

Reference entry: docs/reference/rendering.md, "Store lookahead and bindless readers".

Checked

  • Tests: new make -C dm/gfx/tests GAME=01007EF00011E000 check_t_lookahead_bindless (production mtl_sub_index + sdx_decide on a synthetic three-pass submission, no GPU work): 5 of its 22 checks fail on main (TEXU, TEXA, TEXD, bindless and dynamic reflection), none with the change; a draw with no texture read, or a slot read of another texture, still drops the store. make -C dm/gfx/tests GAME=01007EF00011E000 run: every other target passes as on main; t_border, t_transform_feedback and t_pso_archive fail on main too in a checkout whose path has a space (separate issue), and t_nvn aborts on both.
  • Games run: none. The default replay is unchanged (tbdr_on() still off), so scripts/regress.py was not run.
  • Devices and OS: MacBook, Apple M5, macOS 26.6.2, Xcode 27.2.

Docs

  • The finding is in its docs/reference/rendering.md entry, and python3 dm/tools/docs_symptoms.py was run (the symptom index)
  • No game files, keys or data extracted from a game in the change or the attachments

…s every candidate

With DM_NVN_TBDR=1 the submission index (sdx_walk) noted a draw's texture reads only from its bound slots. A handle read from
a uniform buffer (NVN_RB_TEXU), an array of handles (TEXA, TEXD), or a shader the reflection calls bindless or dynamic gave
no read event, so sdx_decide could meet the attachment's next full clear first and drop the store of an attachment that only
such a draw samples later. Such a draw (and one with no program) is now noted as needing every candidate texture known at that
point (SDX_NEED): the store is kept, the ledger counts it as a read, and no pass merge starts on it (fz_prefilter looks for
SDX_READ). Slot reads and the rest of the decision are unchanged, and the lookahead stays off by default: this makes it correct
for bindless readers, which docs/agents/04-graphics.md gives as the reason it is off, without measuring its gain in a game.

Checked: the new t_lookahead_bindless indexes a three-pass submission with the production mtl_sub_index and asks sdx_decide at
the end of the first pass; 5 of its 22 checks fail on main (TEXU, TEXA, TEXD, bindless and dynamic reflection), none with the
change, and a draw with no texture read or a slot read of another texture still drops the store. make -C dm/gfx/tests
GAME=01007EF00011E000 run on an Apple M5, macOS 26.6.2: every other target passes as on main; t_border, t_transform_feedback and
t_pso_archive fail on main and with the change in a checkout whose path has a space, and t_nvn aborts on both. No game was run
(the default replay is unchanged: tbdr_on() is still off).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bettterlabs

Copy link
Copy Markdown
Contributor

DM_NVN_TBDR=1 in a game, with this PR and #1367: Ocarina of Time v131072 on a 16 GB Mac mini M4, macOS 27.2, docked. The host is david-on-spring's #1386 branch (562c1f4 on f3ac5ea, which carries this PR's 9e141cc unchanged, chain 84aede056dea) + #1396 + #1430 + #1367 (c82a370) + #1390. Same binary, DM_NVN_TBDR unset against =1.

The same save was put back before each run, in Kokiri Forest by the stream. I didn't touch the controller after the load; each run went 2 minutes past it, with DM_NVN_SHOT every 300 frames.

Picture. With =1, the stream draws as flat, pale patches with hard edges. Unset, it is dark and see-through. It is like that in every screenshot after the load (frames 900–3600, 10 of them): the mean level of the same stream area is 102–116 unset and 138–152 with =1. The rest of the frame matches apart from what moves. Watching it, I also saw more flashing with =1, which single screenshots don't show.

DM_NVN_TBDR unset (left) and =1 (right), same save and camera, frame 2400

With =1, one red line came at the load (frame 651):

[RED] xv tag=31d3513f826424c0 texture=14180 level=0 layer=0 reason=submission lookahead read/need pass=75

Speed, the periodic lines after the load:

unset DM_NVN_TBDR=1
frame time p50 35.5 ms 35.5 ms
GPU p50 62.1 ms 63.3 ms
attachments a frame (load / store) 332 / 331 MB 300 / 318 MB
draws a frame about 4,235 about 4,235

Otherwise both runs logged only the title movie's AV1 red line and exited cleanly.

@Copak731

Copak731 commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the run — exactly what these two PRs needed. Two conclusions, and a request.

Speed. No gain where it counts: frame time p50 35.5 ms both ways, GPU p50 62.1 → 63.3 ms, attachment traffic −10 % loads / −4 % stores. In this scene the TBDR mode does not pay for itself, with or without these PRs; neither changes the default (tbdr_on() stays off), and I would not turn it on on this evidence.

The red line. xv ... reason=submission lookahead read/need is the cross-submission ledger (xv_*) meeting a reader it had not expected: an SDX_NEED event, which this PR adds for draws that may read a texture through a handle. The ledger only detects after the fact, so the store it had already skipped in the previous submission is lost for that frame, and the key is then disabled. So #1340 makes the miss visible; it does not make cross-submission store skipping safe for bindless readers, whose reads only show up in a later submission.

The stream. Its wrong look in every screenshot after the load (not just around frame 651) suggests something besides that one skipped store. Could you run the same save with DM_NVN_TBDR=1 and one of these each time, to bisect it?

  1. DM_NVN_UB_DEFER=0 — takes Deferred inline uniform updates in TBDR mode too (toward #159) #1367's change out (uniform updates end the pass again, as before it)
  2. DM_NVN_XV=0 — the cross-submission ledger off
  3. DM_NVN_ZSKIP=0 — the depth skip off
  4. DM_NVN_DEFER=0 — the deferred pass merge off

Whichever one makes the stream dark and see-through again names the part to fix; if none does, the store lookahead itself (this PR's SDX_NEED, or a reader it still misses) is the suspect, and a DM_NVN_PASSDUMP frame of the stream area would show which pass reads what.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation 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.

2 participants