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    /// - `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}