diff options
Diffstat (limited to 'src')
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, ¤t_x, ¤t_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 |