Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
4c277bc
Factor pipeline stages into own functions
jdahlstrom May 14, 2026
1b76521
Add support for light sources
jdahlstrom Dec 1, 2024
23249db
Add uniform parameter also to fragment shader
jdahlstrom Dec 1, 2024
b59e267
Impl Lerp for arrays
jdahlstrom May 12, 2026
3aa86d2
Add support for 2D and 3D Perlin noise generation
jdahlstrom Aug 27, 2024
266e993
Add noise benchmarks
jdahlstrom May 12, 2026
3ae5225
Simplify bench counters
jdahlstrom May 13, 2026
5d7af02
Update README
jdahlstrom May 14, 2026
6334596
Add wallclock time stats
jdahlstrom May 14, 2026
c2a0d96
Use the xterm 6x6x6 RGB cube in curses instead of 3:3:2 8-bit
jdahlstrom May 14, 2026
11d437d
Make text rendering easier
jdahlstrom May 23, 2026
76dae82
Add Layout::grid convenience constructor
jdahlstrom May 23, 2026
8b57bd9
Include a 6x10 font in front, add a font to load it
jdahlstrom May 23, 2026
b080f46
Add fps display to minifb and sdl2 frontends
jdahlstrom May 23, 2026
b25ced7
Add doc comments and doctests to math::float
jdahlstrom May 21, 2026
e8d65a3
Add doctests to math::approx
jdahlstrom May 21, 2026
63d5b04
Add doctests to math::color
jdahlstrom May 21, 2026
7346619
Improve test coverage in math::mat
jdahlstrom May 21, 2026
80b4bc2
Improve test coverage in math::vec
jdahlstrom May 21, 2026
6b79353
Constify some Line2 and other functions
jdahlstrom May 23, 2026
edd8b81
Fix some tests that need approx_eq in no_std
jdahlstrom May 23, 2026
32bcae1
Make Inner::data pub(super), remove the accessor methods
jdahlstrom Jun 17, 2026
dbb4953
Fix buggy Buf2::reshape(), improve doc comments and doctests
jdahlstrom Jun 17, 2026
8921a7f
Add doc comments and doctests to util::pixfmt
jdahlstrom Jun 17, 2026
4220183
Convert Dims to a custom struct from an alias
jdahlstrom Jun 18, 2026
df548fd
Add re-exports to util
jdahlstrom Jun 18, 2026
687c406
Move resolution constants from front to core::util::dims
jdahlstrom Jun 18, 2026
337e02c
Make stats optional and behind a feature flag
jdahlstrom May 14, 2026
d060b25
Add some convenience methods to Dims
jdahlstrom Jun 19, 2026
9163681
Add example code to front README
jdahlstrom Jun 19, 2026
438b806
Enable switching object LoD in solids with , and . keys
jdahlstrom Jun 8, 2026
338c8fc
Fix a couple of mm tests that fail with --all-features
jdahlstrom Jun 28, 2026
089d487
Impl all pixel formats for Color4 and use a blanket impl for Color3
jdahlstrom Jun 28, 2026
15ed088
Add a couple of new pixel formats and SDL2 frontend support for them
jdahlstrom Nov 3, 2025
8a2ff9e
Fix bug with P4 PBM parsing
jdahlstrom Jun 29, 2026
52bef34
Impl Debug for pixel format types
jdahlstrom Jun 29, 2026
7ce1347
Pass format to into_pixel, remove separate into_pixel_fmt
jdahlstrom Jun 30, 2026
1c31fe5
Add 256-color indexed pixel format
jdahlstrom Jun 30, 2026
0665661
Allow Batch to borrow its rendered geometry, avoiding allocations
jdahlstrom Jul 2, 2026
0513d96
Clean up clipping code a bit, add comments
jdahlstrom Jul 2, 2026
1ced0c3
Add From impls equivalent to Vec2::to_vec3() and Point2::to_pt3()
jdahlstrom Jul 6, 2026
70f785e
Add Pos trait to abstract over vertices and points
jdahlstrom Jul 6, 2026
8b96136
Fix Dims constant
jdahlstrom Jul 7, 2026
fbf0e1d
Fix varying interpolation in line drawing
jdahlstrom Jul 8, 2026
d21fec8
Add a mesh wireframe function
jdahlstrom Jun 28, 2026
4c987e6
Add a type for mesh debug visualizations
jdahlstrom Jul 8, 2026
4c56719
Avoid allocating each debug normal separately
jdahlstrom Jul 8, 2026
679bda2
Add mesh debug visualizations to solids demo
jdahlstrom Jul 8, 2026
34aa1aa
Avoid rebuilding debug visualizations on every frame
jdahlstrom Jul 12, 2026
bd701be
Make Matrix::row_vec() and col_vec() const
jdahlstrom Jul 12, 2026
5d7da15
Add to_affine() method to Mat2 and Mat3
jdahlstrom Jul 12, 2026
6dbeb5d
Improve matrix doc comments and doctests
jdahlstrom Jul 12, 2026
3ce2513
Optimize matrix determinant and inversion
jdahlstrom Jul 12, 2026
af9b57e
Considerably improve matrix doc comments and doctests
jdahlstrom May 11, 2026
5b2bf3d
Add matrix inversion benchmark
jdahlstrom May 11, 2026
78d6c73
Add module-level documentation to math::vec
jdahlstrom Jun 27, 2026
9564437
Fix some pedantic clippy lints
jdahlstrom Jul 14, 2026
c90e473
Implement Mat2 and Mat3::solve()
jdahlstrom Jul 12, 2026
6bfa691
Refactor and add more matrix benchmarks
jdahlstrom Jul 14, 2026
8ddd8ed
Add Tri::contains(Point2) method
jdahlstrom Jul 7, 2026
b97623e
Implement ray/tri intersection for 2d and 3d
jdahlstrom Jul 7, 2026
322654c
Implement ray/mesh and ray/obj intersection
jdahlstrom Jul 8, 2026
87c6277
Add screenshots of the Stanford dragon and curses demo
jdahlstrom Jul 15, 2026
a1ea149
Implement Parametric for Polygon, similar to Polyline but wrapping
jdahlstrom Jul 16, 2026
9a14c6f
Impl Index<isize> for Polygon
jdahlstrom Jul 16, 2026
c918694
Add method to Polygon that generates 2D vertex normals
jdahlstrom Jul 16, 2026
773c4ad
Add functions to iterate over Parametrics
jdahlstrom Jul 16, 2026
41a981b
Add cmdline parameter to choose object in solids demo
jdahlstrom Jul 18, 2026
5d9b713
Fix and tweak rendering benchmarks
jdahlstrom Jul 18, 2026
84d2f85
Revamp Clip trait, reducing allocations and copying
jdahlstrom Jul 10, 2026
92b314c
Re-enable and reorganize clip tests
jdahlstrom Jul 18, 2026
7828065
Add missing Target impls for MutSlice2
jdahlstrom Jul 15, 2026
919f274
Change Matrix::to() to take source and dest spaces directly
jdahlstrom Jul 10, 2026
426c6b5
Rename Render to more fitting Primitive
jdahlstrom Jul 10, 2026
96218bf
Move Primitive trait to prim.rs
jdahlstrom Jul 10, 2026
9703f63
Explore impl Primitive for Tri<Vertex>
jdahlstrom Jul 10, 2026
dc2cad0
No need to collect in primitive_assembly anymore
jdahlstrom Jul 10, 2026
3a9cdf0
WIP New Render trait to allow new types of geometry, particularly non…
jdahlstrom Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ clippy.collapsible_if = "allow"
[features]
default = ["std"]
std = ["retrofire-core/std", "retrofire-geom/std"]
stats = ["retrofire-core/stats"]

