embree3/bvh.rs
1//! Safe wrapper over embree's standalone BVH builder (`rtcBuildBVH` +
2//! `rtcThreadLocalAlloc`).
3//!
4//! Unlike the scene BVH that `rtcCommitScene` builds implicitly, this is a
5//! *bring-your-own-node-layout* builder: embree decides the tree topology and
6//! the build heuristic, and for every node it produces it calls back to
7//! allocate and fill the caller's own node type in embree's arena. The caller
8//! therefore owns the in-memory layout and its own traversal; embree owns only
9//! the structure.
10//!
11//! # Entry point
12//!
13//! [`Device::create_bvh`](crate::Device::create_bvh) returns a [`Bvh`]; a build
14//! runs through [`Bvh::build_scoped`], driven by a caller-supplied
15//! [`BvhBuilder`]. The resulting tree is navigated inside the build scope
16//! through [`BvhResult`].
17//!
18//! # Design notes
19//!
20//! - **Generative scope.** `build_scoped` takes a `for<'id>
21//! FnOnce(BvhResult<'id, B>)` closure, so the branded lifetime `'id` is
22//! unique per call: a [`NodePtr`] handle cannot escape the closure or be
23//! resolved against a different build, and a handle becomes a `&Node` only
24//! through [`BvhResult::resolve`]. A plain invariant lifetime would not be
25//! enough: two independent builds could be inferred to share one lifetime,
26//! letting a handle from one resolve against the other's arena; the
27//! `for<'id>` bound makes `'id` fresh per call so the compiler cannot unify
28//! two builds.
29//!
30//! - **Arena memory.** Nodes are bump-allocated through [`Allocator`]
31//! (`rtcThreadLocalAlloc`) and freed wholesale when the [`Bvh`] drops;
32//! **`Drop` never runs on a node**. The [`BvhNode`] bound (`Copy + Send +
33//! Sync`, no owned resources) is what makes that sound. Child links are
34//! opaque [`NodePtr`] handles, never live references held during the build.
35//!
36//! - **Threaded callbacks.** Embree invokes the [`BvhBuilder`] methods from
37//! many worker threads (hence the `Send + Sync` bound). The `extern "C"`
38//! trampolines recover the builder from a raw `userPtr` (`BuildState`) and
39//! rebrand the callback-local pointers to the result's `'id`
40//! (`build_with_brand`); the soundness invariant is that every pointer in one
41//! callback belongs to the build identified by that callback's `userPtr`.
42//!
43//! - **Version-gated.** The safe path relies on a per-node setter-serialization
44//! property audited for embree 3.13.5, checked at runtime against the linked
45//! library's version.
46//!
47//! See [`BvhBuilder`] for the callback contract and a worked example.
48use std::{
49 marker::PhantomData,
50 os::raw::{c_uint, c_void},
51 panic::{catch_unwind, AssertUnwindSafe},
52 ptr::NonNull,
53};
54
55use crate::{sys::*, Bounds, BuildPrimitive, BuildQuality, Device, DeviceProperty, Error};
56
57/// A reference-counted standalone BVH (`RTCBVH`). Build target for
58/// [`Bvh::build_scoped`].
59///
60/// Not `Clone`: the result of a build borrows the `Bvh` exclusively, and a
61/// second handle could rebuild and free the arena out from under a live result.
62pub struct Bvh {
63 device: Device,
64 handle: RTCBVH,
65}
66
67impl Drop for Bvh {
68 fn drop(&mut self) { unsafe { rtcReleaseBVH(self.handle) } }
69}
70
71impl Bvh {
72 pub(crate) fn new(device: &Device) -> Result<Self, Error> {
73 let handle = unsafe { rtcNewBVH(device.handle) };
74 if handle.is_null() {
75 Err(device.get_error())
76 } else {
77 Ok(Self {
78 device: device.clone(),
79 handle,
80 })
81 }
82 }
83}
84
85/// Marker for a type that may live in the BVH arena.
86///
87/// # Safety
88///
89/// The arena frees node memory in bulk without running `Drop`, and embree
90/// creates/mutates nodes across worker threads. Implementors must own no
91/// external resources (a raw pointer to an owned heap allocation would leak; an
92/// embedded foreign reference could dangle) and must be sound to construct and
93/// mutate across threads. A node should contain only plain `Copy` data,
94/// [`Bounds`], and [`NodePtr`] handles from the same build.
95///
96/// **Family-level rebrand requirement.** This trait is implemented across all
97/// lifetimes (`unsafe impl<'id> BvhNode for MyNode<'id>`), and that blanket
98/// impl is what makes the builder's *rebrand* sound: nodes are allocated at a
99/// callback-local lifetime and the wrapper later reinterprets the root as
100/// [`BvhBuilder::Node`]`<'id>` for the result's brand. For any lifetimes `'a`
101/// and `'b`, `MyNode<'a>` and `MyNode<'b>` must therefore be **layout-identical
102/// and safely rebrandable**. That holds automatically for any type meeting the
103/// rule above: Rust layout never depends on a lifetime, and the only permitted
104/// lifetime-carrying members are `NodePtr` phantom brands (which rebrand for
105/// free). Do not embed any other lifetime-dependent state.
106pub unsafe trait BvhNode: Copy + Send + Sync {}
107
108/// Opaque, generatively branded handle to an arena node. `Copy`, pointer-sized,
109/// and not dereferenceable except through [`BvhResult::resolve`] inside the
110/// build scope.
111#[repr(transparent)]
112pub struct NodePtr<'id, N> {
113 ptr: NonNull<N>,
114 _brand: PhantomData<fn(&'id ()) -> &'id ()>,
115}
116
117impl<'id, N> Clone for NodePtr<'id, N> {
118 fn clone(&self) -> Self { *self }
119}
120impl<'id, N> Copy for NodePtr<'id, N> {}
121
122// Deliberate: an opaque non-dereferenceable handle is safe to move/share (the
123// brand still confines it to the scope).
124unsafe impl<'id, N> Send for NodePtr<'id, N> {}
125unsafe impl<'id, N> Sync for NodePtr<'id, N> {}
126
127/// Per-callback-thread bump allocator over the BVH arena
128/// (`rtcThreadLocalAlloc`). `!Send + !Sync`: valid only on its current callback
129/// thread.
130pub struct Allocator<'id> {
131 raw: RTCThreadLocalAllocator,
132 _brand: PhantomData<&'id ()>,
133 _not_send_sync: PhantomData<*mut ()>,
134}
135
136impl<'id> Allocator<'id> {
137 unsafe fn from_raw(raw: RTCThreadLocalAllocator) -> Self {
138 Self {
139 raw,
140 _brand: PhantomData,
141 _not_send_sync: PhantomData,
142 }
143 }
144
145 /// Bump-allocate one `T` in the arena and move `value` into it.
146 ///
147 /// ZSTs are rejected **unconditionally** (also in release): embree relies
148 /// on node-pointer identity, and a zero-size allocation would alias
149 /// every node to one address. This runs behind FFI, so it aborts rather
150 /// than unwinds. (`build_with_brand` also rejects a ZST `Node` up front
151 /// with a clean `Err`; this is the defensive backstop for any other
152 /// `alloc::<T>` call.)
153 pub fn alloc<T: Copy>(&self, value: T) -> &'id mut T {
154 if std::mem::size_of::<T>() == 0 {
155 std::process::abort();
156 }
157 let layout = std::alloc::Layout::new::<T>();
158 let p = unsafe { rtcThreadLocalAlloc(self.raw, layout.size(), layout.align()) } as *mut T;
159 if p.is_null() {
160 // No unwinding across FFI: OOM aborts.
161 std::process::abort();
162 }
163 unsafe {
164 p.write(value);
165 &mut *p
166 }
167 }
168}
169
170/// Checked, view-scoped accessor over embree's `void**` child array.
171pub struct Children<'id, N> {
172 ptr: *const *mut c_void,
173 len: usize,
174 _m: PhantomData<NodePtr<'id, N>>,
175}
176
177impl<'id, N> Children<'id, N> {
178 unsafe fn from_raw(ptr: *mut *mut c_void, len: usize) -> Self {
179 Self {
180 ptr: ptr as *const *mut c_void,
181 len,
182 _m: PhantomData,
183 }
184 }
185 /// The number of children stored in the node.
186 pub fn len(&self) -> usize { self.len }
187 /// Whether the node has no children.
188 pub fn is_empty(&self) -> bool { self.len == 0 }
189 /// The `i`th child handle, or `None` if out of range.
190 pub fn get(&self, i: usize) -> Option<NodePtr<'id, N>> {
191 if i >= self.len {
192 return None;
193 }
194 let raw = unsafe { *self.ptr.add(i) };
195 NonNull::new(raw as *mut N).map(|ptr| NodePtr {
196 ptr,
197 _brand: PhantomData,
198 })
199 }
200}
201
202/// Checked accessor over embree's `RTCBounds*` array. References borrow the
203/// view (the bounds may live in callback-local storage).
204pub struct ChildBounds<'a> {
205 ptr: *const *const RTCBounds,
206 len: usize,
207 _m: PhantomData<&'a Bounds>,
208}
209
210impl<'a> ChildBounds<'a> {
211 unsafe fn from_raw(ptr: *mut *const RTCBounds, len: usize) -> Self {
212 Self {
213 ptr: ptr as *const *const RTCBounds,
214 len,
215 _m: PhantomData,
216 }
217 }
218 /// The number of child bounding boxes in the view.
219 #[inline]
220 pub fn len(&self) -> usize { self.len }
221 /// Whether the view has no child bounds.
222 #[inline]
223 pub fn is_empty(&self) -> bool { self.len == 0 }
224 /// The `i`th child's bounds, borrowed from this view, or `None` if out of
225 /// range.
226 #[inline]
227 pub fn get(&self, i: usize) -> Option<&Bounds> {
228 if i >= self.len {
229 return None;
230 }
231 unsafe { (*self.ptr.add(i)).as_ref() }
232 }
233}
234
235/// Materializes embree's BVH topology into a caller-defined node layout.
236///
237/// [`Bvh::build_scoped`] runs embree's builder, which decides the tree's shape
238/// and, for every node it produces, calls back into this trait to allocate and
239/// fill *your* node type in embree's arena. You own the in-memory layout (and
240/// therefore your own traversal); embree owns only the topology and the build
241/// heuristic. This is the safe wrapper over embree's `rtcBuildBVH` callback
242/// API.
243///
244/// # Callback lifecycle
245///
246/// For each interior node, embree calls (in this order, serialized per node):
247/// 1. [`create_node`](Self::create_node) - allocate the node; *its children do
248/// not exist yet*.
249/// 2. (embree recurses and builds the children.)
250/// 3. [`set_bounds`](Self::set_bounds) - store each child's bounding box into
251/// the node.
252/// 4. [`set_children`](Self::set_children) - store each child's handle into the
253/// node.
254///
255/// For each leaf, embree calls [`create_leaf`](Self::create_leaf) with the
256/// primitives that fall into it. When [`SPATIAL_SPLITS`](Self::SPATIAL_SPLITS)
257/// is enabled (and quality is `HIGH`), [`split`](Self::split) may be called to
258/// divide a primitive across a plane; when [`PROGRESS`](Self::PROGRESS) is
259/// enabled, [`progress`](Self::progress) is called to report.
260///
261/// # Threading
262///
263/// Embree invokes these callbacks from **many worker threads**, so `Self` is
264/// `Send + Sync` and every method takes a shared `&self`. Any per-build mutable
265/// state must therefore live behind atomics or locks, not `&mut self`. (Embree
266/// does not invoke two callbacks for the *same* node concurrently; this is the
267/// one audited assumption noted in the crate docs.)
268///
269/// # Memory
270///
271/// Nodes are bump-allocated in embree's arena via the [`Allocator`] handed to
272/// `create_node`/`create_leaf`, and the whole arena is freed at once when the
273/// [`Bvh`] is dropped - **`Drop` never runs on a node**. The [`BvhNode`] bound
274/// enforces what that requires (`Copy`, `Send + Sync`, no owned resources).
275/// Child links are opaque, scope-branded [`NodePtr`] handles; you can only turn
276/// one back into a `&Node` after the build, through [`BvhResult::resolve`].
277///
278/// # Example
279///
280/// ```no_run
281/// use embree3::{
282/// Allocator, Bounds, BuildConfig, BuildPrimitive, BvhBuilder, BvhNode, ChildBounds, Children,
283/// Device, NodePtr,
284/// };
285///
286/// const EMPTY: Bounds = Bounds {
287/// lower_x: 0.0,
288/// lower_y: 0.0,
289/// lower_z: 0.0,
290/// align0: 0.0,
291/// upper_x: 0.0,
292/// upper_y: 0.0,
293/// upper_z: 0.0,
294/// align1: 0.0,
295/// };
296///
297/// // A single, generatively branded node type (one constant `N` sizes both the child
298/// // array and `MAX_CHILDREN`). Inner children are `Option` because they are filled in
299/// // `set_children`, after `create_node` has already returned the node.
300/// const N: usize = 2;
301/// #[derive(Clone, Copy)]
302/// enum Node<'id> {
303/// Inner {
304/// bounds: [Bounds; N],
305/// kids: [Option<NodePtr<'id, Node<'id>>>; N],
306/// },
307/// Leaf {
308/// prim_id: u32,
309/// },
310/// }
311/// unsafe impl<'id> BvhNode for Node<'id> {}
312///
313/// struct Builder;
314/// impl BvhBuilder for Builder {
315/// type Node<'id> = Node<'id>;
316/// const MAX_CHILDREN: usize = N;
317///
318/// fn create_node<'id>(&self, a: &Allocator<'id>, _n: usize) -> &'id mut Node<'id> {
319/// a.alloc(Node::Inner {
320/// bounds: [EMPTY; N],
321/// kids: [None; N],
322/// })
323/// }
324/// fn set_children<'id>(&self, node: &mut Node<'id>, c: Children<'id, Node<'id>>) {
325/// if let Node::Inner { kids, .. } = node {
326/// for i in 0..c.len().min(N) {
327/// kids[i] = c.get(i);
328/// }
329/// }
330/// }
331/// fn set_bounds<'id>(&self, node: &mut Node<'id>, b: ChildBounds<'_>) {
332/// if let Node::Inner { bounds, .. } = node {
333/// for i in 0..b.len().min(N) {
334/// if let Some(cb) = b.get(i) {
335/// bounds[i] = *cb;
336/// }
337/// }
338/// }
339/// }
340/// fn create_leaf<'id>(
341/// &self,
342/// a: &Allocator<'id>,
343/// prims: &[BuildPrimitive],
344/// ) -> &'id mut Node<'id> {
345/// // With `max_leaf_size = 1` each leaf holds one primitive.
346/// a.alloc(Node::Leaf {
347/// prim_id: prims[0].primID,
348/// })
349/// }
350/// }
351///
352/// let device = Device::new().unwrap();
353/// let mut bvh = device.create_bvh().unwrap();
354/// let mut prims: Vec<BuildPrimitive> = Vec::new(); // fill with your primitive AABBs
355/// let cfg = BuildConfig {
356/// max_leaf_size: 1,
357/// ..Default::default()
358/// };
359///
360/// // Navigate the tree inside the scope; handles cannot escape `f`.
361/// let has_root = bvh
362/// .build_scoped(&cfg, &mut prims, &Builder, |r| r.root().is_some())
363/// .unwrap();
364/// ```
365pub trait BvhBuilder: Send + Sync {
366 /// The arena node type, generatively branded by the build scope `'id`.
367 ///
368 /// Almost always an `enum { Inner { .. }, Leaf { .. } }`. Because
369 /// [`create_node`](Self::create_node) runs before a node's children exist,
370 /// the inner variant stores child handles as `[Option<NodePtr<'id,
371 /// Self::Node<'id>>>; N]` (filled in
372 /// [`set_children`](Self::set_children)); `Option<NodePtr>` is
373 /// niche-optimized to a single pointer, so the `Option` costs nothing.
374 /// `N` is a concrete constant that must equal
375 /// [`MAX_CHILDREN`](Self::MAX_CHILDREN).
376 ///
377 /// The `+ 'id` bound makes `&'id Node<'id>` well-formed; see [`BvhNode`]
378 /// for the family-level rebrand requirement the builder relies on.
379 type Node<'id>: BvhNode + 'id;
380
381 /// Maximum number of children an inner node can hold, i.e. the length of
382 /// the node's child array.
383 ///
384 /// [`Bvh::build_scoped`] rejects a `BuildConfig::max_branching_factor`
385 /// greater than this, so [`set_children`](Self::set_children) can never
386 /// receive more children than the array holds.
387 const MAX_CHILDREN: usize;
388
389 /// Enable spatial splits. When `true`, the [`split`](Self::split) callback
390 /// is registered and embree may split primitives across node boundaries
391 /// (only at `BuildQuality::HIGH`); [`Bvh::build_scoped`] reserves the
392 /// extra primitive-array capacity this needs. Leaving it `false` (the
393 /// default) registers no split callback, so build behavior is unchanged.
394 const SPATIAL_SPLITS: bool = false;
395
396 /// Enable progress reporting through [`progress`](Self::progress). Default
397 /// `false` (no progress callback is registered).
398 const PROGRESS: bool = false;
399
400 /// Allocate an interior node that will have `child_count` children
401 /// (`child_count <= `[`MAX_CHILDREN`](Self::MAX_CHILDREN)) and return it.
402 ///
403 /// Allocate in the arena with [`Allocator::alloc`] and return the
404 /// reference. The children do **not** exist yet, so initialize the
405 /// child slots to a placeholder (e.g. `[None; N]`);
406 /// [`set_bounds`](Self::set_bounds) and
407 /// [`set_children`](Self::set_children) fill them once the children
408 /// have been built.
409 fn create_node<'id>(
410 &self,
411 alloc: &Allocator<'id>,
412 child_count: usize,
413 ) -> &'id mut Self::Node<'id>;
414
415 /// Store the handles of `node`'s children into `node`.
416 ///
417 /// The children were produced earlier by [`create_node`](Self::create_node)
418 /// / [`create_leaf`](Self::create_leaf). `children` is a checked,
419 /// scope-branded view: `children.get(i)` yields the `i`th child's
420 /// [`NodePtr`] (or `None` past the end). Copy the handles into the
421 /// node's child array; a handle can only be dereferenced after the
422 /// build, through [`BvhResult::resolve`].
423 fn set_children<'id>(
424 &self,
425 node: &mut Self::Node<'id>,
426 children: Children<'id, Self::Node<'id>>,
427 );
428
429 /// Store the bounding boxes of `node`'s children into `node`.
430 ///
431 /// `bounds.get(i)` borrows the `i`th child's [`Bounds`] **from the view** -
432 /// embree may keep it in callback-local storage, so copy the value out
433 /// rather than retaining the reference.
434 fn set_bounds<'id>(&self, node: &mut Self::Node<'id>, bounds: ChildBounds<'_>);
435
436 /// Allocate a leaf node over `prims` and return it.
437 ///
438 /// `prims` is the complete set of primitives in this leaf
439 /// (`1 ..= BuildConfig::max_leaf_size`). The leaf must represent **all** of
440 /// them (store their `primID`s, an index range, etc.) - keeping only
441 /// `prims[0]` silently drops the rest unless you build with
442 /// `max_leaf_size == 1`. Allocate via [`Allocator::alloc`]; a
443 /// leaf cannot own heap memory (see [`BvhNode`]).
444 fn create_leaf<'id>(
445 &self,
446 alloc: &Allocator<'id>,
447 prims: &[BuildPrimitive],
448 ) -> &'id mut Self::Node<'id>;
449
450 /// Split a primitive's bounding box by the plane `dim = pos`, returning its
451 /// `(left, right)` sub-boxes.
452 ///
453 /// Only called when [`SPATIAL_SPLITS`](Self::SPATIAL_SPLITS) is `true`.
454 /// `dim` is the axis (`0 = x`, `1 = y`, `2 = z`). The default clips the
455 /// primitive's box at `pos` along `dim` (a valid, conservative
456 /// geometric split); override it for tighter, geometry-aware splits.
457 fn split(&self, prim: &BuildPrimitive, dim: u32, pos: f32) -> (Bounds, Bounds) {
458 let lo_full = bounds_of(prim);
459 let mut lo = lo_full;
460 let mut hi = lo_full;
461 set_axis_upper(&mut lo, dim, pos);
462 set_axis_lower(&mut hi, dim, pos);
463 (lo, hi)
464 }
465
466 /// Report build progress, with `fraction` advancing toward `1.0`.
467 ///
468 /// Only called when [`PROGRESS`](Self::PROGRESS) is `true`. **Report
469 /// only:** embree 3.13.5's standalone builder discards the callback's
470 /// result, so this cannot cancel the build (hence the `()` return
471 /// rather than a `bool`).
472 fn progress(&self, _fraction: f64) {}
473}
474
475/// Build settings. `Default` reproduces `rtcDefaultBuildArguments`.
476#[derive(Clone, Debug)]
477pub struct BuildConfig {
478 /// Build quality / speed-vs-quality trade-off.
479 pub quality: BuildQuality,
480 /// Optimize the build for fast *rebuilds* of dynamic scenes, at the cost of
481 /// higher memory use (embree's `RTC_BUILD_FLAG_DYNAMIC`). `false`
482 /// builds for best query performance.
483 pub dynamic: bool,
484 /// Maximum number of children per interior node. Must not exceed the
485 /// builder's [`MAX_CHILDREN`](BvhBuilder::MAX_CHILDREN).
486 pub max_branching_factor: u32,
487 /// Maximum depth of the tree.
488 pub max_depth: u32,
489 /// Number of primitives per SAH evaluation block.
490 pub sah_block_size: u32,
491 /// Minimum number of primitives in a leaf.
492 pub min_leaf_size: u32,
493 /// Maximum number of primitives in a leaf.
494 pub max_leaf_size: u32,
495 /// Estimated cost of traversing one node, relative to `intersection_cost`
496 /// (tunes the SAH).
497 pub traversal_cost: f32,
498 /// Estimated cost of one primitive intersection, relative to
499 /// `traversal_cost` (tunes the SAH).
500 pub intersection_cost: f32,
501}
502
503impl Default for BuildConfig {
504 fn default() -> Self {
505 Self {
506 quality: BuildQuality::MEDIUM,
507 dynamic: false,
508 max_branching_factor: 2,
509 max_depth: 32,
510 sah_block_size: 1,
511 min_leaf_size: 1,
512 max_leaf_size: 32,
513 traversal_cost: 1.0,
514 intersection_cost: 1.0,
515 }
516 }
517}
518
519impl BuildConfig {
520 fn validate(&self, prim_count: usize, max_children: usize) -> Result<(), Error> {
521 let bad = Err(Error::INVALID_ARGUMENT);
522 match self.quality {
523 BuildQuality::LOW | BuildQuality::MEDIUM | BuildQuality::HIGH => {}
524 _ => return bad,
525 }
526 let branch_cap = if self.quality == BuildQuality::LOW {
527 8
528 } else {
529 16
530 };
531 if self.max_branching_factor < 2
532 || self.max_branching_factor > branch_cap
533 || self.max_branching_factor as usize > max_children
534 {
535 return bad;
536 }
537 if self.max_leaf_size < 1 || self.max_leaf_size > 32 {
538 return bad;
539 }
540 if self.min_leaf_size < 1 || self.min_leaf_size > self.max_leaf_size {
541 return bad;
542 }
543 if self.sah_block_size < 1 || self.max_depth < 1 {
544 return bad;
545 }
546 if !self.traversal_cost.is_finite()
547 || self.traversal_cost <= 0.0
548 || !self.intersection_cost.is_finite()
549 || self.intersection_cost <= 0.0
550 {
551 return bad;
552 }
553 if self.quality == BuildQuality::LOW && prim_count > u32::MAX as usize {
554 return bad;
555 }
556 Ok(())
557 }
558}
559
560/// Result of a build, valid only inside the generative scope. Branded by `'id`.
561pub struct BvhResult<'id, B: BvhBuilder> {
562 root: Option<NodePtr<'id, B::Node<'id>>>,
563 _brand: PhantomData<fn(&'id ()) -> &'id ()>,
564}
565
566impl<'id, B: BvhBuilder> BvhResult<'id, B> {
567 /// The root node, or `None` for an empty tree (zero primitives).
568 pub fn root(&self) -> Option<&B::Node<'id>> { self.root.map(|p| unsafe { p.ptr.as_ref() }) }
569 /// The root handle.
570 pub fn root_ptr(&self) -> Option<NodePtr<'id, B::Node<'id>>> { self.root }
571 /// Resolve a child handle to a node reference. Safe with no runtime check:
572 /// the `'id` brand proves the handle came from this same build, and the
573 /// arena cannot have been freed or rebuilt -- the generative `build_scoped`
574 /// closure that produced this [`BvhResult`] holds the `&mut Bvh`
575 /// exclusively for its whole duration, so no rebuild or drop can run
576 /// while `'id` is live. (The returned reference borrows `self`, tying
577 /// it to the result handle.)
578 pub fn resolve(&self, p: NodePtr<'id, B::Node<'id>>) -> &B::Node<'id> {
579 unsafe { p.ptr.as_ref() }
580 }
581}
582
583/// Internal state passed through `userPtr`. The builder is held as a raw
584/// pointer (valid for the synchronous `rtcBuildBVH` call by the rebrand
585/// invariant), which avoids a `B: 'x` outlives requirement when a trampoline
586/// recovers it at a fresh lifetime. The generative brand lives only on
587/// [`BvhResult`], not here.
588struct BuildState<B> {
589 builder: *const B,
590}
591
592fn catch_abort<R>(f: impl FnOnce() -> R) -> R {
593 match catch_unwind(AssertUnwindSafe(f)) {
594 Ok(r) => r,
595 Err(_) => std::process::abort(),
596 }
597}
598
599// Trampolines.
600// Each recovers `&BuildState` then rebrands all pointers at one inferred
601// lifetime 'x (the unsafe invariant: every pointer in one synchronous callback
602// belongs to the in-flight build whose BuildState is `userPtr`).
603
604// Recover the builder reference from `userPtr`. The returned `&B` lives for an
605// inferred lifetime; soundness is the rebrand invariant (the pointer belongs to
606// the in-flight build).
607#[inline]
608unsafe fn builder_of<'x, B: BvhBuilder>(user: *mut c_void) -> &'x B {
609 &*((*(user as *const BuildState<B>)).builder)
610}
611
612unsafe extern "C" fn create_node_tramp<B: BvhBuilder>(
613 alloc: RTCThreadLocalAllocator,
614 child_count: c_uint,
615 user: *mut c_void,
616) -> *mut c_void {
617 catch_abort(|| unsafe {
618 let builder = builder_of::<B>(user);
619 let allocator = Allocator::from_raw(alloc);
620 let node = builder.create_node(&allocator, child_count as usize);
621 node as *mut _ as *mut c_void
622 })
623}
624
625unsafe extern "C" fn create_leaf_tramp<B: BvhBuilder>(
626 alloc: RTCThreadLocalAllocator,
627 prims: *const RTCBuildPrimitive,
628 prim_count: usize,
629 user: *mut c_void,
630) -> *mut c_void {
631 catch_abort(|| unsafe {
632 let builder = builder_of::<B>(user);
633 let allocator = Allocator::from_raw(alloc);
634 let slice = std::slice::from_raw_parts(prims, prim_count);
635 let node = builder.create_leaf(&allocator, slice);
636 node as *mut _ as *mut c_void
637 })
638}
639
640unsafe extern "C" fn set_children_tramp<B: BvhBuilder>(
641 node: *mut c_void,
642 children: *mut *mut c_void,
643 child_count: c_uint,
644 user: *mut c_void,
645) {
646 catch_abort(|| unsafe {
647 let builder = builder_of::<B>(user);
648 let node = &mut *(node as *mut B::Node<'_>);
649 let kids = Children::from_raw(children, child_count as usize);
650 builder.set_children(node, kids);
651 })
652}
653
654unsafe extern "C" fn set_bounds_tramp<B: BvhBuilder>(
655 node: *mut c_void,
656 bounds: *mut *const RTCBounds,
657 child_count: c_uint,
658 user: *mut c_void,
659) {
660 catch_abort(|| unsafe {
661 let builder = builder_of::<B>(user);
662 let node = &mut *(node as *mut B::Node<'_>);
663 let view = ChildBounds::from_raw(bounds, child_count as usize);
664 builder.set_bounds(node, view);
665 })
666}
667
668unsafe extern "C" fn split_tramp<B: BvhBuilder>(
669 prim: *const RTCBuildPrimitive,
670 dim: c_uint,
671 pos: f32,
672 lbounds: *mut RTCBounds,
673 rbounds: *mut RTCBounds,
674 user: *mut c_void,
675) {
676 catch_abort(|| unsafe {
677 let builder = builder_of::<B>(user);
678 let (lo, hi) = builder.split(&*prim, dim, pos);
679 *lbounds = lo;
680 *rbounds = hi;
681 });
682}
683
684unsafe extern "C" fn progress_tramp<B: BvhBuilder>(user: *mut c_void, n: f64) -> bool {
685 catch_abort(|| unsafe {
686 let builder = builder_of::<B>(user);
687 builder.progress(n);
688 });
689 true
690}
691
692impl Bvh {
693 /// Build a BVH over `primitives` inside a generative scope. The result and
694 /// any [`NodePtr`] handles cannot escape `f`; return owned data
695 /// (counts, a `Vec`, a copied-out tree) instead.
696 ///
697 /// The generative brand makes escaping a handle a compile error, which is
698 /// what prevents resolving a handle against a different build:
699 ///
700 /// ```compile_fail
701 /// use embree3::{
702 /// Allocator, Bvh, BvhBuilder, BvhNode, BuildConfig, BuildPrimitive, ChildBounds,
703 /// Children, Device,
704 /// };
705 /// #[derive(Clone, Copy)]
706 /// struct Node {
707 /// n: u32,
708 /// }
709 /// unsafe impl BvhNode for Node {}
710 /// struct B;
711 /// impl BvhBuilder for B {
712 /// type Node<'id> = Node;
713 /// const MAX_CHILDREN: usize = 2;
714 /// fn create_node<'id>(&self, a: &Allocator<'id>, _n: usize) -> &'id mut Node {
715 /// a.alloc(Node { n: 0 })
716 /// }
717 /// fn set_children<'id>(&self, _n: &mut Node, _c: Children<'id, Node>) {}
718 /// fn set_bounds<'id>(&self, _n: &mut Node, _b: ChildBounds<'_>) {}
719 /// fn create_leaf<'id>(&self, a: &Allocator<'id>, _p: &[BuildPrimitive]) -> &'id mut Node {
720 /// a.alloc(Node { n: 0 })
721 /// }
722 /// }
723 /// let device = Device::new().unwrap();
724 /// let mut bvh = device.create_bvh().unwrap();
725 /// let mut prims: Vec<BuildPrimitive> = Vec::new();
726 /// // ERROR: a branded handle cannot escape the generative scope.
727 /// let _escaped = bvh.build_scoped(&BuildConfig::default(), &mut prims, &B, |r| r.root_ptr());
728 /// ```
729 ///
730 /// The cross-build case directly: a handle from one build cannot be
731 /// resolved by a nested, independent build (their generative brands
732 /// differ):
733 ///
734 /// ```compile_fail
735 /// use embree3::{
736 /// Allocator, Bvh, BvhBuilder, BvhNode, BuildConfig, BuildPrimitive, ChildBounds,
737 /// Children, Device,
738 /// };
739 /// #[derive(Clone, Copy)]
740 /// struct Node {
741 /// n: u32,
742 /// }
743 /// unsafe impl BvhNode for Node {}
744 /// struct B;
745 /// impl BvhBuilder for B {
746 /// type Node<'id> = Node;
747 /// const MAX_CHILDREN: usize = 2;
748 /// fn create_node<'id>(&self, a: &Allocator<'id>, _n: usize) -> &'id mut Node {
749 /// a.alloc(Node { n: 0 })
750 /// }
751 /// fn set_children<'id>(&self, _n: &mut Node, _c: Children<'id, Node>) {}
752 /// fn set_bounds<'id>(&self, _n: &mut Node, _b: ChildBounds<'_>) {}
753 /// fn create_leaf<'id>(&self, a: &Allocator<'id>, _p: &[BuildPrimitive]) -> &'id mut Node {
754 /// a.alloc(Node { n: 0 })
755 /// }
756 /// }
757 /// let device = Device::new().unwrap();
758 /// let mut outer = device.create_bvh().unwrap();
759 /// let mut inner = device.create_bvh().unwrap();
760 /// let mut po: Vec<BuildPrimitive> = Vec::new();
761 /// let mut pi: Vec<BuildPrimitive> = Vec::new();
762 /// let cfg = BuildConfig::default();
763 /// outer.build_scoped(&cfg, &mut po, &B, |ra| {
764 /// // ERROR: `ra`'s handle has a different brand than `rb`'s scope, so it cannot be
765 /// // passed to `rb.resolve`. (Returns a `Copy` field to isolate that one error.)
766 /// inner.build_scoped(&cfg, &mut pi, &B, |rb| {
767 /// rb.resolve(ra.root_ptr().unwrap()).n
768 /// })
769 /// }).unwrap().unwrap();
770 /// ```
771 pub fn build_scoped<B, F, R>(
772 &mut self,
773 config: &BuildConfig,
774 primitives: &mut Vec<BuildPrimitive>,
775 builder: &B,
776 f: F,
777 ) -> Result<R, Error>
778 where
779 B: BvhBuilder,
780 F: for<'id> FnOnce(BvhResult<'id, B>) -> R,
781 {
782 self.build_with_brand(config, primitives, builder, f)
783 }
784
785 fn build_with_brand<'id, B, F, R>(
786 &mut self,
787 config: &BuildConfig,
788 primitives: &mut Vec<BuildPrimitive>,
789 builder: &B,
790 f: F,
791 ) -> Result<R, Error>
792 where
793 B: BvhBuilder,
794 F: FnOnce(BvhResult<'id, B>) -> R,
795 {
796 config.validate(primitives.len(), B::MAX_CHILDREN)?;
797 if std::mem::size_of::<B::Node<'id>>() == 0 {
798 return Err(Error::INVALID_ARGUMENT);
799 }
800 // Version check (NOT hard enforcement): the safe build path relies on the
801 // per-node setter serialization audited for embree 3.13.5. This rejects any
802 // other *reported* version, but is a version check plus a
803 // trusted-native-library assumption -- a patched libembree3 reporting
804 // 31305 with different callback ordering is not detected; hard
805 // enforcement would need a vendored/audited binary.
806 if self.device.get_property(DeviceProperty::VERSION)? != RTC_VERSION as isize {
807 return Err(Error::INVALID_OPERATION);
808 }
809
810 if primitives.is_empty() {
811 return Ok(f(BvhResult {
812 root: None,
813 _brand: PhantomData,
814 }));
815 }
816
817 // Capacity for spatial splits.
818 if B::SPATIAL_SPLITS && config.quality == BuildQuality::HIGH {
819 let need = primitives
820 .len()
821 .checked_mul(2)
822 .ok_or(Error::INVALID_ARGUMENT)?;
823 primitives.reserve(need - primitives.len());
824 }
825 let capacity = primitives.capacity();
826 let count = primitives.len();
827
828 let state = BuildState::<B> {
829 builder: builder as *const B,
830 };
831
832 let mut args: RTCBuildArguments = unsafe { std::mem::zeroed() };
833 args.byteSize = std::mem::size_of::<RTCBuildArguments>();
834 args.buildQuality = config.quality;
835 args.buildFlags = if config.dynamic {
836 RTCBuildFlags::DYNAMIC
837 } else {
838 RTCBuildFlags::NONE
839 };
840 args.maxBranchingFactor = config.max_branching_factor;
841 args.maxDepth = config.max_depth;
842 args.sahBlockSize = config.sah_block_size;
843 args.minLeafSize = config.min_leaf_size;
844 args.maxLeafSize = config.max_leaf_size;
845 args.traversalCost = config.traversal_cost;
846 args.intersectionCost = config.intersection_cost;
847 args.bvh = self.handle;
848 args.primitives = primitives.as_mut_ptr();
849 args.primitiveCount = count;
850 args.primitiveArrayCapacity = capacity;
851 args.createNode = Some(create_node_tramp::<B>);
852 args.setNodeChildren = Some(set_children_tramp::<B>);
853 args.setNodeBounds = Some(set_bounds_tramp::<B>);
854 args.createLeaf = Some(create_leaf_tramp::<B>);
855 args.splitPrimitive = if B::SPATIAL_SPLITS {
856 Some(split_tramp::<B>)
857 } else {
858 None
859 };
860 args.buildProgress = if B::PROGRESS {
861 Some(progress_tramp::<B>)
862 } else {
863 None
864 };
865 args.userPtr = &state as *const BuildState<B> as *mut c_void;
866
867 let _ = self.device.get_error();
868 let root = unsafe { rtcBuildBVH(&args) };
869 if root.is_null() {
870 return Err(match self.device.get_error() {
871 Error::NONE => Error::UNKNOWN,
872 e => e,
873 });
874 }
875 // SAFETY (rebrand 2): the callbacks allocated nodes at a callback-local
876 // lifetime; here the root is reinterpreted as `B::Node<'id>` for the
877 // result's brand. Sound by the `BvhNode` family-level rebrand
878 // requirement (all `Node<'_>` are layout-identical and differ only by
879 // phantom `NodePtr` brands), and the arena outlives `'id`.
880 let root = NonNull::new(root as *mut B::Node<'id>).map(|ptr| NodePtr {
881 ptr,
882 _brand: PhantomData,
883 });
884 Ok(f(BvhResult {
885 root,
886 _brand: PhantomData,
887 }))
888 }
889}
890
891fn bounds_of(p: &BuildPrimitive) -> Bounds {
892 Bounds {
893 lower_x: p.lower_x,
894 lower_y: p.lower_y,
895 lower_z: p.lower_z,
896 align0: 0.0,
897 upper_x: p.upper_x,
898 upper_y: p.upper_y,
899 upper_z: p.upper_z,
900 align1: 0.0,
901 }
902}
903fn set_axis_upper(b: &mut Bounds, dim: u32, pos: f32) {
904 match dim {
905 0 => b.upper_x = pos,
906 1 => b.upper_y = pos,
907 _ => b.upper_z = pos,
908 }
909}
910fn set_axis_lower(b: &mut Bounds, dim: u32, pos: f32) {
911 match dim {
912 0 => b.lower_x = pos,
913 1 => b.lower_y = pos,
914 _ => b.lower_z = pos,
915 }
916}