Skip to content

Rendering: DoF spiral turned per texel, one draw-count rule, far world text without shimmer (#881, #882, #892) - #975

Merged
paul-hammant merged 3 commits into
mainfrom
wip/m6-dof-draws-text
Nov 6, 2026
Merged

paul-hammant merged 3 commits into
mainfrom
wip/m6-dof-draws-text

Conversation

@paul-hammant

Copy link
Copy Markdown
Collaborator

Three rendering fixes, one commit each, each with a changes/2026-11-05-*.md entry. No motion code is touched.

#881 Depth of field

dof_gather read the same 32 spiral taps at every texel, so the spiral's own pattern showed in the blur. The spiral is now turned at each texel by interleaved-gradient noise (the occlusion's dither, counted from the same corner on both back ends), 48 taps; radii unchanged, so an in-focus texel still takes nothing. New CPU check in test_gpu_dof: a hard edge under the widest disc, rows along the edge must differ and their mean must match an even disc's blur within 0.015 (new code 0.0077 off; main's gather 0.029 off and fails; 48 taps without the turn 0.026 off and fails). frames_against main: only the four DoF frames differ, the other 77 byte-identical. GL/Vulkan parity at 1x: 0 channels (as main). Cost at disc 100, zombie_city 1280x720: Vulkan 0.27 ms (main 0.12), OpenGL 0.35 ms (0.18); DoF is off by default, no budget pins it.

#882 Draw counts

The back ends counted different full-screen passes (OpenGL: SSR, TAA, HUD; Vulkan: depth resolve, SSAO). The rule vkframe already stated now applies everywhere: a screen pass is never a draw. draw_quoted_with no longer counts (six save/restore guards removed); OpenGL's SSR/TAA/HUD increments removed. docs/rendering.md gains "What a frame's draw count is". test_backend_parity turns on SSAO, TAA, SSR, DoF one at a time and together: every case must equal the no-post count of 7 on both back ends and match across them (5 checks fail on main). Budgets: zombie_street draw_calls 88 → 87 both back ends; zombie_city shadow-map 696 → 694 (measured on the M6 at 320x180 as ci_scenes does). Not done: the RTX 4070 Ti ray-traced zombie_city*.vulkan.budget.json (285) should now read 284 (283 with ray-traced occlusion off) and need re-recording on that card with ae3d_bench --record; noted in ci_scenes.sh and the changes entry.

#892 World text shimmer

Per the issue (no mips, keep GL and Vulkan identical), the fix is in the shader only: where a pixel spans more than one atlas texel, the pixel is covered by the mean of a grid of field reads over its footprint (up to 4x4), faded in between one and two texels per pixel; symmetric about the pixel centre so OpenGL's upward y maps onto itself. Neither atlas changed; #894 untouched. test_world_text slides the camera past a 5 px/em and a 9 px/em label for 24 frames: coverage within 2% of its mean and of the previous frame on both back ends, back ends agreeing. Main: 4.2%/6.7% and 1.9%/3.6% (4 checks fail); branch 1.3%/1.5% and 0.74%/0.76% at 4x and 1x; moving frames 0 channels apart on the M6, 9 on Intel. bench_world_text within noise.

Verification

  • generate_shaders --check current (glslang 16.6.0); msl_check 60 files; test_quoted_hashes, test_vk_msl, test_gpu_msl, test_gpu_lettering pass; GLSL fixtures regenerated.
  • Validation layer (sync validation on, layer confirmed loaded): parity, world text, view: 0 errors at 4x and 1x; validate.sh with DoF at its widest: 0 for zombie_city and street_life.
  • cachyos (real Intel GPU, gcc 16.2 and clang 23): 0 warnings; parity and world text pass at 4x and 1x on Intel OpenGL and Vulkan, 0 layer errors.
  • Local ./ci.sh: all pass except 4 "exported fixtures stale" checks (tests/fixtures/exported, resources/blender/{showcase,zombie_street,car}), whose only difference is the Blender version string (5.2.2 committed, 5.2.3 installed here); main fails the same way on this Mac. A re-export is a separate commit, not in this PR. 8 hardware skips (DLSS, ray queries, one leak report).

