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