Skip to content

feat(raytracing): share BVH tracing across Canvas2D, WebGL2, and WebGPU - #317

Merged
vshengbro merged 2 commits into
masterfrom
feat/raytrace-shared-bvh-backends
Oct 18, 2026
Merged

vshengbro merged 2 commits into
masterfrom
feat/raytrace-shared-bvh-backends

Conversation

@vshengbro

@vshengbro vshengbro commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Share one RayTraceRenderer API across Canvas2D, WebGL2, and WebGPU, with asynchronous initialization and synchronous rendering, scene updates, and resizing.
  • Add a flat BVH with 16-bin SAH construction, allocation-free iterative traversal, a bounded construction depth, and a linear path for small scenes.
  • Replace example-specific shaders with the shared engine backends and correct pause, resize, error reporting, and resource cleanup behavior.
  • Align generated accessor visibility with field visibility, use Getter for getter-only types, and bump the workspace version from 0.30.10 to 0.31.1.
  • Separate wrapped Markdown inline-code borders without shrinking their padding or changing code-block and table spacing.

Intersection correctness

  • Preserve the ray's original direction parameterization so intersection distances agree with Ray::at(t); use the full sphere quadratic with a = dot(direction, direction).
  • Handle negative directions and inside-origin rays correctly in AABB slab intersections, with outward geometric normals.
  • Reject degenerate and non-finite geometry.
  • Replace the dense-scene test's shared-helper oracle with independent intersection calculations and add inside-box regressions.

Backend fixes

Backend Changes
Canvas2D Upload through the actual Uint8ClampedArray backing the ImageData, rather than modifying the Rust-side copy returned by ImageData::data().
WebGL2 Copy packed data into upload staging, allocate textures on demand instead of reserving the full budget, and check GL errors and device limits explicitly.
WebGL2 Use ceiling-square-root texture layouts with capacity validation. For example, 601 slots under a 25-texel edge limit now fit in 25 by 25 rather than exceeding the limit at 24 by 26.
WebGPU Use the correct shader entry points, await pipeline creation, and propagate pipeline rejection, error-scope failures, and captured GPU errors.
WebGPU Destroy an uncommitted replacement buffer when bind-group creation fails, preserving the old bindings and capacity for retry.
Shared lifecycle Validate and resize the adapter before committing renderer dimensions, canvas dimensions, and pixel-buffer state. Reuse resources and report failures explicitly without automatic backend fallback.

The example now stops rendering while paused, consumes resize results, and owns timers, observers, and callbacks so navigation can release them.

Getter-only derive cleanup

  • Replace Data with Getter on FmtArgs and RayTraceScene, whose fields already explicitly disabled every mutable getter and setter.
  • Remove the 12 get_mut(skip) / set(skip) helper attributes no longer accepted by Getter; preserve all getter configuration and other derives.
  • Compare actual rustdoc JSON before and after the change: method names, signatures, visibility, and generic bounds match across the CLI host, engine host, and engine WASM surfaces. Both versions generate no mutators for these types.
  • Keep Data on types that still generate mutable getters or setters. No call-site-based API reduction is included.
  • The corresponding standards rule was connected to the local skill, full audit, and real global pre-commit hook before migration. Fixtures, real hook rejection/acceptance, and duplicate-skip behavior were verified against lombok-macros 10.2.7. These standards files live outside this repository and are not part of this PR's file diff.

Markdown inline-code spacing

  • Add margin: .15em 0 to inline code while preserving padding: .45em .4em.
  • Reset code margins in table cells, table headers, and preformatted blocks to zero.
  • Add three regression tests; all six Markdown CSS tests pass.
  • Rebuild the actual docs WASM and verify the served artifact. At viewport widths 390, 800, and 1280 pixels, the minimum border gap between consecutive wrapped code-chip lines changes from 0 to 4.1875 pixels. Table and code-block dimensions remain unchanged.

Performance

Both arms use identical harness source and the public closest_hit API, isolated build trees and target directories, and seven alternating passes. Values below are median nanoseconds per ray; scene construction is excluded. These measurements are retained from the earlier raytracing snapshot, not rerun for the subsequent getter/CSS cleanup.

Primitives Baseline ns/ray Current ns/ray Speedup Baseline/current spread Correctness
3 15.38 20.80 0.74x 3.4% / 5.7% Matching hits and checksum
128 580.27 200.00 2.90x 5.3% / 5.7% Matching hits and checksum
1024 4703.08 446.86 10.52x 3.0% / 6.8% Matching hits and checksum
4096 19019.66 570.48 33.34x 2.4% / 8.8% Matching hits and checksum

The three-primitive case regresses despite taking the existing linear shortcut. This remains a known limitation; this PR does not claim universal performance improvement. Background desktop load also limits how broadly the absolute timings should be interpreted.

Local verification

