Praxis

Field guides

Praxis is a 3D engine in Rust for large open-world games. Each guide teaches the ideas behind one part of it through the tickets and pull requests that built that part, with live demos. Each is dated: it names the commit it describes and says what has changed on main since.

Start with Instance Buffer Explained. The path it follows, from the ECS World through prepare and upload to the GPU, still holds, but its map of the frame is from 2026-10-19, when one render pass issued a draw call per Primitive of each Renderable; the frame on main, after the guides, lists its passes now. Each card names its milestone, tickets and pull requests.

  1. Instance Buffer Explained

    M3 · ticket #190 · PR #220

    Follows one frame from the ECS World to the draw call as PR #220 left it, then shows how each Renderable keeps a slot in an Instance buffer fixed at 524,288 slots. It covers what happens past that capacity, per-frame staging, GPU memory accounting, measuring performance, and tests that can fail.

    Read
  2. Distant Textures Explained

    M2 · ticket #153 · PRs #173, #174

    Starts from texels and mips, explains anisotropic filtering, and shows why distant normal maps need Toksvig. It walks through the minification and texture-wall Reference scenes and what it costs to keep 10,000 textures bound.

    Read
  3. HDR Pipeline Explained

    M3 · ticket #189 · PR #219

    Covers light units, exposure, fp16 render targets and tonemapping operators, which you can try in live demos. It also explains the MSAA resolve, dither, colour spaces and HDR displays, and sets out the seven decisions PR #219 put to the owner, all taken as recommended and carried out by #200.

    Read
  4. Acceleration Structures Explained

    M3 · ticket #202 · PRs #234, #235

    Shows how rays find triangles through a tree of boxes, then the two levels Praxis builds: a compacted BLAS per Mesh and a static TLAS anchored at load. It covers the memory the engine estimates, when the static TLAS rebuilds and why it holds 2^18 entries, and the shadow rays, dynamic TLAS, town and crowd that have built on the two structures since, with live demos.

    Read
  5. Depth Pyramid Explained

    M3 · ticket #198 · PRs #240, #241, #242

    Shows how Praxis reduces its 4x MSAA depth into a chain of ever coarser levels every frame, each texel the farthest depth over its share of the screen. It covers why reverse-Z keeps the minimum, how odd sizes share the screen, what reading every sample costs on the M4 Pro, and how phase two of the culling pass bounds any box with four reads, with live demos.

    Read
  6. Instance Culling Explained

    M3 · tickets #194, #196, #198 · PRs #228 to #230, #232, #239, #241, #242

    Follows the culling pass as it builds the frame's one indirect draw in slot order, tests each Instance against the view frustum, and culls in two phases against the depth pyramid. It covers the Render switch that turns culling off, the checks that culling never changes the image, and what each step costs, with live demos.

    Read
  7. Lit Pass Explained

    M3 · ticket #199 · PRs #243, #245, #246, #247

    Builds the lit pass's shading up one term at a time: what a BRDF is, metals and dielectrics in glTF 2.0's Appendix B, Fresnel, D and V, and why Praxis mixes them by lobe. It covers point lights that fall off by inverse square inside KHR_lights_punctual's range window, each scene's ambient and background, the oracles behind every term and what it all costs, with live demos.

    Read
  8. Tonemapping Pass Explained

    M3 · ticket #200 · PRs #250 to #256

    Follows a pixel from measured light to the 8-bit value on screen: photometric lights and an EV100 exposure, the weighted MSAA resolve, Gran Turismo 7's tonemapping curve and Peters's blue-noise dither. It covers why the tonemapping pass alone encodes sRGB, into an 8-bit UNORM target, with live demos.

    Read
  9. Anisotropy Explained

    M3 · ticket #184 · PR #257

    Explains how #184, measuring minification's floor, split Praxis's anisotropic filtering: 16x for colour textures, 4x for data textures such as normal maps. It covers the probe rule, what 16x cost on the M4 Pro, and why fewer probes removed the sparkle, with live demos.

    Read
  10. Froxel Lights Explained

    M3 · ticket #201 · PRs #261, #262, #263

    Shows how the Froxel pass lists each Froxel's point lights from the light buffer, so a pixel shades only the point lights that may reach it. It covers the oracle that checks the lists against brute force, and what the lists cost on each scene and where they pay, with live demos.

    Read
  11. Shadow Rays Explained

    M3 · tickets #203, #205, #260 · PRs #258, #259, #265, #277

    Traces one hard shadow ray per pixel toward each light: where it starts, where it ends, and why it moved out of the lit pass into the shadow mask's compute pass, with live demos.

    Read
  12. Dynamic TLAS Explained

    M3 · ticket #204 · PRs #264, #266

    Shows how Praxis traces shadow rays against Moving Instances: a second TLAS, rebuilt every frame from at most 10,000 of them, which a ray the static TLAS lets through is traced against. It covers the cap and which Instances it leaves out, timing the build on the CPU and the GPU, and what it costs on the orrery and on town, with live demos.

    Read
  13. GPU Ordering Explained

    M3 · ticket #244 · PRs #267, #270

    Follows a flaky test's missed ray to a race on Metal between the writes to a BLAS and the TLAS build that reads them. It shows how #244 narrowed the race down and why the fix waits for BLAS compaction on every backend, with live demos.

    Read
  14. Crowd Shadows Explained

    M3 · ticket #208 · PRs #269, #271, #274, #276

    Follows the crowd scene's 100 figures, which the GPU bends every frame from a rest Mesh, and why their shadows come from rigid per-bone shadow proxies, the form the owner picked for M7: a BLAS built when its Mesh loads keeps the rest shape, and rebuilding 100 figures' BLASes every frame would take 7.8 to 9.3 ms of GPU against a Budget of 1.0 ms. It covers the bend pass, Bends blended between Ticks, the 1,600 proxies in the dynamic TLAS and what they cost, with live demos.

    Read
  15. Town Explained

    M3 · ticket #206 · PRs #272, #273, #275

    Builds M3's dense Reference scene: a 1 × 1 km town placed by hash, 10,000 pigeons as Moving Instances, and a camera path from street level to a circle over the rooftops. It covers culling at target scale, counting lights per lit pixel without the side-effect trap, why the wires need 4x MSAA, and what town costs in its first M4 Pro Baseline, with live demos.

    Read
  16. BLAS Refit Explained

    M3 · ticket #209 · PRs #289, #290

    Follows the crowd's figures to form (a): each one's BLAS refitted every frame through wgpu-hal on Metal, which wgpu's safe API cannot do. It covers rebuild against refit, wgpu's three layers and the snatch lock, the unsafe block's invariants one by one, the Metal shared event that orders the dynamic TLAS's build after the refits, the test that compares refit with rebuild, and timing the refits with a marker pass, with live demos. The refit form's update took 1.37 to 1.38 ms of GPU against a Budget of 1.0 ms, and the owner picked the per-bone proxies for M7; the refit stays behind its Render switch as the reference for the shapes.

    Read
  17. Frame Timing Explained

    M3 · ticket #207 · PRs #298, #299

    Follows #207's first reading of ADR 0019's gate on town, and what a frame's GPU time counts when Metal runs passes and consecutive frames side by side: a span, not a sum. It covers what the gate times and why it leaves shadow rays out, its breach in town's Baseline at 9.8 ms against 6, which sends options B and D to prototypes, shadow-ray time as a difference of two runs, the dynamic TLAS build that hid behind the frame before and was counted twice, and checking frames far along a camera path without drawing them all, with live demos.

    Read
  18. Visibility Pre-pass Explained

    M3 · ticket #296 · prototype branch prototype/visibility-b

    Follows #296's prototype of ADR 0019's option B on town: a visibility ID pass that records which triangle owns each sample through primitive_index, and a compute pass that compacts the lit pass's indices to those triangles by prefix sum, in the frame's order. It covers why, on the M4 Pro, a triangle drawn without its coplanar neighbour covers fewer samples, so the compaction has to keep whole Meshlets to draw A's image, the measurement that reads 13.2 ms against A's 9.7 and the 6 ms gate, and why no free visibility pass would reach it, with live demos. The prototype stays on its branch.

    Read
  19. Specular Antialiasing Explained

    M3 · ticket #351 · PR #361

    Follows #351's fix for the sparkle on helmet's smoothest metals: highlights narrower than the turn the normal makes across a pixel, which one shading point per pixel catches or misses and 4x MSAA leaves alone. It covers filtering the distribution rather than the image, Tokuyoshi and Kaplanyan's widening of α² by the shading normal's screen-space derivatives, κ and the roughness floor, a white furnace that sums over half vectors so it works from the floor up, and the f32 error in D that furnace found, 1.1% of a white metal's light, fixed with |N × H|², with live demos. Together they cost town's lit pass 2.0%.

    Read
  20. Shadow Terminator Explained

    M3 · ticket #350 · PR #366

    Follows #350's fix for the terminator that stepped along a coarse smooth-shaded mesh's triangles, where the shadow mask hid each light from whole triangles that turn from it while their interpolated normals face it. It covers why skipping the ray there leaks light into cast shadows, the Surface normals the depth passes now write, each sample's triangle normal from screen-space derivatives and its interpolated normal, packed as 8-bit octahedral coordinates within 0.95°, and the ray that starts inside the surface and passes only the back face where it leaves its own mesh, with live demos. It costs town's GPU frame about 2.8 ms.

    Read
  21. Meshlet Culling Explained

    M3 · ticket #347 · PRs #363, #371

    Follows #347's two parts. The culling pass now writes the frame's packed indices with a workgroup per Meshlet record, dispatched indirectly with a count the GPU sums. It binds a stand-in buffer, since wgpu puts the indirect buffer and every bound group in one usage scope. The second part, a test of each Meshlet against the frustum, the depth pyramid and its normal cone, was prototyped and kept out of main. The guide covers the apex-free cone test and why any bounding sphere serves, why each Meshlet is tested once under Metal's fast math, and why town gained nothing: most of its Instances draw one Meshlet. With live demos.

    Read
  22. MSAA Fragment Merging Explained

    M3 · tickets #348, #369 · PRs #370, #373

    Follows #348's finding. Under 2x or 4x MSAA, when a fragment stage reads no flat input, the M4 Pro merges the fragments of index-sharing triangles, so a sample can show its neighbour's inputs while coverage stays exact. It covers how that cost option B's ID pass samples, and how it gave the shadow mask one triangle's normals while the lit pass shaded the other, which culling exposed. The fix, #369's, has the normals pass read a flat input, and the guide says why invariance could not do it and what stays open with one GPU measured. With live demos.

    Read
  23. Visibility Buffer Explained

    M3 · ticket #297 · PR #380

    Follows #297's prototype of option D, kept off main. The depth passes write each sample's triangle ID, and a compute material pass rebuilds each pixel's triangles and shades them in place of the lit pass. It covers the 16-bit halves and the phase bit, perspective-correct weights from the corners' clip x, y and w with no divide by w, gradients from neighbouring pixel centres and why the per-quad form matched the lit pass, and MSAA kept by shading each triangle once per pixel. On town the gate stayed within about 2% of A's, so A stays. With live demos.

    Read
  24. Edge Shadows Explained

    M3 · ticket #357 · PR #387

    Follows #357's fix of the one-pixel outline that shadowed silhouettes wore. The shadow mask pass finds the pixels whose samples show a second surface, by each sample's distance from the nearest sample's triangle plane, traces that surface too from a packed list in an indirect dispatch, and keeps its bits in a second layer; each fragment of the lit pass reads the layer of the surface its coverage shows. It covers surfaces against triangles, why tracing a minority of pixels in place costs a whole pass, and how to read Metal's overlapping pass times. With live demos.

    Read
  25. Box Leakage Explained

    M3 · tickets #359, #388 · PR #389

    Follows the arcs on minification's far floor, which #359 found in a reference that averages the checker exactly over each pixel. It covers sampling and Nyquist, the pixel's footprint as a box whose sinc response passes detail past Nyquist as a residue that flips sign and falls only as p/(πw), how one sample per pixel of that residue beats into arcs, why hardware anisotropic filtering is a box along the major axis and LOD bias barely helps, what a Gaussian does instead and what it blurs, and what #388 measured, with live demos. No filter has been chosen.

    Read
  26. Shadow Edge Rays Explained

    M3 · tickets #358, #364 · PR #394

    Follows #358's fix for sun shadow edges that stepped by whole pixels beside 4x MSAA geometry, from #364's survey: one ray per pixel is a point sample, and only more samples recover an edge inside one surface. The shadow mask pass now lists each pixel whose verdict for one of the first seven directional lights differs from a 4-neighbour's, traces that light again from its other samples, and keeps four verdict bits per light in the samples texel; the lit pass shades each fragment by the share of its covered samples the light reaches. It covers Whitted's edge-adaptive sampling and what it misses, the two new dispatches, the bit layout, and the cost: 2.86 ms of town's GPU frame, under a 4 ms ceiling. With live demos.

    Read
  27. Waiting on the GPU Explained

    M3 · ticket #381 · PR #400

    Follows #381's search for why every CPU stage of town ran 20 to 40% slower once the GPU became the longer half of the frame. With two frames in flight the main thread waits for the oldest frame every frame; timed through a fixed chain of 2,000 dependent multiply-adds, its M4 Pro core ran about 30% slower for the whole frame in most runs that blocked, and not when it spin-waited, on the same performance cluster and at priority 45 too. It covers frames in flight and the two ways to wait, Apple silicon's clusters and clocks, reading a clock without counters, the nine runs, how the kernel can cap the QoS class a process started from a shell asks for, and why praxis bench now spin-waits while the window blocks. With live demos.

    Read
  28. Bench History Explained

    Observability track · ticket #435 · PR #487

    Follows how Praxis keeps every bench run as a Bench record on the bench-history branch and finds where a frame time stepped. A record is keyed by a hash of the five paths that can change a measurement, so a docs-only commit inherits its record; a Harness version names what the metrics mean, and its ten versions so far break the charts' lines; only quiet, clean runs of main form a Series. Each publish marks a Suspect where a CPU or GPU frame median leaves the median of the 8 records before it, ± 4 median absolute deviations or 3%, once per step. It covers why the median and its absolute deviation resist one bad run, what the detector cannot see, and what #485 and #486 add. With a live demo.

    Read

