Skip to content

Pipelined rendering, GPU shadow culling and path tracing denoising (Sponza performance) - #213

Merged
SpectralDragon merged 4 commits into
mainfrom
perf/sponza-load-pacing
Nov 10, 2026
Merged

SpectralDragon merged 4 commits into
mainfrom
perf/sponza-load-pacing

Conversation

@SpectralDragon

Copy link
Copy Markdown
Contributor

Summary

Sponza performance work and the path-tracing denoising milestone, plus other work that was uncommitted on this branch. Four commits:

Commit Content
3242536a Faster Sponza loading (about 8–9 s warm), frame hitches removed, per-draw bind groups replaced by a transient cache
0e851d8e Game diagnostics overlay and AdaUI text editor updates
66f03e9d Ada Studio editor, agent tooling and contributor guide updates
d01a30df Pipelined rendering, GPU shadow culling, path tracing denoising, DLSS runtime staging

Rendering (d01a30df)

Pipelined rendering (design)

  • A SceneView render world can update one frame behind its simulation (.pipelinedRendering()), with material snapshots taken at extraction.
  • The SceneView render-texture pool is sized for frames in flight. Before, frames that found no free target were skipped, so the CPU and GPU took turns.
  • ADAENGINE_GPU_TIMINGS=1 reports per-command-buffer GPU time on WebGPU through timestamp queries.

Shadows

  • GPU-driven shadow batching: a persistent caster registry, per-view GPU culling and multi-draw.
  • Meshlet cluster culling for shadow views (design). Local-light shadows dropped from 3.3 to 1.2 ms of GPU time in Sponza.
  • Runs of visible clusters are merged into single draws: tens of thousands of draws per Dawn multi-draw stall the D3D12 queue.

Path tracing denoising (design, accepted with the deviations in §11)

  • Full-detail first hit: the first hit comes from the raster G-buffer, so the full model is visible.
  • Tracer proxies: a new public component, PathTracingGeometry3D (.rasterOnly / .pathTracingOnly), lets a scene raster detail while the tracer intersects a lighter proxy.
  • History: survives camera and light motion; adding, removing or retuning a light restarts it.
  • Denoisers: PathTracing3D.denoiser selects .automatic (DLSS Ray Reconstruction on RTX with DLSS, otherwise portable), .rayReconstruction, .portable (SVGF-style temporal and wavelet filter, about 1.1 ms of GPU time) or .off.

DLSS

  • The NGX runtime is found next to the executable, or in ADAENGINE_DLSS_SDK_PATH. Before, a missing DLL was reported as an adapter or driver problem.
  • Studio builds and packages stage nvngx_dlss.dll and nvngx_dlssd.dll.

Fix: ShaderCache recorded a changed source hash before compiling. A failed compile then left a stale translation in use on every later launch. It now rolls back.

Testing

  • Headless: 45 tests in 9 suites, covering pipelining, SceneView, extraction roles, path tracing settings, draw order and the transient cache.
  • GPU (WebGPU, RTX 4070): ADAENGINE_GOLDEN_WEBGPU=1 swift test --filter Render3DPathTracing, 17 tests. They include history through motion, .off restarting on camera motion, portable output finite and within 15% of a converged reference, and Ray Reconstruction falling back to portable.
  • Sponza demo: captured and measured in the Sponza demo on Windows and an RTX 4070. Ray Reconstruction evaluates 1260×523 → 1890×784 without errors.

Notes for reviewers

  • The commits bundle work from several sessions that was uncommitted on this branch, including the game diagnostics overlay and editor changes, at the owner's request.
  • The commits were made with --no-verify, at the owner's request. SwiftLint is unavailable on Windows, and check_contribution.py was not run.
  • Not verified: Metal, a Ray Reconstruction GPU test, the matrix layout passed to Ray Reconstruction in motion, and Studio install/packaging staging (implemented but not run).
  • Accepted limitations (path tracing design §11):
    • Reprojection follows the camera, not per-object motion vectors.
    • Ray Reconstruction receives no specular hit distance, and its specular guide is F0.
    • First-hit rays use a small offset, so a proxy that differs from the raster model can shadow itself.
  • Sun shadows remain the main GPU cost in heavy Sponza views (about 2.8 ms). The approaches evaluated so far are recorded in the meshlet shadow design.

🤖 Generated with Claude Code

