Repository navigation
What the kernel trusts, held by the data: invariants in the builders, input finite where it enters, checks in tests - #79
Merged
Merged
Conversation
…s, input is finite where it enters, checks in tests and debug builds The kernel follows indices from buffers and checks none of them; the loop guard was the only thing between bad data and a dispatch that never ends. The references (pbrt-v4, Embree, Cycles, three-mesh-bvh, Falcor, the DXR and Vulkan specs, wgpu) hold the invariants in their builders, clean input where it enters, and keep structural checks to debug builds and tests. The same here: - bvh.rs: trees no deeper than the kernel's stack (48; where the levels left could not finish the split SAH wants, the median); items whose box is not finite in no leaf, after every leaf's range, their index kept; check_tree (moved from units.rs) on every tree in tests and debug builds; a build reports its depth (bvh_depth in the load timings). Bistro 18, Kabukicho 26, the district 33: no tree of today's scenes changes. - ltree.rs: LightTree::check (children after the parent, leaves inside the entries, each entry's code leads to its leaf) after every build, growth and refit in tests and debug builds. - Where data enters: World::set_geometry refuses positions that are not finite; a volume's majorant must be a finite number > 0 (Rust and packages/scene: Infinity passed both, and delta tracking never stepped); a world matrix that is not finite places its object at a point, as a hidden instance is; the hardware acceleration structures' vertices are finite (scene::blas_tri: a triangle with a vertex that is not becomes a degenerate one, its index kept). - Garbage-input tests for the BVH builder and the light tree (NaN, infinite and huge values, coincident and collinear items), a depth-cap test, the input checks' tests. - docs/RUNTIME.md "What the kernel trusts"; ROADMAP record. Checks: cargo test --release with the loop guard, 138 passed; render-identity against main guarded (native, merged, software traversal) and native: 0 of 190 changed; web-hello in Chrome passes. No shader changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…reduced box CI (x86_64) failed garbage_in_makes_a_tree_the_kernel_walks where this Mac passed it: a min or max with a NaN gives the NaN under NEON and the other operand under SSE, so on x86 a triangle with one NaN vertex got a finite box and went into a leaf. A triangle is now finite when its three vertices are, a box when its six numbers are; one that is not gets the empty box, which no leaf takes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The kernel follows indices it reads from buffers and checks none of them, so until now the loop guard was the only thing between bad data and a dispatch that never ends. I checked what the references do: pbrt-v4, Embree, Cycles, three-mesh-bvh, Falcor, the DXR and Vulkan specs, wgpu/naga/Tint. They all do three things:
None of them checks a tree before every upload, and none caps a traversal's iterations: the 2^64 loop bound that naga and Tint insert does not stop a hang, and clamping a bad child index can close a cycle. This PR does the same three things here.
What changes
Builders
bvh.rs: no tree is deeper than the kernel's 48-entry stack. Where the levels left could not finish the split SAH wants, the builder splits at the median instead.check_tree(moved here fromunits.rs) runs on every tree in tests and debug builds.bvh_depthin the load timings). Today's scenes: Bistro 18, Kabukichō 26, the district 33. No current tree changes.ltree.rs:LightTree::checkruns after every build, growth and refit in tests and debug builds. It checks that children come after their parent, that leaves stay inside the entries, and that each entry's code leads to its own leaf. Note that the light tree's walks end whatever the data, because their loops are bounded by the encoding. The risk the check covers is out-of-range reads.Where data enters
World::set_geometryrefuses positions that are not finite.packages/scene.Infinityused to pass both checks, and delta tracking then never stepped: the walk through the volume never ended.scene::instance_worldalready uses for hidden instances. Transforms are still kept as set.scene::blas_trimakes the hardware acceleration structures' vertices finite. DXR and Vulkan leave a non-finite vertex as undefined behaviour and define a degenerate triangle as never hit, so a triangle with a non-finite vertex becomes three vertices at the origin and keeps its index.Tests and docs
Checks
cargo test --releasewith the loop guard (GPU tests included): 138 passed.render-identity against main, 190 outputs each, guarded runs first:
THREERT_HWRT=0) + guardtools/web-hello.tsin Chrome: pass.Load time: the district's tree builds in 333 ms against main's 321 ms, one load each. An earlier load of the same build took 338 ms, so the difference is within run-to-run variation.
No shader changed.
🤖 Generated with Claude Code
Note
Medium Risk
Changes core BVH construction and scene-prep invariants that can affect traversal correctness and performance, though shaders are unchanged and identity checks report no output drift.
Overview
Shifts responsibility for safe GPU traversal from the browser loop guard alone to data the kernel consumes: BVH/light-tree builders enforce walk-ending structure, and inputs are sanitized where they enter the pipeline.
BVH (
bvh.rs) trees are capped to the kernel’s 48-entry stack, SAH splits fall back to median splits when depth would exceed the cap, triangles/boxes with non-finite bounds are excluded from leaves (kept inorderafter leaf ranges),check_treelives inbvh.rsand runs in tests/debug builds, and builds reportbvh_depthin load timings.Light tree (
ltree.rs) addsLightTree::check(topology, leaf ranges, path codes) after build/growth/refit in tests/debug.Input gates: finite geometry positions (
World::set_geometry), finite volume majorant > 0 (Rust +VolumeMeshin TS), non-finite world matrices collapse to a point (like hidden instances), andscene::blas_trireplaces non-finite BLAS vertices with origin degenerates for hardware RT upload paths.Adds garbage-input / depth-cap tests and documents the contract in
docs/RUNTIME.md(“What the kernel trusts”) anddocs/ROADMAP.md.Reviewed by Cursor Bugbot for commit b0672ee. Bugbot is set up for automated code reviews on this repo. Configure here.