retrofire_core/render/
raster.rs

1//! Translation of vector shapes into discrete pixels in the framebuffer.
2//!
3//! Rasterization proceeds by turning a primitive such as a triagle into
4//! a sequence of *scanlines*, each corresponding to a horizontal span of
5//! pixels covered by the primitive on a given line. The scanlines, in turn,
6//! are converted into a series of *fragments* that represent potentially
7//! drawn pixels.
8//!
9//! If depth testing (z-buffering) is enabled, the fragments are then tested
10//! against the current depth value in their position. For each fragment that
11//! passes the depth test, a color is computed by the fragment shader and
12//! written into the framebuffer. Fragments that fail the test are discarded.
13
14use core::{
15    fmt::{Debug, Formatter},
16    mem::swap,
17    ops::Range,
18};
19
20use crate::{
21    geom::Vertex,
22    math::{Lerp, Vary, point::Point3},
23    render::Screen,
24};
25
26/// A fragment, or a single "pixel" in a rasterized primitive.
27#[derive(Clone, Debug)]
28pub struct Frag<V> {
29    pub pos: ScreenPt,
30    pub var: V,
31}
32
33/// A horizontal, 1-pixel-thick "slice" of a primitive being rasterized.
34pub struct Scanline<V: Vary> {
35    /// The y coordinate of the line.
36    pub y: usize,
37    /// The range of x coordinates spanned by the line.
38    pub xs: Range<usize>,
39    /// Iterator emitting the varyings on the line.
40    pub vs: <Varyings<V> as Vary>::Iter,
41}
42
43/// Iterator emitting scanlines, linearly interpolating values between the
44/// left and right endpoints as it goes.
45pub struct ScanlineIter<V: Vary> {
46    y: f32,
47    left: <Varyings<V> as Vary>::Iter,
48    right: <f32 as Vary>::Iter,
49    dv_dx: <Varyings<V> as Vary>::Diff,
50    n: u32,
51}
52
53/// Point in screen space.
54/// `x` and `y` are viewport pixel coordinates, `z` is depth.
55pub type ScreenPt = Point3<Screen>;
56
57/// Values to interpolate across a rasterized primitive.
58pub type Varyings<V> = (ScreenPt, V);
59
60impl<V: Vary> Scanline<V> {
61    pub fn fragments(&mut self) -> impl Iterator<Item = Frag<V>> + '_ {
62        self.vs.by_ref().map(|(pos, var)| {
63            // Perspective correct varyings
64            // TODO optimization: only every 16 or so pixels
65            let var = var.z_div(pos.z());
66            Frag { pos, var }
67        })
68    }
69}
70
71impl<V: Vary> Debug for Scanline<V> {
72    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
73        f.debug_struct("Scanline")
74            .field("y", &self.y)
75            .field("xs", &self.xs)
76            .finish_non_exhaustive()
77    }
78}
79
80impl<V: Vary> Iterator for ScanlineIter<V> {
81    type Item = Scanline<V>;
82
83    #[inline]
84    fn next(&mut self) -> Option<Self::Item> {
85        if self.n == 0 {
86            return None;
87        }
88        let v0 = self.left.next()?;
89        let x1 = self.right.next()?;
90
91        // Find the next pixel centers to the right
92        //
93        // If left.pos.x().fract() < 0.5, the pixel is covered and thus drawn;
94        // otherwise it's not, and we skip to the next pixel.
95        //
96        // Similarly, if x_right.fract() < 0.5 that's the "one-past-the-end"
97        // pixel, otherwise it's the last covered pixel and the next one is
98        // the actual one-past-the-end pixel.
99        let (x0, x1) = (round_up_to_half(v0.0.x()), round_up_to_half(x1));
100
101        // Adjust v0 to match the rounded x0
102        let v0 = v0.lerp(&v0.step(&self.dv_dx), x0 - v0.0.x());
103
104        let vs = v0.vary(self.dv_dx.clone(), Some((x1 - x0) as u32));
105
106        let y = self.y as usize;
107        let xs = x0 as usize..x1 as usize;
108
109        self.y += 1.0;
110        self.n -= 1;
111
112        Some(Scanline { y, xs, vs })
113    }
114}
115
116/// Rasterizes a one-pixel-thick line between two vertices.
117///
118/// Invokes `scan_fn` for each pixel drawn.
119///
120// TODO Optimize for cases where >1 pixels are drawn for each line
121// TODO Guarantee subpixel precision
122pub fn line<V, F>([mut v0, mut v1]: [Vertex<ScreenPt, V>; 2], mut scan_fn: F)
123where
124    V: Vary,
125    F: FnMut(Scanline<V>),
126{
127    if v0.pos.y() > v1.pos.y() {
128        swap(&mut v0, &mut v1);
129    }
130    let [dx, dy, _] = (v1.pos - v0.pos).0;
131
132    if dx.abs() > dy {
133        // More wide than tall
134        if dx < 0.0 {
135            // Always draw from left to right
136            swap(&mut v0, &mut v1);
137        }
138        let x0 = round_up_to_half(v0.pos.x());
139        let x1 = round_up_to_half(v1.pos.x());
140
141        let dy_dx = dy / dx;
142        // Adjust y0 to match the rounded x0
143        let y0 = v0.pos.y() + dy_dx * (x0 - v0.pos.x());
144
145        let (xs, mut y) = (x0 as usize..x1 as usize, y0);
146        for x in xs {
147            let vs = (v0.pos, v0.attrib.clone());
148            let vs = vs.clone().vary_to(vs, 1); // TODO a bit silly
149            scan_fn(Scanline {
150                y: y as usize,
151                xs: x..x + 1,
152                vs,
153            });
154            y += dy_dx;
155        }
156    } else {
157        // More tall than wide
158        let y0 = round_up_to_half(v0.pos.y());
159        let y1 = round_up_to_half(v1.pos.y());
160
161        let dx_dy = dx / dy;
162        // Adjust x0 to match the rounded y0
163        let x0 = v0.pos.x() + dx_dy * (y0 - v0.pos.y());
164
165        let mut x = x0;
166        for y in y0 as usize..y1 as usize {
167            let vs = (v0.pos, v0.attrib.clone());
168            let vs = vs.clone().vary_to(vs.clone(), 1);
169            scan_fn(Scanline {
170                y,
171                xs: x as usize..x as usize + 1,
172                vs,
173            });
174            x += dx_dy;
175        }
176    }
177}
178
179/// Rasterizes a filled triangle defined by three vertices.
180///
181/// Converts the triangle into [scanlines][Scanline] and invokes `scanline_fn`
182/// for each scanline. The scanlines are guaranteed to cover exactly those
183/// pixels whose center point lies inside the triangle. For more information
184/// on the scanline conversion, see [`scan`].
185pub fn tri_fill<V, F>(mut verts: [Vertex<ScreenPt, V>; 3], mut scanline_fn: F)
186where
187    V: Vary,
188    F: FnMut(Scanline<V>),
189{
190    // Sort by y coordinate, start from the top
191    verts.sort_by(|a, b| a.pos.y().total_cmp(&b.pos.y()));
192    let [top, mid0, bot] = verts.map(|v| (v.pos, v.attrib));
193
194    let [top_y, mid_y, bot_y] = [top.0.y(), mid0.0.y(), bot.0.y()];
195
196    // Interpolate a point on the "long" edge at the same y as `mid0`
197    let mid1 = top.lerp(&bot, (mid_y - top_y) / (bot_y - top_y));
198
199    let (left, right) = if mid0.0.x() < mid1.0.x() {
200        (mid0, mid1)
201    } else {
202        (mid1, mid0)
203    };
204
205    //                       X <--top
206    //                     ***
207    //                   ******
208    //                 ********
209    //               ** upper **
210    // mid0/left--> X**********X <--right/mid1
211    //                ** lower **
212    //                   ********
213    //                      ******
214    //                         ***
215    //                            X <--bot
216
217    // Rasterize the upper half triangle...
218    scan(top_y..mid_y, &top..&left, &top..&right).for_each(&mut scanline_fn);
219
220    // ...and the lower half triangle
221    scan(mid_y..bot_y, &left..&bot, &right..&bot).for_each(&mut scanline_fn);
222}
223
224/// Returns an iterator that emits a scanline for each line from `y0` to `y1`,
225/// interpolating varyings from `l0` to `l1` on the left and from `r0` to `r1`
226/// on the right side.
227///
228/// The three input ranges define a *trapezoid* with horizontal bases, or, in
229/// the special case where `l0 == r0` or `l1 == r1`, a triangle:
230/// ```text
231///            l0___________ r0
232/// y0        _|____________|     .next()
233///         _|_______________|    .next()
234///       _|__________________|     ...
235///      |_____________________|    ...
236/// y1   l1                     r1
237/// ```
238/// Any convex polygon can be converted into scanlines by dividing it into
239/// trapezoidal segments and calling this function for each segment.
240///
241/// The exact pixels that are drawn are determined by whether the vector shape
242/// *covers* a pixel or not. A pixel is covered, and drawn, if and only if its
243/// center point lies inside the shape. This ensures that if two polygons
244/// share an edge, or several share a vertex, each pixel at the boundary will
245/// be drawn by exactly one of the polygons, with no gaps or overdrawn pixels.
246pub fn scan<V: Vary>(
247    Range { start: y0, end: y1 }: Range<f32>,
248    Range { start: l0, end: l1 }: Range<&Varyings<V>>,
249    Range { start: r0, end: r1 }: Range<&Varyings<V>>,
250) -> ScanlineIter<V> {
251    let recip_dy = (y1 - y0).recip();
252
253    // dv/dy for the left edge
254    let dl_dy = l0.dv_dt(l1, recip_dy);
255    // dv/dy for the right edge
256    let dr_dy = r0.dv_dt(r1, recip_dy);
257
258    // dv/dx is constant for the whole polygon; precompute it
259    let dv_dx = {
260        let (l0, r0) = (l0.step(&dl_dy), r0.step(&dr_dy));
261        let dx = r0.0.x() - l0.0.x();
262        l0.dv_dt(&r0, dx.recip())
263    };
264
265    // Find the y value of the next pixel center (.5) vertically
266    //
267    // We want to draw exactly those pixels whose center is *covered* by this
268    // polygon. Thus if y_range.start.fract() > 0.5, we skip to the next line.
269    // We align the y values with the pixel grid so that on each line, if
270    // x_range.start.fract() <= 0.5, the pixel is covered, otherwise it is not.
271    //
272    // This ensures that whenever two polygons share an edge, every pixel at
273    // the edge belongs to exactly one of the polygons, avoiding both gaps and
274    // overdrawn pixels. For example, on the left edge:
275    //
276    //      COVERED               NOT COVERED             NOT COVERED
277    //   +-----/-----+           +---------/-+           +-----------+
278    //   |    /······|           |        /··|           |     ·     |
279    //   |   p·+·····| p.y=0.5   |     + p···| p.y=0.5   |  ·  +  ·  |
280    //   |  /········|           |      /····|           |   p-------- p.y>0.5
281    //   +-/---------+           +-----/-----+           +--/--------+
282    //    p.x<0.5                    p.x>0.5              p.x<0.5
283    //
284    let y0_rounded = round_up_to_half(y0);
285    let y1_rounded = round_up_to_half(y1);
286
287    let y_tweak = y0_rounded - y0;
288
289    // Adjust varyings to correspond to the aligned y value
290    let l0 = l0.lerp(&l0.step(&dl_dy), y_tweak);
291    let r0 = r0.0.x() + dr_dy.0.x() * y_tweak;
292
293    ScanlineIter {
294        y: y0_rounded,
295        left: l0.vary(dl_dy, None),
296        right: r0.vary(dr_dy.0.x(), None),
297        dv_dx,
298        n: (y1_rounded - y0_rounded) as u32, // saturates to 0
299    }
300}
301
302#[inline]
303fn round_up_to_half(x: f32) -> f32 {
304    crate::math::float::f32::floor(x + 0.5) + 0.5
305}
306
307#[cfg(test)]
308mod tests {
309    use alloc::string::{String, ToString};
310    use core::iter::once;
311
312    use crate::{
313        assert_approx_eq,
314        geom::vertex,
315        math::{point::pt3, vary::Vary, vary::ZDiv},
316        util::buf::Buf2,
317    };
318
319    use super::{Scanline, tri_fill};
320
321    // TODO Test different orientations and various edge cases
322
323    #[test]
324    fn shared_edge_should_not_have_gaps_or_overdraw() {
325        let mut buf = Buf2::new((20, 10));
326
327        let verts = [
328            pt3(8.0, 0.0, 0.0),
329            pt3(0.0, 6.0, 0.0),
330            pt3(14.0, 10.0, 0.0),
331            pt3(20.0, 3.0, 0.0),
332        ]
333        .map(|pos| vertex(pos, 0.0));
334
335        let expected = r"
33600000001110000000000
33700000011111111000000
33800000111111111111100
33900011111111111111111
34000111111111111111110
34101111111111111111100
34200111111111111111000
34300000111111111110000
34400000000011111100000
34500000000000011000000";
346
347        tri_fill([verts[0], verts[1], verts[2]], |sl| {
348            for x in sl.xs {
349                buf[[x as u32, sl.y as u32]] += 1;
350            }
351        });
352        tri_fill([verts[0], verts[2], verts[3]], |sl| {
353            for x in sl.xs {
354                buf[[x as u32, sl.y as u32]] += 1;
355            }
356        });
357
358        let s: String = buf
359            .rows()
360            .flat_map(|r| {
361                once("\n".to_string()).chain(r.iter().map(i32::to_string))
362            })
363            .collect();
364
365        assert_eq!(s, expected);
366    }
367
368    #[test]
369    fn gradient() {
370        use core::fmt::Write;
371        let verts = [(15.0, 2.0, 0.0), (2.0, 8.0, 1.0), (26.0, 14.0, 0.5)]
372            .map(|(x, y, val)| vertex(pt3(x, y, 1.0), val));
373
374        let expected = r"
375              0
376            2110
377          3322211
378       55444332221
379     76665544433222
380   88877666554443322
381    98887766655444332
382        88776665544433
383            77666554443
384                66655444
385                    65544
386                        54
387";
388        let mut s = "\n".to_string();
389
390        super::tri_fill(verts, |mut sl| {
391            write!(s, "{:w$}", " ", w = sl.xs.start).ok();
392
393            for c in sl.fragments().map(|f| (10.0 * f.var) as u8) {
394                write!(s, "{c}").ok();
395            }
396            writeln!(s).ok();
397        });
398        assert_eq!(s, expected);
399    }
400
401    #[test]
402    fn scanline_fragments_iter() {
403        let w0 = 2.0;
404        let w1 = 4.0;
405        let mut sl = Scanline {
406            y: 42,
407            xs: 8..16,
408            vs: Vary::vary_to(
409                (pt3(8.0, 42.0, 1.0 / w0), 3.0f32.z_div(w0)),
410                (pt3(16.0, 42.0, 1.0 / w1), 5.0f32.z_div(w1)),
411                9,
412            ),
413        };
414
415        // Perspective correct values
416        let zs = [
417            2.0f32, 2.1333334, 2.2857144, 2.4615386, 2.6666667, 2.909091, 3.2,
418            3.5555556, 4.0,
419        ];
420        let vars = [
421            3.0f32, 3.1333334, 3.2857144, 3.4615386, 3.6666667, 3.909091,
422            4.2000003, 4.555556, 5.0,
423        ];
424        let mut x = 8.0;
425
426        for ((frag, z), v) in sl.fragments().zip(zs).zip(vars) {
427            assert_approx_eq!(frag.pos, pt3(x, 42.0, z.recip()));
428            assert_approx_eq!(frag.var, v);
429
430            x += 1.0;
431        }
432        // vary_to is inclusive
433        assert_eq!(x, 17.0);
434    }
435}