[dependencies]
retrofire-core = { version = "0.4.0", path = "core" }
Expand Down Expand Up @@ -104,3 +105,11 @@ harness = false
[[bench]]
name = "vec"
harness = false

[[bench]]
name = "noise"
harness = false

[[bench]]
name = "mat"
harness = false
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ for custom allocators is planned in order to make `alloc` optional as well.
* Type-tagged affine and linear transforms and projections
* Perspective-correct texture mapping
* Triangle mesh data structure and a library of shapes
* Point, directional, and spotlight support
* Cubic Bézier, Hermite, Catmull–Rom, and B-splines
* Simple random number generation and distributions
* Simple text rendering with bitmap fonts
Expand All @@ -62,15 +63,14 @@ for custom allocators is planned in order to make `alloc` optional as well.
* Reading and writing pnm image files
* Reading and writing Wavefront .obj files
* Minifb, SDL2, and Wasm frontends
* Procedural noise generation
* Forever emoji-free README and docs
* Forever LLM-free code

## In progress

* Different camera types
* Builtin light source support
* Spherical etc UV mapping
* Procedural noise generation
* Spherical etc. UV mapping
* Terminal frontend with ncurses
* Cube mapping and skyboxes

Expand Down Expand Up @@ -115,8 +115,8 @@ The `retrofire-demos` package depends on `retrofire`.

