retrofire_core/
render.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//! Turning 3D geometry into raster images.
//!
//! This module constitutes the core 3D rendering pipeline of `retrofire`.
//! It contains code for [clipping][clip], [transforming, shading][shader],
//! [texturing][tex], [rasterizing][raster], and [outputting][target] basic
//! geometric shapes such as triangles.

use alloc::{vec, vec::Vec};
use core::fmt::Debug;

use crate::geom::{Tri, Vertex};
use crate::math::{
    mat::{Mat4x4, RealToProj, RealToReal},
    vary::Vary,
    vec::{vec3, ProjVec4, Vec3},
};

use clip::{view_frustum, Clip, ClipVert};
use ctx::{Context, DepthSort, FaceCull};
use raster::tri_fill;
use shader::{FragmentShader, VertexShader};
use stats::Stats;
use target::Target;

pub mod batch;
pub mod cam;
pub mod clip;
pub mod ctx;
pub mod raster;
pub mod shader;
pub mod stats;
pub mod target;
pub mod tex;

/// Model space coordinate basis.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Model;

/// World space coordinate basis.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct World;

/// View (camera) space coordinate basis.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct View;

/// NDC space coordinate basis (normalized device coordinates).
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Ndc;

/// Screen space coordinate basis.
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Screen;

// Mapping from model space to world space.
pub type ModelToWorld = RealToReal<3, Model, World>;

// Mapping from world space to view space.
pub type WorldToView = RealToReal<3, World, View>;

/// Mapping from model space to view space.
pub type ModelToView = RealToReal<3, Model, View>;

/// Mapping from model space to view space.
pub type ModelToProj = RealToProj<Model>;

/// Mapping from view space to projective space.
pub type ViewToProj = RealToProj<View>;

/// Mapping from NDC space to screen space.
pub type NdcToScreen = RealToReal<3, Ndc, Screen>;

/// Alias for combined vertex+fragment shader types
pub trait Shader<Vtx, Var, Uni>:
    VertexShader<Vtx, Uni, Output = Vertex<ProjVec4, Var>> + FragmentShader<Var>
{
}
impl<S, Vtx, Var, Uni> Shader<Vtx, Var, Uni> for S where
    S: VertexShader<Vtx, Uni, Output = Vertex<ProjVec4, Var>>
        + FragmentShader<Var>
{
}

/// Renders the given triangles into `target`.
pub fn render<Vtx: Clone, Var: Vary, Uni: Copy, Shd>(
    tris: impl AsRef<[Tri<usize>]>,
    verts: impl AsRef<[Vtx]>,
    shader: &Shd,
    uniform: Uni,
    to_screen: Mat4x4<NdcToScreen>,
    target: &mut impl Target,
    ctx: &Context,
) where
    Shd: Shader<Vtx, Var, Uni>,
{
    let mut stats = Stats::start();

    stats.calls = 1.0;
    stats.prims.i += tris.as_ref().len();
    stats.verts.i += verts.as_ref().len();

    // Vertex shader: transform vertices to clip space
    let verts: Vec<_> = verts
        .as_ref()
        .iter()
        // TODO Pass vertex as ref to shader
        .cloned()
        .map(|v| ClipVert::new(shader.shade_vertex(v, uniform)))
        .collect();

    // Map triangle vertex indices to actual vertices
    let tris: Vec<_> = tris
        .as_ref()
        .iter()
        .map(|Tri(vs)| Tri(vs.map(|i| verts[i].clone())))
        .collect();

    // Clip against the view frustum
    let mut clipped = vec![];
    tris.clip(&view_frustum::PLANES, &mut clipped);

    if let Some(d) = ctx.depth_sort {
        depth_sort(&mut clipped, d);
    }

    for Tri(vs) in clipped {
        // Transform to screen space
        let vs = vs.map(|v| {
            let [x, y, _, w] = v.pos.0;
            // Perspective division (projection to the real plane)
            //
            // We use the screen-space z coordinate to store the reciprocal
            // of the original view-space depth. The interpolated reciprocal
            // is used in fragment processing for depth testing (larger values
            // are closer) and for perspective correction of the varyings.
            let pos = vec3(x, y, 1.0).z_div(w);
            Vertex {
                // Viewport transform
                pos: to_screen.apply(&pos),
                // Perspective correction
                attrib: v.attrib.z_div(w),
            }
        });

        // Back/frontface culling
        match ctx.face_cull {
            Some(FaceCull::Back) if is_backface(&vs) => continue,
            Some(FaceCull::Front) if !is_backface(&vs) => continue,
            _ => {}
        }

        // Log output stats after culling
        stats.prims.o += 1;
        stats.verts.o += 3;

        // Fragment shader and rasterization
        tri_fill(vs, |scanline| {
            // Convert to fragments and shade
            stats.frags += target.rasterize(scanline, shader, ctx);
        });
    }
    *ctx.stats.borrow_mut() += stats.finish();
}

fn depth_sort<A>(tris: &mut [Tri<ClipVert<A>>], d: DepthSort) {
    tris.sort_unstable_by(|t, u| {
        let z = t.0[0].pos.z() + t.0[1].pos.z() + t.0[2].pos.z();
        let w = u.0[0].pos.z() + u.0[1].pos.z() + u.0[2].pos.z();
        if d == DepthSort::FrontToBack {
            z.total_cmp(&w)
        } else {
            w.total_cmp(&z)
        }
    });
}

fn is_backface<V>(vs: &[Vertex<Vec3<Screen>, V>]) -> bool {
    let v = vs[1].pos - vs[0].pos;
    let u = vs[2].pos - vs[0].pos;
    v[0] * u[1] - v[1] * u[0] > 0.0
}