A ray tracer in one file
This is Peter Shirley's Ray Tracing in One Weekend scene, written as a single
C+ program with no dependencies beyond libc's math functions and write(). It
renders ten spheres — a mix of diffuse, metal, and glass — to an 800 × 450 image
and writes it out as a PPM file.

It is a good first "real" program to read because it exercises a lot of the
language at once — structs and methods, ownership, raw pointers behind unsafe,
C FFI, recursion, and float-heavy inner loops — while staying short enough to
hold in your head.
| Image | 800 × 450, P6 PPM |
| Samples per pixel | 32 |
| Max recursion depth | 15 |
| Scene | 10 spheres (Lambertian / metal / dielectric) |
| RNG | xorshift32, fixed seed 0x12345678 |
| Threading | single-threaded |
How it works
Vectors
Everything is built on a 3-component float vector and a handful of free functions over it. There is no operator overloading here — the math is spelled out, which keeps the codegen obvious.
struct V3 { x: f32, y: f32, z: f32 }
fn add(a: V3, b: V3) -> V3 { return V3 { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }; }
fn scale(a: V3, s: f32) -> V3 { return V3 { x: a.x * s, y: a.y * s, z: a.z * s }; }
fn dot(a: V3, b: V3) -> f32 { return a.x * b.x + a.y * b.y + a.z * b.z; }
fn len2(a: V3) -> f32 { return a.x*a.x + a.y*a.y + a.z*a.z; }
fn norm(a: V3) -> V3 { let l: f32 = f_sqrt(len2(a)); return scale(a, 1.0f32 / l); }
V3 is a plain value type. Passing and returning it by value is the natural
style; the compiler keeps its three fields in registers across these calls
rather than spilling a struct to the stack.
Randomness
The sampler needs a fast, deterministic random stream. A restrict-qualified
pointer to the state makes the aliasing promise explicit, which lets the
optimizer keep the state in a register through the xorshift:
fn rng_next(restrict state: *u32) -> u32 {
let mut x: u32 = unsafe { state[0] };
x = x ^ (x << 13);
x = x ^ (x >> 17);
x = x ^ (x << 5);
unsafe { state[0] = x; }
return x;
}
fn randf(restrict state: *u32) -> f32 {
return ((rng_next(state) >> 8) as f32) * (1.0f32 / 16777216.0f32);
}
The fixed seed is what makes the output bit-for-bit reproducible from run to run.
The by-value hit record
Ray-sphere intersection is the hot path. The classic C version writes the result
through an out-pointer and returns a bool. Here the function just returns the
record by value, with a valid flag that says whether the rest of the fields
mean anything:
struct Hit {
valid: bool,
t: f32,
p: V3,
normal: V3,
front_face: i32,
sphere_idx: i32,
}
fn sphere_hit(restrict scene: *Sphere, idx: i32, r: Ray, t_min: f32, t_max: f32) -> Hit {
let s: Sphere = unsafe { scene[idx as usize] };
let oc: V3 = sub(r.origin, s.center);
let a: f32 = len2(r.dir);
let half_b: f32 = dot(oc, r.dir);
let c: f32 = len2(oc) - s.radius * s.radius;
let disc: f32 = half_b * half_b - a * c;
if disc < 0.0f32 { return miss(); }
let sd: f32 = f_sqrt(disc);
let mut root: f32 = (0.0f32 - half_b - sd) / a;
if root <= t_min || root >= t_max {
root = (0.0f32 - half_b + sd) / a;
if root <= t_min || root >= t_max { return miss(); }
}
let p: V3 = add(r.origin, scale(r.dir, root));
let outward: V3 = scale(sub(p, s.center), 1.0f32 / s.radius);
let ff: i32 = if dot(r.dir, outward) < 0.0f32 { 1 } else { 0 };
let mut n: V3 = outward;
if ff == 0 { n = neg(outward); }
return Hit { valid: true, t: root, p: p, normal: n, front_face: ff, sphere_idx: idx };
}
This is the interesting bit for a systems language: returning a 40-byte struct
by value reads as if it should be slower than threading an out-pointer through
every call, but it isn't. The compiler's scalar-replacement pass splits the
Hit into its individual fields and keeps them in registers, so the return is
free — you get the clean signature and the tight code, with no measurable cost over threading
a hand-written out-pointer through every call.
world_hit just walks the sphere array and keeps the closest valid hit:
fn world_hit(restrict scene: *Sphere, n_spheres: i32, r: Ray, t_min: f32, t_max: f32) -> Hit {
let mut best: Hit = miss();
let mut closest: f32 = t_max;
let mut i: i32 = 0;
while i < n_spheres {
let h: Hit = sphere_hit(scene, i, r, t_min, closest);
if h.valid {
closest = h.t;
best = h;
}
i = i + 1;
}
return best;
}
Materials
Each sphere carries a material tag (0 diffuse, 1 metal, 2 glass). scatter
returns — again by value — whether the ray was absorbed, the attenuation colour,
and the bounced ray:
struct Scattered { valid: bool, atten: V3, scattered: Ray }
- Lambertian (diffuse) scatters around the surface normal toward a random point in the unit sphere.
- Metal reflects the incoming ray and perturbs it by a fuzz factor.
- Dielectric (glass) refracts or reflects using Snell's law and a Schlick approximation for the reflectance, picking between them with the RNG.
Bouncing a ray
ray_color ties it together: find the nearest hit, scatter off it, and recurse
on the bounced ray, multiplying in each surface's attenuation. When nothing is
hit, it returns the sky gradient. Depth is bounded so recursion always
terminates.
fn ray_color(
restrict scene: *Sphere, n_spheres: i32, restrict state: *u32,
r: Ray, depth: i32,
) -> V3 {
if depth <= 0 { return V(0.0f32, 0.0f32, 0.0f32); }
let rec: Hit = world_hit(scene, n_spheres, r, 0.001f32, 1e30f32);
if rec.valid {
let sc: Scattered = scatter(scene, state, r, rec);
if sc.valid {
let c: V3 = ray_color(scene, n_spheres, state, sc.scattered, depth - 1);
return vmul(sc.atten, c);
}
return V(0.0f32, 0.0f32, 0.0f32);
}
let ud: V3 = norm(r.dir);
let t: f32 = 0.5f32 * (ud.y + 1.0f32);
return add(scale(V(1.0f32,1.0f32,1.0f32), 1.0f32 - t), scale(V(0.5f32,0.7f32,1.0f32), t));
}
The render loop
main allocates the scene, RNG, and camera, then loops over every pixel,
averaging 32 jittered samples and gamma-correcting the result before packing it
into a byte buffer. The buffer is written to out.ppm through the raw open /
write / close syscalls.
Full source
The whole program, ready to drop into src/main.cplus:
// Ray Tracing in One Weekend (Peter Shirley) — a complete path tracer in one
// C+ file. Renders 10 spheres (Lambertian, metal, dielectric) to a PPM image.
// Single-threaded; depends only on libc math + open/write/close.
extern fn sqrtf(x: f32) -> f32;
extern fn tanf(x: f32) -> f32;
extern fn fabsf(x: f32) -> f32;
extern fn malloc(n: usize) -> *u8;
extern fn free(p: *u8);
extern fn open(path: *u8, flags: i32, ...) -> i32;
extern fn write(fd: i32, buf: *u8, count: usize) -> isize;
extern fn close(fd: i32) -> i32;
fn f_sqrt(x: f32) -> f32 { return unsafe { sqrtf(x) }; }
fn f_tan(x: f32) -> f32 { return unsafe { tanf(x) }; }
fn f_abs(x: f32) -> f32 { return unsafe { fabsf(x) }; }
struct V3 { x: f32, y: f32, z: f32 }
fn V(x: f32, y: f32, z: f32) -> V3 { return V3 { x: x, y: y, z: z }; }
fn add(a: V3, b: V3) -> V3 { return V3 { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }; }
fn sub(a: V3, b: V3) -> V3 { return V3 { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; }
fn vmul(a: V3, b: V3) -> V3 { return V3 { x: a.x * b.x, y: a.y * b.y, z: a.z * b.z }; }
fn scale(a: V3, s: f32) -> V3 { return V3 { x: a.x * s, y: a.y * s, z: a.z * s }; }
fn neg(a: V3) -> V3 { return V3 { x: 0.0f32 - a.x, y: 0.0f32 - a.y, z: 0.0f32 - a.z }; }
fn dot(a: V3, b: V3) -> f32 { return a.x * b.x + a.y * b.y + a.z * b.z; }
fn len2(a: V3) -> f32 { return a.x*a.x + a.y*a.y + a.z*a.z; }
fn norm(a: V3) -> V3 { let l: f32 = f_sqrt(len2(a)); return scale(a, 1.0f32 / l); }
fn cross(a: V3, b: V3) -> V3 {
return V3 {
x: a.y*b.z - a.z*b.y,
y: a.z*b.x - a.x*b.z,
z: a.x*b.y - a.y*b.x,
};
}
fn reflect(v: V3, n: V3) -> V3 {
let d: f32 = dot(v, n);
let s2: V3 = scale(n, 2.0f32 * d);
return sub(v, s2);
}
fn rng_next(restrict state: *u32) -> u32 {
let mut x: u32 = unsafe { state[0] };
x = x ^ (x << 13);
x = x ^ (x >> 17);
x = x ^ (x << 5);
unsafe { state[0] = x; }
return x;
}
fn randf(restrict state: *u32) -> f32 {
return ((rng_next(state) >> 8) as f32) * (1.0f32 / 16777216.0f32);
}
fn rand_in_unit_sphere(restrict state: *u32) -> V3 {
let mut p: V3 = V(0.0f32, 0.0f32, 0.0f32);
let mut done: bool = false;
while !done {
p = V(
2.0f32 * randf(state) - 1.0f32,
2.0f32 * randf(state) - 1.0f32,
2.0f32 * randf(state) - 1.0f32,
);
if len2(p) < 1.0f32 { done = true; }
}
return p;
}
fn rand_unit_vector(restrict state: *u32) -> V3 { return norm(rand_in_unit_sphere(state)); }
#[repr(C)]
struct Sphere {
center: V3,
radius: f32,
mat: i32,
albedo: V3,
extra: f32,
}
fn fill_scene(restrict scene: *Sphere) {
unsafe {
scene[0] = Sphere { center: V( 0.0f32,-1000.0f32, 0.0f32), radius:1000.0f32, mat: 0, albedo: V(0.5f32,0.5f32,0.5f32), extra: 0.0f32 };
scene[1] = Sphere { center: V( 0.0f32, 1.0f32, 0.0f32), radius: 1.0f32, mat: 2, albedo: V(1.0f32,1.0f32,1.0f32), extra: 1.5f32 };
scene[2] = Sphere { center: V(-4.0f32, 1.0f32, 0.0f32), radius: 1.0f32, mat: 0, albedo: V(0.4f32,0.2f32,0.1f32), extra: 0.0f32 };
scene[3] = Sphere { center: V( 4.0f32, 1.0f32, 0.0f32), radius: 1.0f32, mat: 1, albedo: V(0.7f32,0.6f32,0.5f32), extra: 0.0f32 };
scene[4] = Sphere { center: V(-2.0f32, 0.3f32, 1.5f32), radius: 0.3f32, mat: 0, albedo: V(0.8f32,0.3f32,0.3f32), extra: 0.0f32 };
scene[5] = Sphere { center: V( 2.0f32, 0.3f32, 1.5f32), radius: 0.3f32, mat: 1, albedo: V(0.8f32,0.8f32,0.8f32), extra: 0.3f32 };
scene[6] = Sphere { center: V(-1.0f32, 0.3f32,-1.5f32), radius: 0.3f32, mat: 2, albedo: V(1.0f32,1.0f32,1.0f32), extra: 1.5f32 };
scene[7] = Sphere { center: V( 1.0f32, 0.3f32,-1.5f32), radius: 0.3f32, mat: 0, albedo: V(0.1f32,0.2f32,0.5f32), extra: 0.0f32 };
scene[8] = Sphere { center: V( 0.0f32, 0.3f32, 2.5f32), radius: 0.3f32, mat: 1, albedo: V(0.6f32,0.8f32,0.2f32), extra: 0.0f32 };
scene[9] = Sphere { center: V( 0.0f32, 0.3f32,-2.5f32), radius: 0.3f32, mat: 0, albedo: V(0.9f32,0.7f32,0.2f32), extra: 0.0f32 };
}
return;
}
struct Ray { origin: V3, dir: V3 }
// Hit carries a `valid` flag instead of using an out-pointer. The compiler's
// scalar-replacement pass keeps its fields in registers, so the by-value
// return is free.
struct Hit {
valid: bool,
t: f32,
p: V3,
normal: V3,
front_face: i32,
sphere_idx: i32,
}
fn miss() -> Hit {
return Hit { valid: false, t: 0.0f32, p: V3 { x: 0.0f32, y: 0.0f32, z: 0.0f32 },
normal: V3 { x: 0.0f32, y: 0.0f32, z: 0.0f32 }, front_face: 0, sphere_idx: 0 };
}
fn sphere_hit(restrict scene: *Sphere, idx: i32, r: Ray, t_min: f32, t_max: f32) -> Hit {
let s: Sphere = unsafe { scene[idx as usize] };
let oc: V3 = sub(r.origin, s.center);
let a: f32 = len2(r.dir);
let half_b: f32 = dot(oc, r.dir);
let c: f32 = len2(oc) - s.radius * s.radius;
let disc: f32 = half_b * half_b - a * c;
if disc < 0.0f32 { return miss(); }
let sd: f32 = f_sqrt(disc);
let mut root: f32 = (0.0f32 - half_b - sd) / a;
if root <= t_min || root >= t_max {
root = (0.0f32 - half_b + sd) / a;
if root <= t_min || root >= t_max { return miss(); }
}
let p: V3 = add(r.origin, scale(r.dir, root));
let outward: V3 = scale(sub(p, s.center), 1.0f32 / s.radius);
let ff: i32 = if dot(r.dir, outward) < 0.0f32 { 1 } else { 0 };
let mut n: V3 = outward;
if ff == 0 { n = neg(outward); }
return Hit { valid: true, t: root, p: p, normal: n, front_face: ff, sphere_idx: idx };
}
fn world_hit(restrict scene: *Sphere, n_spheres: i32, r: Ray, t_min: f32, t_max: f32) -> Hit {
let mut best: Hit = miss();
let mut closest: f32 = t_max;
let mut i: i32 = 0;
while i < n_spheres {
let h: Hit = sphere_hit(scene, i, r, t_min, closest);
if h.valid {
closest = h.t;
best = h;
}
i = i + 1;
}
return best;
}
fn schlick(cosv: f32, ref_idx: f32) -> f32 {
let mut r0: f32 = (1.0f32 - ref_idx) / (1.0f32 + ref_idx);
r0 = r0 * r0;
let om: f32 = 1.0f32 - cosv;
return r0 + (1.0f32 - r0) * om*om*om*om*om;
}
struct Scattered { valid: bool, atten: V3, scattered: Ray }
fn absorbed() -> Scattered {
return Scattered {
valid: false,
atten: V(0.0f32, 0.0f32, 0.0f32),
scattered: Ray { origin: V(0.0f32, 0.0f32, 0.0f32), dir: V(0.0f32, 0.0f32, 0.0f32) },
};
}
fn scatter(restrict scene: *Sphere, restrict state: *u32, r_in: Ray, rec: Hit) -> Scattered {
let s: Sphere = unsafe { scene[rec.sphere_idx as usize] };
if s.mat == 0 {
let mut dir: V3 = add(rec.normal, rand_unit_vector(state));
if f_abs(dir.x) < 1e-8f32 {
if f_abs(dir.y) < 1e-8f32 {
if f_abs(dir.z) < 1e-8f32 {
dir = rec.normal;
}
}
}
return Scattered { valid: true, atten: s.albedo, scattered: Ray { origin: rec.p, dir: dir } };
}
if s.mat == 1 {
let refl: V3 = reflect(norm(r_in.dir), rec.normal);
let dir: V3 = add(refl, scale(rand_in_unit_sphere(state), s.extra));
let d: f32 = dot(dir, rec.normal);
if d <= 0.0f32 { return absorbed(); }
return Scattered { valid: true, atten: s.albedo, scattered: Ray { origin: rec.p, dir: dir } };
}
// Dielectric.
let ratio: f32 = if rec.front_face == 1 { 1.0f32 / s.extra } else { s.extra };
let unit_dir: V3 = norm(r_in.dir);
let mut cos_theta: f32 = 0.0f32 - dot(unit_dir, rec.normal);
if cos_theta > 1.0f32 { cos_theta = 1.0f32; }
let sin_theta: f32 = f_sqrt(1.0f32 - cos_theta*cos_theta);
let cannot: bool = (ratio * sin_theta) > 1.0f32;
let mut dir: V3 = V(0.0f32, 0.0f32, 0.0f32);
if cannot || schlick(cos_theta, ratio) > randf(state) {
dir = reflect(unit_dir, rec.normal);
} else {
let r_perp: V3 = scale(add(unit_dir, scale(rec.normal, cos_theta)), ratio);
let mut k: f32 = 1.0f32 - len2(r_perp);
if k < 0.0f32 { k = 0.0f32; }
let r_para: V3 = scale(rec.normal, 0.0f32 - f_sqrt(k));
dir = add(r_perp, r_para);
}
return Scattered { valid: true, atten: V(1.0f32, 1.0f32, 1.0f32),
scattered: Ray { origin: rec.p, dir: dir } };
}
fn ray_color(
restrict scene: *Sphere, n_spheres: i32, restrict state: *u32,
r: Ray, depth: i32,
) -> V3 {
if depth <= 0 { return V(0.0f32, 0.0f32, 0.0f32); }
let rec: Hit = world_hit(scene, n_spheres, r, 0.001f32, 1e30f32);
if rec.valid {
let sc: Scattered = scatter(scene, state, r, rec);
if sc.valid {
let c: V3 = ray_color(scene, n_spheres, state, sc.scattered, depth - 1);
return vmul(sc.atten, c);
}
return V(0.0f32, 0.0f32, 0.0f32);
}
let ud: V3 = norm(r.dir);
let t: f32 = 0.5f32 * (ud.y + 1.0f32);
return add(scale(V(1.0f32,1.0f32,1.0f32), 1.0f32 - t), scale(V(0.5f32,0.7f32,1.0f32), t));
}
struct Camera { origin: V3, ll: V3, hor: V3, ver: V3 }
fn cam_init(restrict c: *Camera, look_from: V3, look_at: V3, vup: V3, vfov_deg: f32, aspect: f32) {
let theta: f32 = vfov_deg * 3.14159265358979323846f32 / 180.0f32;
let h: f32 = f_tan(theta * 0.5f32);
let vh: f32 = 2.0f32 * h;
let vw: f32 = aspect * vh;
let w: V3 = norm(sub(look_from, look_at));
let u: V3 = norm(cross(vup, w));
let v: V3 = cross(w, u);
unsafe {
c[0] = Camera {
origin: look_from,
hor: scale(u, vw),
ver: scale(v, vh),
ll: sub(sub(sub(look_from, scale(scale(u, vw), 0.5f32)), scale(scale(v, vh), 0.5f32)), w),
};
}
return;
}
fn cam_ray(restrict c: *Camera, s: f32, t: f32) -> Ray {
let origin: V3 = unsafe { c[0].origin };
let ll: V3 = unsafe { c[0].ll };
let hor: V3 = unsafe { c[0].hor };
let ver: V3 = unsafe { c[0].ver };
return Ray { origin: origin, dir: sub(add(add(ll, scale(hor, s)), scale(ver, t)), origin) };
}
fn clampf(x: f32, lo: f32, hi: f32) -> f32 {
if x < lo { return lo; }
if x > hi { return hi; }
return x;
}
pub fn main() -> i32 {
let width: i32 = 800;
let height: i32 = 450;
let samples: i32 = 32;
let max_depth: i32 = 15;
let n_spheres: i32 = 10;
let scene: *Sphere = unsafe { malloc((n_spheres as usize) * #size_of::[Sphere]()) as *Sphere };
fill_scene(scene);
let rng: *u32 = unsafe { malloc(4 as usize) as *u32 };
unsafe { rng[0] = 0x12345678 as u32; }
let aspect: f32 = (width as f32) / (height as f32);
let cam: *Camera = unsafe { malloc(#size_of::[Camera]()) as *Camera };
cam_init(cam,
V(13.0f32, 2.0f32, 3.0f32),
V(0.0f32, 0.0f32, 0.0f32),
V(0.0f32, 1.0f32, 0.0f32),
20.0f32, aspect);
let pixel_bytes: usize = (width as usize) * (height as usize) * (3 as usize);
let pixels: *u8 = unsafe { malloc(pixel_bytes) };
let inv_samples: f32 = 1.0f32 / (samples as f32);
let mut j: i32 = height - 1;
while j >= 0 {
let mut i: i32 = 0;
while i < width {
let mut col: V3 = V(0.0f32, 0.0f32, 0.0f32);
let mut sIdx: i32 = 0;
while sIdx < samples {
let u: f32 = ((i as f32) + randf(rng)) / ((width - 1) as f32);
let v: f32 = ((j as f32) + randf(rng)) / ((height - 1) as f32);
col = add(col, ray_color(scene, n_spheres, rng, cam_ray(cam, u, v), max_depth));
sIdx = sIdx + 1;
}
let r: f32 = clampf(f_sqrt(col.x * inv_samples), 0.0f32, 0.999f32);
let g: f32 = clampf(f_sqrt(col.y * inv_samples), 0.0f32, 0.999f32);
let b: f32 = clampf(f_sqrt(col.z * inv_samples), 0.0f32, 0.999f32);
let row_off: usize = ((height - 1 - j) as usize) * (width as usize) * (3 as usize);
let px_off: usize = row_off + (i as usize) * (3 as usize);
unsafe {
pixels[px_off + (0 as usize)] = (256.0f32 * r) as u8;
pixels[px_off + (1 as usize)] = (256.0f32 * g) as u8;
pixels[px_off + (2 as usize)] = (256.0f32 * b) as u8;
}
i = i + 1;
}
j = j - 1;
}
let flags: i32 = (1 as i32)| (0x200 as i32)| (0x400 as i32);
let mode: i32 = 0o644 as i32;
let path: str = "out.ppm";
let fd: i32 = unsafe { open(str_ptr(path), flags, mode) };
if fd < 0 {
unsafe { free(pixels); free(scene as *u8); free(rng as *u8); free(cam as *u8); }
return 2;
}
let header: str = "P6\n800 450\n255\n";
let _h: isize = unsafe { write(fd, str_ptr(header), str_len(header)) };
let mut written: usize = 0 as usize;
while written < pixel_bytes {
let remaining: usize = pixel_bytes -% written;
let q: *u8 = unsafe { pixels + written };
let w: isize = unsafe { write(fd, q, remaining) };
if w <= (0 as isize) { break; }
written = written +% (w as usize);
}
unsafe { close(fd); }
unsafe { free(pixels); }
unsafe { free(scene as *u8); }
unsafe { free(rng as *u8); }
unsafe { free(cam as *u8); }
return 0;
}
Results
Built with cpc build --release and run single-threaded. Build numbers are a
cold rebuild (rm -rf target first); run numbers are best-of-5 on an
otherwise-idle machine.
| Build | |
|---|---|
| Cold build wall-time | 131 ms |
| Binary size | 33,680 bytes |
| Run (best of 5) | |
|---|---|
| Wall time | 0.93 s (0.93–0.96 s run to run) |
| CPU time (user / sys) | 0.93 s / <0.01 s |
| Max resident memory | 2.46 MB |
| Instructions retired | 10.87 billion |
| Cycles elapsed | 3.02 billion |
| Instructions per cycle | ~3.60 |
| Page faults | 1 |
The headline number is the ~3.60 IPC. The Firestorm cores on Apple Silicon
can issue up to four fused multiply-adds per cycle, and the inner loop here is
almost entirely V3 math — dot products, adds, and scales, which are exactly
a*b + c shapes. At the default floating-point settings the compiler fuses each
of those into a single-rounded fmadd, producing FMA-dense inner loops that
keep the core's pipelines close to that ceiling.
The whole render holds in 2.46 MB of resident memory: the scene is ten spheres, and the only large allocation is the 1.08 MB pixel buffer.
Floating-point note. The default build fuses multiply-adds, which is faster and visually identical. If you want output that is bit-identical across machines and compilers, build with
--fp-contract=offto suppress the fusion. The two builds produce different (but both correct) images; their MD5s are listed under Reproduce, below.
Reproduce
You need cpc 0.0.14 or newer.
brew install cplus
cpc --version # cpc 0.0.14
1. Project layout. Put the full source listed above in src/main.cplus and
add a Cplus.toml next to it:
[package]
name = "raytracer"
version = "0.0.1"
edition = "2026"
[[bin]]
name = "raytracer"
path = "src/main.cplus"
[dependencies]
2. Build and run. The program takes no arguments and writes out.ppm to the
current directory:
cpc build --release
./target/release/raytracer
3. View it. PPM is a raw-pixel format; convert it to PNG with whatever you have installed:
magick out.ppm out.png # ImageMagick
pnmtopng out.ppm > out.png # netpbm
ffmpeg -i out.ppm out.png # ffmpeg
python3 -c 'from PIL import Image; Image.open("out.ppm").save("out.png")'
4. Verify the output. The render is deterministic (fixed RNG seed), so the image is reproducible byte-for-byte:
| Output | |
|---|---|
| Format / size | P6 PPM, 800 × 450, 1,080,015 bytes |
| MD5 (default build) | f642a7fa391cbc4472984c0ce202030b |
MD5 (--fp-contract=off) |
7730fff3105ebbe75e7d00d1099aef85 |
Notes on the numbers
- Hardware: MacBook Pro (18,2), Apple M1 Max (8 performance + 2 efficiency cores), 32 GB, macOS 26.5.1. The timings above were taken on battery; plugged in, the same binary runs a little faster, because macOS lifts the boost ceiling on AC. Re-bench on your own machine before quoting absolute numbers.
- Method: best-of-5 wall-clock on an idle machine with no thermal throttling
observed. Resource counters (max RSS, instructions, cycles, page faults) come
from
/usr/bin/time -lon macOS. IPC isinstructions / cyclesfrom a single run and varies by under 2% between runs.
‹ Back to all examples