Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 18 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,33 @@ retrofire-core = { version = "0.4.0", path = "core" }
retrofire-front = { version = "0.4.0", path = "front" }
retrofire-geom = { version = "0.4.0", path = "geom" }

[dev-dependencies]
divan = "0.1.21"

[profile.release]
opt-level = 2
codegen-units = 1
codegen-units = 4
lto = "thin"
debug = 1

[profile.bench]
opt-level = 3
codegen-units = 1
lto = "fat"
lto = "thin"

[profile.dev]
opt-level = 1
split-debuginfo = "unpacked"


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

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

[[bench]]
name = "isect"
harness = false
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ 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
* Cubic Bézier, Hermite, Catmull–Rom, and B-splines
* Simple random number generation and distributions
* Simple text rendering with bitmap fonts
* Fully customizable rasterization stage
* Collecting rendering performance data
* Reading and writing pnm image files
* Reading and writing Wavefront .obj files
* Cubic Bezier curves and splines
* Simple random number generation and distributions
* Minifb, SDL2, and Wasm frontends
* Forever emoji-free README and docs
* Forever LLM-free code
Expand Down
91 changes: 91 additions & 0 deletions benches/clip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
//! Triangle clipping benchmarks.

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

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

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

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

#[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)
})
.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
})
}

#[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)
})
.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
})
}

#[divan::bench(args = [1, 10, 100, 1000, 10_000])]
fn clip_all_outside(b: Bencher, n: usize) {
let mut rng = DEFAULT_RNG;
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)
})
.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
})
}

fn main() {
divan::main()
}
101 changes: 101 additions & 0 deletions benches/fill.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! Fillrate benchmarks.

use core::iter::zip;

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

use retrofire_core::{
geom::{Tri, vertex},
math::{Color3, Color3f, color::gray, pt3, rgb},
render::{
Texture, raster::ScreenPt, raster::tri_fill, tex::SamplerRepeatPot, uv,
},
util::{buf::Buf2, pnm::save_ppm},
};

const SIZES: [f32; 5] = [4.0, 16.0, 64.0, 256.0, 1024.0];

const VERTS: [ScreenPt; 3] =
[pt3(0.1, 0.1, 0.0), pt3(0.9, 0.3, 0.5), pt3(0.4, 0.9, 1.0)];

#[divan::bench(args = SIZES)]
fn flat(b: Bencher, sz: f32) {
let mut buf: Buf2<Color3> = Buf2::new((1024, 1024));

b.with_inputs(|| VERTS.map(|p| vertex(p * sz, ())))
.input_counter(move |vs| ItemsCount::new(Tri(*vs).area() as usize))
.bench_local_values(|vs| {
tri_fill(vs, |sl| {
buf[sl.y][sl.xs].fill(gray(0xCC));
});
});

save_ppm("benches_fill_flat.ppm", buf).unwrap();
}
#[divan::bench(args = SIZES)]
fn gouraud(b: Bencher, sz: f32) {
let mut buf: Buf2<Color3f> = Buf2::new((1024, 1024));

b.with_inputs(|| {
[
vertex(VERTS[0] * sz, rgb(0.9, 0.1, 0.0)),
vertex(VERTS[1] * sz, rgb(0.1, 0.8, 0.1)),
vertex(VERTS[2] * sz, rgb(0.2, 0.3, 1.0)),
]
})
.input_counter(move |vs| ItemsCount::new(Tri(*vs).area() as usize))
.bench_local_values(|vs| {
tri_fill(vs, |sl| {
let y = sl.y;
let xs = sl.xs.clone();
let span = &mut buf[y][xs];

for ((_, col), pix) in zip(sl.vs, span) {
*pix = col;
}
});
});

let buf = Buf2::new_from(
(1024, 1024),
buf.data().into_iter().map(|c| c.to_color3()),
);
save_ppm("benches_fill_color.ppm", buf).unwrap();
}

#[divan::bench(args = SIZES)]
fn texture(b: Bencher, sz: f32) {
let mut buf: Buf2<Color3> = Buf2::new((1024, 1024));

let tex = Texture::from(Buf2::<Color3>::new_from(
(2, 2),
[gray(0xFF), gray(0x33), gray(0x33), gray(0xFF)],
));
let sampler = SamplerRepeatPot::new(&tex);

b.with_inputs(|| {
[
vertex(VERTS[0] * sz, uv(0.0, 0.0)),
vertex(VERTS[1] * sz, uv(4.0, 0.0)),
vertex(VERTS[2] * sz, uv(0.0, 4.0)),
]
})
.input_counter(move |vs| ItemsCount::new(Tri(*vs).area() as usize))
.bench_local_values(|vs| {
tri_fill(vs, |sl| {
let y = sl.y;
let xs = sl.xs.clone();
let span = &mut buf[y][xs];

for ((_, uv), pix) in zip(sl.vs, span) {
*pix = sampler.sample(&tex, uv);
}
});
});

save_ppm("benches_fill_tex.ppm", buf).unwrap();
}

