commit 3a9fe44d65a54a96c824025df4b4237d9799da8a
parent eb9dc03a5a62e74b8b1fc459c6067fc6e4aec848
Author: MikoĊaj Lenczewski <mblenczewski@gmail.com>
Date: Mon, 9 Jun 2025 23:32:07 +0100
Implemented up to chapter 8 of book 1.
Diffstat:
8 files changed, 734 insertions(+), 1 deletion(-)
diff --git a/rt1/bitmap.c b/rt1/bitmap.c
@@ -0,0 +1,11 @@
+#include "rt.h"
+
+extern inline pixel_t
+pixel_argb32(uint8_t a, uint8_t r, uint8_t g, uint8_t b);
+
+extern inline void
+pixel_format_set_masks(enum pixel_format format,
+ uint32_t *r, uint32_t *g, uint32_t *b, uint32_t *a);
+
+extern inline void
+bitmap_write(int fd, pixel_t *buf, size_t width, size_t height, enum pixel_format format);
diff --git a/rt1/main.c b/rt1/main.c
@@ -1,5 +1,60 @@
+#include "rt.h"
+
int
main(int argc, char **argv)
{
- return 0;
+ char *filepath = "/tmp/test.bmp";
+
+ int fd = open(filepath, O_WRONLY | O_CREAT, 0644);
+ if (fd < 0) {
+ fprintf(stderr, "Failed to open file: %s: ", filepath);
+ perror("open");
+ exit(EXIT_FAILURE);
+ }
+
+ // ===================================================================
+
+ // image
+ size_t width = 1024, height = 1024;
+ pixel_t *buf = malloc(sizeof *buf * width * height);
+ assert(buf);
+
+ // world
+ struct world world;
+
+ world_init(&world);
+
+ struct sphere sphere = {
+ .centre = VEC3(point_t, 0, 0, -1),
+ .radius = 0.5,
+
+ .hittable.impl = sphere_hittable_impl,
+ };
+
+ world_push(&world, &sphere.hittable);
+
+ struct sphere ground = {
+ .centre = VEC3(point_t, 0, -100.5, -1),
+ .radius = 100,
+
+ .hittable.impl = sphere_hittable_impl,
+ };
+
+ world_push(&world, &ground.hittable);
+
+ // rendering
+ render(buf, width, height, &world);
+
+ // ===================================================================
+
+ bitmap_write(fd, buf, width, height, PIXEL_FORMAT_ARGB32);
+
+ close(fd);
+
+ exit(EXIT_SUCCESS);
}
+
+#include "math.c"
+#include "raytracing.c"
+#include "bitmap.c"
+#include "rt.c"
diff --git a/rt1/math.c b/rt1/math.c
@@ -0,0 +1 @@
+#include "rt.h"
diff --git a/rt1/memory.c b/rt1/memory.c
@@ -0,0 +1,25 @@
+#include "rt.h"
+
+extern inline void
+arena_reset(struct arena *arena);
+
+extern inline void *
+arena_alloc(struct arena *arena, size_t size, size_t alignment);
+
+extern inline void
+list_node_link(struct list_node *node, struct list_node *prev, struct list_node *next);
+
+extern inline struct list_node *
+list_node_unlink(struct list_node *node);
+
+extern inline void
+list_push_head(struct list_node *list, struct list_node *node);
+
+extern inline void
+list_push_tail(struct list_node *list, struct list_node *node);
+
+extern inline struct list_node *
+list_pop_head(struct list_node *list);
+
+extern inline struct list_node *
+list_pop_tail(struct list_node *list);
diff --git a/rt1/raytracing.c b/rt1/raytracing.c
@@ -0,0 +1,14 @@
+#include "rt.h"
+
+extern inline vec3f_t
+ray_at(struct ray const *ray, float t);
+
+extern inline int
+interval_contains(struct interval interval, float v);
+
+extern inline int
+interval_surrounds(struct interval interval, float v);
+
+extern inline int
+sphere_hittable_impl(struct hittable *data, struct ray const *ray,
+ struct interval t, struct hit_record *record);
diff --git a/rt1/rt.c b/rt1/rt.c
@@ -0,0 +1,78 @@
+#include "rt.h"
+
+extern inline void
+world_init(struct world *world);
+
+extern inline void
+world_push(struct world *world, struct hittable *hittable);
+
+inline color_t
+ray_color(struct ray *ray, struct list_node *objects)
+{
+ struct hit_record hit_record;
+ struct interval t = { .min = 0, .max = INFINITY, };
+ int hit_something = 0;
+
+ struct hittable *it;
+ LIST_ENTRY_ITER(objects, it, list_node) {
+ if (it->impl(it, ray, t, &hit_record)) {
+ hit_something = 1;
+ t.max = hit_record.t;
+ }
+ }
+
+ if (hit_something) {
+ return VEC3MULS(color_t,
+ VEC3ADDS(vec3f_t, hit_record.normal, 1), 0.5);
+ }
+
+ vec3f_t unit_direction = VEC3UNIT(ray->direction);
+ float a = 0.5 * (unit_direction.xyz.y + 1);
+ return VEC3LERP(VEC31(color_t), VEC3(color_t, 0, 0, 1), a);
+}
+
+void
+render(pixel_t *image, size_t image_width, size_t image_height, struct world *world)
+{
+ // camera
+ float aspect_ratio = (float) image_width / image_height;
+
+ float focal_length = 1.0;
+
+ vec3f_t camera_centre = VEC30(vec3f_t);
+
+ float viewport_width = 4.0, viewport_height = viewport_width / aspect_ratio;
+ vec3f_t viewport_u = VEC3(vec3f_t, +viewport_width, 0, 0);
+ vec3f_t viewport_v = VEC3(vec3f_t, 0, -viewport_height, 0);
+ vec3f_t pixel_du = VEC3DIVS(vec3f_t, viewport_u, image_width);
+ vec3f_t pixel_dv = VEC3DIVS(vec3f_t, viewport_v, image_height);
+
+ vec3f_t viewport_origin = VEC3SUB(vec3f_t,
+ VEC3SUB(vec3f_t,
+ VEC3SUB(vec3f_t, camera_centre, VEC3(vec3f_t, 0, 0, focal_length)),
+ VEC3DIVS(vec3f_t, viewport_u, 2)),
+ VEC3DIVS(vec3f_t, viewport_v, 2));
+
+ vec3f_t pixel00 = VEC3ADD(vec3f_t,
+ viewport_origin,
+ VEC3MULS(vec3f_t, VEC3ADD(vec3f_t, pixel_du, pixel_dv), 0.5));
+
+ // rendering
+ for (size_t j = 0; j < image_height; j++) {
+ fprintf(stderr, "Scanline: %zu/%zu\n", j, image_height);
+ for (size_t i = 0; i < image_width; i++) {
+ vec3f_t pixel_centre = VEC3ADD(vec3f_t,
+ pixel00,
+ VEC3ADD(vec3f_t,
+ VEC3MULS(vec3f_t, pixel_du, i),
+ VEC3MULS(vec3f_t, pixel_dv, j)));
+
+ vec3f_t ray_direction = VEC3SUB(vec3f_t, pixel_centre, camera_centre);
+
+ struct ray ray = { .origin = camera_centre, .direction = ray_direction, };
+ color_t color = ray_color(&ray, &world->objects);
+
+ image[j * image_width + i] = pixel_from_color(color, PIXEL_FORMAT_ARGB32);
+ }
+ }
+}
diff --git a/rt1/rt.h b/rt1/rt.h
@@ -0,0 +1,538 @@
+#ifndef RT_H
+#define RT_H
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <fcntl.h>
+
+// memory utilities
+// ===========================================================================
+
+#include <stdalign.h>
+#include <stddef.h>
+
+#define IS_POW2(v) (((v) & ((v) - 1)) == 0)
+
+#define IS_ALIGNED(v, align) (((v) & ((align) - 1)) == 0)
+#define ALIGN_PREV(v, align) ((v) & ~((align) - 1))
+#define ALIGN_NEXT(v, align) ALIGN_PREV((v) + ((align) - 1), (align))
+
+struct arena {
+ void *ptr;
+ size_t cap, len;
+};
+
+inline void
+arena_reset(struct arena *arena)
+{
+ arena->len = 0;
+}
+
+inline void *
+arena_alloc(struct arena *arena, size_t size, size_t alignment)
+{
+ assert(size);
+ assert(alignment);
+ assert(IS_POW2(alignment));
+
+ uintptr_t cur_ptr = (uintptr_t) arena->ptr, end_ptr = cur_ptr + arena->cap;
+
+ uintptr_t aligned_ptr = ALIGN_NEXT(cur_ptr, alignment);
+ if (aligned_ptr + size > end_ptr)
+ return NULL;
+
+ arena->len = (aligned_ptr + size) - end_ptr;
+
+ return (void *) aligned_ptr;
+}
+
+#define ARENA_ALLOC_ARRAY(arena, T, n) \
+ arena_alloc((arena), sizeof(T) * (n), alignof(T))
+
+#define ARENA_ALLOC_SIZED(arena, T) ARENA_ALLOC_ARRAY((arena), T, 1)
+
+#define TO_PARENT_PTR(ptr, T, member) \
+ ((ptr) ? ((T *) (((uintptr_t) (ptr)) - offsetof(T, member))) : NULL)
+
+struct list_node {
+ struct list_node *prev, *next;
+};
+
+#define LIST_INIT(list) ((struct list_node) { &(list), &(list), })
+
+#define LIST_HEAD(list) ((list)->next)
+#define LIST_TAIL(list) ((list)->prev)
+
+#define LIST_IS_EMPTY(list) \
+ (LIST_HEAD(list) == (list) && LIST_TAIL(list) == (list))
+
+#define LIST_NODE_ITER(list, it) \
+ for ((it) = LIST_HEAD(list); (it) != (list); (it) = LIST_HEAD(it))
+
+#define LIST_NODE_RITER(list, it) \
+ for ((it) = LIST_TAIL(list); (it) != (list); (it) = LIST_TAIL(it))
+
+#define LIST_NODE_ENTRY(node, T, member) TO_PARENT_PTR((node), T, member)
+
+#define LIST_ENTRY_ITER(list, it, member) \
+ for ((it) = LIST_NODE_ENTRY(LIST_HEAD(list), __typeof__ (*(it)), member); \
+ &(it)->member != (list); \
+ (it) = LIST_NODE_ENTRY(LIST_HEAD(&(it)->member), __typeof__ (*(it)), member))
+
+#define LIST_ENTRY_RITER(list, it, member) \
+ for ((it) = LIST_NODE_ENTRY(LIST_TAIL(list), __typeof__ (*(it)), member); \
+ &(it)->member != (list); \
+ (it) = LIST_NODE_ENTRY(LIST_TAIL(&(it)->member), __typeof__ (*(it)), member))
+
+inline void
+list_node_link(struct list_node *node, struct list_node *prev, struct list_node *next)
+{
+ node->prev = prev;
+ prev->next = node;
+ node->next = next;
+ next->prev = node;
+}
+
+inline struct list_node *
+list_node_unlink(struct list_node *node)
+{
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ return node;
+}
+
+inline void
+list_push_head(struct list_node *list, struct list_node *node)
+{
+ list_node_link(node, list, LIST_HEAD(list));
+}
+
+inline void
+list_push_tail(struct list_node *list, struct list_node *node)
+{
+ list_node_link(node, LIST_TAIL(list), list);
+}
+
+inline struct list_node *
+list_pop_head(struct list_node *list)
+{
+ struct list_node *res = list_node_unlink(LIST_HEAD(list));
+ return res;
+}
+
+inline struct list_node *
+list_pop_tail(struct list_node *list)
+{
+ struct list_node *res = list_node_unlink(LIST_TAIL(list));
+ return res;
+}
+
+// math utilities
+// ===========================================================================
+
+#include <tgmath.h>
+
+typedef union vec3i {
+ int32_t vs[3];
+ struct {
+ int32_t x, y, z;
+ } xyz;
+} vec3i_t;
+
+typedef union vec3u {
+ uint32_t vs[3];
+ struct {
+ uint32_t x, y, z;
+ } xyz;
+} vec3u_t;
+
+typedef union vec3f {
+ float vs[3];
+ struct {
+ float x, y, z;
+ } xyz;
+} vec3f_t;
+
+typedef union vec3d {
+ double vs[3];
+ struct {
+ double x, y, z;
+ } xyz;
+} vec3d_t;
+
+#define VEC3(T, x, y, z) ((T) { .vs = { (x), (y), (z), }, })
+
+#define VEC30(T) VEC3(T, 0, 0, 0)
+#define VEC31(T) VEC3(T, 1, 1, 1)
+
+#define VEC3NEG(lhs) \
+ VEC3(__typeof__ (lhs), -(lhs).xyz.x, -(lhs).xyz.y, -(lhs).xyz.z)
+
+#define VEC3ADD(T, lhs, rhs) \
+ VEC3(T, (lhs).xyz.x + (rhs).xyz.x, (lhs).xyz.y + (rhs).xyz.y, (lhs).xyz.z + (rhs).xyz.z)
+#define VEC3SUB(T, lhs, rhs) \
+ VEC3(T, (lhs).xyz.x - (rhs).xyz.x, (lhs).xyz.y - (rhs).xyz.y, (lhs).xyz.z - (rhs).xyz.z)
+#define VEC3MUL(T, lhs, rhs) \
+ VEC3(T, (lhs).xyz.x * (rhs).xyz.x, (lhs).xyz.y * (rhs).xyz.y, (lhs).xyz.z * (rhs).xyz.z)
+#define VEC3DIV(T, lhs, rhs) \
+ VEC3(T, (lhs).xyz.x / (rhs).xyz.x, (lhs).xyz.y / (rhs).xyz.y, (lhs).xyz.z / (rhs).xyz.z)
+
+#define VEC3ADDS(T, lhs, scalar) \
+ VEC3(T, (lhs).xyz.x + scalar, (lhs).xyz.y + scalar, (lhs).xyz.z + scalar)
+#define VEC3SUBS(T, lhs, scalar) \
+ VEC3(T, (lhs).xyz.x - scalar, (lhs).xyz.y - scalar, (lhs).xyz.z - scalar)
+#define VEC3MULS(T, lhs, scalar) \
+ VEC3(T, (lhs).xyz.x * scalar, (lhs).xyz.y * scalar, (lhs).xyz.z * scalar)
+#define VEC3DIVS(T, lhs, scalar) \
+ VEC3(T, (lhs).xyz.x / scalar, (lhs).xyz.y / scalar, (lhs).xyz.z / scalar)
+
+#define VEC3LEN2(lhs) \
+ (((lhs).xyz.x * (lhs).xyz.x) + ((lhs).xyz.y * (lhs).xyz.y) + ((lhs).xyz.z * (lhs).xyz.z))
+
+#define VEC3LEN(lhs) sqrt(VEC3LEN2(lhs))
+
+#define VEC3LERP(a, b, t) \
+ VEC3ADD(__typeof__ (a), \
+ VEC3MULS(__typeof__ (a), (a), (1 - (t))), \
+ VEC3MULS(__typeof__ (b), (b), (t)))
+
+#define VEC3DOT(lhs, rhs) \
+ (((lhs).xyz.x * (rhs).xyz.x) + ((lhs).xyz.y * (rhs).xyz.y) + ((lhs).xyz.z * (rhs).xyz.z))
+
+#define VEC3CROSS(T, lhs, rhs) \
+ VEC3(T, \
+ (((lhs).xyz.y * (rhs).xyz.z) - ((lhs).xyz.z * (rhs).xyz.y)), \
+ (((lhs).xyz.z * (rhs).xyz.x) - ((lhs).xyz.x * (rhs).xyz.z)), \
+ (((lhs).xyz.x * (rhs).xyz.y) - ((lhs).xyz.y * (rhs).xyz.x)))
+
+#define VEC3UNIT(lhs) \
+ VEC3(__typeof__ (lhs), \
+ (lhs).xyz.x / VEC3LEN(lhs), \
+ (lhs).xyz.y / VEC3LEN(lhs), \
+ (lhs).xyz.z / VEC3LEN(lhs))
+
+typedef union vec4i {
+ int32_t vs[4];
+ struct {
+ int32_t x, y, z, w;
+ } xyzw;
+} vec4i_t;
+
+typedef union vec4u {
+ uint32_t vs[4];
+ struct {
+ uint32_t x, y, z, w;
+ } xyzw;
+} vec4u_t;
+
+typedef union vec4f {
+ float vs[4];
+ struct {
+ float x, y, z, w;
+ } xyzw;
+} vec4f_t;
+
+typedef union vec4d {
+ double vs[4];
+ struct {
+ double x, y, z, w;
+ } xyzw;
+} vec4d_t;
+
+#define VEC4(T, x, y, z, w) ((T) { .vs = { (x), (y), (z), (w), }, })
+
+#define VEC40(T) VEC4(T, 0, 0, 0, 0)
+#define VEC41(T) VEC4(T, 1, 1, 1, 1)
+
+#define VEC4NEG(lhs) \
+ VEC4(__typeof__ (lhs), -(lhs).xyzw.x, -(lhs).xyzw.y, -(lhs).xyzw.z, -(lhs).xyzw.w)
+
+#define VEC4ADD(T, lhs, rhs) \
+ VEC4(T, (lhs).xyzw.x + (rhs).xyzw.x, (lhs).xyzw.y + (rhs).xyzw.y, (lhs).xyzw.z + (rhs).xyzw.z, (lhs).xyzw.w + (rhs).xyzw.w)
+#define VEC4SUB(T, lhs, rhs) \
+ VEC4(T, (lhs).xyzw.x - (rhs).xyzw.x, (lhs).xyzw.y - (rhs).xyzw.y, (lhs).xyzw.z - (rhs).xyzw.z, (lhs).xyzw.w - (rhs).xyzw.w)
+#define VEC4MUL(T, lhs, rhs) \
+ VEC4(T, (lhs).xyzw.x * (rhs).xyzw.x, (lhs).xyzw.y * (rhs).xyzw.y, (lhs).xyzw.z * (rhs).xyzw.z, (lhs).xyzw.w * (rhs).xyzw.w)
+#define VEC4DIV(T, lhs, rhs) \
+ VEC4(T, (lhs).xyzw.x / (rhs).xyzw.x, (lhs).xyzw.y / (rhs).xyzw.y, (lhs).xyzw.z / (rhs).xyzw.z, (lhs).xyzw.w / (rhs).xyzw.w)
+
+#define VEC4ADDS(T, lhs, scalar) \
+ VEC4(T, (lhs).xyzw.x + scalar, (lhs).xyzw.y + scalar, (lhs).xyzw.z + scalar, (lhs).xyzw.w + scalar)
+#define VEC4SUBS(T, lhs, scalar) \
+ VEC4(T, (lhs).xyzw.x - scalar, (lhs).xyzw.y - scalar, (lhs).xyzw.z - scalar, (lhs).xyzw.w - scalar)
+#define VEC4MULS(T, lhs, scalar) \
+ VEC4(T, (lhs).xyzw.x * scalar, (lhs).xyzw.y * scalar, (lhs).xyzw.z * scalar, (lhs).xyzw.w * scalar)
+#define VEC4DIVS(T, lhs, scalar) \
+ VEC4(T, (lhs).xyzw.x / scalar, (lhs).xyzw.y / scalar, (lhs).xyzw.z / scalar, (lhs).xyzw.w / scalar)
+
+#define VEC4LEN2(lhs) \
+ (((lhs).xyzw.x * (lhs).xyzw.x) + ((lhs).xyzw.y * (lhs).xyzw.y) + ((lhs).xyzw.z * (lhs).xyzw.z) + ((lhs).xyzw.w * (lhs).xyzw.w))
+
+#define VEC4LEN(lhs) sqrt(VEC4LEN2(lhs))
+
+#define VEC4LERP(a, b, t) \
+ VEC4ADD(__typeof__ (a), \
+ VEC4MULS(__typeof__ (a), (a), (1 - (t))), \
+ VEC4MULS(__typeof__ (b), (b), (t)))
+
+#define VEC4DOT(lhs, rhs) \
+ (((lhs).xyzw.x * (rhs).xyzw.x) + ((lhs).xyzw.y * (rhs).xyzw.y) + ((lhs).xyzw.z * (rhs).xyzw.z))
+
+#define VEC4UNIT(lhs) \
+ VEC4(__typeof__ (lhs), \
+ (lhs).xyzw.x / VEC4LEN(lhs), \
+ (lhs).xyzw.y / VEC4LEN(lhs), \
+ (lhs).xyzw.z / VEC4LEN(lhs), \
+ (lhs).xyzw.w / VEC4LEN(lhs))
+
+#define DEG2RAD(degrees) (((degrees) * M_PI) / 180)
+
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+
+#define CLAMP(max, min, v) MAX(MIN((max), (v)), (min))
+
+// raytracing utilities
+// ==========================================================================
+
+typedef vec3f_t point_t;
+
+struct ray {
+ point_t origin;
+ vec3f_t direction;
+};
+
+inline vec3f_t
+ray_at(struct ray const *ray, float t)
+{
+ return VEC3ADD(vec3f_t, ray->origin, VEC3MULS(vec3f_t, ray->direction, t));
+}
+
+struct hit_record {
+ point_t point;
+ vec3f_t normal;
+ float t;
+
+ int front_face;
+};
+
+inline void
+hit_record_set_face_normal(struct hit_record *record, struct ray const *ray,
+ vec3f_t outward_normal)
+{
+ record->front_face = VEC3DOT(ray->direction, outward_normal) < 0;
+ record->normal = record->front_face ? outward_normal : VEC3NEG(outward_normal);
+}
+
+struct interval {
+ float min, max;
+};
+
+#define EMPTY_INTERVAL ((struct interval) { +INFINITY, -INFINITY, })
+#define UNIVERSE_INTERVAL ((struct interval) { -INFINITY, +INFINITY, })
+
+inline int
+interval_contains(struct interval interval, float v)
+{
+ return interval.min <= v && v <= interval.max;
+}
+
+inline int
+interval_surrounds(struct interval interval, float v)
+{
+ return interval.min < v && v < interval.max;
+}
+
+struct hittable {
+ int (*impl)(struct hittable *data, struct ray const *ray,
+ struct interval t, struct hit_record *record);
+
+ struct list_node list_node;
+};
+
+struct sphere {
+ point_t centre;
+ float radius;
+
+ struct hittable hittable;
+};
+
+inline int
+sphere_hittable_impl(struct hittable *data, struct ray const *ray,
+ struct interval t, struct hit_record *record)
+{
+ struct sphere *self = TO_PARENT_PTR(data, struct sphere, hittable);
+
+ vec3f_t oc = VEC3SUB(point_t, self->centre, ray->origin);
+
+ float a = VEC3LEN2(ray->direction);
+ float h = VEC3DOT(ray->direction, oc);
+ float c = VEC3LEN2(oc) - (self->radius * self->radius);
+ float discriminant = (h * h) - (a * c);
+
+ if (discriminant < 0)
+ return 0;
+
+ float sqrtd = sqrt(discriminant);
+
+ float root = (h - sqrtd) / a;
+ if (interval_surrounds(t, root))
+ goto found_valid_root;
+
+ root = (h + sqrtd) / a;
+ if (interval_surrounds(t, root))
+ goto found_valid_root;
+
+ return 0;
+
+found_valid_root:
+ record->t = root;
+ record->point = ray_at(ray, record->t);
+
+ vec3f_t outward_normal = VEC3DIVS(vec3f_t,
+ VEC3SUB(vec3f_t, record->point, self->centre),
+ self->radius);
+ hit_record_set_face_normal(record, ray, outward_normal);
+
+ return 1;
+}
+
+typedef vec3f_t color_t;
+
+// bitmap utilities
+// ===========================================================================
+
+typedef uint32_t pixel_t;
+
+enum pixel_format {
+ PIXEL_FORMAT_ARGB32,
+};
+
+inline pixel_t
+pixel_from_argb32(uint8_t a, uint8_t r, uint8_t g, uint8_t b)
+{
+ return (((uint32_t) a) << 24) | \
+ (((uint32_t) r) << 16) | \
+ (((uint32_t) g) << 8) | \
+ (((uint32_t) b));
+}
+
+inline pixel_t
+pixel_from_color(color_t color, enum pixel_format format)
+{
+ assert(format == PIXEL_FORMAT_ARGB32);
+
+ uint8_t a = 255;
+ uint8_t r = color.xyz.x * 255;
+ uint8_t g = color.xyz.y * 255;
+ uint8_t b = color.xyz.z * 255;
+
+ switch (format) {
+ case PIXEL_FORMAT_ARGB32: return pixel_from_argb32(a, r, g, b);
+ }
+}
+
+inline void
+pixel_format_set_masks(enum pixel_format format,
+ uint32_t *r, uint32_t *g, uint32_t *b, uint32_t *a)
+{
+ /* NOTE: bitmap field masks are big endian! */
+ switch (format) {
+ case PIXEL_FORMAT_ARGB32:
+ *r = 0x0000ff00;
+ *g = 0x00ff0000;
+ *b = 0xff000000;
+ *a = 0x000000ff;
+ break;
+ }
+}
+
+/* see: https://en.wikipedia.org/wiki/BMP_file_format */
+inline void
+bitmap_write(int fd, pixel_t *buf, size_t width, size_t height, enum pixel_format format)
+{
+ uint32_t pixels = width * height;
+ uint32_t pixel_bytes = pixels * sizeof(pixel_t);
+
+ uint32_t dib_header_size = 108;
+ uint32_t bitmap_header_size = 14;
+ uint32_t header_size = bitmap_header_size + dib_header_size;
+
+ uint32_t file_size = header_size + pixel_bytes;
+ uint32_t reserved = 0;
+
+ int32_t xres = width, yres = -height;
+ uint16_t planes = 1, bits_per_pixel = sizeof(pixel_t) * 8;
+ uint32_t compression = 3; /* 3 = bitfields */
+ uint32_t xdpi = 0, ydpi = 0;
+ uint32_t palette = 0, important = 0;
+
+ uint32_t r_mask, g_mask, b_mask, a_mask;
+ pixel_format_set_masks(format, &r_mask, &g_mask, &b_mask, &a_mask);
+
+ unsigned char colorspace[4] = "sRGB";
+ unsigned char endpoints[36];
+ memset(endpoints, 0, sizeof endpoints);
+
+ uint32_t r_gamma = 0, g_gamma = 0, b_gamma = 0;
+
+ /* bitmap header */
+ write(fd, "BM", 2);
+ write(fd, &file_size, sizeof file_size);
+ write(fd, &reserved, sizeof reserved);
+ write(fd, &header_size, sizeof header_size);
+
+ /* dib header: BITMAPV4HEADER */
+ write(fd, &dib_header_size, sizeof dib_header_size);
+ write(fd, &xres, sizeof xres);
+ write(fd, &yres, sizeof yres);
+ write(fd, &planes, sizeof planes);
+ write(fd, &bits_per_pixel, sizeof bits_per_pixel);
+ write(fd, &compression, sizeof compression);
+ write(fd, &pixel_bytes, sizeof pixel_bytes);
+ write(fd, &xdpi, sizeof xdpi);
+ write(fd, &ydpi, sizeof ydpi);
+ write(fd, &palette, sizeof palette);
+ write(fd, &important, sizeof important);
+ write(fd, &r_mask, sizeof r_mask);
+ write(fd, &g_mask, sizeof g_mask);
+ write(fd, &b_mask, sizeof b_mask);
+ write(fd, &a_mask, sizeof a_mask);
+ write(fd, colorspace, sizeof colorspace);
+ write(fd, endpoints, sizeof endpoints);
+ write(fd, &r_gamma, sizeof r_gamma);
+ write(fd, &g_gamma, sizeof g_gamma);
+ write(fd, &b_gamma, sizeof b_gamma);
+
+ /* pixel data */
+ write(fd, buf, pixel_bytes);
+}
+
+// rt definitions
+// ===========================================================================
+
+struct world {
+ struct list_node objects;
+};
+
+inline void
+world_init(struct world *world)
+{
+ world->objects = LIST_INIT(world->objects);
+}
+
+inline void
+world_push(struct world *world, struct hittable *hittable)
+{
+ list_push_tail(&world->objects, &hittable->list_node);
+}
+
+void
+render(pixel_t *image, size_t image_width, size_t image_height, struct world *world);
+
+#endif /* RT_H */
diff --git a/rt1/utils.c b/rt1/utils.c
@@ -0,0 +1,11 @@
+#include "rt.h"
+
+extern inline pixel_t
+pixel_argb32(uint8_t a, uint8_t r, uint8_t g, uint8_t b);
+
+extern inline void
+pixel_format_set_masks(enum pixel_format format,
+ uint32_t *r, uint32_t *g, uint32_t *b, uint32_t *a);
+
+extern inline void
+bitmap_write(int fd, pixel_t *buf, size_t width, size_t height, enum pixel_format format);