# Screenshots

The classic Stanford bunny.
![The classic Stanford bunny 3D model.](docs/bunny.jpg)
The classic Stanford dragon.
![A detailed 3D model of a dragon sculpture.](docs/dragon.jpg)

A first-person mouse-and-keyboard scene with many "Rust crates" strewn on a
checkered floor.
Expand All @@ -125,6 +125,9 @@ checkered floor.
Ten thousand spherical particles positioned randomly in a sphere.
![Ten thousand spherical particles in random positions.](docs/sprites.jpg)

A colorful torus rendered in a terminal.
![A torus rendered in a terminal using block characters.](docs/curses.png)

# License

Copyright 2020-2025 Johannes Dahlström.
Expand Down
66 changes: 26 additions & 40 deletions benches/clip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,88 +2,74 @@

use core::{array, iter::repeat_with};

use divan::{Bencher, counter::ItemsCount};
use divan::{AllocProfiler, Bencher};

use retrofire_core::{
geom::{Tri, vertex},
math::rand::{DEFAULT_RNG, DefaultRng, Distrib},
math::{orthographic, pt3},
render::clip::{ClipVert, view_frustum},
math::rand::{DefaultRng, Distrib},
math::{ProjMat3, orthographic, pt3},
render::View,
render::clip::{Clip, ClipVert, view_frustum},
};

//#[global_allocator]
//static ALLOC: AllocProfiler = AllocProfiler::system();
#[global_allocator]
static ALLOC: AllocProfiler = AllocProfiler::system();

static PROJ: ProjMat3<View> =
orthographic(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0));

#[divan::bench(args = [1, 10, 100, 1000, 10_000])]
fn clip_mixed(b: Bencher, n: usize) {
let rng = &mut DefaultRng::default();
let pts = pt3(-10.0, -10.0, -10.0)..pt3(10.0, 10.0, 10.0);
let proj = orthographic(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0));

b.with_inputs(|| {
repeat_with(|| {
let vs = array::from_fn(|_| {
ClipVert::new(vertex(proj.apply(&pts.sample(rng)), ()))
});
Tri(vs)
Tri(array::from_fn(|_| {
ClipVert::new(vertex(PROJ.apply(&pts.sample(rng)), ()))
}))
})
.take(n)
.collect::<Vec<_>>()
})
.input_counter(|tris| ItemsCount::of_iter(tris))
.bench_local_values(|tris| {
let mut out = Vec::new();
view_frustum::clip(tris.as_slice(), &mut out);
out
})
.counter(n)
.bench_local_values(|tris| Tri::clip(tris, &view_frustum::PLANES))
}

