summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/camera.c11
-rw-r--r--src/camera.h26
-rw-r--r--src/colours.h38
-rw-r--r--src/core.c52
-rw-r--r--src/core.h22
-rw-r--r--src/defines.h31
-rw-r--r--src/log.c56
-rw-r--r--src/log.h54
-rw-r--r--src/logos/threadpool.c141
-rw-r--r--src/logos/threadpool.h94
-rw-r--r--src/maths/maths.h200
-rw-r--r--src/maths/maths_types.h63
-rw-r--r--src/renderer/backends/backend_opengl.c62
-rw-r--r--src/renderer/backends/backend_test.c1
-rw-r--r--src/renderer/backends/backend_vulkan.c1
-rw-r--r--src/renderer/render.c42
-rw-r--r--src/renderer/render.h16
-rw-r--r--src/renderer/render_backend.h15
-rw-r--r--src/renderer/render_types.h64
-rw-r--r--src/resources/gltf.c1
-rw-r--r--src/resources/loaders.h9
-rw-r--r--src/resources/obj.c1
-rw-r--r--src/std/containers/darray.h147
-rw-r--r--src/std/containers/ring_queue.c66
-rw-r--r--src/std/containers/ring_queue.h35
-rw-r--r--src/std/mem.h14
-rw-r--r--src/std/str.c14
-rw-r--r--src/std/str.h30
-rw-r--r--src/systems/input.c77
-rw-r--r--src/systems/input.h41
-rw-r--r--src/systems/keys.h6
-rw-r--r--src/systems/screenspace.h53
-rw-r--r--src/systems/text.c1
-rw-r--r--src/systems/text.h58
34 files changed, 1539 insertions, 3 deletions
diff --git a/src/camera.c b/src/camera.c
new file mode 100644
index 0000000..c2b864d
--- /dev/null
+++ b/src/camera.c
@@ -0,0 +1,11 @@
+#include "camera.h"
+
+#include "maths.h"
+
+void camera_view_projection(camera *c, f32 screen_height, f32 screen_width, mat4 *out_view_proj) {
+ mat4 proj = mat4_perspective(c->fov * 3.14 / 180.0, screen_width / screen_height, 0.1, 100.0);
+ vec3 camera_direction = vec3_add(c->position, c->front);
+ mat4 view = mat4_look_at(c->position, camera_direction, c->up);
+ mat4 out_mat = mat4_mult(view, proj);
+ *out_view_proj = out_mat;
+} \ No newline at end of file
diff --git a/src/camera.h b/src/camera.h
new file mode 100644
index 0000000..226f80e
--- /dev/null
+++ b/src/camera.h
@@ -0,0 +1,26 @@
+/**
+ * @file camera.h
+ * @author your name (you@domain.com)
+ * @brief
+ * @version 0.1
+ * @date 2024-02-24
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
+#pragma once
+
+#include "maths_types.h"
+
+typedef struct camera {
+ vec3 position;
+ vec3 front;
+ vec3 up;
+ f32 fov;
+} camera;
+
+/** @brief create a camera */
+camera camera_create(vec3 pos, vec3 front, vec3 up, f32 fov);
+
+/** @brief get a 4x4 transform matrix for the view and perspective projection */
+void camera_view_projection(camera *c, f32 screen_height, f32 screen_width, mat4 *out_view_proj); \ No newline at end of file
diff --git a/src/colours.h b/src/colours.h
new file mode 100644
index 0000000..bbd9476
--- /dev/null
+++ b/src/colours.h
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "defines.h"
+
+typedef struct rgba {
+ f32 r, g, b, a;
+} rgba;
+
+#define COLOUR_BLACK ((rgba){ 0.02, 0.02, 0.0, 1.0 })
+#define COLOUR_IMPERIAL_RED ((rgba){ 0.97, 0.09, 0.21, 1.0 })
+#define COLOUR_TRUE_BLUE ((rgba){ 0.11, 0.41, 0.77, 1.0 })
+#define COLOUR_SEA_GREEN ((rgba){ 0.18, 0.77, 0.71, 1.0 })
+#define COLOUR_WHITE ((rgba){ 1.0, 1.0, 1.0, 1.0 })
+
+// Thanks ChatGPT
+#define STONE_50 ((rgba){ 0.980, 0.980, 0.976, 1.0 })
+#define STONE_100 ((rgba){ 0.961, 0.961, 0.957, 1.0 })
+#define STONE_200 ((rgba){ 0.906, 0.898, 0.894, 1.0 })
+#define STONE_300 ((rgba){ 0.839, 0.827, 0.819, 1.0 })
+#define STONE_400 ((rgba){ 0.659, 0.635, 0.620, 1.0 })
+#define STONE_500 ((rgba){ 0.471, 0.443, 0.424, 1.0 })
+#define STONE_600 ((rgba){ 0.341, 0.325, 0.306, 1.0 })
+#define STONE_700 ((rgba){ 0.267, 0.251, 0.235, 1.0 })
+#define STONE_800 ((rgba){ 0.161, 0.145, 0.141, 1.0 })
+#define STONE_900 ((rgba){ 0.110, 0.098, 0.090, 1.0 })
+#define STONE_950 ((rgba){ 0.047, 0.043, 0.035, 1.0 })
+
+#define CYAN_50 ((rgba){ 0.930, 1.000, 1.000, 1.0 })
+#define CYAN_100 ((rgba){ 0.810, 0.980, 1.000, 1.0 })
+#define CYAN_200 ((rgba){ 0.650, 0.953, 0.988, 1.0 })
+#define CYAN_300 ((rgba){ 0.404, 0.910, 0.976, 1.0 })
+#define CYAN_400 ((rgba){ 0.133, 0.827, 0.933, 1.0 })
+#define CYAN_500 ((rgba){ 0.023, 0.714, 0.831, 1.0 })
+#define CYAN_600 ((rgba){ 0.031, 0.569, 0.698, 1.0 })
+#define CYAN_700 ((rgba){ 0.055, 0.455, 0.565, 1.0 })
+#define CYAN_800 ((rgba){ 0.082, 0.369, 0.459, 1.0 })
+#define CYAN_900 ((rgba){ 0.086, 0.306, 0.388, 1.0 })
+#define CYAN_950 ((rgba){ 0.033, 0.200, 0.263, 1.0 })
diff --git a/src/core.c b/src/core.c
new file mode 100644
index 0000000..affd8c8
--- /dev/null
+++ b/src/core.c
@@ -0,0 +1,52 @@
+#include "core.h"
+
+#include <stdlib.h>
+
+#include "log.h"
+#include "render.h"
+#include "render_types.h"
+#include "threadpool.h"
+
+#define SCR_WIDTH 1080
+#define SCR_HEIGHT 800
+
+core* core_bringup() {
+ INFO("Initiate Core bringup");
+ core* c = malloc(sizeof(core));
+ renderer_config conf = { .window_name = { "Celeritas Engine Core" },
+ .scr_width = SCR_WIDTH,
+ .scr_height = SCR_HEIGHT,
+ .clear_colour = (vec3){ .08, .08, .1 } };
+ c->renderer.config = conf;
+ c->renderer.backend_state = NULL;
+
+ threadpool_create(&c->threadpool, 6, 256);
+ threadpool_set_ctx(&c->threadpool, c); // Gives the threadpool access to the core
+
+ // initialise all subsystems
+ if (!renderer_init(&c->renderer)) {
+ // FATAL("Failed to start renderer");
+ ERROR_EXIT("Failed to start renderer\n");
+ }
+ if (!input_system_init(&c->input, c->renderer.window)) {
+ // the input system needs the glfw window which is created by the renderer
+ // hence the order here is important
+ FATAL("Failed to start input system");
+ ERROR_EXIT("Failed to start input system\n");
+ }
+ /*
+ if (!text_system_init(&c->text)) {
+ // FATAL("Failed to start text system");
+ ERROR_EXIT("Failed to start text system\n");
+ }
+ if (!screenspace_2d_init(&c->screenspace)) {
+ // FATAL("Failed to start screenspace 2d plugin");
+ ERROR_EXIT("Failed to start screenspace 2d plugin\n");
+ }
+ */
+
+ // c->underworld.models = model_darray_new(10);
+ // c->underworld.renderables = render_entity_darray_new(10);
+
+ return c;
+} \ No newline at end of file
diff --git a/src/core.h b/src/core.h
new file mode 100644
index 0000000..8a3d037
--- /dev/null
+++ b/src/core.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include "defines.h"
+#include "input.h"
+#include "render_types.h"
+#include "screenspace.h"
+#include "text.h"
+#include "threadpool.h"
+
+typedef struct core {
+ renderer renderer;
+ threadpool threadpool;
+ input_state input;
+ text_system_state text;
+ screenspace_state screenspace;
+} core;
+
+// --- Lifecycle
+core* core_bringup();
+void core_shutdown(core* core);
+
+void core_input_update(core* core); \ No newline at end of file
diff --git a/src/defines.h b/src/defines.h
index 2129d94..79b735d 100644
--- a/src/defines.h
+++ b/src/defines.h
@@ -1,7 +1,6 @@
/**
* @file defines.h
- * @author your name (you@domain.com)
- * @brief
+ * @brief
* @date 2024-02-24
* @copyright Copyright (c) 2024
*/
@@ -41,4 +40,30 @@ _Static_assert(sizeof(f64) == 8, "type f64 should be 8 bytes");
_Static_assert(sizeof(ptrdiff_t) == 8, "");
-#define alignof(x) _Alignof(x) \ No newline at end of file
+#define alignof(x) _Alignof(x)
+
+/*
+Possible platform defines:
+#define CEL_PLATFORM_LINUX 1
+#define CEL_PLATFORM_WINDOWS 1
+#define CEL_PLATFORM_MAC 1
+#define CEL_PLATFORM_HEADLESS 1
+*/
+
+/*
+Renderer backend defines:
+#define CEL_REND_BACKEND_OPENGL 1
+#define CEL_REND_BACKEND_VULKAN 1
+#define CEL_REND_BACKEND_METAL 1
+*/
+
+// Platform will inform renderer backend (unless user overrides)
+#if defined(CEL_PLATFORM_LINUX) || defined(CEL_PLATFORM_WINDOWS)
+#define CEL_REND_BACKEND_OPENGL 1
+// #define CEL_REND_BACKEND_VULKAN 1
+#endif
+
+#if defined(CEL_PLATFORM_MAC)
+#define CEL_REND_BACKEND_METAL 1
+// #define CEL_REND_BACKEND_OPENGL 1
+#endif \ No newline at end of file
diff --git a/src/log.c b/src/log.c
new file mode 100644
index 0000000..1b82f77
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,56 @@
+#include "log.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "defines.h"
+
+// Regular text
+#define BLK "\e[0;30m"
+#define RED "\e[0;31m"
+#define GRN "\e[0;32m"
+#define YEL "\e[0;33m"
+#define BLU "\e[0;34m"
+#define MAG "\e[0;35m"
+#define CYN "\e[0;36m"
+#define WHT "\e[0;37m"
+#define CRESET "\e[0m"
+
+static const char *level_strings[6] = { "[FATAL]: ", "[ERROR]: ", "[WARN]: ",
+ "[INFO]: ", "[DEBUG]: ", "[TRACE]: " };
+static const char *level_colours[6] = { RED, RED, YEL, BLU, CYN, MAG };
+
+bool logger_init() {
+ // TODO: create log file
+ return true;
+}
+
+void logger_shutdown() {
+ // does nothing right now
+}
+
+void log_output(log_level level, const char *message, ...) {
+ char out_message[32000];
+ memset(out_message, 0, sizeof(out_message));
+
+ // format original message
+ __builtin_va_list arg_ptr;
+ va_start(arg_ptr, message);
+ vsnprintf(out_message, 32000, message, arg_ptr);
+ va_end(arg_ptr);
+
+ char out_message2[32006];
+ // prepend log level string
+ sprintf(out_message2, "%s%s%s%s\n", level_colours[level], level_strings[level], out_message,
+ CRESET);
+
+ // print message to console
+ printf("%s", out_message2);
+}
+
+void report_assertion_failure(const char *expression, const char *message, const char *file,
+ i32 line) {
+ log_output(LOG_LEVEL_FATAL, "Assertion failure: %s, message: '%s', in file: %s, on line %d\n",
+ expression, message, file, line);
+} \ No newline at end of file
diff --git a/src/log.h b/src/log.h
new file mode 100644
index 0000000..e7a90ea
--- /dev/null
+++ b/src/log.h
@@ -0,0 +1,54 @@
+#pragma once
+
+#include <stdbool.h>
+
+#define ERROR_EXIT(...) \
+ { \
+ fprintf(stderr, __VA_ARGS__); \
+ exit(1); \
+ }
+
+#define LOG_WARN_ENABLED 1
+#define LOG_INFO_ENABLED 1
+
+#ifdef CRELEASE
+#define LOG_DEBUG_ENABLED 0
+#define LOG_TRACE_ENABLED 0
+#else
+#define LOG_DEBUG_ENABLED 1
+#define LOG_TRACE_ENABLED 1
+#endif
+
+typedef enum log_level {
+ LOG_LEVEL_FATAL = 0,
+ LOG_LEVEL_ERROR = 1,
+ LOG_LEVEL_WARN = 2,
+ LOG_LEVEL_INFO = 3,
+ LOG_LEVEL_DEBUG = 4,
+ LOG_LEVEL_TRACE = 5,
+} log_level;
+
+bool logger_init();
+void logger_shutdown();
+
+// TODO: macro that outputs logger macros for a specific subsystem or string prefix e.g. "MEMORY" ->
+// logs now have more context potentially have line numbers too?
+
+void log_output(log_level level, const char* message, ...);
+
+#define FATAL(message, ...) log_output(LOG_LEVEL_FATAL, message, ##__VA_ARGS__);
+#define ERROR(message, ...) log_output(LOG_LEVEL_ERROR, message, ##__VA_ARGS__);
+#define WARN(message, ...) log_output(LOG_LEVEL_WARN, message, ##__VA_ARGS__);
+#define INFO(message, ...) log_output(LOG_LEVEL_INFO, message, ##__VA_ARGS__);
+
+#if LOG_DEBUG_ENABLED == 1
+#define DEBUG(message, ...) log_output(LOG_LEVEL_DEBUG, message, ##__VA_ARGS__);
+#else
+#define DEBUG(message, ...)
+#endif
+
+#if LOG_TRACE_ENABLED == 1
+#define TRACE(message, ...) log_output(LOG_LEVEL_TRACE, message, ##__VA_ARGS__);
+#else
+#define TRACE(message, ...)
+#endif \ No newline at end of file
diff --git a/src/logos/threadpool.c b/src/logos/threadpool.c
new file mode 100644
index 0000000..02cf347
--- /dev/null
+++ b/src/logos/threadpool.c
@@ -0,0 +1,141 @@
+#include "threadpool.h"
+
+#include <pthread.h>
+
+#include "defines.h"
+#include "log.h"
+#include "ring_queue.h"
+
+static void *worker_factory(void *arg) {
+ threadpool_worker *worker = arg;
+ // INFO("Starting job thread %d", worker->id);
+
+ // Run forever, waiting for jobs.
+ while (true) {
+ pthread_mutex_lock(&worker->pool->mutex);
+ pthread_cond_wait(&worker->pool->has_tasks, &worker->pool->mutex); // wait for work to be ready
+
+ task t;
+ if (ring_queue_dequeue(worker->pool->task_queue, &t)) {
+ // DEBUG("Job thread %d picked up task %d", worker->id, t.task_id);
+ } else {
+ // WARN("Job thread %d didnt pick up a task as queue was empty", worker->id);
+ pthread_mutex_unlock(&worker->pool->mutex);
+ break;
+ }
+
+ pthread_mutex_unlock(&worker->pool->mutex);
+
+ // Do the work
+ bool result = t.do_task(t.params, t.result_data);
+
+ // INFO("Task result was %s", result ? "success" : "failure");
+ if (result) {
+ pthread_mutex_lock(&worker->pool->mutex);
+ if (t.buffer_result_for_main_thread) {
+ deferred_task_result dtr = { .task_id = t.task_id,
+ .callback = t.on_success,
+ .result_data = t.result_data,
+ .result_data_size = t.result_data_size };
+ deferred_task_result_darray_push(worker->pool->results, dtr);
+ } else {
+ // call on complete from here.
+ }
+ } else {
+ // TODO
+ }
+ pthread_mutex_unlock(&worker->pool->mutex);
+ }
+
+ return NULL;
+}
+
+bool threadpool_create(threadpool *pool, u8 thread_count, u32 queue_size) {
+ INFO("Threadpool init");
+ pool->next_task_id = 0;
+ pool->context = NULL;
+
+ u8 num_worker_threads = thread_count;
+ if (thread_count > MAX_NUM_THREADS) {
+ ERROR_EXIT("Threadpool has a hard limit of %d threads, you tried to start one with %d",
+ MAX_NUM_THREADS, thread_count)
+ num_worker_threads = MAX_NUM_THREADS;
+ }
+
+ DEBUG("creating task queue with max length %d", queue_size);
+ pool->task_queue = ring_queue_new(sizeof(task), queue_size, NULL);
+
+ DEBUG("creating mutex and condition");
+ pthread_mutex_init(&pool->mutex, NULL);
+ pthread_cond_init(&pool->has_tasks, NULL);
+
+ pool->results = deferred_task_result_darray_new(256);
+
+ DEBUG("Spawning %d threads for the threadpool", thread_count);
+ for (u8 i = 0; i < num_worker_threads; i++) {
+ pool->workers[i].id = i;
+ pool->workers[i].pool = pool;
+ if (pthread_create(&pool->workers[i].thread, NULL, worker_factory, &pool->workers[i]) != 0) {
+ FATAL("OS error creating job thread");
+ return false;
+ };
+ }
+
+ return true;
+}
+
+bool threadpool_add_task(threadpool *pool, tpool_task_start do_task,
+ tpool_task_on_complete on_success, tpool_task_on_complete on_fail,
+ bool buffer_result_for_main_thread, void *param_data, u32 param_data_size,
+ u32 result_data_size) {
+ void *result_data = malloc(result_data_size);
+
+ task *work_task = malloc(sizeof(task));
+ work_task->task_id = 0;
+ work_task->do_task = do_task;
+ work_task->on_success = on_success;
+ work_task->on_failure = on_fail;
+ work_task->buffer_result_for_main_thread = buffer_result_for_main_thread;
+ work_task->param_size = param_data_size;
+ work_task->params = param_data;
+ work_task->result_data_size = result_data_size;
+ work_task->result_data = result_data;
+
+ // START critical section
+ if (pthread_mutex_lock(&pool->mutex) != 0) {
+ ERROR("Unable to get threadpool lock.");
+ return false;
+ }
+
+ work_task->task_id = pool->next_task_id;
+ pool->next_task_id++;
+
+ ring_queue_enqueue(pool->task_queue, work_task);
+ DEBUG("Enqueued job");
+ pthread_cond_broadcast(&pool->has_tasks);
+
+ if (pthread_mutex_unlock(&pool->mutex) != 0) {
+ ERROR("couldnt unlock threadpool after adding task.");
+ return false; // ?
+ }
+ // END critical section
+
+ return true;
+}
+
+void threadpool_process_results(threadpool *pool, int _num_to_process) {
+ pthread_mutex_lock(&pool->mutex);
+ size_t num_results = deferred_task_result_darray_len(pool->results);
+ if (num_results > 0) {
+ u32 _size = ((deferred_task_result *)pool->results->data)[num_results].result_data_size;
+ deferred_task_result res;
+ deferred_task_result_darray_pop(pool->results, &res);
+ pthread_mutex_unlock(&pool->mutex);
+ task_globals globals = { .pool = pool, .ctx = pool->context };
+ res.callback(&globals, res.result_data);
+ } else {
+ pthread_mutex_unlock(&pool->mutex);
+ }
+}
+
+void threadpool_set_ctx(threadpool *pool, void *ctx) { pool->context = ctx; } \ No newline at end of file
diff --git a/src/logos/threadpool.h b/src/logos/threadpool.h
new file mode 100644
index 0000000..d5df2cd
--- /dev/null
+++ b/src/logos/threadpool.h
@@ -0,0 +1,94 @@
+/**
+ A Threadpool has a number of "workers", each which process "tasks"
+*/
+#pragma once
+
+#include <pthread.h>
+
+#include "darray.h"
+#include "defines.h"
+#include "ring_queue.h"
+
+#define MAX_NUM_THREADS 16
+
+struct threadpool;
+typedef struct threadpool threadpool;
+
+typedef struct task_globals {
+ threadpool *pool;
+ void *ctx;
+} task_globals;
+
+/* function pointer */
+typedef bool (*tpool_task_start)(void *, void *);
+
+/* function pointer */
+typedef void (*tpool_task_on_complete)(task_globals *, void *);
+
+typedef struct threadpool_worker {
+ u16 id;
+ pthread_t thread;
+ threadpool *pool; // pointer back to the pool so we can get the mutex and cond
+} threadpool_worker;
+
+typedef enum tpool_task_status {
+ TASK_STATUS_READY,
+} task_status;
+
+typedef struct tpool_task {
+ u64 task_id;
+ tpool_task_start do_task;
+ tpool_task_on_complete on_success;
+ tpool_task_on_complete on_failure;
+ bool buffer_result_for_main_thread;
+ /** @brief a pointer to the parameters data that will be passed into the task. */
+ void *params;
+ u32 param_size;
+ void *result_data;
+ u32 result_data_size;
+} task;
+
+typedef struct deferred_task_result {
+ u64 task_id;
+ tpool_task_on_complete callback;
+ u32 result_data_size;
+ // this gets passed to the void* argument of `tpool_task_on_complete`
+ void *result_data;
+} deferred_task_result;
+
+#ifndef TYPED_TASK_RESULT_ARRAY
+KITC_DECL_TYPED_ARRAY(deferred_task_result) // creates "deferred_task_result_darray"
+#define TYPED_TASK_RESULT_ARRAY
+#endif
+
+struct threadpool {
+ ring_queue *task_queue;
+ pthread_mutex_t mutex;
+ pthread_cond_t has_tasks;
+ threadpool_worker workers[MAX_NUM_THREADS];
+ deferred_task_result_darray *results;
+ u64 next_task_id;
+
+ void *context;
+};
+
+/**
+ * @param pool where to store the created threadpool
+ * @param thread_count how many threads to spawn
+ * @param queue_size max size of task queue
+ */
+bool threadpool_create(threadpool *pool, u8 thread_count, u32 queue_size);
+void threadpool_destroy(threadpool *pool);
+
+/** @brief set a context variable for the threadpool that task data has access to */
+void threadpool_set_ctx(threadpool *pool, void *ctx);
+
+/**
+ * @brief Add a task to the threadpool
+ */
+bool threadpool_add_task(threadpool *pool, tpool_task_start do_task,
+ tpool_task_on_complete on_success, tpool_task_on_complete on_fail,
+ bool buffer_result_for_main_thread, void *param_data, u32 param_data_size,
+ u32 result_data_size);
+
+void threadpool_process_results(threadpool *pool, int num_to_process); \ No newline at end of file
diff --git a/src/maths/maths.h b/src/maths/maths.h
new file mode 100644
index 0000000..7352aeb
--- /dev/null
+++ b/src/maths/maths.h
@@ -0,0 +1,200 @@
+/**
+ * @file maths.h
+ * @author your name (you@domain.com)
+ * @brief
+ * @version 0.1
+ * @date 2024-02-24
+ * @copyright Copyright (c) 2024
+ */
+#pragma once
+
+#include <math.h>
+#include "maths_types.h"
+
+// --- Vector Implementations
+
+// Dimension 3
+static inline vec3 vec3_create(f32 x, f32 y, f32 z) { return (vec3){ x, y, z }; }
+static inline vec3 vec3_add(vec3 a, vec3 b) { return (vec3){ a.x + b.x, a.y + b.y, a.z + b.z }; }
+static inline vec3 vec3_sub(vec3 a, vec3 b) { return (vec3){ a.x - b.x, a.y - b.y, a.z - b.z }; }
+static inline vec3 vec3_mult(vec3 a, f32 s) { return (vec3){ a.x * s, a.y * s, a.z * s }; }
+static inline vec3 vec3_div(vec3 a, f32 s) { return (vec3){ a.x / s, a.y / s, a.z / s }; }
+
+static inline f32 vec3_len_squared(vec3 a) { return (a.x * a.x) + (a.y * a.y) + (a.z * a.z); }
+static inline f32 vec3_len(vec3 a) { return sqrtf(vec3_len_squared(a)); }
+static inline vec3 vec3_negate(vec3 a) { return (vec3){ -a.x, -a.y, -a.z }; }
+static inline vec3 vec3_normalise(vec3 a) {
+ f32 length = vec3_len(a);
+ return vec3_div(a, length);
+}
+
+static inline f32 vec3_dot(vec3 a, vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
+static inline vec3 vec3_cross(vec3 a, vec3 b) {
+ return (
+ vec3){ .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 };
+}
+
+#define VEC3_ZERO ((vec3){ .x = 0.0, .y = 0.0, .z = 0.0 })
+#define VEC3_X ((vec3){ .x = 1.0, .y = 0.0, .z = 0.0 })
+#define VEC3_NEG_X ((vec3){ .x = -1.0, .y = 0.0, .z = 0.0 })
+#define VEC3_Y ((vec3){ .x = 0.0, .y = 1.0, .z = 0.0 })
+#define VEC3_NEG_Y ((vec3){ .x = 0.0, .y = -1.0, .z = 0.0 })
+#define VEC3_Z ((vec3){ .x = 0.0, .y = 0.0, .z = 1.0 })
+#define VEC3_NEG_Z ((vec3){ .x = 0.0, .y = 0.0, .z = -1.0 })
+
+// TODO: Dimension 2
+static inline vec2 vec2_create(f32 x, f32 y) { return (vec2){ x, y }; }
+
+// TODO: Dimension 4
+#define VEC4_ZERO ((vec4){ .x = 0.0, .y = 0.0, .z = 0.0, .w = 0.0 })
+
+// --- Matrix Implementations
+
+static inline mat4 mat4_ident() {
+ return (mat4){ .data = { 1.0, 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.0 } };
+}
+
+static inline mat4 mat4_translation(vec3 position) {
+ mat4 out_matrix = mat4_ident();
+ out_matrix.data[12] = position.x;
+ out_matrix.data[13] = position.y;
+ out_matrix.data[14] = position.z;
+ return out_matrix;
+}
+
+static inline mat4 mat4_scale(f32 scale) {
+ mat4 out_matrix = mat4_ident();
+ out_matrix.data[0] = scale;
+ out_matrix.data[5] = scale;
+ out_matrix.data[10] = scale;
+ return out_matrix;
+}
+
+static inline 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;
+}
+
+/** @brief Creates a perspective projection matrix */
+static inline mat4 mat4_perspective(f32 fov_radians, f32 aspect_ratio, f32 near_clip,
+ f32 far_clip) {
+ 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_clip + near_clip) / (far_clip - near_clip));
+ out_matrix.data[11] = -1.0f;
+ out_matrix.data[14] = -((2.0f * far_clip * near_clip) / (far_clip - near_clip));
+ return out_matrix;
+}
+
+/** @brief Creates an orthographic projection matrix */
+static inline mat4 mat4_orthographic(f32 left, f32 right, f32 bottom, f32 top, f32 near_clip,
+ f32 far_clip) {
+ // source: kohi game engine.
+ mat4 out_matrix = mat4_ident();
+
+ f32 lr = 1.0f / (left - right);
+ f32 bt = 1.0f / (bottom - top);
+ f32 nf = 1.0f / (near_clip - far_clip);
+
+ out_matrix.data[0] = -2.0f * lr;
+ out_matrix.data[5] = -2.0f * bt;
+ out_matrix.data[10] = 2.0f * nf;
+
+ out_matrix.data[12] = (left + right) * lr;
+ out_matrix.data[13] = (top + bottom) * bt;
+ out_matrix.data[14] = (far_clip + near_clip) * nf;
+
+ return out_matrix;
+}
+
+static inline mat4 mat4_look_at(vec3 position, vec3 target, vec3 up) {
+ mat4 out_matrix;
+ vec3 z_axis;
+ z_axis.x = target.x - position.x;
+ z_axis.y = target.y - position.y;
+ z_axis.z = target.z - position.z;
+
+ z_axis = vec3_normalise(z_axis);
+ vec3 x_axis = vec3_normalise(vec3_cross(z_axis, up));
+ vec3 y_axis = vec3_cross(x_axis, z_axis);
+
+ out_matrix.data[0] = x_axis.x;
+ out_matrix.data[1] = y_axis.x;
+ out_matrix.data[2] = -z_axis.x;
+ out_matrix.data[3] = 0;
+ out_matrix.data[4] = x_axis.y;
+ out_matrix.data[5] = y_axis.y;
+ out_matrix.data[6] = -z_axis.y;
+ out_matrix.data[7] = 0;
+ out_matrix.data[8] = x_axis.z;
+ out_matrix.data[9] = y_axis.z;
+ out_matrix.data[10] = -z_axis.z;
+ out_matrix.data[11] = 0;
+ out_matrix.data[12] = -vec3_dot(x_axis, position);
+ out_matrix.data[13] = -vec3_dot(y_axis, position);
+ out_matrix.data[14] = vec3_dot(z_axis, position);
+ out_matrix.data[15] = 1.0f;
+
+ return out_matrix;
+}
+
+// ...
+
+// --- Quaternion Implementations
+
+// --- Transform Implementations
+
+#define TRANSFORM_DEFAULT \
+ ((transform){ .position = VEC3_ZERO, \
+ .rotation = (quat){ .x = 0., .y = 0., .z = 0., .w = 0. }, \
+ .scale = 1.0, \
+ .is_dirty = false })
+
+static transform transform_create(vec3 pos, quat rot, f32 scale) {
+ return (transform){ .position = pos, .rotation = rot, .scale = scale, .is_dirty = false };
+}
+
+static inline mat4 transform_to_mat(transform *tf) {
+ // TODO: rotation
+ return mat4_mult(mat4_translation(tf->position), mat4_scale(tf->scale));
+}
+
+// --- Sizing asserts
+
+_Static_assert(alignof(vec3) == 4, "vec3 is 4 byte aligned");
+_Static_assert(sizeof(vec3) == 12, "vec3 is 12 bytes so has no padding");
+
+_Static_assert(alignof(vec4) == 4, "vec4 is 4 byte aligned");
+
+// --- Some other types
+typedef struct u32x3 {
+ union {
+ struct {
+ u32 x;
+ u32 y;
+ u32 z;
+ };
+ struct {
+ u32 r;
+ u32 g;
+ u32 b;
+ };
+ };
+} u32x3;
+#define u32x3(x, y, z) ((u32x3){ x, y, z })
diff --git a/src/maths/maths_types.h b/src/maths/maths_types.h
new file mode 100644
index 0000000..ba741b9
--- /dev/null
+++ b/src/maths/maths_types.h
@@ -0,0 +1,63 @@
+/**
+ * @file maths_types.h
+ * @author Omniscient
+ * @brief Maths types
+ * @date 2024-02-24
+ * @copyright Copyright (c) 2024
+ */
+#pragma once
+
+#include "defines.h"
+
+// --- Constants
+#define PI 3.14159265358979323846
+#define HALF_PI 1.57079632679489661923
+#define TAU (2.0 * PI)
+
+// --- Helpers
+#define deg_to_rad(x) (x * 3.14 / 180.0)
+#define min(a, b) (a < b ? a : b)
+#define max(a, b) (a > b ? a : b)
+
+// --- Types
+
+/** @brief 2D Vector */
+typedef struct vec2 {
+ f32 x, y;
+} vec2;
+
+/** @brief 3D Vector */
+typedef struct vec3 {
+ f32 x, y, z;
+} vec3;
+
+/** @brief 4D Vector */
+typedef struct vec4 {
+ f32 x, y, z, w;
+} vec4;
+
+/** @brief Quaternion */
+typedef vec4 quat;
+
+/** @brief 4x4 Matrix */
+typedef struct mat4 {
+ // TODO: use this format for more readable code: vec4 x_axis, y_axis, z_axis, w_axis;
+ f32 data[16];
+} mat4;
+
+/** @brief Three dimensional bounding box */
+typedef struct bbox_3d {
+ vec3 min; // minimum point of the box
+ vec3 max; // maximum point of the box
+} bbox_3d;
+
+/** @brief 3D Axis-aligned bounding box */
+typedef bbox_3d aabb_3d;
+
+/** @brief 3D affine transformation */
+typedef struct transform {
+ vec3 position;
+ quat rotation;
+ f32 scale;
+ bool is_dirty;
+} transform; \ No newline at end of file
diff --git a/src/renderer/backends/backend_opengl.c b/src/renderer/backends/backend_opengl.c
new file mode 100644
index 0000000..ed2c70f
--- /dev/null
+++ b/src/renderer/backends/backend_opengl.c
@@ -0,0 +1,62 @@
+#include <stdlib.h>
+#define CEL_PLATFORM_LINUX
+
+#include "defines.h"
+#include "log.h"
+#include "maths_types.h"
+#include "render_types.h"
+
+#if CEL_REND_BACKEND_OPENGL
+
+#include <glad/glad.h>
+
+#include <GLFW/glfw3.h>
+
+/** @brief Internal backend state */
+typedef struct opengl_state {
+} opengl_state;
+
+bool gfx_backend_init(renderer *ren) {
+ INFO("loading OpenGL backend");
+
+ // glfwInit(); // Already handled in `renderer_init`
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+
+ // glad: load all OpenGL function pointers
+ if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
+ ERROR("Failed to initialise GLAD \n");
+
+ return false;
+ }
+
+ glEnable(GL_DEPTH_TEST);
+
+ opengl_state *internal = malloc(sizeof(opengl_state));
+ ren->backend_state = (void *)internal;
+
+ return true;
+}
+void gfx_backend_shutdown(renderer *ren) {}
+
+void uniform_vec3f(u32 program_id, const char *uniform_name, vec3 *value) {
+ glUniform3fv(glGetUniformLocation(program_id, uniform_name), 1, &value->x);
+}
+void uniform_f32(u32 program_id, const char *uniform_name, f32 value) {
+ glUniform1f(glGetUniformLocation(program_id, uniform_name), value);
+}
+void uniform_i32(u32 program_id, const char *uniform_name, i32 value) {
+ glUniform1i(glGetUniformLocation(program_id, uniform_name), value);
+}
+void uniform_mat4f(u32 program_id, const char *uniform_name, mat4 *value) {
+ glUniformMatrix4fv(glGetUniformLocation(program_id, uniform_name), 1, GL_FALSE, value->data);
+}
+
+void clear_screen(vec3 colour) {
+ glClearColor(colour.x, colour.y, colour.z, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+}
+
+#endif \ No newline at end of file
diff --git a/src/renderer/backends/backend_test.c b/src/renderer/backends/backend_test.c
new file mode 100644
index 0000000..6347e27
--- /dev/null
+++ b/src/renderer/backends/backend_test.c
@@ -0,0 +1 @@
+// #FUTURE \ No newline at end of file
diff --git a/src/renderer/backends/backend_vulkan.c b/src/renderer/backends/backend_vulkan.c
new file mode 100644
index 0000000..6347e27
--- /dev/null
+++ b/src/renderer/backends/backend_vulkan.c
@@ -0,0 +1 @@
+// #FUTURE \ No newline at end of file
diff --git a/src/renderer/render.c b/src/renderer/render.c
new file mode 100644
index 0000000..ce908d6
--- /dev/null
+++ b/src/renderer/render.c
@@ -0,0 +1,42 @@
+#include "render.h"
+
+#include <GLFW/glfw3.h>
+
+#include "log.h"
+#include "render_backend.h"
+
+bool renderer_init(renderer* ren) {
+ INFO("Renderer init");
+
+ // NOTE: all platforms use GLFW at the moment but thats subject to change
+ glfwInit();
+
+ // glfw window creation
+ GLFWwindow* window = glfwCreateWindow(ren->config.scr_width, ren->config.scr_height,
+ ren->config.window_name, NULL, NULL);
+ if (window == NULL) {
+ printf("Failed to create GLFW window\n");
+ glfwTerminate();
+ return false;
+ }
+ ren->window = window;
+
+ glfwMakeContextCurrent(ren->window);
+
+ if (!gfx_backend_init(ren)) {
+ FATAL("Couldnt load graphics api backend");
+ return false;
+ }
+
+ return true;
+}
+
+void render_frame_begin(renderer* ren) {
+ vec3 color = ren->config.clear_colour;
+ clear_screen(color);
+}
+void render_frame_end(renderer* ren) {
+ // present frame
+ glfwSwapBuffers(ren->window);
+ glfwPollEvents();
+} \ No newline at end of file
diff --git a/src/renderer/render.h b/src/renderer/render.h
new file mode 100644
index 0000000..c89c364
--- /dev/null
+++ b/src/renderer/render.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "render_types.h"
+
+// --- Lifecycle
+/** @brief initialise the render system frontend */
+bool renderer_init(renderer* ren);
+/** @brief shutdown the render system frontend */
+void renderer_shutdown(renderer* ren);
+
+// --- Frame
+
+void render_frame_begin(renderer* ren);
+void render_frame_end(renderer* ren);
+
+// --- \ No newline at end of file
diff --git a/src/renderer/render_backend.h b/src/renderer/render_backend.h
new file mode 100644
index 0000000..61c7ab5
--- /dev/null
+++ b/src/renderer/render_backend.h
@@ -0,0 +1,15 @@
+/**
+ * @brief
+ */
+#pragma once
+
+#include "maths_types.h"
+#include "render_types.h"
+
+/// --- Lifecycle
+bool gfx_backend_init(renderer* ren);
+void gfx_backend_shutdown(renderer* ren);
+
+void clear_screen(vec3 colour);
+
+// --- Uniforms
diff --git a/src/renderer/render_types.h b/src/renderer/render_types.h
new file mode 100644
index 0000000..896a1a5
--- /dev/null
+++ b/src/renderer/render_types.h
@@ -0,0 +1,64 @@
+/**
+ * @file render_types.h
+ * @author Omniscient
+ * @brief Type definitions for the majority of data required by the renderer system
+ * @date 2024-02-24
+ *
+ */
+#pragma once
+
+#include "darray.h"
+#include "maths_types.h"
+
+struct GLFWwindow;
+
+/** @brief configuration passed to the renderer at init time */
+typedef struct renderer_config {
+ char window_name[256];
+ u32 scr_width, scr_height;
+ vec3 clear_colour; /** colour that the screen gets cleared to every frame */
+} renderer_config;
+
+typedef struct renderer {
+ struct GLFWwindow *window; /** Currently all platforms use GLFW*/
+ void *backend_state; /** Graphics API-specific state */
+ renderer_config config;
+} renderer;
+
+/** @brief Vertex format for a static mesh */
+typedef struct vertex {
+ vec3 position;
+ vec3 normal;
+ vec2 uv;
+} vertex;
+
+#ifndef TYPED_VERTEX_ARRAY
+KITC_DECL_TYPED_ARRAY(vertex) // creates "vertex_darray"
+#define TYPED_VERTEX_ARRAY
+#endif
+
+// --- Models & Meshes
+
+typedef struct mesh {
+ vertex_darray *vertices;
+ u32 vertex_size; /** size in bytes of each vertex including necessary padding */
+ bool has_indices;
+ u32 *indices;
+ u32 indices_len;
+ size_t material_index;
+ u32 vbo, vao; /** OpenGL data */
+} mesh;
+
+#ifndef TYPED_MESH_ARRAY
+KITC_DECL_TYPED_ARRAY(mesh) // creates "mesh_darray"
+#define TYPED_MESH_ARRAY
+#endif
+
+typedef struct model {
+ char name[256];
+} model;
+
+#ifndef TYPED_MODEL_ARRAY
+KITC_DECL_TYPED_ARRAY(model) // creates "model_darray"
+#define TYPED_MODEL_ARRAY
+#endif \ No newline at end of file
diff --git a/src/resources/gltf.c b/src/resources/gltf.c
new file mode 100644
index 0000000..b646f58
--- /dev/null
+++ b/src/resources/gltf.c
@@ -0,0 +1 @@
+// TODO: Port code from old repo \ No newline at end of file
diff --git a/src/resources/loaders.h b/src/resources/loaders.h
new file mode 100644
index 0000000..ba38ec4
--- /dev/null
+++ b/src/resources/loaders.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "defines.h"
+
+struct core;
+typedef u32 model_handle;
+
+model_handle model_load_obj(struct core *core, const char *path, bool invert_texture_y);
+model_handle model_load_gltf(struct core *core, const char *path, bool invert_texture_y); \ No newline at end of file
diff --git a/src/resources/obj.c b/src/resources/obj.c
new file mode 100644
index 0000000..b646f58
--- /dev/null
+++ b/src/resources/obj.c
@@ -0,0 +1 @@
+// TODO: Port code from old repo \ No newline at end of file
diff --git a/src/std/containers/darray.h b/src/std/containers/darray.h
new file mode 100644
index 0000000..729b4cf
--- /dev/null
+++ b/src/std/containers/darray.h
@@ -0,0 +1,147 @@
+/**
+ * @file darray.h
+ * @brief Typed dynamic array
+ * @copyright Copyright (c) 2023
+ */
+// COPIED FROM KITC WITH SOME MINOR ADJUSTMENTS
+
+#ifndef KITC_TYPED_ARRAY_H
+#define KITC_TYPED_ARRAY_H
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define DARRAY_DEFAULT_CAPACITY 64
+#define DARRAY_RESIZE_FACTOR 3
+
+/** @brief create a new darray type and functions with type `N` */
+#define typed_array(T) \
+ struct { \
+ /* @brief current number of items in the array */ \
+ size_t len; \
+ size_t capacity; \
+ T *data; \
+ }
+
+#define typed_array_iterator(T) \
+ struct { \
+ T##_darray *array; \
+ size_t current_idx; \
+ }
+
+#define PREFIX static
+
+#define KITC_DECL_TYPED_ARRAY(T) \
+ typedef typed_array(T) T##_darray; \
+ typedef typed_array_iterator(T) T##_darray_iter; \
+ \
+ /* Create a new one growable array */ \
+ PREFIX T##_darray *T##_darray_new(size_t starting_capacity) { \
+ T##_darray *d = malloc(sizeof(T##_darray)); \
+ T *data = malloc(starting_capacity * sizeof(T)); \
+ \
+ d->len = 0; \
+ d->capacity = starting_capacity; \
+ d->data = data; \
+ \
+ return d; \
+ } \
+ \
+ PREFIX void T##_darray_free(T##_darray *d) { \
+ if (d != NULL) { \
+ free(d->data); \
+ free(d); \
+ } \
+ } \
+ \
+ PREFIX T *T##_darray_resize(T##_darray *d, size_t capacity) { \
+ /* resize the internal data block */ \
+ T *new_data = realloc(d->data, sizeof(T) * capacity); \
+ /* TODO: handle OOM error */ \
+ \
+ d->capacity = capacity; \
+ d->data = new_data; \
+ return new_data; \
+ } \
+ \
+ PREFIX void T##_darray_push(T##_darray *d, T value) { \
+ if (d->len >= d->capacity) { \
+ size_t new_capacity = \
+ d->capacity > 0 ? d->capacity * DARRAY_RESIZE_FACTOR : DARRAY_DEFAULT_CAPACITY; \
+ T *resized = T##_darray_resize(d, new_capacity); \
+ } \
+ \
+ d->data[d->len] = value; \
+ d->len += 1; \
+ } \
+ \
+ PREFIX void T##_darray_push_copy(T##_darray *d, const T *value) { \
+ if (d->len >= d->capacity) { \
+ size_t new_capacity = \
+ d->capacity > 0 ? d->capacity * DARRAY_RESIZE_FACTOR : DARRAY_DEFAULT_CAPACITY; \
+ T *resized = T##_darray_resize(d, new_capacity); \
+ } \
+ \
+ T *place = d->data + d->len; \
+ d->len += 1; \
+ memcpy(place, value, sizeof(T)); \
+ } \
+ \
+ PREFIX void T##_darray_pop(T##_darray *d, T *dest) { \
+ T *item = d->data + (d->len - 1); \
+ d->len -= 1; \
+ memcpy(dest, item, sizeof(T)); \
+ } \
+ \
+ PREFIX void T##_darray_ins(T##_darray *d, const T *value, size_t index) { \
+ /* check if requires resize */ \
+ if (d->len + 1 > d->capacity) { \
+ size_t new_capacity = \
+ d->capacity > 0 ? d->capacity * DARRAY_RESIZE_FACTOR : DARRAY_DEFAULT_CAPACITY; \
+ T *resized = T##_darray_resize(d, new_capacity); \
+ } \
+ \
+ /* shift existing data after index */ \
+ T *insert_dest = d->data + index; \
+ T *shift_dest = insert_dest + 1; \
+ \
+ int num_items = d->len - index; \
+ \
+ d->len += 1; \
+ memcpy(shift_dest, insert_dest, num_items * sizeof(T)); \
+ memcpy(insert_dest, value, sizeof(T)); \
+ } \
+ \
+ PREFIX void T##_darray_clear(T##_darray *d) { \
+ d->len = 0; \
+ memset(d->data, 0, d->capacity * sizeof(T)); \
+ } \
+ \
+ PREFIX size_t T##_darray_len(T##_darray *d) { return d->len; } \
+ \
+ PREFIX void T##_darray_print(T##_darray *d) { \
+ printf("len: %zu ", d->len); \
+ printf("capacity: %zu\n", d->capacity); \
+ for (int i = 0; i < d->len; i++) { \
+ printf("Index %d holds value %d\n", i, d->data[i]); \
+ } \
+ } \
+ \
+ PREFIX T##_darray_iter T##_darray_iter_new(T##_darray *d) { \
+ T##_darray_iter iterator; \
+ iterator.array = d; \
+ iterator.current_idx = 0; \
+ return iterator; \
+ } \
+ \
+ PREFIX void *T##_darray_iter_next(T##_darray_iter *iterator) { \
+ if (iterator->current_idx < iterator->array->len) { \
+ return &iterator->array->data[iterator->current_idx++]; \
+ } else { \
+ return NULL; \
+ } \
+ }
+
+#endif // KITC_TYPED_ARRAY_H
diff --git a/src/std/containers/ring_queue.c b/src/std/containers/ring_queue.c
new file mode 100644
index 0000000..a9d3506
--- /dev/null
+++ b/src/std/containers/ring_queue.c
@@ -0,0 +1,66 @@
+#include "ring_queue.h"
+#include <stdlib.h>
+#include "defines.h"
+
+ring_queue* ring_queue_new(size_t type_size, size_t capacity, void* memory) {
+ ring_queue* q = malloc(sizeof(ring_queue));
+ q->len = 0;
+ q->capacity = capacity;
+ q->type_size = type_size;
+ q->head = 0;
+ q->tail = -1;
+
+ if (memory) {
+ // caller owns the memory
+ q->owns_memory = false;
+ q->data = memory;
+ } else {
+ // ring queue should own the memory
+ q->owns_memory = true;
+ q->data = malloc(capacity * type_size);
+ }
+
+ return q;
+}
+
+void ring_queue_free(ring_queue* queue) {
+ if (queue) {
+ if (queue->owns_memory) {
+ free(queue->data);
+ }
+ free(queue);
+ }
+}
+
+bool ring_queue_enqueue(ring_queue* queue, const void* value) {
+ if (queue->len == queue->capacity) {
+ return false;
+ }
+
+ queue->tail = (queue->tail + 1) % queue->capacity;
+ memcpy(queue->data + (queue->tail * queue->type_size), value, queue->type_size);
+ queue->len++;
+ return true;
+}
+
+bool ring_queue_dequeue(ring_queue* queue, void* out_value) {
+ if (queue->len == 0) {
+ // queue is empty
+ return false;
+ }
+
+ memcpy(out_value, queue->data + (queue->head * queue->type_size), queue->type_size);
+ queue->head = (queue->head + 1) % queue->capacity;
+ queue->len--;
+ return true;
+}
+
+bool ring_queue_peek(const ring_queue* queue, void* out_value) {
+ if (queue->len == 0) {
+ // queue is empty
+ return false;
+ }
+
+ memcpy(out_value, queue->data + (queue->head * queue->type_size), queue->type_size);
+ return true;
+} \ No newline at end of file
diff --git a/src/std/containers/ring_queue.h b/src/std/containers/ring_queue.h
new file mode 100644
index 0000000..15d5da4
--- /dev/null
+++ b/src/std/containers/ring_queue.h
@@ -0,0 +1,35 @@
+/**
+ * @file ring_queue.h
+ * @author your name (you@domain.com)
+ * @brief
+ * @version 0.1
+ * @date 2024-02-24
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
+#pragma once
+#include "defines.h"
+
+/**
+ * @brief a fixed-size ring queue
+ */
+typedef struct ring_queue {
+ size_t len;
+ size_t capacity;
+ size_t type_size;
+ void* data;
+ bool owns_memory;
+ int32_t head;
+ int32_t tail;
+} ring_queue;
+
+ring_queue* ring_queue_new(size_t type_size, size_t capacity, void* memory_block);
+
+void ring_queue_free(ring_queue* queue);
+
+bool ring_queue_enqueue(ring_queue* queue, const void* value);
+
+bool ring_queue_dequeue(ring_queue* queue, void* out_value);
+
+bool ring_queue_peek(const ring_queue* queue, void* out_value); \ No newline at end of file
diff --git a/src/std/mem.h b/src/std/mem.h
new file mode 100644
index 0000000..74222a7
--- /dev/null
+++ b/src/std/mem.h
@@ -0,0 +1,14 @@
+/**
+ * @file mem.h
+ * @brief Allocators, memory tracking
+ * @version 0.1
+ * @date 2024-02-24
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
+#pragma once
+
+#include "defines.h"
+
+typedef void* (*alloc)(size_t amount); \ No newline at end of file
diff --git a/src/std/str.c b/src/std/str.c
new file mode 100644
index 0000000..27f8f68
--- /dev/null
+++ b/src/std/str.c
@@ -0,0 +1,14 @@
+#include "str.h"
+
+bool str8_equals(str8 a, str8 b) {
+ if (a.len != b.len) {
+ return false;
+ }
+
+ for (size_t i = 0; i < a.len; i++) {
+ if (a.buf[i] != b.buf[i]) {
+ return false;
+ }
+ }
+ return true;
+} \ No newline at end of file
diff --git a/src/std/str.h b/src/std/str.h
new file mode 100644
index 0000000..3d3cb41
--- /dev/null
+++ b/src/std/str.h
@@ -0,0 +1,30 @@
+/**
+ * @brief
+ *
+ */
+#pragma once
+
+#include "defines.h"
+
+/**
+ * @brief Fat pointer representing a UTF8 (TODO) encoded string
+ * @note when using `printf` you must use %s.*s length, string
+ */
+typedef struct {
+ u8 *buf;
+ size_t len;
+} str8;
+
+/** @brief Compare two strings for exact equality */
+bool str8_equals(str8 a, str8 b);
+
+/**
+ * @brief Compare the first `first_nchars` of each string for equality
+ If either of the strings are shorter than the number only the characters up until the end
+ of the shorter string will be compared.
+ * @returns 0 if they are fully equal up until `first_nchars`, i.e they never differed, else it
+ returns the index at which the first string differed from the second string.
+*/
+size_t str8_nequals(str8 a, str8 b, size_t first_nchars);
+
+bool str8_ends_with(str8 input_str, str8 suffix); \ No newline at end of file
diff --git a/src/systems/input.c b/src/systems/input.c
new file mode 100644
index 0000000..3b7ab9e
--- /dev/null
+++ b/src/systems/input.c
@@ -0,0 +1,77 @@
+#include "input.h"
+
+#include <GLFW/glfw3.h>
+
+#include "log.h"
+
+bool input_system_init(input_state *input, GLFWwindow *window) {
+ INFO("Input init");
+ input->window = window;
+ // Set everything to false. Could just set memory to zero but where's the fun in that
+ for (int i = 0; i < KEYCODE_MAX; i++) {
+ input->depressed_keys[i] = false;
+ input->just_pressed_keys[i] = false;
+ input->just_released_keys[i] = false;
+ }
+
+ return true;
+}
+
+void input_update(input_state *input) {
+ // --- update keyboard input
+
+ // if we go from un-pressed -> pressed, set as "just pressed"
+ // if we go from pressed -> un-pressed, set as "just released"
+ for (int i = 0; i < KEYCODE_MAX; i++) {
+ bool new_state = false;
+ if (glfwGetKey(input->window, i) == GLFW_PRESS) {
+ new_state = true;
+ } else {
+ new_state = false;
+ }
+ if (!input->depressed_keys[i] == false && new_state) {
+ input->just_pressed_keys[i] = true;
+ } else {
+ input->just_pressed_keys[i] = false;
+ }
+
+ if (input->depressed_keys[i] && !new_state) {
+ input->just_released_keys[i] = true;
+ } else {
+ input->just_released_keys[i] = false;
+ }
+
+ input->depressed_keys[i] = new_state;
+ }
+
+ // --- update mouse input
+
+ // cursor position
+ f64 current_x, current_y;
+ glfwGetCursorPos(input->window, &current_x, &current_y);
+ i32 quantised_cur_x = (i32)current_x;
+ i32 quantised_cur_y = (i32)current_y;
+
+ mouse_state new_mouse_state = { 0 };
+ new_mouse_state.x = quantised_cur_x;
+ new_mouse_state.y = quantised_cur_y;
+ new_mouse_state.x_delta = quantised_cur_x - input->mouse.x;
+ new_mouse_state.y_delta = quantised_cur_y - input->mouse.y;
+
+ // buttons
+ int left_state = glfwGetMouseButton(input->window, GLFW_MOUSE_BUTTON_LEFT);
+ // int right_state = glfwGetMouseButton(input->window, GLFW_MOUSE_BUTTON_RIGHT);
+
+ new_mouse_state.prev_left_btn_pressed = input->mouse.left_btn_pressed;
+ if (left_state == GLFW_PRESS) {
+ new_mouse_state.left_btn_pressed = true;
+ } else {
+ new_mouse_state.left_btn_pressed = false;
+ }
+
+ // this was dumb! need to also check button state changes lol
+ // if (new_mouse_state.x != input->mouse.x || new_mouse_state.y != input->mouse.y)
+ // TRACE("Mouse (x,y) = (%d,%d)", input->mouse.x, input->mouse.y);
+
+ input->mouse = new_mouse_state;
+}
diff --git a/src/systems/input.h b/src/systems/input.h
new file mode 100644
index 0000000..c119016
--- /dev/null
+++ b/src/systems/input.h
@@ -0,0 +1,41 @@
+/**
+ * @brief
+ */
+#pragma once
+
+#include "defines.h"
+#include "keys.h"
+
+struct core;
+struct GLFWWindow;
+
+typedef struct mouse_state {
+ i32 x;
+ i32 y;
+ i32 x_delta;
+ i32 y_delta;
+ bool prev_left_btn_pressed;
+ bool left_btn_pressed;
+} mouse_state;
+
+typedef struct input_state {
+ struct GLFWwindow *window;
+ mouse_state mouse;
+ bool depressed_keys[KEYCODE_MAX];
+ bool just_pressed_keys[KEYCODE_MAX];
+ bool just_released_keys[KEYCODE_MAX];
+} input_state;
+
+/** @brief `key` is currently being held down */
+bool key_is_pressed(keycode key);
+
+/** @brief `key` was just pressed */
+bool key_just_pressed(keycode key);
+
+/** @brief `key` was just released */
+bool key_just_released(keycode key);
+
+// --- Lifecycle
+bool input_system_init(input_state *input, struct GLFWwindow *window);
+void input_system_shutdown(input_state *input);
+void input_update(input_state *state); \ No newline at end of file
diff --git a/src/systems/keys.h b/src/systems/keys.h
new file mode 100644
index 0000000..090bb49
--- /dev/null
+++ b/src/systems/keys.h
@@ -0,0 +1,6 @@
+#pragma once
+
+typedef enum keycode {
+ // TODO: add all keycodes
+ KEYCODE_MAX
+} keycode; \ No newline at end of file
diff --git a/src/systems/screenspace.h b/src/systems/screenspace.h
new file mode 100644
index 0000000..d36404a
--- /dev/null
+++ b/src/systems/screenspace.h
@@ -0,0 +1,53 @@
+/**
+ * @brief Drawing shapes for UI or other reasons in screenspace
+ */
+#pragma once
+
+#include "colours.h"
+#include "darray.h"
+#include "defines.h"
+#include "render_types.h"
+
+/** A draw_cmd packet for rendering a rectangle */
+struct draw_rect {
+ i32 x, y; // signed ints so we can draw things offscreen (e.g. a window half inside the viewport)
+ u32 width, height;
+ rgba colour;
+ // TODO: border colour, gradients
+};
+
+/** A draw_cmd packet for rendering a circle */
+struct draw_circle {
+ i32 x, y;
+ f32 radius;
+ rgba colour;
+};
+
+/** @brief Tagged union that represents a UI shape to be drawn. */
+typedef struct draw_cmd {
+ enum { RECT, CIRCLE } draw_cmd_type;
+ union {
+ struct draw_rect rect;
+ struct draw_circle circle;
+ };
+} draw_cmd;
+
+KITC_DECL_TYPED_ARRAY(draw_cmd)
+
+typedef struct screenspace_state {
+ u32 rect_vbo;
+ u32 rect_vao;
+ // shader rect_shader;
+ draw_cmd_darray* draw_cmd_buf;
+} screenspace_state;
+
+// --- Lifecycle
+bool screenspace_2d_init(screenspace_state* state);
+void screenspace_2d_shutdown(screenspace_state* state);
+/** Drains the draw_cmd buffer and emits draw calls to render each one */
+void screenspace_2d_render(screenspace_state* state);
+
+struct core;
+
+/** @brief Draw a rectangle to the screen. (0,0) is the bottom-left */
+void draw_rectangle(struct core* core, rgba colour, i32 x, i32 y, u32 width, u32 height); \ No newline at end of file
diff --git a/src/systems/text.c b/src/systems/text.c
new file mode 100644
index 0000000..2bb5399
--- /dev/null
+++ b/src/systems/text.c
@@ -0,0 +1 @@
+// TODO: Port from previous repo \ No newline at end of file
diff --git a/src/systems/text.h b/src/systems/text.h
new file mode 100644
index 0000000..3c580df
--- /dev/null
+++ b/src/systems/text.h
@@ -0,0 +1,58 @@
+/**
+ * @brief
+ */
+#pragma once
+
+#include <stb_truetype.h>
+
+#include "darray.h"
+#include "defines.h"
+#include "render_types.h"
+
+struct core;
+typedef struct texture_handle {
+ u32 raw
+} texture_handle;
+typedef struct shader {
+ u32 raw
+} shader;
+
+/** @brief internal font struct */
+typedef struct font {
+ const char *name;
+ stbtt_fontinfo stbtt_font;
+ stbtt_bakedchar c_data[96];
+ texture_handle bitmap_tex;
+} font;
+
+typedef struct draw_text_packet {
+ char *contents;
+ f32 x;
+ f32 y;
+} draw_text_packet;
+
+KITC_DECL_TYPED_ARRAY(draw_text_packet)
+
+typedef struct text_system_state {
+ font default_font;
+ shader glyph_shader;
+ u32 glyph_vbo;
+ u32 glyph_vao;
+ draw_text_packet_darray *draw_cmd_buf;
+ // TODO: fonts array or hashtable
+} text_system_state;
+
+void text_system_render(text_system_state *text);
+
+// --- Lifecycle functions
+bool text_system_init(text_system_state *text);
+void text_system_shutdown(text_system_state *text);
+
+// --- Drawing
+
+/**
+ * @brief immediate mode draw text.
+ * @note immediately emits draw calls causing a shader program switch if you weren't previously
+ drawing text in the current frame.
+*/
+void draw_text(struct core *core, f32 x, f32 y, char *contents); \ No newline at end of file