SpectralDragon and others added 4 commits November 9, 2026 19:13
Load (87.6 s -> ~8 s): decode and mip glTF textures on background threads
ahead of the serial GPU upload, LUT-based sRGB mip filter that reproduces
the previous rounding exactly, raw-byte alpha masks, concurrent external
image reads and material/mesh overlap for unbounded imports.

Hitches: the Windows loop slept with Task.sleep (15.6 ms ticks, occasional
~250 ms oversleep) at the 60 FPS fallback whenever an on-demand window
skipped a frame. Use a high-resolution waitable timer and the display's
idle cadence for visible display-synchronized windows.

Frame cost: main-pass bind groups include render attachments and were
recreated for every draw. Cache them for a few submissions after last use
(TransientResourceCache) and group opaque draws by material.

Also adds the approved pipelined-rendering design and the measurement report.

Pre-commit hook skipped at the user's request (SwiftLint has no Windows binary).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Includes the window overlay pass, diagnostics session and plugin, and
large-document text editor work. Fixes the overlay view's required
initializer, main-actor isolation of installWindowOverlayPass, and SPI
access to camera computed data in the UI render node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Editor project files, build targets, scene YAML payload decoding, code
highlighting state, agent MCP and Windows ACP integration, localization,
and the ada_tool CLI with its tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pipelined rendering (Documentation/Designs/pipelined-rendering.md):
- RenderPipelining lets a SceneView render world update one frame behind
  its simulation, with material snapshots taken at extraction.
- The SceneView render-texture pool is sized for frames in flight, so
  frames overlap the GPU instead of being skipped.
- ADAENGINE_GPU_TIMINGS=1 reports per-command-buffer GPU time on WebGPU.

Shadows:
- GPU-driven shadow batching (shadow caster registry, per-view GPU
  culling, multi-draw).
- Meshlet cluster culling for shadow views. Runs of visible clusters are
  merged into single draws, because tens of thousands of draws per Dawn
  multi-draw stall the D3D12 queue.

Path tracing (Documentation/Designs/path-tracing-denoising.md):
- The first hit comes from the raster G-buffer. PathTracingGeometry3D
  separates raster-only detail from traced proxies.
- History survives camera and light motion.
- A portable SVGF-style denoiser, and DLSS Ray Reconstruction through
  the Dawn/NGX bridge, selected by PathTracing3D.denoiser.

DLSS:
- The NGX runtime DLLs are found next to the executable or in the SDK
  (ADAENGINE_DLSS_SDK_PATH).
- Studio builds and packages stage nvngx_dlss.dll and nvngx_dlssd.dll.
- A missing runtime is reported as such.

Fixes:
- The shader cache rolls back the source hash of a stage that failed to
  compile, instead of reusing a stale translation forever.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-10T05:44:33.598274Z d01a30d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@SpectralDragon
SpectralDragon merged commit 8d8a0fb into main Nov 10, 2026
9 of 16 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d01a30df40

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +65 to +67
if let pending = pipelinedUpdate {
pipelinedUpdate = nil
try await pending.value

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the in-flight update visible until it finishes

When tooling access and the next frame both wait for an in-flight pipelined render, the first waiter clears pipelinedUpdate before suspending. The second waiter then sees no pending task and proceeds immediately. In particular, withWorldAccess can be awaiting this task while updateFrameContents starts another extraction into the still-running render world, violating the isolation boundary and allowing concurrent resource mutation. Keep the pending update discoverable by all waiters until completion, and ensure tooling safe-point acquisition remains serialized with incoming frames.

Useful? React with 👍 / 👎.

s.geometric=s.normal;
s.color=textureLod(sampler2D(u_Albedo,u_Nearest),uv,0).rgb;
s.emission=max(textureLod(sampler2D(u_Emission,u_Nearest),uv,0).rgb,vec3(0));
s.roughness=clamp(normalRoughness.w,0.04,1.0); s.metal=clamp(positionMetallic.w,0.0,1.0); s.receives=true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the first-hit surface's receive-shadows setting

For a path-traced mesh configured with receiveShadows: false, the new G-buffer first-hit path unconditionally sets s.receives=true, so sampleLight now traces occlusion rays and shadows that surface anyway. Previously, PathTracing3DScene encoded source.receiveShadows in the triangle flags and surfaceAt honored it. Carry the receive-shadows flag through the raster surface outputs and restore it here so enabling hybrid first hits does not change the authored shadow behavior.

Useful? React with 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant