rast/graphics/
rasterizer.rs

1use std::array;
2use std::collections::LinkedList;
3use std::error::Error;
4use std::fmt::{self, Display, Formatter};
5use std::sync::{Arc, Mutex};
6
7use nalgebra::{Point2, Point3, Vector2};
8use rayon::prelude::*;
9
10use super::blending::Blendable;
11use super::framebuffer::{Framebuffer, MutableScanline};
12use super::scissor::Scissor;
13use super::shader::{FragmentContext, Shader, VertexContext, VertexOutput};
14
15#[derive(Debug)]
16pub enum RasterizerError {
17    NoRenderTarget,
18    RenderTargetUnfinished,
19}
20
21impl Display for RasterizerError {
22    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
23        write!(
24            f,
25            "{}",
26            match self {
27                Self::NoRenderTarget => "No render target pushed to the stack!",
28                Self::RenderTargetUnfinished => "Render target still present on the stack!",
29            }
30        )
31    }
32}
33
34impl Error for RasterizerError {}
35
36#[derive(Debug, Clone, Copy)]
37pub enum BlendFactor {
38    Zero,
39    One,
40    SrcAlpha,
41    OneMinusSrcAlpha,
42    DstAlpha,
43    OneMinusDstAlpha,
44}
45
46#[derive(Debug, Clone, Copy)]
47pub enum BlendOp {
48    Add,
49    SrcSubDst,
50    DstSubSrc,
51}
52
53#[derive(Debug)]
54pub struct ComponentBlendOp {
55    pub op: BlendOp,
56    pub src_factor: BlendFactor,
57    pub dst_factor: BlendFactor,
58}
59
60#[derive(Debug)]
61pub struct BlendAttachment {
62    pub color: Option<ComponentBlendOp>,
63    pub alpha: Option<ComponentBlendOp>,
64}
65
66fn color_to_channels(color: u32) -> [f32; 4] {
67    color.to_be_bytes().map(|c| (c as f32) / 256.0)
68}
69
70fn channels_to_color(channels: [f32; 4]) -> u32 {
71    u32::from_be_bytes(channels.map(|c| (c * 256.0) as u8))
72}
73
74struct BlendContext {
75    src_alpha: f32,
76    dst_alpha: f32,
77}
78
79impl ComponentBlendOp {
80    fn channel_term(value: f32, factor: &BlendFactor, context: &BlendContext) -> f32 {
81        let coeff = match factor {
82            BlendFactor::Zero => 0.0,
83            BlendFactor::One => 1.0,
84            BlendFactor::SrcAlpha => context.src_alpha,
85            BlendFactor::OneMinusSrcAlpha => 1.0 - context.src_alpha,
86            BlendFactor::DstAlpha => context.dst_alpha,
87            BlendFactor::OneMinusDstAlpha => 1.0 - context.dst_alpha,
88        };
89
90        coeff * value
91    }
92
93    fn blend(&self, src: f32, dst: f32, context: &BlendContext) -> f32 {
94        let src_term = Self::channel_term(src, &self.src_factor, context);
95        let dst_term = Self::channel_term(dst, &self.dst_factor, context);
96
97        match &self.op {
98            BlendOp::Add => src_term + dst_term,
99            BlendOp::SrcSubDst => src_term - dst_term,
100            BlendOp::DstSubSrc => dst_term - src_term,
101        }
102    }
103}
104
105impl BlendAttachment {
106    fn blend_colors(&self, src: u32, dst: u32) -> u32 {
107        let src_channels = color_to_channels(src);
108        let dst_channels = color_to_channels(dst);
109
110        let context = BlendContext {
111            src_alpha: src_channels[3],
112            dst_alpha: dst_channels[3],
113        };
114
115        channels_to_color(array::from_fn(|i| {
116            let component_op = match i {
117                3 => &self.alpha,
118                _ => &self.color,
119            };
120
121            match component_op {
122                Some(op) => op.blend(src_channels[i], dst_channels[i], &context),
123                None => src_channels[i],
124            }
125        }))
126    }
127}
128
129#[derive(Debug, Clone, Copy)]
130pub enum DepthMode {
131    DontCare,
132    Test,
133    Write,
134}
135
136impl DepthMode {
137    fn should_test(&self) -> bool {
138        match self {
139            DepthMode::DontCare => false,
140            _ => true,
141        }
142    }
143
144    fn should_write(&self) -> bool {
145        match self {
146            DepthMode::Write => true,
147            _ => false,
148        }
149    }
150}
151
152#[derive(Debug, Clone, Copy)]
153pub enum WindingOrder {
154    Clockwise,
155    CounterClockwise,
156}
157
158#[derive(Debug)]
159pub struct Pipeline<T: Shader> {
160    pub depth: DepthMode,
161
162    pub cull_back: bool,
163    pub winding_order: WindingOrder,
164
165    pub blending: Option<Vec<BlendAttachment>>,
166
167    pub shader: T,
168}
169
170pub struct IndexedRenderCall<'a, T: Shader> {
171    pub pipeline: &'a Pipeline<T>,
172
173    pub vertex_offset: usize,
174    pub first_instance: usize,
175    pub instance_count: usize,
176
177    pub scissor: Option<Scissor>,
178
179    pub indices: &'a [u16],
180    pub data: &'a T::Uniform,
181}
182
183pub fn gen_scissor(uv: &[Point2<f32>], max_width: usize, max_height: usize) -> Scissor {
184    let mut x0 = max_width;
185    let mut y0 = max_height;
186
187    let mut x1: usize = 0;
188    let mut y1: usize = 0;
189
190    for point in uv {
191        let x = point.x.clamp(0.0, 1.0) * max_width as f32;
192        let y = point.y.clamp(0.0, 1.0) * max_height as f32;
193
194        x0 = (x.floor() as usize).min(x0);
195        y0 = (y.floor() as usize).min(y0);
196
197        x1 = (x.ceil() as usize).max(x1);
198        y1 = (y.ceil() as usize).max(y1);
199    }
200
201    Scissor {
202        x: x0,
203        y: y0,
204        width: x1 - x0,
205        height: y1 - y0,
206    }
207}
208
209fn rotate_cw(v: &Vector2<f32>) -> Vector2<f32> {
210    Vector2::new(-v.y, v.x)
211}
212
213fn rotate_ccw(v: &Vector2<f32>) -> Vector2<f32> {
214    Vector2::new(v.y, -v.x)
215}
216
217fn signed_triangle_area(points: [&Point2<f32>; 3], winding: WindingOrder) -> f32 {
218    let a = points[0];
219    let b = points[1];
220    let c = points[2];
221
222    let ab = b - a;
223    let ac = c - a;
224
225    let normal = match winding {
226        // rotate counterclockwise 90 deg
227        WindingOrder::CounterClockwise => rotate_ccw(&ab),
228
229        // rotate clockwise 90 deg
230        WindingOrder::Clockwise => rotate_cw(&ab),
231    };
232
233    ac.dot(&normal) / 2.0
234}
235
236pub const VERTICES_PER_FACE: usize = 3;
237
238struct FragmentInfo {
239    depth: f32,
240    weights: [f32; VERTICES_PER_FACE],
241}
242
243fn process_fragment_geometry<T: Shader>(
244    triangle: &[Point3<f32>; VERTICES_PER_FACE],
245    point: &Point2<f32>,
246    pipeline: &Pipeline<T>,
247) -> Option<FragmentInfo> {
248    let screen_points = triangle.each_ref().map(|p| p.xy());
249    let areas: [_; VERTICES_PER_FACE] = array::from_fn(|i| {
250        let a = &screen_points[(i + 1) % VERTICES_PER_FACE];
251        let b = &screen_points[(i + 2) % VERTICES_PER_FACE];
252
253        signed_triangle_area([a, b, point], pipeline.winding_order)
254    });
255
256    // im not gonna bother trying to make this more idiomatic
257    let areas_valid = areas.each_ref().map(|area| *area >= 0.0);
258    let mut should_keep = areas_valid.iter().all(|valid| *valid);
259
260    if !pipeline.cull_back {
261        // if we dont cull, also keep back
262        should_keep |= areas_valid.iter().all(|valid| !*valid);
263    }
264
265    if should_keep {
266        let area_sum = areas.iter().sum::<f32>();
267        let flat_weights = areas.map(|area| area / area_sum);
268
269        let inverse_depths = triangle.each_ref().map(|p| 1.0 / p.z);
270        let inverse_depth = flat_weights
271            .iter()
272            .zip(inverse_depths.iter())
273            .map(|(w, d)| w * d)
274            .sum::<f32>();
275
276        Some(FragmentInfo {
277            depth: 1.0 / inverse_depth,
278            weights: array::from_fn(|i| flat_weights[i] * inverse_depths[i] / inverse_depth),
279        })
280    } else {
281        None
282    }
283}
284
285// returns false if fragment should be discarded
286fn depth_test(x: usize, current_depth: f32, scanline: &MutableScanline) -> bool {
287    if let Some(depth) = &scanline.depth {
288        let closest_depth = depth[x];
289
290        current_depth <= closest_depth
291    } else {
292        true
293    }
294}
295
296struct FaceContext<'a, T: Shader> {
297    instance_id: usize,
298    call: &'a IndexedRenderCall<'a, T>,
299    vertex_output: &'a [VertexOutput<T::Working>; VERTICES_PER_FACE],
300
301    fb_width: usize,
302    fb_height: usize,
303}
304
305fn should_discard_fragment(
306    x: usize,
307    depth_mode: &DepthMode,
308    current_depth: f32,
309    scanline: &MutableScanline,
310) -> bool {
311    if current_depth < 0.0 {
312        true
313    } else {
314        depth_mode.should_test() && !depth_test(x, current_depth, scanline)
315    }
316}
317
318fn render_fragment<T: Shader>(
319    x: usize,
320    context: &FaceContext<T>,
321    scanline: &mut MutableScanline,
322    point: Point2<f32>,
323    frag: FragmentInfo,
324) {
325    let color = context
326        .call
327        .pipeline
328        .shader
329        .fragment_stage(&FragmentContext {
330            instance_id: context.instance_id,
331            position: Point3::new(point.x, point.y, frag.depth),
332            data: &context.call.data,
333            working: T::Working::blend(
334                &context.vertex_output.each_ref().map(|output| &output.data),
335                &frag.weights,
336            ),
337        });
338
339    for i in 0..scanline.color.len() {
340        let row = &mut scanline.color[i];
341
342        row[x] = match &context.call.pipeline.blending {
343            Some(blending) => blending[i].blend_colors(color, row[x]),
344            None => color,
345        };
346    }
347
348    if context.call.pipeline.depth.should_write()
349        && let Some(depth_row) = &mut scanline.depth
350    {
351        depth_row[x] = frag.depth;
352    }
353}
354
355fn process_pixel<T: Shader>(x: usize, context: &FaceContext<T>, scanline: &mut MutableScanline) {
356    let point = Point2::new(
357        (((x as f32 + 0.5) / context.fb_width as f32) * 2.0) - 1.0,
358        (((scanline.y as f32 + 0.5) / context.fb_height as f32) * 2.0) - 1.0,
359    );
360
361    let vertex_positions = context.vertex_output.each_ref().map(|data| data.position);
362    if let Some(frag) = process_fragment_geometry(&vertex_positions, &point, &context.call.pipeline)
363    {
364        if should_discard_fragment(x, &context.call.pipeline.depth, frag.depth, scanline) {
365            return;
366        }
367
368        render_fragment(x, context, scanline, point, frag);
369    }
370}
371
372#[derive(Debug, Default)]
373pub struct RenderStats {
374    pub faces_processed: usize,
375    pub faces_rendered: usize,
376    pub instances: usize,
377    pub calls: usize,
378}
379
380pub struct Rasterizer {
381    stats: RenderStats,
382    render_targets: LinkedList<Arc<Mutex<Framebuffer>>>,
383}
384
385impl Rasterizer {
386    pub fn new() -> Rasterizer {
387        Rasterizer {
388            stats: RenderStats::default(),
389            render_targets: LinkedList::new(),
390        }
391    }
392
393    pub fn new_frame(&mut self) -> Result<(), RasterizerError> {
394        if !self.render_targets.is_empty() {
395            Err(RasterizerError::RenderTargetUnfinished)
396        } else {
397            self.stats = RenderStats::default();
398            Ok(())
399        }
400    }
401
402    pub fn stats<'a>(&'a self) -> &'a RenderStats {
403        &self.stats
404    }
405
406    pub fn push_render_target(&mut self, target: Arc<Mutex<Framebuffer>>) {
407        self.render_targets.push_back(target);
408    }
409
410    pub fn pop_render_target(&mut self) -> Result<(), RasterizerError> {
411        match self.render_targets.pop_back() {
412            Some(_) => Ok(()),
413            None => Err(RasterizerError::NoRenderTarget),
414        }
415    }
416
417    pub fn current_render_target(&mut self) -> Result<Arc<Mutex<Framebuffer>>, RasterizerError> {
418        match self.render_targets.back() {
419            Some(top) => Ok(top.clone()),
420            None => Err(RasterizerError::NoRenderTarget),
421        }
422    }
423
424    fn render_face<T: Shader + Sync>(
425        &mut self,
426        instance_id: usize,
427        face_index: usize,
428        call: &IndexedRenderCall<T>,
429        framebuffer: &mut Framebuffer,
430    ) {
431        let index_offset = face_index * VERTICES_PER_FACE;
432        let (fb_width, fb_height) = framebuffer.size();
433
434        let vertex_output = array::from_fn(|i| {
435            call.pipeline.shader.vertex_stage(&VertexContext {
436                vertex_id: call.indices[index_offset + i] as usize,
437                instance_id: instance_id,
438                data: call.data,
439            })
440        });
441
442        let uv = vertex_output
443            .each_ref()
444            .map(|output| output.position.xy().map(|x| (x + 1.0) / 2.0));
445
446        let generated_scissor = gen_scissor(&uv, fb_width, fb_height);
447        let final_scissor = match &call.scissor {
448            Some(user_scissor) => generated_scissor.intersect_with(user_scissor),
449            None => Some(generated_scissor), // move
450        };
451
452        if let Some(scissor) = final_scissor {
453            let fc = FaceContext {
454                instance_id,
455                call,
456                vertex_output: &vertex_output,
457                fb_width,
458                fb_height,
459            };
460
461            framebuffer
462                .scanlines(scissor.y, scissor.height)
463                .par_iter_mut()
464                .for_each(|scanline| {
465                    for delta_x in 0..scissor.width {
466                        process_pixel(scissor.x + delta_x, &fc, scanline);
467                    }
468                });
469
470            self.stats.faces_rendered += 1;
471        }
472    }
473
474    pub fn render_indexed<T: Shader + Sync>(
475        &mut self,
476        call: &IndexedRenderCall<T>,
477    ) -> Result<(), RasterizerError> {
478        let face_count = call.indices.len() / VERTICES_PER_FACE;
479
480        // todo: do we care about unused indices?
481
482        let top = self.current_render_target()?;
483        let mut framebuffer = top.lock().unwrap();
484
485        for i in 0..call.instance_count {
486            for j in 0..face_count {
487                self.render_face(call.first_instance + i, j, call, &mut framebuffer);
488                self.stats.faces_processed += 1;
489            }
490
491            self.stats.instances += 1;
492        }
493
494        self.stats.calls += 1;
495        Ok(())
496    }
497}