This file provides guidance to AI agents when working with code in this repository.
A small GPU path tracer written in Rust + WGSL, using wgpu. It renders a fractal (a Mandelbulb-like distance-estimated field) via ray marching, accumulating samples across frames into an HDR texture. Runs natively (winit window) and is intended to also run in-browser via WebGPU/wasm (currently broken — see Known issues below).
Build and run natively (release mode is expected — debug is too slow for path tracing):
cargo run --release
Select a specific GPU interactively:
cargo run --release select
Select a GPU via environment variable (substring match on adapter name):
WGPU_ADAPTER_NAME=1080 cargo run --release
There are no automated tests in this repo, and no lint/format CI config — cargo check / cargo clippy can be used ad hoc but aren't wired into any workflow.
Build for web (wasm32 + wasm-bindgen, then serves via a local Python HTTP server on port 8000):
./build_and_run_web.sh
This requires rustup target add wasm32-unknown-unknown and a matching wasm-bindgen-cli version installed first (see comments at the top of the script for the exact version). Currently the web target is known not to run correctly (see readme).
The rendering logic is split cleanly between "boilerplate" (GPU/window setup) and the actual path tracer:
src/compute.wgsl— the entire path tracer. Takes a pixel coordinate, ray-marches a distance-estimated fractal field (map()), and returns a color via Monte Carlo path tracing (diffuse/specular/Fresnel bounces, depth of field, anti-aliasing, "bloom" via Phong-distributed blur). This is the only file where rendering/graphics logic lives; everything else is infrastructure to get pixels on screen.src/render.wgsl— trivial fullscreen-triangle shader that just blits the accumulated texture to the screen.src/main.rs— high-level app state (Application,GameState,CameraUniform) and camera/input handling. Defines theCameraUniformstruct that is uploaded to the GPU every frame and must stay byte-layout-identical to theCameraUniformstruct incompute.wgsl(same field order, no implicit padding — see comments in both files aboutVec3alignment).src/wgpu_boilerplate.rs— all directwgpuAPI calls: adapter/device setup, buffer/texture/bind-group/pipeline creation, dispatching the compute pass + render pass each frame, and reading a texture back to a PNG. Exposes aBoilerplatetrait implemented forWgpuDriver.src/window_boilerplate.rs—winitevent loop plumbing (RenderLooptrait), shared between native and wasm targets via#[cfg(target_arch = "wasm32")]branches.Applicationinmain.rsimplementsRenderLoop.
The renderer accumulates one sample per pixel per frame into a running sum, rather than re-rendering from scratch. Because a compute shader cannot read and write the same storage texture, two textures are used in ping-pong fashion (images: [Texture; 2] in WgpuObjects): each frame reads the previous frame's texture and writes the new accumulated sum into the other one, alternating by frames_start % 2. render.wgsl and compute.wgsl bind groups are precomputed for both orderings (compute_bind_groups, render_bundles, each length 2) to avoid rebuilding them every frame. The image's 4th (alpha) channel is repurposed as a running ray/sample count, not transparency — division by this count happens at PNG export time (in read_texture_to_png) for on-screen accumulation is implicit since more samples just get brighter and are not normalized on-screen. Any change to texture format/usage or to this alternation logic needs to be mirrored in both init() and resize() in main.rs.
IMAGE_RESOLUTION in main.rs can pin the render resolution independently of the window size (for rendering at higher quality than the display). When None, the image resolution tracks window resize events and all GPU resources (textures, bind groups, render bundles) are recreated in resize().
CameraUniform.v: Vec4 is a set of four free f32 slots read directly in compute.wgsl (cam.v.x/y/z/w) and used for ad hoc tuning of fractal coloring during development (see surface_info() in compute.wgsl). They're adjusted at runtime with the V/F, B/G, N/J, M/K key pairs in main.rs — this is a debug mechanism, not meaningful named config.
W/A/S/D/Space/LShift move the camera, arrow keys rotate it, Q/E adjust focus distance, Z/C adjust aperture, Right Shift scales all movement to 20% for fine control, P saves the current accumulated image as a PNG (also auto-saves at power-of-2 frame counts once past frame 32), R resets state. See readme.md for the full table.
- The program can crash on Intel integrated GPU drivers with a "parent device is lost" error — this is a driver bug, not something fixable in this codebase.
- The wasm/WebGPU build does not currently run correctly in-browser, despite
build_and_run_web.shexisting to build it.