fidget_raster/
lib.rs

1//! 2D and 3D rendering
2//!
3//! To render something, build a configuration object then call its `run`
4//! function, e.g. [`ImageRenderConfig::run`] and [`VoxelRenderConfig::run`].
5#![warn(missing_docs)]
6use crate::config::Tile;
7use fidget_core::{
8    eval::Function,
9    render::{ImageSize, RenderHandle, ThreadPool, TileSizes, VoxelSize},
10    shape::{Shape, ShapeVars},
11};
12use nalgebra::Point2;
13use rayon::prelude::*;
14use zerocopy::{FromBytes, Immutable, IntoBytes};
15
16mod config;
17mod render2d;
18mod render3d;
19
20pub mod effects;
21pub use config::{ImageRenderConfig, VoxelRenderConfig};
22pub use render2d::DistancePixel;
23
24use render2d::render as render2d;
25use render3d::render as render3d;
26
27/// Helper struct to borrow from [`TileSizes`]
28///
29/// This object has the same guarantees as `TileSizes`, but trims items off the
30/// front of the `Vec<usize>` based on the image size.
31pub(crate) struct TileSizesRef<'a>(&'a [usize]);
32
33impl<'a> std::ops::Index<usize> for TileSizesRef<'a> {
34    type Output = usize;
35
36    fn index(&self, i: usize) -> &Self::Output {
37        &self.0[i]
38    }
39}
40
41impl TileSizesRef<'_> {
42    /// Builds a new `TileSizesRef` based on the maximum tile size
43    fn new(tiles: &TileSizes, max_size: usize) -> TileSizesRef<'_> {
44        let i = tiles
45            .iter()
46            .position(|t| *t < max_size)
47            .unwrap_or(tiles.len())
48            .saturating_sub(1);
49        TileSizesRef(&tiles[i..])
50    }
51
52    /// Returns the last (smallest) tile size
53    pub fn last(&self) -> usize {
54        *self.0.last().unwrap()
55    }
56
57    /// Gets a tile size by index
58    pub fn get(&self, i: usize) -> Option<usize> {
59        self.0.get(i).copied()
60    }
61
62    /// Returns the data offset of a global pixel position within a root tile
63    ///
64    /// The root tile is implicit: it's set by the largest tile size and aligned
65    /// to multiples of that size.
66    #[inline]
67    pub(crate) fn pixel_offset(&self, pos: Point2<usize>) -> usize {
68        // Find the relative position within the root tile
69        let x = pos.x % self.0[0];
70        let y = pos.y % self.0[0];
71
72        // Apply the relative offset and find the data index
73        x + y * self.0[0]
74    }
75}
76
77/// Grand unified render function
78///
79/// This handles tile generation and building + calling render workers in
80/// parallel (using [`rayon`] for parallelism at the tile level).
81///
82/// It returns a set of output tiles, or `None` if rendering has been cancelled
83pub(crate) fn render_tiles<'a, F: Function, W: RenderWorker<'a, F, T>, T>(
84    shape: Shape<F, T>,
85    vars: &ShapeVars<f32>,
86    config: &'a W::Config,
87) -> Option<Vec<(Tile<2>, W::Output)>>
88where
89    W::Config: Send + Sync,
90    T: Sync,
91{
92    use rayon::prelude::*;
93
94    let tile_sizes = config.tile_sizes();
95
96    let mut tiles = vec![];
97    let t = tile_sizes[0];
98    let width = config.width() as usize;
99    let height = config.height() as usize;
100    for i in 0..width.div_ceil(t) {
101        for j in 0..height.div_ceil(t) {
102            tiles.push(Tile::new(Point2::new(
103                i * tile_sizes[0],
104                j * tile_sizes[0],
105            )));
106        }
107    }
108
109    let mut rh = RenderHandle::new(shape);
110
111    let _ = rh.i_tape(&mut vec![]); // populate i_tape before cloning
112    let init = || {
113        let rh = rh.clone();
114        let worker = W::new(config);
115        (worker, rh)
116    };
117
118    match config.threads() {
119        None => {
120            let mut worker = W::new(config);
121            tiles
122                .into_iter()
123                .map(|tile| {
124                    if config.is_cancelled() {
125                        Err(())
126                    } else {
127                        let pixels = worker.render_tile(&mut rh, vars, tile);
128                        Ok((tile, pixels))
129                    }
130                })
131                .collect::<Result<Vec<_>, ()>>()
132                .ok()
133        }
134
135        Some(p) => p.run(|| {
136            tiles
137                .into_par_iter()
138                .map_init(init, |(w, rh), tile| {
139                    if config.is_cancelled() {
140                        Err(())
141                    } else {
142                        let pixels = w.render_tile(rh, vars, tile);
143                        Ok((tile, pixels))
144                    }
145                })
146                .collect::<Result<Vec<_>, ()>>()
147                .ok()
148        }),
149    }
150}
151
152/// Helper trait for tiled rendering configuration
153pub(crate) trait RenderConfig {
154    fn width(&self) -> u32;
155    fn height(&self) -> u32;
156    fn tile_sizes(&self) -> TileSizesRef<'_>;
157    fn threads(&self) -> Option<&ThreadPool>;
158    fn is_cancelled(&self) -> bool;
159}
160
161/// Helper trait for a tiled renderer worker
162pub(crate) trait RenderWorker<'a, F: Function, T> {
163    type Config: RenderConfig;
164    type Output: Send;
165
166    /// Build a new worker
167    ///
168    /// Workers are typically built on a per-thread basis
169    fn new(cfg: &'a Self::Config) -> Self;
170
171    /// Render a single tile, returning a worker-dependent output
172    fn render_tile(
173        &mut self,
174        shape: &mut RenderHandle<F, T>,
175        vars: &ShapeVars<f32>,
176        tile: config::Tile<2>,
177    ) -> Self::Output;
178}
179
180/// Generic image type
181///
182/// The image is laid out in row-major order, and can be indexed either by a
183/// `usize` index or a `(row, column)` tuple.
184///
185/// ```text
186///        0 ------------> width (columns)
187///        |             |
188///        |             |
189///        |             |
190///        V--------------
191///   height (rows)
192/// ```
193#[derive(Clone)]
194pub struct Image<P, S = ImageSize> {
195    data: Vec<P>,
196    size: S,
197}
198
199/// Helper trait to make images generic across [`ImageSize`] and [`VoxelSize`]
200pub trait ImageSizeLike {
201    /// Returns the width of the region, in pixels / voxels
202    fn width(&self) -> u32;
203    /// Returns the height of the region, in pixels / voxels
204    fn height(&self) -> u32;
205}
206
207impl ImageSizeLike for ImageSize {
208    fn width(&self) -> u32 {
209        self.width()
210    }
211    fn height(&self) -> u32 {
212        self.height()
213    }
214}
215
216impl ImageSizeLike for VoxelSize {
217    fn width(&self) -> u32 {
218        self.width()
219    }
220    fn height(&self) -> u32 {
221        self.height()
222    }
223}
224
225impl<P: Send, S: ImageSizeLike + Sync> Image<P, S> {
226    /// Generates an image by computing a per-pixel function
227    ///
228    /// This should be called on the _output_ image; the closure takes `(x, y)`
229    /// tuples and is expected to capture one or more source images.
230    pub fn apply_effect<F: Fn(usize, usize) -> P + Send + Sync>(
231        &mut self,
232        f: F,
233        threads: Option<&ThreadPool>,
234    ) {
235        let r = |(y, row): (usize, &mut [P])| {
236            for (x, v) in row.iter_mut().enumerate() {
237                *v = f(x, y);
238            }
239        };
240
241        if let Some(threads) = threads {
242            threads.run(|| {
243                self.data
244                    .par_chunks_mut(self.size.width() as usize)
245                    .enumerate()
246                    .for_each(r)
247            })
248        } else {
249            self.data
250                .chunks_mut(self.size.width() as usize)
251                .enumerate()
252                .for_each(r)
253        }
254    }
255}
256
257impl<P, S: Default> Default for Image<P, S> {
258    fn default() -> Self {
259        Image {
260            data: vec![],
261            size: S::default(),
262        }
263    }
264}
265
266impl<P: Default + Clone, S: ImageSizeLike> Image<P, S> {
267    /// Builds a new image filled with `P::default()`
268    pub fn new(size: S) -> Self {
269        Self {
270            data: vec![
271                P::default();
272                size.width() as usize * size.height() as usize
273            ],
274            size,
275        }
276    }
277}
278
279impl<P, S: Clone> Image<P, S> {
280    /// Returns the image size
281    pub fn size(&self) -> S {
282        self.size.clone()
283    }
284
285    /// Generates an image by mapping a simple function over each pixel
286    pub fn map<T, F: Fn(&P) -> T>(&self, f: F) -> Image<T, S> {
287        let data = self.data.iter().map(f).collect();
288        Image {
289            data,
290            size: self.size.clone(),
291        }
292    }
293
294    /// Decomposes the image into its components
295    pub fn take(self) -> (Vec<P>, S) {
296        (self.data, self.size)
297    }
298}
299
300impl<P, S: ImageSizeLike> Image<P, S> {
301    /// Returns the image width
302    pub fn width(&self) -> usize {
303        self.size.width() as usize
304    }
305
306    /// Returns the image height
307    pub fn height(&self) -> usize {
308        self.size.height() as usize
309    }
310
311    /// Checks a `(row, column)` position
312    ///
313    /// Returns the input position in the 1D array if valid; panics otherwise
314    fn decode_position(&self, pos: (usize, usize)) -> usize {
315        let (row, col) = pos;
316        assert!(
317            row < self.height(),
318            "row ({row}) must be less than image height ({})",
319            self.height()
320        );
321        assert!(
322            col < self.width(),
323            "column ({col}) must be less than image width ({})",
324            self.width()
325        );
326        row * self.width() + col
327    }
328}
329
330impl<P, S> Image<P, S> {
331    /// Iterates over pixel values
332    pub fn iter(&self) -> impl Iterator<Item = &P> + '_ {
333        self.data.iter()
334    }
335
336    /// Returns the number of pixels in the image
337    pub fn len(&self) -> usize {
338        self.data.len()
339    }
340
341    /// Checks whether the image is empty
342    pub fn is_empty(&self) -> bool {
343        self.data.is_empty()
344    }
345}
346
347impl<'a, P: 'a, S> IntoIterator for &'a Image<P, S> {
348    type Item = &'a P;
349    type IntoIter = std::slice::Iter<'a, P>;
350    fn into_iter(self) -> Self::IntoIter {
351        self.data.iter()
352    }
353}
354
355impl<P, S> IntoIterator for Image<P, S> {
356    type Item = P;
357    type IntoIter = std::vec::IntoIter<P>;
358    fn into_iter(self) -> Self::IntoIter {
359        self.data.into_iter()
360    }
361}
362
363impl<P, S> std::ops::Index<usize> for Image<P, S> {
364    type Output = P;
365    fn index(&self, index: usize) -> &Self::Output {
366        &self.data[index]
367    }
368}
369
370impl<P, S> std::ops::IndexMut<usize> for Image<P, S> {
371    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
372        &mut self.data[index]
373    }
374}
375
376macro_rules! define_image_index {
377    ($ty:ty) => {
378        impl<P, S> std::ops::Index<$ty> for Image<P, S> {
379            type Output = [P];
380            fn index(&self, index: $ty) -> &Self::Output {
381                &self.data[index]
382            }
383        }
384
385        impl<P, S> std::ops::IndexMut<$ty> for Image<P, S> {
386            fn index_mut(&mut self, index: $ty) -> &mut Self::Output {
387                &mut self.data[index]
388            }
389        }
390    };
391}
392
393define_image_index!(std::ops::Range<usize>);
394define_image_index!(std::ops::RangeTo<usize>);
395define_image_index!(std::ops::RangeFrom<usize>);
396define_image_index!(std::ops::RangeInclusive<usize>);
397define_image_index!(std::ops::RangeToInclusive<usize>);
398define_image_index!(std::ops::RangeFull);
399
400/// Indexes an image with `(row, col)`
401impl<P, S: ImageSizeLike> std::ops::Index<(usize, usize)> for Image<P, S> {
402    type Output = P;
403    fn index(&self, pos: (usize, usize)) -> &Self::Output {
404        let index = self.decode_position(pos);
405        &self.data[index]
406    }
407}
408
409impl<P, S: ImageSizeLike> std::ops::IndexMut<(usize, usize)> for Image<P, S> {
410    fn index_mut(&mut self, pos: (usize, usize)) -> &mut Self::Output {
411        let index = self.decode_position(pos);
412        &mut self.data[index]
413    }
414}
415
416/// Pixel type for a [`GeometryBuffer`]
417///
418/// This type can be passed directly in a buffer to the GPU.
419#[repr(C)]
420#[derive(Debug, Default, Copy, Clone, IntoBytes, FromBytes, Immutable)]
421pub struct GeometryPixel {
422    /// Z position of this pixel, in voxel units
423    pub depth: u32, // TODO should this be `f32`?
424    /// Function gradients at this pixel
425    pub normal: [f32; 3],
426}
427
428impl GeometryPixel {
429    /// Converts the normal into a normalized RGB value
430    pub fn to_color(&self) -> [u8; 3] {
431        let [dx, dy, dz] = self.normal;
432        let s = (dx.powi(2) + dy.powi(2) + dz.powi(2)).sqrt();
433        if s != 0.0 {
434            let scale = u8::MAX as f32 / s;
435            [
436                (dx.abs() * scale) as u8,
437                (dy.abs() * scale) as u8,
438                (dz.abs() * scale) as u8,
439            ]
440        } else {
441            [0; 3]
442        }
443    }
444}
445
446/// Image containing depth and normal at each pixel
447pub type GeometryBuffer = Image<GeometryPixel, VoxelSize>;
448
449impl<P: Default + Copy + Clone> Image<P, VoxelSize> {
450    /// Returns the image depth in voxels
451    pub fn depth(&self) -> usize {
452        self.size.depth() as usize
453    }
454}
455
456/// Three-channel color image
457pub type ColorImage = Image<[u8; 3]>;