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::fmt::Debug;
15use core::ops::Range;
16
17use crate::geom::Vertex;
18use crate::math::{Vary, Vec3};
19
20use super::Screen;
21
22/// A fragment, or a single "pixel" in a rasterized primitive.
23#[derive(Clone, Debug)]
24pub struct Frag<V> {
25    pub pos: ScreenVec,
26    pub var: V,
27}
28
29/// A horizontal, 1-pixel-thick "slice" of a primitive being rasterized.
30pub struct Scanline<V: Vary> {
31    /// The y coordinate of the line.
32    pub y: usize,
33    /// The range of x coordinates spanned by the line.
34    pub xs: Range<usize>,
35    /// Iterator emitting the varyings on the line.
36    pub vs: <Varyings<V> as Vary>::Iter,
37}
38
39/// Iterator emitting scanlines, linearly interpolating values between the
40/// left and right endpoints as it goes.
41pub struct ScanlineIter<V: Vary> {
42    y: f32,
43    left: <Varyings<V> as Vary>::Iter,
44    right: <f32 as Vary>::Iter,
45    dv_dx: <Varyings<V> as Vary>::Diff,
46    n: u32,
47}
48
49/// Vector in screen space.
50/// `x` and `y` are viewport pixel coordinates, `z` is depth.
51pub type ScreenVec = Vec3<Screen>;
52
53/// Values to interpolate across a rasterized primitive.
54pub type Varyings<V> = (ScreenVec, V);
55
56impl<V: Vary> Scanline<V> {
57    pub fn fragments(&mut self) -> impl Iterator<Item = Frag<V>> + '_ {
58        self.vs.by_ref().map(|(pos, var)| {
59            // Perspective correct varyings
60            // TODO optimization: only every 16 or so pixels
61            let var = var.z_div(pos.z());
62            Frag { pos, var }
63        })
64    }
65}
66
67impl<V: Vary> Iterator for ScanlineIter<V> {
68    type Item = Scanline<V>;
69
70    #[inline]
71    fn next(&mut self) -> Option<Self::Item> {
72        if self.n == 0 {
73            return None;
74        }
75        let v0 = self.left.next()?;
76        let x1 = self.right.next()?;
77        let y = self.y;
78
79        // Find the next pixel centers to the right
80        //
81        // If left.pos.x().fract() < 0.5, the pixel is covered and thus drawn;
82        // otherwise it's not, and we skip to the next pixel.
83        //
84        // Similarly, if x_right.fract() < 0.5 that's the "one-past-the-end"
85        // pixel, otherwise it's the last covered pixel and the next one is
86        // the actual one-past-the-end pixel.
87        let (x0, x1) = (round_up_to_half(v0.0.x()), round_up_to_half(x1));
88
89        // Adjust v0 to match the rounded x0
90        let v0 = v0.lerp(&v0.step(&self.dv_dx), x0 - v0.0.x());
91
92        let vs = v0.vary(self.dv_dx.clone(), Some((x1 - x0) as u32));
93
94        self.y += 1.0;
95        self.n -= 1;
96
97        Some(Scanline {
98            y: y as usize,
99            xs: x0 as usize..x1 as usize,
100            vs,
101        })
102    }
103}
104
105/// Converts a triangle defined by vertices `verts` into scanlines and calls
106/// `scanline_fn` for each scanline. The scanlines are guaranteed to cover
107/// exactly those pixels whose center point lies inside the triangle. For more
108/// information on the scanline conversion, see [`scan`].
109pub fn tri_fill<V, F>(mut verts: [Vertex<ScreenVec, V>; 3], mut scanline_fn: F)
110where
111    V: Vary,
112    F: FnMut(Scanline<V>),
113{
114    // Sort by y coordinate, start from the top
115    verts.sort_by(|a, b| a.pos.y().partial_cmp(&b.pos.y()).unwrap());
116    let [top, mid0, bot] = verts.map(|v| (v.pos, v.attrib));
117
118    let [top_y, mid_y, bot_y] = [top.0.y(), mid0.0.y(), bot.0.y()];
119
120    // Interpolate a point on the "long" edge at the same y as `mid0`
121    let mid1 = top.lerp(&bot, (mid_y - top_y) / (bot_y - top_y));
122
123    let (left, right) = if mid0.0.x() < mid1.0.x() {
124        (mid0, mid1)
125    } else {
126        (mid1, mid0)
127    };
128
129    //                       X <--top
130    //                     ***
131    //                   ******
132    //                 ********
133    //               ** upper **
134    // mid0/left--> X**********X <--right/mid1
135    //                ** lower **
136    //                   ********
137    //                      ******
138    //                         ***
139    //                            X <--bot
140
141    // Rasterize the upper half triangle...
142    scan(top_y..mid_y, &top..&left, &top..&right).for_each(&mut scanline_fn);
143
144    // ...and the lower half triangle
145    scan(mid_y..bot_y, &left..&bot, &right..&bot).for_each(&mut scanline_fn);
146}
147
148/// Returns an iterator that emits a scanline for each line from `y0` to `y1`,
149/// interpolating varyings from `l0` to `l1` on the left and from `r0` to `r1`
150/// on the right side.
151///
152/// The three input ranges define a *trapezoid* with horizontal bases, or, in
153/// the special case where `l0 == r0` or `l1 == r1`, a triangle:
154/// ```text
155///            l0___________ r0
156/// y0        _|____________|     .next()
157///         _|_______________|    .next()
158///       _|__________________|     ...
159///      |_____________________|    ...
160/// y1   l1                     r1
161/// ```
162/// Any convex polygon can be converted into scanlines by dividing it into
163/// trapezoidal segments and calling this function for each segment.
164///
165/// The exact pixels that are drawn are determined by whether the vector shape
166/// *covers* a pixel or not. A pixel is covered, and drawn, if and only if its
167/// center point lies inside the shape. This ensures that if two polygons
168/// share an edge, or several share a vertex, each pixel at the boundary will
169/// be drawn by exactly one of the polygons, with no gaps or overdrawn pixels.
170pub fn scan<V: Vary>(
171    Range { start: y0, end: y1 }: Range<f32>,
172    Range { start: l0, end: l1 }: Range<&Varyings<V>>,
173    Range { start: r0, end: r1 }: Range<&Varyings<V>>,
174) -> ScanlineIter<V> {
175    let recip_dy = (y1 - y0).recip();
176
177    // dv/dy for the left edge
178    let dl_dy = l0.dv_dt(l1, recip_dy);
179    // dv/dy for the right edge
180    let dr_dy = r0.dv_dt(r1, recip_dy);
181
182    // dv/dx is constant for the whole polygon; precompute it
183    let dv_dx = {
184        let (l0, r0) = (l0.step(&dl_dy), r0.step(&dr_dy));
185        let dx = r0.0.x() - l0.0.x();
186        l0.dv_dt(&r0, dx.recip())
187    };
188
189    // Find the y value of the next pixel center (.5) vertically
190    //
191    // We want to draw exactly those pixels whose center is *covered* by this
192    // polygon. Thus if y_range.start.fract() > 0.5, we skip to the next line.
193    // We align the y values with the pixel grid so that on each line, if
194    // x_range.start.fract() <= 0.5, the pixel is covered, otherwise it is not.
195    //
196    // This ensures that whenever two polygons share an edge, every pixel at
197    // the edge belongs to exactly one of the polygons, avoiding both gaps and
198    // overdrawn pixels. For example, on the left edge:
199    //
200    //      COVERED               NOT COVERED             NOT COVERED
201    //   +-----/-----+           +---------/-+           +-----------+
202    //   |    /······|           |        /··|           |     ·     |
203    //   |   p·+·····| p.y=0.5   |     + p···| p.y=0.5   |  ·  +  ·  |
204    //   |  /········|           |      /····|           |   p-------- p.y>0.5
205    //   +-/---------+           +-----/-----+           +--/--------+
206    //    p.x<0.5                    p.x>0.5              p.x<0.5
207    //
208    let y0_rounded = round_up_to_half(y0);
209    let y1_rounded = round_up_to_half(y1);
210
211    let y_tweak = y0_rounded - y0;
212
213    // Adjust varyings to correspond to the aligned y value
214    let l0 = l0.lerp(&l0.step(&dl_dy), y_tweak);
215    let r0 = r0.0.x() + dr_dy.0.x() * y_tweak;
216
217    ScanlineIter {
218        y: y0_rounded,
219        left: l0.vary(dl_dy, None),
220        right: r0.vary(dr_dy.0.x(), None),
221        dv_dx,
222        n: (y1_rounded - y0_rounded) as u32, // saturates to 0
223    }
224}
225
226#[inline]
227fn round_up_to_half(x: f32) -> f32 {
228    #[cfg(feature = "fp")]
229    {
230        use crate::math::float::f32;
231        f32::floor(x + 0.5) + 0.5
232    }
233    #[cfg(not(feature = "fp"))]
234    {
235        (x + 0.5) as i32 as f32 + 0.5
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use alloc::string::{String, ToString};
242    use core::iter::once;
243
244    use crate::assert_approx_eq;
245    use crate::geom::vertex;
246    use crate::math::vary::Vary;
247    use crate::math::vec3;
248    use crate::util::buf::Buf2;
249
250    use super::{tri_fill, Frag, Scanline};
251
252    // TODO Test different orientations and various edge cases
253
254    #[test]
255    fn shared_edge_should_not_have_gaps_or_overdraw() {
256        let mut buf = Buf2::new((20, 10));
257
258        let verts = [
259            vec3(8.0, 0.0, 0.0),
260            vec3(0.0, 6.0, 0.0),
261            vec3(14.0, 10.0, 0.0),
262            vec3(20.0, 3.0, 0.0),
263        ]
264        .map(|pos| vertex(pos, 0.0));
265
266        let expected = r"
26700000001110000000000
26800000011111111000000
26900000111111111111100
27000011111111111111111
27100111111111111111110
27201111111111111111100
27300111111111111111000
27400000111111111110000
27500000000011111100000
27600000000000011000000";
277
278        tri_fill([verts[0], verts[1], verts[2]], |sl| {
279            for x in sl.xs {
280                buf[[x as u32, sl.y as u32]] += 1;
281            }
282        });
283        tri_fill([verts[0], verts[2], verts[3]], |sl| {
284            for x in sl.xs {
285                buf[[x as u32, sl.y as u32]] += 1;
286            }
287        });
288
289        let s: String = buf
290            .rows()
291            .flat_map(|r| {
292                once("\n".to_string()).chain(r.iter().map(i32::to_string))
293            })
294            .collect();
295
296        assert_eq!(s, expected);
297    }
298
299    #[test]
300    fn gradient() {
301        use core::fmt::Write;
302        let verts = [
303            vec3::<_, ()>(15.0, 2.0, 0.0),
304            vec3(2.0, 8.0, 1.0),
305            vec3(26.0, 14.0, 0.5),
306        ]
307        .map(|pos| vertex(vec3(pos.x(), pos.y(), 1.0), pos.z()));
308
309        let expected = r"
310              0
311            2110
312          3322211
313       55444332221
314     76665544433222
315   88877666554443322
316    98887766655444332
317        88776665544433
318            77666554443
319                66655444
320                    65544
321                        54
322";
323        let mut s = "\n".to_string();
324
325        super::tri_fill(verts, |mut sl| {
326            write!(s, "{:w$}", " ", w = sl.xs.start).ok();
327
328            for c in sl.fragments().map(|f| ((10.0 * f.var) as u8)) {
329                write!(s, "{c}").ok();
330            }
331            writeln!(s).ok();
332        });
333        assert_eq!(s, expected);
334    }
335
336    #[test]
337    fn scanline_fragments_iter() {
338        let w0 = 2.0;
339        let w1 = 4.0;
340        let mut sl = Scanline {
341            y: 42,
342            xs: 8..16,
343            vs: Vary::vary_to(
344                (vec3(8.0, 42.0, 1.0 / w0), 3.0.z_div(w0)),
345                (vec3(16.0, 42.0, 1.0 / w1), 5.0.z_div(w1)),
346                8,
347            ),
348        };
349
350        // Perspective correct values
351        let zs = [
352            2.0f32, 2.1333334, 2.2857144, 2.4615386, 2.6666667, 2.909091, 3.2,
353            3.5555556, 4.0,
354        ];
355        let vars = [
356            3.0f32, 3.1333334, 3.2857144, 3.4615386, 3.6666667, 3.909091,
357            4.2000003, 4.555556, 5.0,
358        ];
359        let mut x = 8.0;
360
361        for ((Frag { pos, var }, z), v) in sl.fragments().zip(zs).zip(vars) {
362            assert_approx_eq!(pos, vec3(x, 42.0, z.recip()));
363            assert_approx_eq!(var, v);
364
365            x += 1.0;
366        }
367        // vary_to is inclusive
368        assert_eq!(x, 17.0);
369    }
370}