Rusterizer
A software rasterizer made in Rust for the masterclass hosted by Luca Quartesan and Traverse Research.
Details
Version: v1.0
Language(s): Rust
Overview
Over a period of 3 weeks I worked on making a software raterizer in Rust. The first three days of this consisted of a masterclass held at BUas presented by Luca Quartesan. In this masterclass I learned the basics before I was let loose to do my own things.
There was a competition tied to this project, which I won. As a prize I got this beautiful 3D printed trophy

The engine
I made my own rasterisation engine as a crate for which the idea was to have a module that holds an interface for an app that the user can define their own functions in and a set of basic types related to rendering. A basic explanation of all features can be seen on the GitHub repo.
I’ll go into more detail here on a couple of aspects here.
Textures
My idea with textures and samplers was to mimic the GPU structs as much as possible. That’s why I also made a system that supports a huge variety of texture formats. I achieved this by making use of Enums:
pub enum TextureDataHolder<T> {
Linear(Vec<Vec<T>>),
Morton2D(Vec<ZArray2D<T>>),
Morton3D(Vec<ZArray3D<T>>),
}
// The size of this is constant, so no memory is wasted
pub enum TextureData {
U8(TextureDataHolder<u8>),
U16(TextureDataHolder<u16>),
U32(TextureDataHolder<u32>),
U64(TextureDataHolder<u64>),
U128(TextureDataHolder<u128>),
}
The container type is then determined with
let unpacked_format = unpack_format(config.format);
let data_size = (unpacked_format.0.component_count() * unpacked_format.1.byte_size())
.next_power_of_two();
Tiled rendering
To achieve tiled rendering properly I needed to optimise the time each bin took to process. I noticed that my main bottleneck was the locking of a mutex. This led to me creating what I call my least sane Rust struct to date
#[derive(Debug)]
pub struct UnsafeHandle<T> {
handle: usize,
owned: bool,
phantom: PhantomData<T>,
}
I read and write to this by casting the handle from and to pointers
pub unsafe fn read(&self) -> &T {
unsafe { (self.handle as *const T).as_ref().unwrap() }
}
#[allow(clippy::mut_from_ref)]
pub unsafe fn write(&self) -> &mut T {
unsafe { (self.handle as *mut T).as_mut().unwrap() }
}
For my triangle binning struct I also had to cast to and from usize to get optimal performance on all threads
rayon::scope(|s| {
let bins = self.triangle_bins.as_mut_ptr();
unsafe {
for i in 0..self.triangle_bins.len() {
let bin_handle = bins.add(i) as usize;
let offset = self.offsets[i].cast::<f32>().unwrap();
s.spawn(move |_| {
let bin = (bin_handle as *mut Vec<Triangle>).as_mut().unwrap();
// snip...
});
}
}
};
As one of the colleagues from Traverse already stated this needs a lot of unsafe code to work properly.