euv_engine/raytracing/pipeline/data/impl.rs
1use super::*;
2
3/// Implements the constructors and validation for [`RayTraceSettings`].
4impl RayTraceSettings {
5 /// Returns the default settings: one ray per pixel and the
6 /// [`RAYTRACE_DEFAULT_MAX_BOUNCES_LIMIT`] bounce limit.
7 ///
8 /// # Returns
9 ///
10 /// - `RayTraceSettings` - The default settings.
11 pub fn new_default() -> RayTraceSettings {
12 RayTraceSettings::new(
13 RAYTRACE_DEFAULT_SAMPLES_PER_AXIS,
14 RAYTRACE_DEFAULT_MAX_BOUNCES_LIMIT,
15 )
16 }
17
18 /// Returns a settings block with an explicit sampling grid and bounce
19 /// limit.
20 ///
21 /// The arguments are not validated here; call
22 /// [`RayTraceSettings::validate`] before packing or rendering.
23 ///
24 /// # Arguments
25 ///
26 /// - `u32` - The supersampling grid resolution per axis.
27 ///
28 /// # Returns
29 ///
30 /// - `RayTraceSettings` - The new settings.
31 pub fn with_quality(samples_per_axis: u32, max_bounces: u32) -> RayTraceSettings {
32 RayTraceSettings::new(samples_per_axis, max_bounces)
33 }
34
35 /// Returns the total number of rays traced per pixel, which is
36 /// `samples_per_axis` squared.
37 ///
38 /// # Returns
39 ///
40 /// - `u32` - The per-pixel ray count.
41 pub fn rays_per_pixel(&self) -> u32 {
42 let axis: u32 = self.get_samples_per_axis();
43 axis * axis
44 }
45
46 /// Checks that both fields are inside the range the CPU renderer and
47 /// both shaders support.
48 ///
49 /// `samples_per_axis` must be within
50 /// `1..=`[`RAYTRACE_SAMPLES_PER_AXIS_MAX`] and `max_bounces` within
51 /// `0..=`[`RAYTRACE_MAX_BOUNCES_MAX`]. Anything else is rejected rather
52 /// than clamped, so a caller never silently gets a different image than
53 /// it asked for.
54 ///
55 /// # Returns
56 ///
57 /// - `Result<(), String>` - `Ok(())` when both fields are in range,
58 /// otherwise a message naming the offending field.
59 pub fn validate(&self) -> Result<(), String> {
60 let samples: u32 = self.get_samples_per_axis();
61 if !(RAYTRACE_SAMPLES_PER_AXIS_MIN..=RAYTRACE_SAMPLES_PER_AXIS_MAX).contains(&samples) {
62 return Err(RAYTRACE_ERROR_SAMPLES_PER_AXIS.to_string());
63 }
64 let bounces: u32 = self.get_max_bounces();
65 if !(RAYTRACE_MAX_BOUNCES_MIN..=RAYTRACE_MAX_BOUNCES_MAX).contains(&bounces) {
66 return Err(RAYTRACE_ERROR_MAX_BOUNCES.to_string());
67 }
68 Ok(())
69 }
70}
71
72/// Implements the GPU packing entry point for [`RayTraceScene`].
73impl RayTraceScene {
74 /// Packs the scene into the `vec4` array both shader backends read.
75 ///
76 /// The layout is frozen by the shader contract:
77 ///
78 /// ```text
79 /// vec4[0]: node_count, primitive_count, primitive_offset, 0
80 /// vec4[1 + 3 * n]: node lower.xyz, start
81 /// vec4[2 + 3 * n]: node upper.xyz, count
82 /// vec4[3 + 3 * n]: node escape, 0, 0, 0
83 /// vec4[primitive_offset + 8 * p]: primitive records
84 /// ```
85 ///
86 /// Primitives are stored in `indices` order so a leaf's `start` field
87 /// reads directly into the packed primitive array while each record
88 /// still carries the original primitive index the closest-hit tie
89 /// breaker compares. The source occluder ordering is never altered.
90 ///
91 /// An empty scene packs a two-slot header with `node_count == 0`, which
92 /// both shaders detect and treat as "every ray misses".
93 ///
94 /// Non-finite geometry components, components whose magnitude exceeds
95 /// the finite `f32` range, and indices above `2^24` (the exact-integer
96 /// limit of `f32`) are rejected rather than truncated, because a
97 /// truncated node bound would round inward and lose real geometry.
98 ///
99 /// # Returns
100 ///
101 /// - `Result<Vec<f32>, String>` - The packed `vec4` array, or the
102 /// reason the scene cannot be represented on a GPU.
103 pub fn encode_gpu(&self) -> Result<Vec<f32>, String> {
104 let nodes: &[RayTraceNode] = self.get_nodes();
105 let indices: &[usize] = self.get_indices();
106 let node_count: usize = nodes.len();
107 let primitive_count: usize = indices.len();
108 let node_slots: usize = node_count
109 .checked_mul(RAYTRACE_PACKED_NODE_VEC4S)
110 .ok_or_else(|| RAYTRACE_ERROR_SCENE_OVERFLOW.to_string())?;
111 let primitive_slots: usize = primitive_count
112 .checked_mul(RAYTRACE_PACKED_PRIMITIVE_VEC4S)
113 .ok_or_else(|| RAYTRACE_ERROR_SCENE_OVERFLOW.to_string())?;
114 let total_slots: usize = RAYTRACE_PACKED_SCENE_HEADER_VEC4S
115 .checked_add(node_slots)
116 .and_then(|sum: usize| sum.checked_add(primitive_slots))
117 .ok_or_else(|| RAYTRACE_ERROR_SCENE_OVERFLOW.to_string())?;
118 let total_floats: usize = total_slots
119 .checked_mul(4)
120 .ok_or_else(|| RAYTRACE_ERROR_SCENE_OVERFLOW.to_string())?;
121 if total_floats > RAYTRACE_MAX_PACKED_FLOATS {
122 return Err(RAYTRACE_ERROR_SCENE_OVERFLOW.to_string());
123 }
124 let mut out: Vec<f32> = Vec::with_capacity(total_floats);
125 let primitive_offset: usize = RAYTRACE_PACKED_SCENE_HEADER_VEC4S + node_slots;
126 out.extend_from_slice(&[
127 pack_index(node_count)?,
128 pack_index(primitive_count)?,
129 pack_index(primitive_offset)?,
130 0.0,
131 ]);
132 for node in nodes.iter() {
133 pack_node(&mut out, node)?;
134 }
135 let occluders: &[Occluder] = self.get_occluders();
136 for index in indices.iter() {
137 let occluder: &Occluder = occluders.get(*index).ok_or_else(|| {
138 format!(
139 "{}: primitive index {} is out of range for {} occluders",
140 RAYTRACE_ERROR_INDEX_RANGE,
141 index,
142 occluders.len()
143 )
144 })?;
145 pack_primitive(&mut out, occluder, *index)?;
146 }
147 Ok(out)
148 }
149}