The following checks were rerun on the final source snapshot for the getter/CSS follow-up, with source and dependency fingerprints unchanged throughout execution:

Check Recorded result
audit_rust_standards.py 54/54 passed
Accessor-derive verifier and independent Syn inventory 1,447 Rust files scanned; zero remaining getter-only Data candidates
Accessor-rule self-test and hook wiring Passed, including duplicate-skip, arrow, tuple-field, and missing-attribute cases
Real global pre-commit hook Rejection/acceptance verified in scratch clones; exact final five-file payload accepted with zero new violations
cargo clippy --offline --workspace --all-targets --all-features -- -D warnings Exit 0
cargo fmt --all -- --check and crate fmt --check Exit 0; repeated formatting produced no source changes
cargo test --offline --workspace --all-features -- --nocapture 2,869 passed, 0 failed, 286 ignored
Release WASM workspace check, excluding the host-only euv-cli package Exit 0
Getter-only API parity using before/after rustdoc JSON Identical generated APIs across three host/WASM surfaces
Test-layout, dependency-order, and CI no-bump gates Passed; applicable fixers were idempotent
Actual docs build and spacing browser checks Passed at 390 / 800 / 1280 pixels

An earlier full accessor check inspected 2,504 generated accessors across core, UI, and engine with zero findings. That broader census is historical; the fresh compiler-boundary check above specifically verifies the two getter-only migrations. Macro-expansion checks complement source checks: getter, mutable-getter, and setter visibility must be verified independently.

Outstanding validation and limitations

  • Earlier three-backend browser acceptance recorded 189 passing checks, but it has not been rerun against the final source snapshot. That earlier result is not presented as fresh acceptance of the PR head.
  • Browser validation of the complete raytracing example, including pause/resume, adaptive resize, navigation cleanup, and return navigation, remains outstanding. The Markdown browser checks do not substitute for this validation.
  • Host tests skip browser-dependent cases, including WebGPU lifecycle behavior. Passing host checks do not substitute for executing these paths in a browser.
  • The three-primitive performance regression remains as reported above.

CPU 光追核心改为 flat BVH(16 箱 SAH,固定栈迭代遍历,零堆分配,
MAX_DEPTH=48 上限保证线性链不丢近端子节点),并修正精确求交:
t 保留在射线原始参数化下、球用完整二次式、AABB 负方向交换 near/far、
inside 判定为 t_enter<=0&&t_exit>=0、法线朝外、退化几何不再命中。

Canvas2D / WebGL2 / WebGPU 三后端共用 RayTraceRenderer,只有 init 是
async,其余为同步显式 Result。关键修复:
- Canvas: ImageData::data() 返回 Rust 侧副本,写入后 put_image_data
  仍 blit 初始透明黑;改为持有真实 Uint8ClampedArray 直接 copy_from
- WebGL: upload_packed 只 resize 未拷贝,上传的全是零,着色器读到
  node_count==0;改为逐元素拷贝并回填尾部归零;删除 512MiB 预分配
- WebGPU: pipeline 错误域此前被吞,init 在真实 GPUValidationError 下
  仍成功;现同时检查 promise、popErrorScope 调用与其结果、非空 GPUError
- WebGPU: 扩容绑定失败时新 buffer 泄漏(live 2->3);现失败即销毁新
  buffer,保留旧 binding/容量并可重试,错误不被清理结果覆盖
- resize 改为事务:validate -> adapter.resize -> 才提交 canvas 与像素缓冲
- WebGL packed_shape: floor sqrt 会产生 height 超限(cap25 601 slots ->
  24x26),改 ceil-sqrt,容量检查与形状一致,不再静默截断

性能(同 public closest_hit、同 harness 源码、7 轮交替中位数、
checksum 逐点一致):128 原语 2.90x、1024 原语 10.52x、4096 原语
33.34x;3 原语 0.74x(线性短路已覆盖,差额为新增的方向校验开销,
非 BVH 建树成本,如实记录)。

规范:53/53 PASS,workspace clippy -D warnings 0,fmt 0,
全仓测试 2866 passed / 0 failed,宏展开访问器 2504 个 0 违规。
@ghfind-review ghfind-review Bot added the review: high ghfind author score; see https://ghfind.com label Oct 17, 2026
@vshengbro vshengbro changed the title feat(raytracing): 共享 BVH 光追后端,Canvas2D/WebGL2/WebGPU 统一契约 feat(raytracing): share BVH tracing across Canvas2D, WebGL2, and WebGPU Oct 18, 2026
Replace getter-only Data derives while preserving generated APIs, add vertical spacing and regression tests for wrapped inline code, and bump the workspace to 0.31.1.
@vshengbro
vshengbro merged commit d9709dc into master Oct 18, 2026
8 checks passed
@vshengbro
vshengbro deleted the feat/raytrace-shared-bvh-backends branch October 18, 2026 02:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review: high ghfind author score; see https://ghfind.com

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant