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