Skip to main content

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}