retrofire_core/
render.rs

1//! Turning 3D geometry into raster images.
2//!
3//! This module constitutes the core 3D rendering pipeline of `retrofire`.
4//! It contains code for [clipping][clip], [transforming, shading][shader],
5//! [texturing][tex], [rasterizing][raster], and [outputting][target] basic
6//! geometric shapes such as triangles.
7
8use alloc::vec::Vec;
9use core::fmt::Debug;
10
11use crate::geom::Vertex;
12use crate::math::{
13    Mat4x4, Vary,
14    mat::{RealToProj, RealToReal},
15    vec::ProjVec3,
16};
17
18use self::{
19    clip::{ClipVert, view_frustum},
20    ctx::DepthSort,
21    raster::Scanline,
22};
23
24pub use self::{
25    batch::Batch,
26    cam::Camera,
27    clip::Clip,
28    ctx::Context,
29    shader::{FragmentShader, VertexShader},
30    stats::Stats,
31    target::{Colorbuf, Framebuf, Target},
32    tex::{TexCoord, Texture, uv},
33    text::Text,
34};
35
36pub mod batch;
37pub mod cam;
38pub mod clip;
39pub mod ctx;
40pub mod prim;
41pub mod raster;
42pub mod scene;
43pub mod shader;
44pub mod stats;
45pub mod target;
46pub mod tex;
47pub mod text;
48
49/// Renderable geometric primitive.
50pub trait Render<V: Vary> {
51    /// The type of this primitive in clip space
52    type Clip;
53
54    /// The type for which `Clip` is implemented.
55    type Clips: Clip<Item = Self::Clip> + ?Sized;
56
57    /// The type of this primitive in screen space.
58    type Screen;
59
60    /// Maps the indexes of the argument to vertices.
61    fn inline(ixd: Self, vs: &[ClipVert<V>]) -> Self::Clip;
62
63    /// Returns the (average) depth of the argument.
64    fn depth(_clip: &Self::Clip) -> f32 {
65        f32::INFINITY
66    }
67
68    /// Returns whether the argument is facing away from the camera.
69    fn is_backface(_: &Self::Screen) -> bool {
70        false
71    }
72
73    /// Transforms the argument from NDC to screen space.
74    fn to_screen(clip: Self::Clip, tf: &Mat4x4<NdcToScreen>) -> Self::Screen;
75
76    /// Rasterizes the argument by calling the function for each scanline.
77    fn rasterize<F: FnMut(Scanline<V>)>(scr: Self::Screen, scanline_fn: F);
78}
79
80/// Model space coordinate basis.
81#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
82pub struct Model;
83
84/// World space coordinate basis.
85#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
86pub struct World;
87
88/// View (camera) space coordinate basis.
89#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
90pub struct View;
91
92/// NDC space coordinate basis (normalized device coordinates).
93#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
94pub struct Ndc;
95
96/// Screen space coordinate basis.
97#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
98pub struct Screen;
99
100// Mapping from model space to world space.
101pub type ModelToWorld = RealToReal<3, Model, World>;
102
103// Mapping from world space to view space.
104pub type WorldToView = RealToReal<3, World, View>;
105
106/// Mapping from model space to view space.
107pub type ModelToView = RealToReal<3, Model, View>;
108
109/// Mapping from model space to view space.
110pub type ModelToProj = RealToProj<Model>;
111
112/// Mapping from view space to projective space.
113pub type ViewToProj = RealToProj<View>;
114
115/// Mapping from NDC space to screen space.
116pub type NdcToScreen = RealToReal<3, Ndc, Screen>;
117
118/// Alias for combined vertex+fragment shader types
119pub trait Shader<Vtx, Var, Uni>:
120    VertexShader<Vtx, Uni, Output = Vertex<ProjVec3, Var>> + FragmentShader<Var>
121{
122}
123impl<S, Vtx, Var, Uni> Shader<Vtx, Var, Uni> for S where
124    S: VertexShader<Vtx, Uni, Output = Vertex<ProjVec3, Var>>
125        + FragmentShader<Var>
126{
127}
128
129/// Renders the given primitives into `target`.
130pub fn render<Prim, Vtx: Clone, Var, Uni: Copy, Shd>(
131    prims: impl AsRef<[Prim]>,
132    verts: impl AsRef<[Vtx]>,
133    shader: &Shd,
134    uniform: Uni,
135    to_screen: Mat4x4<NdcToScreen>,
136    target: &mut impl Target,
137    ctx: &Context,
138) where
139    Prim: Render<Var> + Clone,
140    [<Prim>::Clip]: Clip<Item = Prim::Clip>,
141    Var: Vary,
142    Shd: Shader<Vtx, Var, Uni>,
143{
144    // 0. Preparations
145    let verts = verts.as_ref();
146    let prims = prims.as_ref();
147
148    let mut stats = Stats::start();
149    stats.calls = 1.0;
150    stats.prims.i = prims.len();
151    stats.verts.i = verts.len();
152
153    // 1. Vertex shader: transform vertices to clip space
154    let verts: Vec<_> = verts
155        // verts is borrowed, can't consume
156        .iter()
157        // TODO Pass vertex as ref to shader
158        .cloned()
159        .map(|v| shader.shade_vertex(v, uniform))
160        .map(ClipVert::new)
161        .collect();
162
163    // 2. Primitive assembly: map vertex indices to actual vertices
164    let prims: Vec<_> = prims
165        .iter()
166        .map(|tri| Prim::inline(tri.clone(), &verts))
167        // Collect needed because clip takes a slice...
168        .collect();
169
170    // 3. Clipping: clip against the view frustum
171    // TODO capacity is just a heuristic, should retain vector between calls somehow
172    let mut clipped = Vec::with_capacity(prims.len() / 2);
173    view_frustum::clip(&prims[..], &mut clipped);
174
175    // Optional depth sorting for use case such as transparency
176    if let Some(d) = ctx.depth_sort {
177        depth_sort::<Prim, _>(&mut clipped, d);
178    }
179
180    // For each primitive in the view frustum:
181    for prim in clipped {
182        // Transform to screen space
183        let prim = Prim::to_screen(prim, &to_screen);
184        // Back/frontface culling
185        // TODO This could also be done earlier, before or as part of clipping
186        if ctx.face_cull(Prim::is_backface(&prim)) {
187            continue;
188        }
189
190        // Log output stats after culling
191        stats.prims.o += 1;
192        stats.verts.o += 3; // TODO Get number of verts in prim somehow
193
194        // 4. Fragment shader and rasterization
195        Prim::rasterize(prim, |scanline| {
196            // Convert to fragments, shade, and draw to target
197            stats.frags += target.rasterize(scanline, shader, ctx);
198        });
199    }
200    *ctx.stats.borrow_mut() += stats.finish();
201}
202
203fn depth_sort<P: Render<V>, V: Vary>(prims: &mut [P::Clip], d: DepthSort) {
204    prims.sort_unstable_by(|t, u| {
205        let z = P::depth(t);
206        let w = P::depth(u);
207        if d == DepthSort::FrontToBack {
208            z.total_cmp(&w)
209        } else {
210            w.total_cmp(&z)
211        }
212    });
213}