summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend_mtl.m263
-rw-r--r--src/camera.c13
-rw-r--r--src/core.c51
-rw-r--r--src/debug_strings.c5
-rw-r--r--src/geometry.c85
-rw-r--r--src/impl.c4
-rw-r--r--src/log.c6
-rw-r--r--src/maths.c42
-rw-r--r--src/mem.c88
-rw-r--r--src/scene.c26
10 files changed, 576 insertions, 7 deletions
diff --git a/src/backend_mtl.m b/src/backend_mtl.m
new file mode 100644
index 0000000..48e0ab0
--- /dev/null
+++ b/src/backend_mtl.m
@@ -0,0 +1,263 @@
+#include <celeritas.h>
+
+#ifdef GPU_METAL
+
+#define MTL_DEBUG_LAYER 1 // enable all metal validation layers
+
+// Obj-C imports
+#import <Foundation/Foundation.h>
+#import <Metal/Metal.h>
+#import <MetalKit/MetalKit.h>
+#import <QuartzCore/CAMetalLayer.h>
+#include <CoreGraphics/CGGeometry.h>
+
+#define GLFW_INCLUDE_NONE
+#import <GLFW/glfw3.h>
+#define GLFW_EXPOSE_NATIVE_COCOA
+#import <GLFW/glfw3native.h>
+#include "stb_image.h"
+
+NAMESPACED_LOGGER(metal);
+
+// --- RAL types
+
+struct gpu_swapchain {
+ int width, height;
+ CAMetalLayer* swapchain;
+};
+
+struct gpu_encoder {
+ id<MTLCommandBuffer> cmd_buffer;
+ id<MTLRenderCommandEncoder> cmd_encoder;
+};
+
+typedef struct metal_pipeline {
+ id<MTLRenderPipelineState> pso;
+} metal_pipeline;
+
+typedef struct metal_buffer {
+ id<MTLBuffer> id;
+} metal_buffer;
+
+typedef struct metal_texture {
+ id<MTLTexture> id;
+} metal_texture;
+
+TYPED_POOL(metal_buffer, buf);
+TYPED_POOL(metal_texture, tex);
+TYPED_POOL(metal_pipeline, pipeline);
+
+typedef struct metal_context {
+ GLFWwindow* window;
+ NSWindow* metal_window;
+
+ id<MTLDevice> device;
+ id<CAMetalDrawable> surface;
+ gpu_swapchain default_swapchain;
+ id<MTLLibrary> default_library;
+
+ id<MTLCommandQueue> command_queue;
+
+ /* pools */
+ buf_pool bufpool;
+ tex_pool texpool;
+ pipeline_pool psopool; // pso = pipeline state object
+} metal_context;
+
+static metal_context ctx;
+
+void ral_backend_init(const char* window_name, struct GLFWwindow* window) {
+ TRACE("loading Metal backend");
+
+ TRACE("gpu device creation");
+ const id<MTLDevice> gpu = MTLCreateSystemDefaultDevice();
+ ctx.device = gpu;
+
+ TRACE("window init");
+ glfwMakeContextCurrent(window);
+ NSWindow* nswindow = glfwGetCocoaWindow(window);
+ ctx.metal_window = nswindow;
+
+ int width, height;
+ glfwGetFramebufferSize(window, &width, &height);
+
+ // effectively the "framebuffer"
+ CAMetalLayer* metal_layer = [CAMetalLayer layer];
+ metal_layer.device = gpu;
+ metal_layer.pixelFormat = MTLPixelFormatBGRA8Unorm;
+ metal_layer.drawableSize = CGSizeMake(width, height);
+ ctx.metal_window.contentView.layer = metal_layer;
+ ctx.metal_window.contentView.wantsLayer = true;
+ ctx.default_swapchain.swapchain = metal_layer;
+
+ TRACE("command queue creation");
+ const id<MTLCommandQueue> queue = [ctx.device newCommandQueue];
+ ctx.command_queue = queue;
+
+ TRACE("resource pool init");
+ metal_buffer* buffer_storage = malloc(sizeof(metal_buffer) * 100);
+ ctx.bufpool = buf_pool_create(buffer_storage, 100, sizeof(metal_buffer));
+
+ metal_texture* texture_storage = malloc(sizeof(metal_texture) * 100);
+ ctx.texpool = tex_pool_create(texture_storage, 100, sizeof(metal_texture));
+
+ metal_pipeline* pipeline_storage = malloc(sizeof(metal_pipeline) * 100);
+ ctx.psopool = pipeline_pool_create(pipeline_storage, 100, sizeof(metal_pipeline));
+
+ TRACE("create default metal lib");
+ NSError* nserr = 0x0;
+ id<MTLLibrary> default_library = [ctx.device newLibraryWithFile:@"build/shaders/default.metallib" error:&nserr];
+ if (!default_library) {
+ ERROR("Error loading metal lib\n");
+ exit(1);
+ }
+ ctx.default_library = default_library;
+
+ INFO("Successfully initialised Metal RAL backend");
+}
+
+void ral_backend_shutdown() {
+ // no-op
+}
+
+buf_handle ral_buffer_create(u64 size, const void *data) {
+ buf_handle handle;
+ metal_buffer* buffer = buf_pool_alloc(&ctx.bufpool, &handle);
+ buffer->id = [ctx.device newBufferWithBytes:data length:size options:MTLResourceStorageModeShared];
+
+ return handle;
+}
+
+tex_handle ral_texture_create(texture_desc desc, bool create_view, const void *data) {
+ tex_handle handle;
+ metal_texture* texture = tex_pool_alloc(&ctx.texpool, &handle);
+
+ MTLTextureDescriptor* texture_descriptor = [[MTLTextureDescriptor alloc] init];
+ [texture_descriptor setPixelFormat:MTLPixelFormatRGBA8Unorm];
+ [texture_descriptor setWidth:desc.width];
+ [texture_descriptor setHeight:desc.height];
+
+ texture->id = [ctx.device newTextureWithDescriptor:texture_descriptor];
+
+ MTLRegion region = MTLRegionMake2D(0, 0, desc.width, desc.height);
+ u32 bytes_per_row = 4 * desc.width;
+
+ [texture->id replaceRegion:region mipmapLevel:0 withBytes:data bytesPerRow:bytes_per_row];
+
+ [texture_descriptor release];
+
+ return handle;
+}
+
+tex_handle ral_texture_load_from_file(const char* filepath) {
+ texture_desc desc;
+
+ stbi_set_flip_vertically_on_load(true);
+ unsigned char* image = stbi_load(filepath, &desc.width, &desc.height, &desc.num_channels, STBI_rgb_alpha);
+ assert(image != NULL);
+
+ tex_handle handle = ral_texture_create(desc, false, image);
+ stbi_image_free(image);
+ return handle;
+}
+
+pipeline_handle ral_gfx_pipeline_create(gfx_pipeline_desc desc) {
+ TRACE("creating graphics pipeline");
+
+ pipeline_handle handle;
+ metal_pipeline* p = pipeline_pool_alloc(&ctx.psopool, &handle);
+
+ @autoreleasepool {
+ // setup vertex and fragment shaders
+ NSString* vertex_entry_point = [NSString stringWithUTF8String:desc.vertex.entry_point];
+ id<MTLFunction> vertex_func = [ctx.default_library newFunctionWithName:vertex_entry_point];
+ assert(vertex_func);
+
+ NSString* fragment_entry_point = [NSString stringWithUTF8String:desc.fragment.entry_point];
+ id<MTLFunction> fragment_func = [ctx.default_library newFunctionWithName:fragment_entry_point];
+ assert(fragment_func);
+
+ NSError* err = 0x0;
+ MTLRenderPipelineDescriptor* pld = [[MTLRenderPipelineDescriptor alloc] init];
+ // in auto release pool so dont need to call release()
+
+ [pld setLabel:@"Pipeline"];
+ [pld setVertexFunction:vertex_func];
+ [pld setFragmentFunction:fragment_func];
+ pld.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
+ pld.colorAttachments[0].blendingEnabled = YES;
+ assert(pld);
+
+ id<MTLRenderPipelineState> pso = [ctx.device newRenderPipelineStateWithDescriptor:pld error:&err];
+ assert(pso);
+ p->pso = pso;
+ }
+
+ return handle;
+}
+
+gpu_encoder* ral_render_encoder(render_pass_desc rpass_desc) {
+ id<MTLCommandBuffer> buffer = [ctx.command_queue commandBuffer];
+
+ // create renderpass descriptor
+ MTLRenderPassDescriptor* rpd = [[MTLRenderPassDescriptor alloc] init];
+ MTLRenderPassColorAttachmentDescriptor* cd = rpd.colorAttachments[0];
+ [cd setTexture:ctx.surface.texture];
+ [cd setLoadAction:MTLLoadActionClear];
+ MTLClearColor clearColor = MTLClearColorMake(41.0f/255.0f, 42.0f/255.0f, 48.0f/255.0f, 1.0);
+ [cd setClearColor:clearColor];
+ [cd setStoreAction:MTLStoreActionStore];
+
+ id<MTLRenderCommandEncoder> encoder = [buffer renderCommandEncoderWithDescriptor:rpd];
+
+ gpu_encoder* enc = malloc(sizeof(gpu_encoder));
+ enc->cmd_buffer = buffer;
+ enc->cmd_encoder = encoder;
+
+ return enc;
+}
+
+void ral_encoder_finish_and_submit(gpu_encoder* enc) {
+ [enc->cmd_encoder endEncoding];
+ [enc->cmd_buffer presentDrawable:ctx.surface];
+ [enc->cmd_buffer commit];
+ [enc->cmd_buffer waitUntilCompleted];
+}
+
+void ral_encode_bind_pipeline(gpu_encoder *enc, pipeline_handle pipeline) {
+ metal_pipeline* p = pipeline_pool_get(&ctx.psopool, pipeline);
+ [enc->cmd_encoder setRenderPipelineState:p->pso];
+}
+
+void ral_encode_set_vertex_buf(gpu_encoder *enc, buf_handle vbuf) {
+ metal_buffer* b = buf_pool_get(&ctx.bufpool, vbuf);
+ [enc->cmd_encoder setVertexBuffer:b->id offset:0 atIndex:0 ];
+}
+
+void ral_encode_set_texture(gpu_encoder* enc, tex_handle texture, u32 slot) {
+ metal_texture* t = tex_pool_get(&ctx.texpool, texture);
+ [enc->cmd_encoder setFragmentTexture:t->id atIndex:slot];
+}
+
+void ral_encode_draw_tris(gpu_encoder* enc, size_t start, size_t count) {
+ MTLPrimitiveType tri_primitive = MTLPrimitiveTypeTriangle;
+ [enc->cmd_encoder drawPrimitives:tri_primitive vertexStart:start vertexCount:count];
+}
+
+void ral_frame_start() {}
+
+void ral_frame_draw(scoped_draw_commands draw_fn) {
+ @autoreleasepool {
+ ctx.surface = [ctx.default_swapchain.swapchain nextDrawable];
+ draw_fn();
+ }
+}
+
+void ral_frame_end() {}
+
+void ral_backend_resize_framebuffer(int width, int height) {
+ TRACE("resizing framebuffer");
+ ctx.default_swapchain.swapchain.drawableSize = CGSizeMake((float)width, (float)height);
+}
+
+#endif \ No newline at end of file
diff --git a/src/camera.c b/src/camera.c
new file mode 100644
index 0000000..eb8fc7d
--- /dev/null
+++ b/src/camera.c
@@ -0,0 +1,13 @@
+#include <celeritas.h>
+
+mat4 camera_view_proj(camera camera, f32 lens_height, f32 lens_width, mat4* out_view, mat4* out_proj) {
+ mat4 projection_matrix = mat4_perspective(camera.fov, lens_width / lens_height, 0.1, 1000.0);
+ // TODO: store near/far on camera rather than hard-coding here.
+
+ vec3 camera_direction = vec3_add(camera.position, camera.forwards);
+ mat4 view_matrix = mat4_look_at(camera.position, camera_direction, camera.up);
+ if (out_view) *out_view = view_matrix;
+ if (out_proj) *out_proj = projection_matrix;
+
+ return mat4_mult(view_matrix, projection_matrix);
+} \ No newline at end of file
diff --git a/src/core.c b/src/core.c
index 158615c..ed09597 100644
--- a/src/core.c
+++ b/src/core.c
@@ -1,15 +1,58 @@
// The engine "core"
#include <celeritas.h>
+#include <stdlib.h>
+#include "glfw3.h"
NAMESPACED_LOGGER(core);
-void Core_Bringup(const char* window_name, struct GLFWwindow* optional_window) {
- // INFO("Initiate Core bringup");
+core g_core = { 0 };
+
+#ifdef GPU_METAL
+static const char* gapi = "Metal";
+#else
+static const char* gapi = "Vulkan";
+#endif
+
+// forward declares
+void key_callback(GLFWwindow* win, int key, int scancode, int action, int mods);
+void resize_callback(GLFWwindow* win, int width, int height);
+
+void core_bringup(const char* window_name, struct GLFWwindow* optional_window) {
INFO("Initiate Core bringup");
INFO("Create GLFW window");
+ glfwInit();
+
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+
+ char* full_window_name = malloc(sizeof(char) * 100);
+ sprintf(full_window_name, "%s (%s)", window_name, gapi);
+
+ if (optional_window) {
+ g_core.window = optional_window;
+ } else {
+ GLFWwindow* glfw_window = glfwCreateWindow(800, 600, full_window_name, NULL, NULL);
+ g_core.window = glfw_window;
+ }
+
+ // This may move into a renderer struct
+ ral_backend_init(window_name, g_core.window);
+
+ glfwSetKeyCallback(g_core.window, key_callback);
+ glfwSetFramebufferSizeCallback(g_core.window, resize_callback);
+}
+void core_shutdown() {
+ ral_backend_shutdown();
+ glfwTerminate();
+}
+
+bool app_should_exit() { return glfwWindowShouldClose(g_core.window) || g_core.should_exit; }
+
+void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) {
+ if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
+ g_core.should_exit = true;
+ }
}
-void Core_Shutdown() {}
-bool AppShouldExit() { return false; }
+void resize_callback(GLFWwindow* window, int width, int height) { ral_backend_resize_framebuffer(width, height); } \ No newline at end of file
diff --git a/src/debug_strings.c b/src/debug_strings.c
new file mode 100644
index 0000000..b1e09aa
--- /dev/null
+++ b/src/debug_strings.c
@@ -0,0 +1,5 @@
+#include <celeritas.h>
+
+const char* keyframe_kind_strings[4] = { "ROTATION", "TRANSLATION", "SCALE", "WEIGHTS" };
+
+const char* interpolation_strings[3] = { "Step", "Linear", "Cubic" }; \ No newline at end of file
diff --git a/src/geometry.c b/src/geometry.c
new file mode 100644
index 0000000..05414d3
--- /dev/null
+++ b/src/geometry.c
@@ -0,0 +1,85 @@
+#include <celeritas.h>
+
+typedef struct static_3d_vert {
+ vec4 pos;
+ vec4 norm;
+ vec2 uv;
+ vec2 pad;
+} static_3d_vert;
+
+vertex_desc static_3d_vertex_format() {
+ vertex_desc desc;
+ desc.label = "Static 3D Vertex";
+ desc.attributes[0] = ATTR_F32x4; // position
+ desc.attributes[1] = ATTR_F32x4; // normal
+ desc.attributes[2] = ATTR_F32x2; // tex coord
+ desc.attribute_count = 3;
+ desc.padding = 16; // 16 bytes padding
+
+ return desc;
+}
+
+geometry geo_cuboid(f32 x_scale, f32 y_scale, f32 z_scale) {
+ vec4 BACK_BOT_LEFT = (vec4){ 0, 0, 0, 0 };
+ vec4 BACK_BOT_RIGHT = (vec4){ 1, 0, 0, 0 };
+ vec4 BACK_TOP_LEFT = (vec4){ 0, 1, 0, 0 };
+ vec4 BACK_TOP_RIGHT = (vec4){ 1, 1, 0, 0 };
+ vec4 FRONT_BOT_LEFT = (vec4){ 0, 0, 1, 0 };
+ vec4 FRONT_BOT_RIGHT = (vec4){ 1, 0, 1, 0 };
+ vec4 FRONT_TOP_LEFT = (vec4){ 0, 1, 1, 0 };
+ vec4 FRONT_TOP_RIGHT = (vec4){ 1, 1, 1, 0 };
+
+ // allocate the data
+ static_3d_vert* vertices = malloc(36 * 64);
+
+ vertices[0] = (static_3d_vert){ .pos = BACK_TOP_RIGHT, .norm = (v3tov4(VEC3_NEG_Z)), .uv = { 0, 0 } };
+ vertices[1] = (static_3d_vert){ .pos = BACK_BOT_LEFT, .norm = v3tov4(VEC3_NEG_Z), .uv = { 0, 1 } };
+ vertices[2] = (static_3d_vert){ .pos = BACK_TOP_LEFT, .norm = v3tov4(VEC3_NEG_Z), .uv = { 0, 0 } };
+ vertices[3] = (static_3d_vert){ .pos = BACK_TOP_RIGHT, .norm = v3tov4(VEC3_NEG_Z), .uv = { 1, 0 } };
+ vertices[4] = (static_3d_vert){ .pos = BACK_BOT_RIGHT, .norm = v3tov4(VEC3_NEG_Z), .uv = { 1, 1 } };
+ vertices[5] = (static_3d_vert){ .pos = BACK_BOT_LEFT, .norm = v3tov4(VEC3_NEG_Z), .uv = { 0, 1 } };
+
+ // front faces
+ vertices[6] = (static_3d_vert){ .pos = FRONT_BOT_LEFT, .norm = v3tov4(VEC3_Z), .uv = { 0, 1 } };
+ vertices[7] = (static_3d_vert){ .pos = FRONT_TOP_RIGHT, .norm = v3tov4(VEC3_Z), .uv = { 1, 0 } };
+ vertices[8] = (static_3d_vert){ .pos = FRONT_TOP_LEFT, .norm = v3tov4(VEC3_Z), .uv = { 0, 0 } };
+ vertices[9] = (static_3d_vert){ .pos = FRONT_BOT_LEFT, .norm = v3tov4(VEC3_Z), .uv = { 0, 1 } };
+ vertices[10] = (static_3d_vert){ .pos = FRONT_BOT_RIGHT, .norm = v3tov4(VEC3_Z), .uv = { 1, 1 } };
+ vertices[11] = (static_3d_vert){ .pos = FRONT_TOP_RIGHT, .norm = v3tov4(VEC3_Z), .uv = { 1, 0 } };
+
+ // top faces
+ vertices[12] = (static_3d_vert){ .pos = BACK_TOP_LEFT, .norm = v3tov4(VEC3_Y), .uv = { 0, 0 } };
+ vertices[13] = (static_3d_vert){ .pos = FRONT_TOP_LEFT, .norm = v3tov4(VEC3_Y), .uv = { 0, 1 } };
+ vertices[14] = (static_3d_vert){ .pos = FRONT_TOP_RIGHT, .norm = v3tov4(VEC3_Y), .uv = { 1, 1 } };
+ vertices[15] = (static_3d_vert){ .pos = BACK_TOP_LEFT, .norm = v3tov4(VEC3_Y), .uv = { 0, 0 } };
+ vertices[16] = (static_3d_vert){ .pos = FRONT_TOP_RIGHT, .norm = v3tov4(VEC3_Y), .uv = { 1, 1 } };
+ vertices[17] = (static_3d_vert){ .pos = BACK_TOP_RIGHT, .norm = v3tov4(VEC3_Y), .uv = { 1, 0 } };
+
+ // bottom faces
+ vertices[18] = (static_3d_vert){ .pos = BACK_BOT_LEFT, .norm = v3tov4(VEC3_NEG_Y), .uv = { 0, 1 } };
+ vertices[19] = (static_3d_vert){ .pos = FRONT_BOT_RIGHT, .norm = v3tov4(VEC3_NEG_Y), .uv = { 1, 1 } };
+ vertices[20] = (static_3d_vert){ .pos = FRONT_BOT_LEFT, .norm = v3tov4(VEC3_NEG_Y), .uv = { 0, 1 } };
+ vertices[21] = (static_3d_vert){ .pos = BACK_BOT_LEFT, .norm = v3tov4(VEC3_NEG_Y), .uv = { 0, 1 } };
+ vertices[22] = (static_3d_vert){ .pos = BACK_BOT_RIGHT, .norm = v3tov4(VEC3_NEG_Y), .uv = { 1, 1 } };
+ vertices[23] = (static_3d_vert){ .pos = FRONT_BOT_RIGHT, .norm = v3tov4(VEC3_NEG_Y), .uv = { 0, 1 } };
+
+ // right faces
+ vertices[24] = (static_3d_vert){ .pos = FRONT_TOP_RIGHT, .norm = v3tov4(VEC3_X), .uv = { 0, 0 } };
+ vertices[25] = (static_3d_vert){ .pos = BACK_BOT_RIGHT, .norm = v3tov4(VEC3_X), .uv = { 1, 1 } };
+ vertices[26] = (static_3d_vert){ .pos = BACK_TOP_RIGHT, .norm = v3tov4(VEC3_X), .uv = { 1, 0 } };
+ vertices[27] = (static_3d_vert){ .pos = BACK_BOT_RIGHT, .norm = v3tov4(VEC3_X), .uv = { 1, 1 } };
+ vertices[28] = (static_3d_vert){ .pos = FRONT_TOP_RIGHT, .norm = v3tov4(VEC3_X), .uv = { 0, 0 } };
+ vertices[29] = (static_3d_vert){ .pos = FRONT_BOT_RIGHT, .norm = v3tov4(VEC3_X), .uv = { 0, 1 } };
+
+ // left faces
+ vertices[30] = (static_3d_vert){ .pos = FRONT_TOP_LEFT, .norm = v3tov4(VEC3_NEG_X), .uv = { 0, 0 } };
+ vertices[31] = (static_3d_vert){ .pos = BACK_TOP_LEFT, .norm = v3tov4(VEC3_NEG_X), .uv = { 0, 0 } };
+ vertices[32] = (static_3d_vert){ .pos = BACK_BOT_LEFT, .norm = v3tov4(VEC3_NEG_X), .uv = { 0, 0 } };
+ vertices[33] = (static_3d_vert){ .pos = BACK_BOT_LEFT, .norm = v3tov4(VEC3_NEG_X), .uv = { 0, 0 } };
+ vertices[34] = (static_3d_vert){ .pos = FRONT_BOT_LEFT, .norm = v3tov4(VEC3_NEG_X), .uv = { 0, 0 } };
+ vertices[35] = (static_3d_vert){ .pos = FRONT_TOP_LEFT, .norm = v3tov4(VEC3_NEG_X), .uv = { 0, 0 } };
+
+ return (geometry){
+ .vertex_format = static_3d_vertex_format(), .vertex_data = vertices, .has_indices = false, .indices = NULL
+ };
+} \ No newline at end of file
diff --git a/src/impl.c b/src/impl.c
new file mode 100644
index 0000000..18f2549
--- /dev/null
+++ b/src/impl.c
@@ -0,0 +1,4 @@
+// For pulling in implementation files of single-header libraries
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h" \ No newline at end of file
diff --git a/src/log.c b/src/log.c
index f0dfd87..66ffe2d 100644
--- a/src/log.c
+++ b/src/log.c
@@ -1,7 +1,9 @@
#include <celeritas.h>
-void log_output(char* module, LogLevel level, const char* message, ...) {
+static const char* log_level_strings[] = { "FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" };
+
+void log_output(char* module, loglevel level, const char* message, ...) {
char out_msg[4096];
- printf("Msg: %s\n", message);
+ printf("[%s] %s - %s\n", module, log_level_strings[level], message);
}
diff --git a/src/maths.c b/src/maths.c
index 3ad1e2e..c1fab2a 100644
--- a/src/maths.c
+++ b/src/maths.c
@@ -1,3 +1,43 @@
#include <celeritas.h>
-Vec3 Vec3_Create(f32 x, f32 y, f32 z) { return (Vec3){ x, y, z }; }
+vec3 vec3_create(f32 x, f32 y, f32 z) { return (vec3){ x, y, z }; }
+
+vec3 vec3_add(vec3 u, vec3 v) { return (vec3){ .x = u.x + v.x, .y = u.y + v.y, .z = u.z + v.z }; }
+
+vec4 vec4_create(f32 x, f32 y, f32 z, f32 w) { return (vec4){ x, y, z, w }; }
+
+mat4 mat4_ident() { return (mat4){ .data = { 1.0, 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.0 } }; }
+
+mat4 mat4_mult(mat4 lhs, mat4 rhs) {
+ mat4 out_matrix = mat4_ident();
+
+ const f32* m1_ptr = lhs.data;
+ const f32* m2_ptr = rhs.data;
+ f32* dst_ptr = out_matrix.data;
+
+ for (i32 i = 0; i < 4; ++i) {
+ for (i32 j = 0; j < 4; ++j) {
+ *dst_ptr = m1_ptr[0] * m2_ptr[0 + j] + m1_ptr[1] * m2_ptr[4 + j] + m1_ptr[2] * m2_ptr[8 + j] +
+ m1_ptr[3] * m2_ptr[12 + j];
+ dst_ptr++;
+ }
+ m1_ptr += 4;
+ }
+
+ return out_matrix;
+}
+
+mat4 mat4_perspective(f32 fov_radians, f32 aspect_ratio, f32 near_z, f32 far_z) {
+ f32 half_tan_fov = tanf(fov_radians * 0.5f);
+ mat4 out_matrix = { .data = { 0 } };
+ out_matrix.data[0] = 1.0f / (aspect_ratio * half_tan_fov);
+ out_matrix.data[5] = 1.0f / half_tan_fov;
+ out_matrix.data[10] = -((far_z + near_z) / (far_z - near_z));
+ out_matrix.data[11] = -1.0f;
+ out_matrix.data[14] = -((2.0f * far_z * near_z) / (far_z - near_z));
+ return out_matrix;
+}
+
+mat4 mat4_look_at(vec3 position, vec3 target, vec3 up) {
+ // TODO
+} \ No newline at end of file
diff --git a/src/mem.c b/src/mem.c
index e69de29..2649db5 100644
--- a/src/mem.c
+++ b/src/mem.c
@@ -0,0 +1,88 @@
+#include <celeritas.h>
+
+void_pool void_pool_create(void* storage, const char* debug_label, u64 capacity, u64 entry_size) {
+ size_t _memory_requirements = capacity * entry_size;
+ // void* backing_buf = arena_alloc(a, memory_requirements);
+
+ assert(entry_size >= sizeof(void_pool_header)); // TODO: create my own assert with error message
+
+ void_pool pool = { .capacity = capacity,
+ .entry_size = entry_size,
+ .count = 0,
+ .backing_buffer = storage,
+ .free_list_head = NULL,
+ .debug_label = debug_label };
+
+ void_pool_free_all(&pool);
+
+ return pool;
+}
+
+void void_pool_free_all(void_pool* pool) {
+ // set all entries to be free
+ for (u64 i = 0; i < pool->capacity; i++) {
+ void* ptr = &pool->backing_buffer[i * pool->entry_size];
+ void_pool_header* free_node = (void_pool_header*)ptr; // we reuse the actual entry itself to hold the header
+ if (i == (pool->capacity - 1)) {
+ // if the last one we make its next pointer NULL indicating its full
+ free_node->next = NULL;
+ }
+ free_node->next = pool->free_list_head;
+ // now the head points to this entry
+ pool->free_list_head = free_node;
+ }
+}
+
+void* void_pool_get(void_pool* pool, u32 raw_handle) {
+ // An handle is an index into the array essentially
+ void* ptr = pool->backing_buffer + (raw_handle * pool->entry_size);
+ return ptr;
+}
+
+void* void_pool_alloc(void_pool* pool, u32* out_raw_handle) {
+ // get the next free node
+ if (pool->count == pool->capacity) {
+ // WARN("Pool is full!");
+ return NULL;
+ }
+ if (pool->free_list_head == NULL) {
+ // ERROR("%s Pool is full (head = null)", pool->debug_label);
+ return NULL;
+ }
+ void_pool_header* free_node = pool->free_list_head;
+
+ // What index does this become?
+ uintptr_t start = (uintptr_t)pool->backing_buffer;
+ uintptr_t cur = (uintptr_t)free_node;
+ // TRACE("%ld %ld ", start, cur);
+ assert(cur > start);
+ u32 index = (u32)((cur - start) / pool->entry_size);
+ /* printf("Index %d\n", index); */
+ if (out_raw_handle != NULL) {
+ *out_raw_handle = index;
+ }
+
+ pool->free_list_head = free_node->next;
+
+ memset(free_node, 0, pool->entry_size);
+ pool->count++;
+ return (void*)free_node;
+}
+
+void void_pool_dealloc(void_pool* pool, u32 raw_handle) {
+ // push free node back onto the free list
+ void* ptr = void_pool_get(pool, raw_handle);
+ void_pool_header* freed_node = (void_pool_header*)ptr;
+
+ freed_node->next = pool->free_list_head;
+ pool->free_list_head = freed_node;
+
+ pool->count--;
+}
+
+u32 void_pool_insert(void_pool* pool, void* item) {
+ u32 raw_handle;
+ void* item_dest = void_pool_alloc(pool, &raw_handle);
+ memcpy(item_dest, item, pool->entry_size);
+ return raw_handle;
+}
diff --git a/src/scene.c b/src/scene.c
new file mode 100644
index 0000000..26e19dc
--- /dev/null
+++ b/src/scene.c
@@ -0,0 +1,26 @@
+/**
+ * @file scene.c
+ * @author your name (you@domain.com)
+ * @brief
+ * @version 0.1
+ * @date 2024-10-18
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
+#include <celeritas.h>
+
+// Retained mode scene tree that handles performant transform propagation, and allows systems, or other languages via
+// bindings, to manipulate rendering/scene data without *owning* said data.
+
+typedef struct scene_tree_node {
+ const char* label;
+} scene_tree_node;
+
+DEFINE_HANDLE(scene_node_handle);
+TYPED_POOL(scene_tree_node, scene_node);
+
+typedef struct render_scene_tree {
+} render_scene_tree;
+
+// What kind of operations and mutations can we perform on the tree?