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}