One frame as the main branch runs it at commit 270c5e4 (2026-10-26), with the guide that explains each step. On the CPU, prepare reads the ECS World into Frame data, and upload writes the frame's uniforms, lights and Instance writes into per-frame staging (Instance Buffer Explained). Upload records their copies into GPU buffers at the head of the frame's commands, with the static TLAS's rebuild in a frame that writes or frees a static Instance (Acceleration Structures Explained). The GPU receives the steps below in this order, though on Metal a step can run beside the steps around it, or even start before an earlier one, where neither writes what the other binds:

  1. The dynamic TLAS's build over the frame's Moving Instances, in frames that have any and once more, empty, when the last one goes, in a command buffer submitted ahead of the frame's commands (Dynamic TLAS Explained).
  2. The bend pass, in frames that bend Meshes, which only the crowd scene does: each figure bent from its rest Mesh (Crowd Shadows Explained). With the crowd's refit form, chosen by a Render switch and available on Metal only, the frame's commands up to the bend pass are submitted first, with the refit of each figure's BLAS, and the dynamic TLAS's build follows them (BLAS Refit Explained).
  3. Phase one of the culling pass, which builds the draw of the Instances in view that were visible the frame before (Instance Culling Explained).
  4. The Froxel pass, which lists for each Froxel the point lights that may reach it (Froxel Lights Explained).
  5. The depth pre-pass, which draws phase one's Instances into the 4x MSAA depth target, and with shadow rays on each sample's surface normals beside it (Instance Culling Explained; the normals since #350).
  6. The depth pyramid pass, which reduces that depth, level by level, to the farthest depth over each texel's share of the screen (Depth Pyramid Explained).
  7. Phase two of the culling pass, which builds the draw of the other Instances in view that the pyramid does not hide (Instance Culling Explained).
  8. With shadow rays on, as Praxis ships: phase two's depth pass, which draws phase two's Instances into depth and the surface normals, then the shadow mask pass, which traces each pixel's shadow rays against both TLAS into the shadow mask (Shadow Rays Explained), and, in a second dispatch, those of each edge pixel's second surface into the mask's second layer (Edge Shadows Explained; since #357), then, in two more, lists the pixels where a directional light's verdict differs from a neighbour's and traces that light again from their other samples (Shadow Edge Rays Explained; since #358).
  9. The lit pass, which shades both phases' Instances, reading each pixel's point lights from its Froxel's list and its shadows from the shadow mask, and resolves its 4x MSAA colour into the HDR target (Lit Pass Explained).
  10. The tonemapping pass, which maps the HDR target through GT7, encodes it as sRGB and dithers it into the 8-bit target (Tonemapping Pass Explained).
  11. With the count_point_lights Render switch on, which ships off: the light count pass, which counts each lit pixel's point lights for the GPU stats (Town Explained).

Four guides explain what these passes rest on: Distant Textures Explained and Anisotropy Explained, how Praxis cooks and samples textures; HDR Pipeline Explained, the survey behind the light units, the HDR target and the tonemapping; and GPU Ordering Explained, why a frame that loads Meshes first waits for their BLASes to be compacted.