fn main() {
divan::main()
}
151 changes: 151 additions & 0 deletions benches/isect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
//! Intersection testing benchmarks.

use core::hint::black_box;

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

use retrofire::core::{
geom::{Ray, Sphere},
math::rand::*,
math::{Point3, degs, pt3, spherical},
render::scene::BBox,
};
use retrofire::geom::Intersect;

#[divan::bench]
fn ray_bbox_hit(b: Bencher) {
let mut rng = DefaultRng::default();
let bbox = BBox::<()>(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0));

b.with_inputs(|| {
let v = 100.0 * UnitSphere.sample(&mut rng);
Ray(v.to_pt(), -v * (0.0..10.0).sample(&mut rng))
})
.counter(ItemsCount::new(1usize))
.bench_local_values(|ray| {
assert!(ray.intersect(&black_box(bbox)).is_some())
});
}
#[divan::bench]
fn ray_bbox_hit_2(b: Bencher) {
let mut rng = DefaultRng::default();
let bbox = BBox::<()>(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0));

let min = spherical(0.0, degs(-180.0), degs(-90.0));
let max = spherical(10.0, degs(180.0), degs(-45.0));
b.with_inputs(|| {
let v = (min..max).sample(&mut rng);
Ray(pt3(0.0, 2.0, 0.0), v.to_cart())
})
.counter(ItemsCount::new(1usize))
.bench_local_values(|ray| {
assert!(ray.intersect(&black_box(bbox)).is_some())
});
}
#[divan::bench]
fn ray_bbox_inside(b: Bencher) {
let mut rng = DefaultRng::default();
let bbox = BBox::<()>(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0));

b.with_inputs(|| {
let pt = PointsInUnitBall.sample(&mut rng);
let dir = VectorsInUnitBall.sample(&mut rng);
Ray(pt, dir)
})
.counter(ItemsCount::new(1usize))
.bench_local_values(|ray| {
assert!(ray.intersect(&black_box(bbox)).is_some())
});
}

#[divan::bench]
fn ray_bbox_miss(b: Bencher) {
let mut rng = DefaultRng::default();
let bbox = BBox::<()>(pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0));

b.with_inputs(|| {
let v = (spherical(0.0, degs(-180.0), degs(-45.0))
..spherical(10.0, degs(180.0), degs(90.0)))
.sample(&mut rng);

Ray(pt3(0.0, 3.0, 0.0), v.to_cart())
})
.counter(ItemsCount::new(1usize))
.bench_local_values(|ray| {
assert!(ray.intersect(&black_box(bbox)).is_none())
});
}

#[divan::bench]
fn ray_bbox_mixed(b: Bencher) {
let mut rng = DefaultRng::default();
let (p, q) = (pt3(-1.0, -1.0, -1.0), pt3(1.0, 1.0, 1.0));
let bbox = BBox::<()>(p, q);

b.with_inputs(|| {
// Approximately one third of the rays hits the box
let orig = (p..q).sample(&mut rng);
let dir = (p..q).sample(&mut rng);
Ray(2.0 * orig, 100.0 * dir.to_vec())
})
.counter(ItemsCount::new(1usize))
.bench_local_values(|ray| ray.intersect(&black_box(bbox)));
}

#[divan::bench]
fn ray_sphere_miss(b: Bencher) {
let mut rng = DefaultRng::default();

let sphere = Sphere(<Point3>::origin(), 1.0);

let min = spherical(0.0, degs(-180.0), degs(-45.0));
let max = spherical(10.0, degs(180.0), degs(90.0));
b.with_inputs(|| {
let v = (min..max).sample(&mut rng);
Ray(pt3(0.0, 3.0, 0.0), v.to_cart())
})
.counter(ItemsCount::new(1usize))
.bench_local_values(|ray| {
let ip = ray.intersect(&black_box(sphere));
assert!(ip.is_none());
ip
});
}

#[divan::bench]
fn ray_sphere_hit(b: Bencher) {
let mut rng = DefaultRng::default();

let sphere = Sphere(<Point3>::origin(), 1.0);

b.with_inputs(|| {
let v = (spherical(0.0, degs(-180.0), degs(-90.0))
..spherical(10.0, degs(180.0), degs(-45.0)))
.sample(&mut rng);
Ray(pt3(0.0, 2.0f32.sqrt(), 0.0), v.to_cart())
})
.counter(ItemsCount::new(1usize))
.bench_local_values(|ray| {
let ip = ray.intersect(&black_box(sphere));
assert!(ip.is_some());
ip
});
}

#[divan::bench]
fn ray_sphere_mixed(b: Bencher) {
let mut rng = DefaultRng::default();

let sphere = Sphere(<Point3>::origin(), 1.0);

b.with_inputs(|| {
let v = VectorsInUnitBall.sample(&mut rng);
Ray(pt3(0.0, 2.0, 0.0), v)
})
.counter(ItemsCount::new(1usize))
.bench_local_values(|ray| ray.intersect(&black_box(sphere)));
}

fn main() {
divan::main();
}
Loading
Loading