🤖 Generated with Claude Code

paul-hammant and others added 3 commits November 5, 2026 22:54
The gather read the same 32 places on a golden-angle spiral at every
texel, so the blur of a wide disc showed the spiral itself. It now turns
the spiral by an interleaved-gradient noise of the texel (the occlusion's
dither, counted from the same corner on both renderers) and takes 48 taps;
the radii stay where they were, so a texel in focus still takes nothing.

test_gpu_dof gains an edge under the widest disc: its rows must differ and
average to an even disc's blur within 0.015. Turned: 0.0077. Main's gather
(32, unturned) is 0.029 out and fails; 48 unturned is 0.026 and fails.

M6, against main: frames_against 81 frames, only the four depth of field
frames differ (the change), the rest byte-identical (clouds, city within
#793/#794). Parity GL vs Vulkan: 1x 0 of 147,456 channels on both
focuses (as main), 4x 2,115 / 1,969 (main 1,991 / 1,989). Cost at the
widest disc, zombie_city 1280x720: Vulkan 0.27 ms (main 0.12), OpenGL
0.35 ms (main 0.18); off by default, no budget pins it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…er (#882)

The two renderers counted different screen passes: OpenGL the
reflections' pass, the temporal pass and the HUD; Vulkan the depth's
resolve and the occlusion's pass (drawn inside the scene, before its
count is read). So the counts disagreed by whichever passes were on:
test_backend_parity's scene, 7 with nothing on, was GL 7 / VK 9 with
the occlusion, 8 / 8 with TAA, 8 / 8 with SSR, 7 / 8 with DoF, 9 / 9
with all four. zombie_street's 88 on both was two different 88s.

The rule vkframe already stated, applied to all: a quoted pass
(vkframe.draw_quoted_with) no longer counts, and the counted/restore
guards around it go (dead for the post chain); OpenGL's SSR, TAA and
HUD draws no longer count. docs/rendering.md says what the count is.

test_backend_parity: each of SSAO, TAA, SSR, DoF on and all four on
holds the count with none (7) on both renderers, and the renderers
to each other; the old renderers fail 5 of those checks.

Budgets, draw_calls only (M6, 320x180, as ci_scenes runs them):
zombie_street 88 -> 87 on both; zombie_city shadow-map budgets
(12 m and 28 m near bands) 696 -> 694. The RTX 4070 Ti ray-traced
city budgets (285) need recording again on that card.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…892)

A far, small label sized in the world's units read its glyph field at
one point a pixel, many atlas texels apart, so it found a stroke one
frame and the gap beside it the next and shimmered as the camera moved.
Where a pixel spans more than a texel (dFdx/dFdy of the atlas place),
the shader now covers it by the mean of a grid of reads over its
footprint, up to 4 by 4, faded in from one texel a pixel to two. No
mips, as #892 (Nic 876-3) has it: the two APIs' mip filters would part
the frames. The grid is symmetric about the pixel's centre, so GL's
y-up turns it into itself; neither atlas changed (#894 untouched).

test_world_text moves the camera past a label 5 and one 9 px to the em
for 24 frames and holds each frame's coverage (read in the scene's
light) within 2% of its mean and of the frame before, both renderers,
and their frames to each other. Main: 4.2% / 6.7% (far) and 1.9% /
3.6% (4 checks fail); now 1.3% / 1.5% and 0.74% / 0.76% at 4x and 1x,
the renderers 0 channels apart; the suite's other GL/VK comparisons
are main's (108, 51 channels). frames_against: no parity frame moved
by it (the four DoF frames of #881 only). bench_world_text: 500
labels within noise of main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@paul-hammant
paul-hammant merged commit ced6cda into main Nov 6, 2026
18 of 20 checks passed
@paul-hammant
paul-hammant deleted the wip/m6-dof-draws-text branch November 6, 2026 03:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant