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}