#[divan::bench(args = [1, 10, 100, 1000, 10_000])]
fn clip_all_inside(b: Bencher, n: usize) {
let rng = &mut DefaultRng::default();
let pts = pt3(-1.0, -1.0, -1.0)..pt3(1.0, 1.0, 1.0);
let proj = orthographic(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0));

b.with_inputs(|| {
repeat_with(|| {
let vs = array::from_fn(|_| {
ClipVert::new(vertex(proj.apply(&pts.sample(rng)), ()))
});
Tri(vs)
Tri(array::from_fn(|_| {
ClipVert::new(vertex(PROJ.apply(&pts.sample(rng)), ()))
}))
})
.take(n)
.collect::<Vec<_>>()
})
.input_counter(|tris| ItemsCount::of_iter(tris))
.bench_local_values(|tris| {
let mut out = Vec::new();
view_frustum::clip(tris.as_slice(), &mut out);
out
})
.counter(n)
.bench_local_values(|tris| Tri::clip(tris, &view_frustum::PLANES))
}

#[divan::bench(args = [1, 10, 100, 1000, 10_000])]
fn clip_all_outside(b: Bencher, n: usize) {
let mut rng = DEFAULT_RNG;
let rng = &mut DefaultRng::default();
let pts = pt3(2.0, -10.0, -10.0)..pt3(10.0, 10.0, 10.0);
let proj = orthographic(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0));

b.with_inputs(|| {
repeat_with(|| {
let vs = ([pts.start; 3]..[pts.end; 3])
.sample(&mut rng)
.map(|pt| ClipVert::new(vertex(proj.apply(&pt), ())));
Tri(vs)
Tri(([pts.start; 3]..[pts.end; 3])
.sample(rng)
.map(|pt| ClipVert::new(vertex(PROJ.apply(&pt), ()))))
})
.take(n)
.collect::<Vec<_>>()
})
.input_counter(|tris| ItemsCount::of_iter(tris))
.bench_local_values(|tris| {
let mut out = Vec::with_capacity(tris.len());
view_frustum::clip(tris.as_slice(), &mut out);
out
})
.counter(n)
.bench_local_values(|tris| Tri::clip(tris, &view_frustum::PLANES))
}

fn main() {
Expand Down
63 changes: 30 additions & 33 deletions benches/e2e.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,46 @@

use divan::Bencher;

use retrofire_core::geom::{Normal3, Vertex3, tri, vertex};
use retrofire_core::math::{
Color3f, Color4, Color4f, ProjMat3, perspective, pt2, pt3, rgb, rgba,
translate, viewport,
use retrofire_core::{
geom::{Normal3, Tri, Vertex3, vertex},
math::{
Color3f, Color4, Color4f, ProjMat3, perspective, pt2, pt3, rgb, rgba,
translate, viewport,
},
render::{Context, Frag, Model, debug::dir_to_rgb, render, shader},
util::{Buf2, dims, pnm},
};
use retrofire_core::render::debug::dir_to_rgb;
use retrofire_core::render::{Context, Frag, Model, render, shader};
use retrofire_core::util::{buf::Buf2, pnm};
use retrofire_geom::solids::{Build, Sphere};

#[cfg(false)]
#[global_allocator]
static ALLOC: divan::AllocProfiler = divan::AllocProfiler::system();

#[divan::bench(args = [1, 4, 16, 64, 256, 1024], max_time = 2)]
fn triangle(b: Bencher, n: u32) {
let verts = [
vertex(pt3(-1.0, 1.0, 0.0), rgb(1.0, 0.0, 0.0)),
vertex(pt3(1.0, 1.0, 0.0), rgb(0.0, 0.8, 0.0)),
vertex(pt3(0.0, -1.0, 0.0), rgb(0.4, 0.4, 1.0)),
vertex(pt3(-1.0, -1.0, 0.0), rgb(1.0, 0.0, 0.0)),
vertex(pt3(0.0, 1.0, 0.0), rgb(0.0, 0.8, 0.0)),
vertex(pt3(1.0, -1.0, 0.0), rgb(0.4, 0.4, 1.0)),
];

let shader = shader::new(
|v: Vertex3<Color3f>, mvp: &ProjMat3<Model>| {
vertex(mvp.apply(&v.pos), v.attrib)
},
|frag: Frag<Color3f<_>>| frag.var.to_color4(),
|frag: Frag<Color3f<_>>, _: &_| frag.var.to_color4(),
);

let dims @ (w, h) = (640, 480);
let dims = dims::VGA_640_480;
let modelview = translate((0.0, 0.0, 2.0)).to();
let project = perspective(1.0, w as f32 / h as f32, 0.1..1000.0);
let viewport = viewport(pt2(0, h)..pt2(w, 0));
let project = perspective(1.0, dims.aspect(), 0.1..1000.0);
let viewport = viewport(pt2(0, dims.1)..pt2(dims.0, 0));

let mut framebuf = Buf2::<Color4>::new(dims);

b.bench_local(|| {
b.counter(n).bench_local(|| {
for _ in 0..n {
render(
[tri(0, 1, 2)],
verts,
Tri(verts),
&shader,
&modelview.then(&project),
viewport,
Expand All @@ -52,14 +51,13 @@ fn triangle(b: Bencher, n: u32) {
}
});

let center_pixel = framebuf[[w / 2, h / 2]];
let center_pixel = framebuf[[dims.0 / 2, dims.1 / 2]];
assert_eq!(center_pixel, rgba(89, 127, 64, 255));

assert_eq!(center_pixel, rgba(151, 128, 187, 255));

pnm::save_ppm("benches_e2e_triangle.ppm", framebuf).unwrap();
pnm::save_ppm("benches/out/e2e_triangle.ppm", framebuf).unwrap();
}

#[divan::bench(args=[4, 16, 64, 256, 1024], min_time=5, max_time=5)]
#[divan::bench(args=[4, 16, 64, 256, 1024], min_time=1, max_time=2)]
fn sphere(b: Bencher, res: u32) {
let sphere = Sphere {
sectors: res,
Expand All @@ -72,20 +70,19 @@ fn sphere(b: Bencher, res: u32) {
|v: Vertex3<Normal3>, mvp: &ProjMat3<Model>| {
vertex(mvp.apply(&v.pos), dir_to_rgb(v.attrib))
},
|frag: Frag<Color4f>| frag.var.to_color4(),
|frag: Frag<Color4f>, _: &_| frag.var.to_color4(),
);

let dims @ (w, h) = (640, 480);
let dims = dims::VGA_640_480;
let modelview = translate((0.0, 0.0, 2.0)).to();
let project = perspective(1.0, w as f32 / h as f32, 0.1..1000.0);
let viewport = viewport(pt2(0, h)..pt2(w, 0));
let project = perspective(1.0, dims.aspect(), 0.1..1000.0);
let viewport = viewport(pt2(0, dims.1)..pt2(dims.0, 0));

let mut framebuf = Buf2::<Color4>::new(dims);

b.bench_local(|| {
b.counter(sphere.faces.len()).bench_local(|| {
render(
&sphere.faces,
&sphere.verts,
sphere.clone(),
&shader,
&modelview.then(&project),
viewport,
Expand All @@ -94,11 +91,11 @@ fn sphere(b: Bencher, res: u32) {
);
});

let center_pixel = framebuf[[w / 2, h / 2]];

let center_pixel = framebuf[[dims.0 / 2, dims.1 / 2]];
assert_eq!(center_pixel, rgba(128, 127, 0, 255));

pnm::save_ppm("benches_e2e_sphere.ppm", framebuf).unwrap();
pnm::save_ppm(format!("benches/out/e2e_sphere_{res}.ppm"), framebuf)
.unwrap();
}

fn main() {
Expand Down
Loading
Loading