summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/animation.c38
-rw-r--r--src/animation.h72
-rw-r--r--src/camera.c14
-rw-r--r--src/camera.h3
-rw-r--r--src/colours.h50
-rw-r--r--src/core.c20
-rw-r--r--src/core.h8
-rw-r--r--src/defines.h6
-rw-r--r--src/empty.c3
-rw-r--r--src/logos/jobs.h3
-rw-r--r--src/maths/maths.h157
-rw-r--r--src/maths/maths_types.h10
-rw-r--r--src/maths/primitives.h163
-rw-r--r--src/platform/file.c30
-rw-r--r--src/platform/file.h9
-rw-r--r--src/platform/path.c20
-rw-r--r--src/platform/path.h16
-rw-r--r--src/renderer/backends/backend_opengl.c113
-rw-r--r--src/renderer/backends/backend_vulkan.c1984
-rw-r--r--src/renderer/backends/vulkan/vulkan_glossary.md18
-rw-r--r--src/renderer/backends/vulkan_helpers.h166
-rw-r--r--src/renderer/cleanroom/README.md1
-rw-r--r--src/renderer/cleanroom/backend_vulkan.c65
-rw-r--r--src/renderer/cleanroom/backend_vulkan.h27
-rw-r--r--src/renderer/cleanroom/ral.h86
-rw-r--r--src/renderer/cleanroom/renderer.c4
-rw-r--r--src/renderer/cleanroom/renderer.h14
-rw-r--r--src/renderer/cleanroom/simda.h18
-rw-r--r--src/renderer/cleanroom/types.h182
-rw-r--r--src/renderer/render.c373
-rw-r--r--src/renderer/render.h32
-rw-r--r--src/renderer/render_backend.h26
-rw-r--r--src/renderer/render_types.h216
-rw-r--r--src/resources/gltf.c765
-rw-r--r--src/resources/loaders.h4
-rw-r--r--src/resources/obj.c390
-rw-r--r--src/std/containers/darray.h57
-rw-r--r--src/std/containers/ring_queue.c2
-rw-r--r--src/std/mem.c10
-rw-r--r--src/std/mem.h1
-rw-r--r--src/std/str.c26
-rw-r--r--src/std/str.h14
-rw-r--r--src/systems/input.c19
-rw-r--r--src/systems/keys.h15
-rw-r--r--src/systems/physics.c1
-rw-r--r--src/systems/physics.h33
-rw-r--r--src/systems/screenspace.h2
-rw-r--r--src/systems/text.h4
-rw-r--r--src/transform_hierarchy.c184
-rw-r--r--src/transform_hierarchy.h77
50 files changed, 5433 insertions, 118 deletions
diff --git a/src/animation.c b/src/animation.c
new file mode 100644
index 0000000..7a79529
--- /dev/null
+++ b/src/animation.c
@@ -0,0 +1,38 @@
+#include "animation.h"
+#include "log.h"
+#include "maths.h"
+
+keyframe animation_sample(animation_sampler *sampler, f32 t) {
+ size_t previous_index = 0;
+ f32 previous_time = 0.0;
+ // look forwards
+ // DEBUG("%d\n", sampler->animation.values.kind);
+ TRACE("Total timestamps %d", sampler->animation.n_timestamps);
+ for (u32 i = 0; i < sampler->animation.n_timestamps; i++) {
+ f32 current_time = sampler->animation.timestamps[i];
+ if (current_time > t) {
+ break;
+ }
+ previous_time = sampler->animation.timestamps[i];
+ previous_index = i;
+ }
+
+ size_t next_index = (previous_index + 1) % sampler->animation.n_timestamps;
+ f32 next_time = sampler->animation.timestamps[next_index];
+ printf("%d %f %d %f\n", previous_index, previous_time, next_index, next_time);
+
+ keyframe prev_value = sampler->animation.values.values[previous_index];
+ keyframe next_value = sampler->animation.values.values[next_index];
+
+ printf("%d %d\n", previous_index, next_index);
+
+ f32 time_diff =
+ sampler->animation.timestamps[next_index] - sampler->animation.timestamps[previous_index];
+ f32 percent = (t - previous_time) / time_diff;
+
+ quat interpolated_rot =
+ quat_slerp(sampler->animation.values.values[previous_index].rotation,
+ sampler->animation.values.values[next_index].rotation, percent);
+
+ return (keyframe){ .rotation = interpolated_rot };
+} \ No newline at end of file
diff --git a/src/animation.h b/src/animation.h
new file mode 100644
index 0000000..5462e65
--- /dev/null
+++ b/src/animation.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include "darray.h"
+#include "defines.h"
+#include "maths_types.h"
+
+KITC_DECL_TYPED_ARRAY(f32)
+
+typedef enum interpolation { INTERPOLATION_LINEAR, INTERPOLATION_COUNT } interpolation;
+
+typedef enum keyframe_kind {
+ KEYFRAME_ROTATION,
+ KEYFRAME_TRANSLATION,
+ KEYFRAME_SCALE,
+ KEYFRAME_WEIGHTS,
+} keyframe_kind;
+
+typedef union keyframe {
+ quat rotation;
+ vec3 translation;
+ vec3 scale;
+ float* weights;
+} keyframe;
+
+typedef struct keyframes {
+ keyframe_kind kind;
+ keyframe* values;
+ size_t count;
+} keyframes;
+
+typedef struct joint {
+ char* name; // optional
+ transform transform_components;
+ mat4 inverse_bind_matrix;
+ mat4 local_transform;
+} joint;
+
+typedef struct animation_spline {
+ f32* timestamps;
+ size_t n_timestamps;
+ keyframes values;
+ interpolation interpolation;
+} animation_spline;
+
+typedef struct animation_sampler {
+ int current_index;
+ f32 min;
+ f32 max;
+ animation_spline animation;
+} animation_sampler;
+
+/** @brief Sample an animation at a given time `t` */
+keyframe animation_sample(animation_sampler* sampler, f32 t);
+
+typedef struct animation_clip {
+ // A clip contains one or more animation curves
+ // for now I think we can just enumerate all of the properties (assuming *only* one per type is in
+ // a clip) NULL = this property is not animated in this clip
+ animation_sampler* rotation;
+ animation_sampler* translation;
+ animation_sampler* scale;
+ animation_sampler* weights;
+} animation_clip;
+
+typedef struct skinned_animation {
+ mat4* joint_matrices;
+ size_t n_joints;
+} skinned_animation;
+
+// void animation_update_joint_matrices(animation_clip* )
+
+void animation_play(animation_clip* clip); \ No newline at end of file
diff --git a/src/camera.c b/src/camera.c
index c2b864d..8ec1251 100644
--- a/src/camera.c
+++ b/src/camera.c
@@ -2,10 +2,16 @@
#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);
+camera camera_create(vec3 pos, vec3 front, vec3 up, f32 fov) {
+ camera c = { .position = pos, .front = front, .up = up, .fov = fov };
+ return c;
+}
+
+void camera_view_projection(camera *c, f32 screen_height, f32 screen_width, mat4 *out_view,
+ mat4 *out_proj) {
+ mat4 proj = mat4_perspective(c->fov, 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;
+ *out_view = view;
+ *out_proj = proj;
} \ No newline at end of file
diff --git a/src/camera.h b/src/camera.h
index 226f80e..f7bc6eb 100644
--- a/src/camera.h
+++ b/src/camera.h
@@ -23,4 +23,5 @@ typedef struct 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
+void camera_view_projection(camera *c, f32 screen_height, f32 screen_width, mat4 *out_view,
+ mat4 *out_proj); \ No newline at end of file
diff --git a/src/colours.h b/src/colours.h
index bbd9476..a981c6c 100644
--- a/src/colours.h
+++ b/src/colours.h
@@ -12,6 +12,8 @@ typedef struct rgba {
#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 })
+#define rgba_to_vec4(color) (vec4(color.r, color.g, color.b, color.a))
+
// 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 })
@@ -36,3 +38,51 @@ typedef struct rgba {
#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 })
+
+#define GRAY_50 ((rgba){ 0.976, 0.980, 0.984, 1.0 })
+#define GRAY_100 ((rgba){ 0.953, 0.957, 0.965, 1.0 })
+#define GRAY_200 ((rgba){ 0.898, 0.906, 0.922, 1.0 })
+#define GRAY_300 ((rgba){ 0.820, 0.835, 0.859, 1.0 })
+#define GRAY_400 ((rgba){ 0.612, 0.639, 0.686, 1.0 })
+#define GRAY_500 ((rgba){ 0.420, 0.447, 0.502, 1.0 })
+#define GRAY_600 ((rgba){ 0.294, 0.333, 0.388, 1.0 })
+#define GRAY_700 ((rgba){ 0.216, 0.255, 0.318, 1.0 })
+#define GRAY_800 ((rgba){ 0.122, 0.161, 0.216, 1.0 })
+#define GRAY_900 ((rgba){ 0.067, 0.094, 0.153, 1.0 })
+#define GRAY_950 ((rgba){ 0.012, 0.027, 0.071, 1.0 })
+
+#define RED_50 ((rgba){ 0.996, 0.949, 0.949, 1.0 })
+#define RED_100 ((rgba){ 0.996, 0.886, 0.886, 1.0 })
+#define RED_200 ((rgba){ 0.996, 0.792, 0.792, 1.0 })
+#define RED_300 ((rgba){ 0.988, 0.647, 0.647, 1.0 })
+#define RED_400 ((rgba){ 0.973, 0.443, 0.443, 1.0 })
+#define RED_500 ((rgba){ 0.937, 0.267, 0.267, 1.0 })
+#define RED_600 ((rgba){ 0.863, 0.149, 0.149, 1.0 })
+#define RED_700 ((rgba){ 0.725, 0.110, 0.110, 1.0 })
+#define RED_800 ((rgba){ 0.600, 0.106, 0.106, 1.0 })
+#define RED_900 ((rgba){ 0.498, 0.114, 0.114, 1.0 })
+#define RED_950 ((rgba){ 0.271, 0.039, 0.039, 1.0 })
+
+#define ORANGE_50 ((rgba){ 1.000, 0.969, 0.929, 1.0 })
+#define ORANGE_100 ((rgba){ 1.000, 0.929, 0.835, 1.0 })
+#define ORANGE_200 ((rgba){ 0.996, 0.843, 0.667, 1.0 })
+#define ORANGE_300 ((rgba){ 0.992, 0.729, 0.455, 1.0 })
+#define ORANGE_400 ((rgba){ 0.984, 0.573, 0.235, 1.0 })
+#define ORANGE_500 ((rgba){ 0.976, 0.451, 0.086, 1.0 })
+#define ORANGE_600 ((rgba){ 0.918, 0.345, 0.047, 1.0 })
+#define ORANGE_700 ((rgba){ 0.761, 0.255, 0.047, 1.0 })
+#define ORANGE_800 ((rgba){ 0.604, 0.204, 0.071, 1.0 })
+#define ORANGE_900 ((rgba){ 0.486, 0.176, 0.071, 1.0 })
+#define ORANGE_950 ((rgba){ 0.263, 0.078, 0.027, 1.0 })
+
+#define AMBER_50 ((rgba){ 1.000, 0.984, 0.922, 1.0 })
+#define AMBER_100 ((rgba){ 0.996, 0.953, 0.780, 1.0 })
+#define AMBER_200 ((rgba){ 0.992, 0.902, 0.541, 1.0 })
+#define AMBER_300 ((rgba){ 0.988, 0.827, 0.302, 1.0 })
+#define AMBER_400 ((rgba){ 0.984, 0.749, 0.141, 1.0 })
+#define AMBER_500 ((rgba){ 0.961, 0.620, 0.043, 1.0 })
+#define AMBER_600 ((rgba){ 0.851, 0.467, 0.024, 1.0 })
+#define AMBER_700 ((rgba){ 0.706, 0.325, 0.035, 1.0 })
+#define AMBER_800 ((rgba){ 0.573, 0.251, 0.055, 1.0 })
+#define AMBER_900 ((rgba){ 0.471, 0.208, 0.059, 1.0 })
+#define AMBER_950 ((rgba){ 0.271, 0.102, 0.012, 1.0 })
diff --git a/src/core.c b/src/core.c
index affd8c8..0db8962 100644
--- a/src/core.c
+++ b/src/core.c
@@ -2,13 +2,16 @@
#include <stdlib.h>
+#include "glfw3.h"
+#include "input.h"
+#include "keys.h"
#include "log.h"
#include "render.h"
#include "render_types.h"
#include "threadpool.h"
-#define SCR_WIDTH 1080
-#define SCR_HEIGHT 800
+#define SCR_WIDTH 1000
+#define SCR_HEIGHT 1000
core* core_bringup() {
INFO("Initiate Core bringup");
@@ -45,8 +48,17 @@ core* core_bringup() {
}
*/
- // c->underworld.models = model_darray_new(10);
- // c->underworld.renderables = render_entity_darray_new(10);
+ c->models = model_darray_new(10);
return c;
+}
+
+void core_shutdown(core* core) {
+ // threadpool_destroy(&core->threadpool);
+ input_system_shutdown(&core->input);
+ renderer_shutdown(&core->renderer);
+}
+
+bool should_exit(core* core) {
+ return key_just_released(KEYCODE_ESCAPE) || glfwWindowShouldClose(core->renderer.window);
} \ No newline at end of file
diff --git a/src/core.h b/src/core.h
index 8a3d037..68bf957 100644
--- a/src/core.h
+++ b/src/core.h
@@ -2,21 +2,25 @@
#include "defines.h"
#include "input.h"
-#include "render_types.h"
+#include "ral.h"
+// #include "render_types.h"
#include "screenspace.h"
#include "text.h"
#include "threadpool.h"
typedef struct core {
+ // TODO: Add application name
renderer renderer;
threadpool threadpool;
input_state input;
text_system_state text;
screenspace_state screenspace;
+ model_darray* models;
} core;
// --- Lifecycle
core* core_bringup();
void core_shutdown(core* core);
+bool should_exit(core* core);
-void core_input_update(core* core); \ No newline at end of file
+void core_input_update(core* core);
diff --git a/src/defines.h b/src/defines.h
index 52aa7b0..8cd4f98 100644
--- a/src/defines.h
+++ b/src/defines.h
@@ -1,6 +1,6 @@
/**
* @file defines.h
- * @brief
+ * @brief Typedefs for common integer/floating point types and very basic macros
* @date 2024-02-24
* @copyright Copyright (c) 2024
*/
@@ -71,6 +71,6 @@ Renderer backend defines:
#endif
#if defined(CEL_PLATFORM_MAC)
-#define CEL_REND_BACKEND_METAL 1
-// #define CEL_REND_BACKEND_OPENGL 1
+// #define CEL_REND_BACKEND_METAL 1
+#define CEL_REND_BACKEND_OPENGL 1
#endif \ No newline at end of file
diff --git a/src/empty.c b/src/empty.c
new file mode 100644
index 0000000..b40cc85
--- /dev/null
+++ b/src/empty.c
@@ -0,0 +1,3 @@
+// For some reason on Mac we need an empty file so that 'ar' has something
+// to run.
+int add(int a, int b) { return a + b; } \ No newline at end of file
diff --git a/src/logos/jobs.h b/src/logos/jobs.h
new file mode 100644
index 0000000..cc2c8fa
--- /dev/null
+++ b/src/logos/jobs.h
@@ -0,0 +1,3 @@
+/**
+ * Common jobs that get run
+*/ \ No newline at end of file
diff --git a/src/maths/maths.h b/src/maths/maths.h
index 7352aeb..e0d39d7 100644
--- a/src/maths/maths.h
+++ b/src/maths/maths.h
@@ -9,12 +9,19 @@
#pragma once
#include <math.h>
+#include <stdio.h>
#include "maths_types.h"
+// --- 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)
+
// --- Vector Implementations
// Dimension 3
static inline vec3 vec3_create(f32 x, f32 y, f32 z) { return (vec3){ x, y, z }; }
+#define vec3(x, y, z) (vec3_create(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 }; }
@@ -42,12 +49,86 @@ static inline vec3 vec3_cross(vec3 a, vec3 b) {
#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 })
+static inline void print_vec3(vec3 v) { printf("{ x: %f, y: %f, z: %f )\n", v.x, v.y, v.z); }
+
// TODO: Dimension 2
static inline vec2 vec2_create(f32 x, f32 y) { return (vec2){ x, y }; }
// TODO: Dimension 4
+static inline vec4 vec4_create(f32 x, f32 y, f32 z, f32 w) { return (vec4){ x, y, z, w }; }
+#define vec4(x, y, z, w) (vec4_create(x, y, z, w))
#define VEC4_ZERO ((vec4){ .x = 0.0, .y = 0.0, .z = 0.0, .w = 0.0 })
+// --- Quaternion Implementations
+
+static inline f32 quat_dot(quat a, quat b) { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; }
+
+static inline quat quat_normalise(quat a) {
+ f32 length = sqrtf(quat_dot(a, a)); // same as len squared
+
+ return (quat){ a.x / length, a.y / length, a.z / length, a.w / length };
+}
+
+static inline quat quat_ident() { return (quat){ .x = 0.0, .y = 0.0, .z = 0.0, .w = 1.0 }; }
+
+static quat quat_from_axis_angle(vec3 axis, f32 angle, bool normalize) {
+ const f32 half_angle = 0.5f * angle;
+ f32 s = sinf(half_angle);
+ f32 c = cosf(half_angle);
+
+ quat q = (quat){ s * axis.x, s * axis.y, s * axis.z, c };
+ if (normalize) {
+ return quat_normalise(q);
+ }
+ return q;
+}
+
+// TODO: grok this.
+static inline quat quat_slerp(quat a, quat b, f32 percentage) {
+ quat out_quaternion;
+
+ quat q0 = quat_normalise(a);
+ quat q1 = quat_normalise(b);
+
+ // Compute the cosine of the angle between the two vectors.
+ f32 dot = quat_dot(q0, q1);
+
+ // If the dot product is negative, slerp won't take
+ // the shorter path. Note that v1 and -v1 are equivalent when
+ // the negation is applied to all four components. Fix by
+ // reversing one quaternion.
+ if (dot < 0.0f) {
+ q1.x = -q1.x;
+ q1.y = -q1.y;
+ q1.z = -q1.z;
+ q1.w = -q1.w;
+ dot = -dot;
+ }
+
+ const f32 DOT_THRESHOLD = 0.9995f;
+ if (dot > DOT_THRESHOLD) {
+ // If the inputs are too close for comfort, linearly interpolate
+ // and normalize the result.
+ out_quaternion =
+ (quat){ q0.x + ((q1.x - q0.x) * percentage), q0.y + ((q1.y - q0.y) * percentage),
+ q0.z + ((q1.z - q0.z) * percentage), q0.w + ((q1.w - q0.w) * percentage) };
+
+ return quat_normalise(out_quaternion);
+ }
+
+ // Since dot is in range [0, DOT_THRESHOLD], acos is safe
+ f32 theta_0 = cos(dot); // theta_0 = angle between input vectors
+ f32 theta = theta_0 * percentage; // theta = angle between v0 and result
+ f32 sin_theta = sin(theta); // compute this value only once
+ f32 sin_theta_0 = sin(theta_0); // compute this value only once
+
+ f32 s0 = cos(theta) - dot * sin_theta / sin_theta_0; // == sin(theta_0 - theta) / sin(theta_0)
+ f32 s1 = sin_theta / sin_theta_0;
+
+ return (quat){ (q0.x * s0) + (q1.x * s1), (q0.y * s0) + (q1.y * s1), (q0.z * s0) + (q1.z * s1),
+ (q0.w * s0) + (q1.w * s1) };
+}
+
// --- Matrix Implementations
static inline mat4 mat4_ident() {
@@ -70,6 +151,26 @@ static inline mat4 mat4_scale(f32 scale) {
return out_matrix;
}
+// TODO: double check this
+static inline mat4 mat4_rotation(quat rotation) {
+ mat4 out_matrix = mat4_ident();
+ quat n = quat_normalise(rotation);
+
+ out_matrix.data[0] = 1.0f - 2.0f * n.y * n.y - 2.0f * n.z * n.z;
+ out_matrix.data[1] = 2.0f * n.x * n.y - 2.0f * n.z * n.w;
+ out_matrix.data[2] = 2.0f * n.x * n.z + 2.0f * n.y * n.w;
+
+ out_matrix.data[4] = 2.0f * n.x * n.y + 2.0f * n.z * n.w;
+ out_matrix.data[5] = 1.0f - 2.0f * n.x * n.x - 2.0f * n.z * n.z;
+ out_matrix.data[6] = 2.0f * n.y * n.z - 2.0f * n.x * n.w;
+
+ out_matrix.data[8] = 2.0f * n.x * n.z - 2.0f * n.y * n.w;
+ out_matrix.data[9] = 2.0f * n.y * n.z + 2.0f * n.x * n.w;
+ out_matrix.data[10] = 1.0f - 2.0f * n.x * n.x - 2.0f * n.y * n.y;
+
+ return out_matrix;
+}
+
static inline mat4 mat4_mult(mat4 lhs, mat4 rhs) {
mat4 out_matrix = mat4_ident();
@@ -89,6 +190,43 @@ static inline mat4 mat4_mult(mat4 lhs, mat4 rhs) {
return out_matrix;
}
+static mat4 mat4_transposed(mat4 matrix) {
+ mat4 out_matrix = mat4_ident();
+ out_matrix.data[0] = matrix.data[0];
+ out_matrix.data[1] = matrix.data[4];
+ out_matrix.data[2] = matrix.data[8];
+ out_matrix.data[3] = matrix.data[12];
+ out_matrix.data[4] = matrix.data[1];
+ out_matrix.data[5] = matrix.data[5];
+ out_matrix.data[6] = matrix.data[9];
+ out_matrix.data[7] = matrix.data[13];
+ out_matrix.data[8] = matrix.data[2];
+ out_matrix.data[9] = matrix.data[6];
+ out_matrix.data[10] = matrix.data[10];
+ out_matrix.data[11] = matrix.data[14];
+ out_matrix.data[12] = matrix.data[3];
+ out_matrix.data[13] = matrix.data[7];
+ out_matrix.data[14] = matrix.data[11];
+ out_matrix.data[15] = matrix.data[15];
+ return out_matrix;
+}
+
+#if defined(CEL_REND_BACKEND_VULKAN)
+/** @brief Creates a perspective projection matrix compatible with Vulkan */
+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; // Flip Y-axis for Vulkan
+ 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;
+}
+#else
/** @brief Creates a perspective projection matrix */
static inline mat4 mat4_perspective(f32 fov_radians, f32 aspect_ratio, f32 near_clip,
f32 far_clip) {
@@ -101,6 +239,7 @@ static inline mat4 mat4_perspective(f32 fov_radians, f32 aspect_ratio, f32 near_
out_matrix.data[14] = -((2.0f * far_clip * near_clip) / (far_clip - near_clip));
return out_matrix;
}
+#endif
/** @brief Creates an orthographic projection matrix */
static inline mat4 mat4_orthographic(f32 left, f32 right, f32 bottom, f32 top, f32 near_clip,
@@ -156,23 +295,23 @@ static inline mat4 mat4_look_at(vec3 position, vec3 target, vec3 up) {
// ...
-// --- Quaternion Implementations
-
// --- Transform Implementations
#define TRANSFORM_DEFAULT \
((transform){ .position = VEC3_ZERO, \
- .rotation = (quat){ .x = 0., .y = 0., .z = 0., .w = 0. }, \
+ .rotation = (quat){ .x = 0., .y = 0., .z = 0., .w = 1. }, \
.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 };
+ return (transform){ .position = pos, .rotation = rot, .scale = scale, .is_dirty = true };
}
static inline mat4 transform_to_mat(transform *tf) {
- // TODO: rotation
- return mat4_mult(mat4_translation(tf->position), mat4_scale(tf->scale));
+ mat4 scale = mat4_scale(tf->scale);
+ mat4 rotation = mat4_rotation(tf->rotation);
+ mat4 translation = mat4_translation(tf->position);
+ return mat4_mult(translation, mat4_mult(rotation, scale));
}
// --- Sizing asserts
@@ -198,3 +337,9 @@ typedef struct u32x3 {
};
} u32x3;
#define u32x3(x, y, z) ((u32x3){ x, y, z })
+
+typedef struct u32x2 {
+ u32 x;
+ u32 y;
+} u32x2;
+#define u32x2(x, y) ((u32x3){ x, y }) \ No newline at end of file
diff --git a/src/maths/maths_types.h b/src/maths/maths_types.h
index ba741b9..53cac55 100644
--- a/src/maths/maths_types.h
+++ b/src/maths/maths_types.h
@@ -60,4 +60,12 @@ typedef struct transform {
quat rotation;
f32 scale;
bool is_dirty;
-} transform; \ No newline at end of file
+} transform;
+
+typedef struct vec4i {
+ i32 x, y, z, w;
+} vec4i;
+
+typedef struct vec4u {
+ u32 x, y, z, w;
+} vec4u; \ No newline at end of file
diff --git a/src/maths/primitives.h b/src/maths/primitives.h
new file mode 100644
index 0000000..ed52c8c
--- /dev/null
+++ b/src/maths/primitives.h
@@ -0,0 +1,163 @@
+#pragma once
+
+#include <assert.h>
+#include <stdlib.h>
+#include "core.h"
+#include "maths.h"
+#include "render_types.h"
+
+static const vec3 BACK_BOT_LEFT = (vec3){ 0, 0, 0 };
+static const vec3 BACK_BOT_RIGHT = (vec3){ 1, 0, 0 };
+static const vec3 BACK_TOP_LEFT = (vec3){ 0, 1, 0 };
+static const vec3 BACK_TOP_RIGHT = (vec3){ 1, 1, 0 };
+static const vec3 FRONT_BOT_LEFT = (vec3){ 0, 0, 1 };
+static const vec3 FRONT_BOT_RIGHT = (vec3){ 1, 0, 1 };
+static const vec3 FRONT_TOP_LEFT = (vec3){ 0, 1, 1 };
+static const vec3 FRONT_TOP_RIGHT = (vec3){ 1, 1, 1 };
+
+static mesh prim_cube_mesh_create() {
+ mesh cube = { 0 };
+ cube.vertices = vertex_darray_new(36);
+
+ // back faces
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_BOT_LEFT, .normal = VEC3_NEG_Z, .uv = (vec2){ 0, 1 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_TOP_LEFT, .normal = VEC3_NEG_Z, .uv = (vec2){ 0, 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_TOP_RIGHT, .normal = VEC3_NEG_Z, .uv = (vec2){ 1, 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_TOP_RIGHT, .normal = VEC3_NEG_Z, .uv = (vec2){ 1, 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_BOT_RIGHT, .normal = VEC3_NEG_Z, .uv = (vec2){ 1, 1 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_BOT_LEFT, .normal = VEC3_NEG_Z, .uv = (vec2){ 0, 1 } });
+
+ // front faces
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_BOT_LEFT, .normal = VEC3_Z, .uv = (vec2){ 0, 1 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_TOP_RIGHT, .normal = VEC3_Z, .uv = (vec2){ 1, 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_TOP_LEFT, .normal = VEC3_Z, .uv = (vec2){ 0, 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_BOT_LEFT, .normal = VEC3_Z, .uv = (vec2){ 0, 1 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_BOT_RIGHT, .normal = VEC3_Z, .uv = (vec2){ 1, 1 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_TOP_RIGHT, .normal = VEC3_Z, .uv = (vec2){ 1, 0 } });
+
+ // top faces
+ vertex_darray_push(cube.vertices,
+ (vertex){ .position = BACK_TOP_LEFT, .normal = VEC3_Y, .uv = (vec2){ 0, 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_TOP_LEFT, .normal = VEC3_Y, .uv = (vec2){ 0, 1 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_TOP_RIGHT, .normal = VEC3_Y, .uv = (vec2){ 1, 1 } });
+ vertex_darray_push(cube.vertices,
+ (vertex){ .position = BACK_TOP_LEFT, .normal = VEC3_Y, .uv = (vec2){ 0, 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_TOP_RIGHT, .normal = VEC3_Y, .uv = (vec2){ 1, 1 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_TOP_RIGHT, .normal = VEC3_Y, .uv = (vec2){ 1, 0 } });
+
+ // bottom faces
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_BOT_LEFT, .normal = VEC3_NEG_Y, .uv = (vec2){ 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_BOT_RIGHT, .normal = VEC3_NEG_Y, .uv = (vec2){ 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_BOT_LEFT, .normal = VEC3_NEG_Y, .uv = (vec2){ 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_BOT_LEFT, .normal = VEC3_NEG_Y, .uv = (vec2){ 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_BOT_RIGHT, .normal = VEC3_NEG_Y, .uv = (vec2){ 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_BOT_RIGHT, .normal = VEC3_NEG_Y, .uv = (vec2){ 0 } });
+
+ // right faces
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_TOP_RIGHT, .normal = VEC3_X, .uv = (vec2){ 0, 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_BOT_RIGHT, .normal = VEC3_X, .uv = (vec2){ 1, 1 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_TOP_RIGHT, .normal = VEC3_X, .uv = (vec2){ 1, 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_BOT_RIGHT, .normal = VEC3_X, .uv = (vec2){ 1, 1 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_TOP_RIGHT, .normal = VEC3_X, .uv = (vec2){ 0, 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_BOT_RIGHT, .normal = VEC3_X, .uv = (vec2){ 0, 1 } });
+
+ // left faces
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_TOP_LEFT, .normal = VEC3_NEG_X, .uv = (vec2){ 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_TOP_LEFT, .normal = VEC3_NEG_X, .uv = (vec2){ 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_BOT_LEFT, .normal = VEC3_NEG_X, .uv = (vec2){ 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = BACK_BOT_LEFT, .normal = VEC3_NEG_X, .uv = (vec2){ 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_BOT_LEFT, .normal = VEC3_NEG_X, .uv = (vec2){ 0 } });
+ vertex_darray_push(
+ cube.vertices,
+ (vertex){ .position = FRONT_TOP_LEFT, .normal = VEC3_NEG_X, .uv = (vec2){ 0 } });
+
+ cube.indices_len = cube.vertices->len;
+ cube.indices = malloc(sizeof(u32) * cube.indices_len);
+
+ for (u32 i = 0; i < cube.indices_len; i++) {
+ cube.indices[i] = i;
+ }
+
+ cube.has_indices = true;
+
+ return cube;
+}
+
+/** @brief create a new model with the shape of a cube */
+static model_handle prim_cube_new(core* core) {
+ model model = { 0 };
+ mesh cube = prim_cube_mesh_create();
+
+ mesh_darray_push(model.meshes, cube);
+ assert(mesh_darray_len(model.meshes) == 1);
+
+ u32 index = (u32)model_darray_len(core->models);
+ model_darray_push_copy(core->models, &model);
+ return (model_handle){ .raw = index };
+} \ No newline at end of file
diff --git a/src/platform/file.c b/src/platform/file.c
index 44aa9d0..6030620 100644
--- a/src/platform/file.c
+++ b/src/platform/file.c
@@ -60,4 +60,34 @@ str8_opt str8_from_file(arena *a, str8 path) {
result.has_value = true;
return result;
+}
+
+FileData load_spv_file(const char *path) {
+ FILE *f = fopen(path, "rb");
+ if (f == NULL) {
+ perror("Error opening file");
+ return (FileData){ NULL, 0 };
+ }
+
+ fseek(f, 0, SEEK_END);
+ long fsize = ftell(f);
+ rewind(f);
+
+ char *data = (char *)malloc(fsize);
+ if (data == NULL) {
+ perror("Memory allocation failed");
+ fclose(f);
+ return (FileData){ NULL, 0 };
+ }
+
+ size_t bytesRead = fread(data, 1, fsize, f);
+ if (bytesRead < fsize) {
+ perror("Failed to read the entire file");
+ free(data);
+ fclose(f);
+ return (FileData){ NULL, 0 };
+ }
+
+ fclose(f);
+ return (FileData){ data, bytesRead };
} \ No newline at end of file
diff --git a/src/platform/file.h b/src/platform/file.h
index 8bb22c8..a8aa8ea 100644
--- a/src/platform/file.h
+++ b/src/platform/file.h
@@ -16,4 +16,11 @@ typedef struct str8_opt {
const char* string_from_file(const char* path);
-str8_opt str8_from_file(arena* a, str8 path); \ No newline at end of file
+str8_opt str8_from_file(arena* a, str8 path);
+
+typedef struct {
+ char* data;
+ size_t size;
+} FileData;
+
+FileData load_spv_file(const char* path); \ No newline at end of file
diff --git a/src/platform/path.c b/src/platform/path.c
new file mode 100644
index 0000000..9572941
--- /dev/null
+++ b/src/platform/path.c
@@ -0,0 +1,20 @@
+#include "path.h"
+
+#include <libgen.h>
+#include <stdlib.h>
+#include <string.h>
+#include "mem.h"
+#include "str.h"
+
+#if defined(CEL_PLATFORM_LINUX) || defined(CEL_PLATFORM_MAC)
+path_opt path_parent(arena* a, const char* path) {
+ // Duplicate the string because dirname doesnt like const literals
+ char* path_copy = arena_alloc(a, strlen(path) + 1);
+ strcpy(path_copy, path);
+ char* path_dirname = dirname(path_copy);
+ return (path_opt){ .path = str8_cstr_view(path_dirname), .has_value = true };
+}
+#endif
+#ifdef CEL_PLATFORM_WINDOWS
+// TODO: path_opt path_parent(const char* path)
+#endif \ No newline at end of file
diff --git a/src/platform/path.h b/src/platform/path.h
new file mode 100644
index 0000000..73063ea
--- /dev/null
+++ b/src/platform/path.h
@@ -0,0 +1,16 @@
+/**
+ * @file path.h
+ * @brief
+ * @date 2024-03-11
+ * @copyright Copyright (c) 2024
+ */
+#pragma once
+
+#include "str.h"
+
+typedef struct path_opt {
+ str8 path;
+ bool has_value;
+} path_opt;
+
+path_opt path_parent(arena* a, const char* path); // TODO: convert to using str8 \ No newline at end of file
diff --git a/src/renderer/backends/backend_opengl.c b/src/renderer/backends/backend_opengl.c
index 6022dbf..7467416 100644
--- a/src/renderer/backends/backend_opengl.c
+++ b/src/renderer/backends/backend_opengl.c
@@ -1,10 +1,14 @@
#include <stdlib.h>
+#include "camera.h"
#define CEL_PLATFORM_LINUX
#include "defines.h"
+#include "file.h"
#include "log.h"
#include "maths_types.h"
-#include "render_types.h"
+// #include "render_types.h"
+#include "cleanroom/types.h"
+#include "ral.h"
#if CEL_REND_BACKEND_OPENGL
@@ -39,6 +43,9 @@ bool gfx_backend_init(renderer *ren) {
return true;
}
+
+void gfx_backend_draw_frame(renderer *ren, camera *cam, mat4 model, texture *tex) {}
+
void gfx_backend_shutdown(renderer *ren) {}
void uniform_vec3f(u32 program_id, const char *uniform_name, vec3 *value) {
@@ -59,4 +66,108 @@ void clear_screen(vec3 colour) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
+void texture_data_upload(texture *tex) {
+ printf("Texture name %s\n", tex->name);
+ TRACE("Upload texture data");
+ u32 texture_id;
+ glGenTextures(1, &texture_id);
+ glBindTexture(GL_TEXTURE_2D, texture_id);
+ tex->texture_id = texture_id;
+
+ // set the texture wrapping parameters
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
+ GL_REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method)
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ // set texture filtering parameters
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tex->width, tex->height, 0, tex->channel_type,
+ GL_UNSIGNED_BYTE, tex->image_data);
+ glGenerateMipmap(GL_TEXTURE_2D);
+ DEBUG("Freeing texture image data after uploading to GPU");
+ // stbi_image_free(tex->image_data); // data is on gpu now so we dont need it around
+}
+
+void bind_texture(shader s, texture *tex, u32 slot) {
+ // printf("bind texture slot %d with texture id %d \n", slot, tex->texture_id);
+ glActiveTexture(GL_TEXTURE0 + slot);
+ glBindTexture(GL_TEXTURE_2D, tex->texture_id);
+}
+
+void bind_mesh_vertex_buffer(void *_backend, mesh *mesh) { glBindVertexArray(mesh->vao); }
+
+static inline GLenum to_gl_prim_topology(enum cel_primitive_topology primitive) {
+ switch (primitive) {
+ case CEL_PRIMITIVE_TOPOLOGY_TRIANGLE:
+ return GL_TRIANGLES;
+ case CEL_PRIMITIVE_TOPOLOGY_POINT:
+ case CEL_PRIMITIVE_TOPOLOGY_LINE:
+ case CEL_PRIMITIVE_TOPOLOGY_LINE_STRIP:
+ case CEL_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
+ case CEL_PRIMITIVE_TOPOLOGY_COUNT:
+ break;
+ }
+}
+
+void draw_primitives(cel_primitive_topology primitive, u32 start_index, u32 count) {
+ u32 gl_primitive = to_gl_prim_topology(primitive);
+ glDrawArrays(gl_primitive, start_index, count);
+}
+
+shader shader_create_separate(const char *vert_shader, const char *frag_shader) {
+ INFO("Load shaders at %s and %s", vert_shader, frag_shader);
+ int success;
+ char info_log[512];
+
+ u32 vertex = glCreateShader(GL_VERTEX_SHADER);
+ const char *vertex_shader_src = string_from_file(vert_shader);
+ if (vertex_shader_src == NULL) {
+ ERROR("EXIT: couldnt load shader");
+ exit(-1);
+ }
+ glShaderSource(vertex, 1, &vertex_shader_src, NULL);
+ glCompileShader(vertex);
+ glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
+ if (!success) {
+ glGetShaderInfoLog(vertex, 512, NULL, info_log);
+ printf("%s\n", info_log);
+ ERROR("EXIT: vertex shader compilation failed");
+ exit(-1);
+ }
+
+ // fragment shader
+ u32 fragment = glCreateShader(GL_FRAGMENT_SHADER);
+ const char *fragment_shader_src = string_from_file(frag_shader);
+ if (fragment_shader_src == NULL) {
+ ERROR("EXIT: couldnt load shader");
+ exit(-1);
+ }
+ glShaderSource(fragment, 1, &fragment_shader_src, NULL);
+ glCompileShader(fragment);
+ glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
+ if (!success) {
+ glGetShaderInfoLog(fragment, 512, NULL, info_log);
+ printf("%s\n", info_log);
+ ERROR("EXIT: fragment shader compilation failed");
+ exit(-1);
+ }
+
+ u32 shader_prog;
+ shader_prog = glCreateProgram();
+
+ glAttachShader(shader_prog, vertex);
+ glAttachShader(shader_prog, fragment);
+ glLinkProgram(shader_prog);
+ glDeleteShader(vertex);
+ glDeleteShader(fragment);
+ free((char *)vertex_shader_src);
+ free((char *)fragment_shader_src);
+
+ shader s = { .program_id = shader_prog };
+ return s;
+}
+
+void set_shader(shader s) { glUseProgram(s.program_id); }
+
#endif \ No newline at end of file
diff --git a/src/renderer/backends/backend_vulkan.c b/src/renderer/backends/backend_vulkan.c
index 6347e27..4d3a14e 100644
--- a/src/renderer/backends/backend_vulkan.c
+++ b/src/renderer/backends/backend_vulkan.c
@@ -1 +1,1983 @@
-// #FUTURE \ No newline at end of file
+#include "camera.h"
+#include "primitives.h"
+#define CDEBUG
+// #define CEL_PLATFORM_LINUX
+#if CEL_REND_BACKEND_VULKAN
+// ^ Temporary
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <vulkan/vk_platform.h>
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_core.h>
+#include "colours.h"
+#include "str.h"
+
+#include "darray.h"
+#include "defines.h"
+#include "file.h"
+#include "log.h"
+#include "maths.h"
+#include "maths_types.h"
+#include "render_backend.h"
+#include "render_types.h"
+#include "vulkan_helpers.h"
+
+#include <stdlib.h>
+
+#define SCR_WIDTH 1000
+#define SCR_HEIGHT 1000
+
+#include <glad/glad.h>
+
+#include <glfw3.h>
+
+KITC_DECL_TYPED_ARRAY(VkLayerProperties)
+
+typedef struct vulkan_device {
+ VkPhysicalDevice physical_device;
+ VkDevice logical_device;
+ vulkan_swapchain_support_info swapchain_support;
+ i32 graphics_queue_index;
+ i32 present_queue_index;
+ i32 compute_queue_index;
+ i32 transfer_queue_index;
+ VkQueue graphics_queue;
+ VkQueue present_queue;
+ VkQueue compute_queue;
+ VkQueue transfer_queue;
+ VkCommandPool gfx_command_pool;
+ VkPhysicalDeviceProperties properties;
+ VkPhysicalDeviceFeatures features;
+ VkPhysicalDeviceMemoryProperties memory;
+ VkFormat depth_format;
+} vulkan_device;
+
+typedef struct vulkan_image {
+ VkImage handle;
+ VkDeviceMemory memory;
+ VkImageView view;
+ u32 width;
+ u32 height;
+} vulkan_image;
+
+typedef struct vulkan_texture_data {
+ vulkan_image image;
+ VkSampler sampler;
+} vulkan_texture_data;
+
+typedef enum vulkan_renderpass_state {
+ READY,
+ RECORDING,
+ IN_RENDER_PASS,
+ RECORDING_ENDING,
+ SUBMITTED,
+ NOT_ALLOCATED
+} vulkan_renderpass_state;
+
+typedef struct vulkan_renderpass {
+ VkRenderPass handle;
+ vec4 render_area;
+ vec4 clear_colour;
+ f32 depth;
+ u32 stencil;
+ vulkan_renderpass_state state;
+} vulkan_renderpass;
+
+typedef struct vulkan_framebuffer {
+ VkFramebuffer handle;
+ u32 attachment_count;
+ VkImageView* attachments;
+ vulkan_renderpass* renderpass;
+} vulkan_framebuffer;
+
+KITC_DECL_TYPED_ARRAY(vulkan_framebuffer)
+
+typedef struct vulkan_swapchain {
+ VkSurfaceFormatKHR image_format;
+ u8 max_frames_in_flight;
+ VkSwapchainKHR handle;
+ u32 image_count;
+ VkImage* images;
+ VkImageView* views;
+ vulkan_image depth_attachment;
+ vulkan_framebuffer_darray* framebuffers;
+} vulkan_swapchain;
+
+// overengineered
+typedef enum vulkan_command_buffer_state {
+ COMMAND_BUFFER_STATE_READY,
+ COMMAND_BUFFER_STATE_IN_RENDER_PASS,
+ COMMAND_BUFFER_STATE_RECORDING,
+ COMMAND_BUFFER_STATE_RECORDING_ENDED,
+ COMMAND_BUFFER_STATE_SUBMITTED,
+ COMMAND_BUFFER_STATE_NOT_ALLOCATED,
+} vulkan_command_buffer_state;
+
+typedef struct vulkan_command_buffer {
+ VkCommandBuffer handle;
+ vulkan_command_buffer_state state;
+} vulkan_command_buffer;
+
+KITC_DECL_TYPED_ARRAY(vulkan_command_buffer)
+
+typedef struct vulkan_fence {
+ VkFence handle;
+ bool is_signaled;
+} vulkan_fence;
+
+typedef struct vulkan_shader_stage {
+ VkShaderModuleCreateInfo create_info;
+ VkShaderModule handle;
+ VkPipelineShaderStageCreateInfo stage_create_info;
+} vulkan_shader_stage;
+
+typedef struct vulkan_pipeline {
+ VkPipeline handle;
+ VkPipelineLayout layout;
+} vulkan_pipeline;
+
+typedef struct global_object_uniform {
+ mat4 projection; // 64 bytes
+ mat4 view; // 64 bytes
+ f32 padding[32];
+} global_object_uniform;
+
+typedef struct object_uniform {
+ vec4 diffuse_colour;
+ vec4 v_reserved0;
+ vec4 v_reserved1;
+ vec4 v_reserved2;
+} object_uniform;
+
+#define MAX_OBJECT_COUNT 1024
+#define VULKAN_OBJECT_SHADER_DESCRIPTOR_COUNT 1
+
+typedef struct geometry_render_data {
+ u32 id;
+ mat4 model;
+ texture* textures[16];
+} geometry_render_data;
+
+typedef struct vulkan_buffer {
+ u64 total_size;
+ VkBuffer handle;
+ VkBufferUsageFlagBits usage;
+ bool is_locked;
+ VkDeviceMemory memory;
+ i32 memory_index;
+ u32 memory_property_flags;
+} vulkan_buffer;
+
+#define SHADER_STAGE_COUNT 2
+
+typedef struct vulkan_shader {
+ // vertex, fragment
+ vulkan_shader_stage stages[SHADER_STAGE_COUNT];
+ vulkan_pipeline pipeline;
+
+ // descriptors
+ VkDescriptorPool descriptor_pool;
+ VkDescriptorSetLayout descriptor_set_layout;
+ VkDescriptorSet descriptor_sets[3]; // one for each in-flight frame
+
+ vulkan_buffer global_uniforms_buffer;
+
+ // Data that's global for all objects drawn
+ global_object_uniform global_ubo;
+ object_uniform object_ubo;
+ vulkan_texture_data* texture_data;
+} vulkan_shader;
+
+typedef struct vulkan_context {
+ VkInstance instance;
+ VkAllocationCallbacks* allocator;
+ VkSurfaceKHR surface;
+ vulkan_device device;
+ u32 framebuffer_width;
+ u32 framebuffer_height;
+ vulkan_swapchain swapchain;
+ vulkan_renderpass main_renderpass;
+ vulkan_buffer object_vertex_buffer;
+ vulkan_buffer object_index_buffer;
+ u64 geometry_vertex_offset;
+ u64 geometry_index_offset;
+
+ vulkan_command_buffer_darray* gfx_command_buffers;
+
+ VkSemaphore* image_available_semaphores;
+ VkSemaphore* queue_complete_semaphores;
+ u32 in_flight_fence_count;
+ vulkan_fence* in_flight_fences;
+ vulkan_fence** images_in_flight;
+
+ u32 image_index;
+ u32 current_frame;
+
+ vulkan_shader object_shader;
+
+ // TODO: swapchain recreation
+
+#if defined(DEBUG)
+ VkDebugUtilsMessengerEXT vk_debugger;
+#endif
+} vulkan_context;
+
+static vulkan_context context;
+
+static i32 find_memory_index(vulkan_context* context, u32 type_filter, u32 property_flags) {
+ VkPhysicalDeviceMemoryProperties memory_properties;
+ vkGetPhysicalDeviceMemoryProperties(context->device.physical_device, &memory_properties);
+
+ for (u32 i = 0; i < memory_properties.memoryTypeCount; ++i) {
+ // Check each memory type to see if its bit is set to 1.
+ if (type_filter & (1 << i) &&
+ (memory_properties.memoryTypes[i].propertyFlags & property_flags) == property_flags) {
+ return i;
+ }
+ }
+
+ WARN("Unable to find suitable memory type!");
+ return -1;
+}
+
+/** @brief Internal backend state */
+typedef struct vulkan_state {
+} vulkan_state;
+
+typedef struct vertex_pos {
+ vec3 pos;
+ vec3 normal;
+} vertex_pos;
+
+// pipeline stuff
+bool vulkan_graphics_pipeline_create(vulkan_context* context, vulkan_renderpass* renderpass,
+ u32 attribute_count,
+ VkVertexInputAttributeDescription* attributes,
+ u32 descriptor_set_layout_count,
+ VkDescriptorSetLayout* descriptor_set_layouts, u32 stage_count,
+ VkPipelineShaderStageCreateInfo* stages, VkViewport viewport,
+ VkRect2D scissor, bool is_wireframe,
+ vulkan_pipeline* out_pipeline) {
+ VkPipelineViewportStateCreateInfo viewport_state = {
+ VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO
+ };
+ viewport_state.viewportCount = 1;
+ viewport_state.pViewports = &viewport;
+ viewport_state.scissorCount = 1;
+ viewport_state.pScissors = &scissor;
+
+ // Rasterizer
+ VkPipelineRasterizationStateCreateInfo rasterizer_create_info = {
+ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO
+ };
+ rasterizer_create_info.depthClampEnable = VK_FALSE;
+ rasterizer_create_info.rasterizerDiscardEnable = VK_FALSE;
+ rasterizer_create_info.polygonMode = is_wireframe ? VK_POLYGON_MODE_LINE : VK_POLYGON_MODE_FILL;
+ rasterizer_create_info.lineWidth = 1.0f;
+ rasterizer_create_info.cullMode = VK_CULL_MODE_BACK_BIT;
+ rasterizer_create_info.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
+ rasterizer_create_info.depthBiasEnable = VK_FALSE;
+ rasterizer_create_info.depthBiasConstantFactor = 0.0;
+ rasterizer_create_info.depthBiasClamp = 0.0;
+ rasterizer_create_info.depthBiasSlopeFactor = 0.0;
+
+ // Multisampling
+ VkPipelineMultisampleStateCreateInfo ms_create_info = {
+ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO
+ };
+ ms_create_info.sampleShadingEnable = VK_FALSE;
+ ms_create_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+ ms_create_info.minSampleShading = 1.0;
+ ms_create_info.pSampleMask = 0;
+ ms_create_info.alphaToCoverageEnable = VK_FALSE;
+ ms_create_info.alphaToOneEnable = VK_FALSE;
+
+ // Depth and stencil testing
+ VkPipelineDepthStencilStateCreateInfo depth_stencil = {
+ VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO
+ };
+ depth_stencil.depthTestEnable = VK_TRUE;
+ depth_stencil.depthWriteEnable = VK_TRUE;
+ depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS;
+ depth_stencil.depthBoundsTestEnable = VK_FALSE;
+ depth_stencil.stencilTestEnable = VK_FALSE;
+ depth_stencil.pNext = 0;
+
+ VkPipelineColorBlendAttachmentState color_blend_attachment_state;
+ color_blend_attachment_state.blendEnable = VK_TRUE;
+ color_blend_attachment_state.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+ color_blend_attachment_state.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ color_blend_attachment_state.colorBlendOp = VK_BLEND_OP_ADD;
+ color_blend_attachment_state.srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+ color_blend_attachment_state.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ color_blend_attachment_state.alphaBlendOp = VK_BLEND_OP_ADD;
+ color_blend_attachment_state.colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
+ VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+
+ VkPipelineColorBlendStateCreateInfo color_blend = {
+ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO
+ };
+ color_blend.logicOpEnable = VK_FALSE;
+ color_blend.logicOp = VK_LOGIC_OP_COPY;
+ color_blend.attachmentCount = 1;
+ color_blend.pAttachments = &color_blend_attachment_state;
+
+ const u32 dynamic_state_count = 3;
+ VkDynamicState dynamic_states[3] = {
+ VK_DYNAMIC_STATE_VIEWPORT,
+ VK_DYNAMIC_STATE_SCISSOR,
+ VK_DYNAMIC_STATE_LINE_WIDTH,
+ };
+
+ VkPipelineDynamicStateCreateInfo dynamic_state = {
+ VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO
+ };
+ dynamic_state.dynamicStateCount = dynamic_state_count;
+ dynamic_state.pDynamicStates = dynamic_states;
+
+ // Vertex input
+ VkVertexInputBindingDescription binding_desc;
+ binding_desc.binding = 0;
+ binding_desc.stride = sizeof(vertex);
+ binding_desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+ VkPipelineVertexInputStateCreateInfo vertex_input_info = {
+ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
+ };
+ vertex_input_info.vertexBindingDescriptionCount = 1;
+ vertex_input_info.pVertexBindingDescriptions = &binding_desc;
+ vertex_input_info.vertexAttributeDescriptionCount = attribute_count;
+ vertex_input_info.pVertexAttributeDescriptions = attributes;
+
+ VkPipelineInputAssemblyStateCreateInfo input_assembly = {
+ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO
+ };
+ input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+ input_assembly.primitiveRestartEnable = VK_FALSE;
+
+ VkPipelineLayoutCreateInfo pipeline_layout_create_info = {
+ VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO
+ };
+
+ // Pushconstants
+ VkPushConstantRange push_constant;
+ push_constant.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+ push_constant.offset = sizeof(mat4) * 0;
+ push_constant.size = sizeof(mat4) * 2;
+
+ pipeline_layout_create_info.pushConstantRangeCount = 1;
+ pipeline_layout_create_info.pPushConstantRanges = &push_constant;
+
+ pipeline_layout_create_info.setLayoutCount = descriptor_set_layout_count;
+ pipeline_layout_create_info.pSetLayouts = descriptor_set_layouts;
+
+ vkCreatePipelineLayout(context->device.logical_device, &pipeline_layout_create_info,
+ context->allocator, &out_pipeline->layout);
+
+ VkGraphicsPipelineCreateInfo pipeline_create_info = {
+ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO
+ };
+ pipeline_create_info.stageCount = stage_count;
+ pipeline_create_info.pStages = stages;
+ pipeline_create_info.pVertexInputState = &vertex_input_info;
+ pipeline_create_info.pInputAssemblyState = &input_assembly;
+
+ pipeline_create_info.pViewportState = &viewport_state;
+ pipeline_create_info.pRasterizationState = &rasterizer_create_info;
+ pipeline_create_info.pMultisampleState = &ms_create_info;
+ pipeline_create_info.pDepthStencilState = &depth_stencil;
+ pipeline_create_info.pColorBlendState = &color_blend;
+ pipeline_create_info.pDynamicState = &dynamic_state;
+ pipeline_create_info.pTessellationState = 0;
+
+ pipeline_create_info.layout = out_pipeline->layout;
+
+ pipeline_create_info.renderPass = renderpass->handle;
+ pipeline_create_info.subpass = 0;
+ pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE;
+ pipeline_create_info.basePipelineIndex = -1;
+
+ VkResult result =
+ vkCreateGraphicsPipelines(context->device.logical_device, VK_NULL_HANDLE, 1,
+ &pipeline_create_info, context->allocator, &out_pipeline->handle);
+ if (result != VK_SUCCESS) {
+ FATAL("graphics pipeline creation failed. its fked mate");
+ ERROR_EXIT("Doomed");
+ }
+
+ return true;
+}
+
+void vulkan_pipeline_bind(vulkan_command_buffer* command_buffer, VkPipelineBindPoint bind_point,
+ vulkan_pipeline* pipeline) {
+ vkCmdBindPipeline(command_buffer->handle, bind_point, pipeline->handle);
+}
+
+void vulkan_buffer_bind(vulkan_context* context, vulkan_buffer* buffer, u64 offset) {
+ vkBindBufferMemory(context->device.logical_device, buffer->handle, buffer->memory, offset);
+}
+
+bool vulkan_buffer_create(vulkan_context* context, u64 size, VkBufferUsageFlagBits usage,
+ u32 memory_property_flags, bool bind_on_create,
+ vulkan_buffer* out_buffer) {
+ memset(out_buffer, 0, sizeof(vulkan_buffer));
+ out_buffer->total_size = size;
+ out_buffer->usage = usage;
+ out_buffer->memory_property_flags = memory_property_flags;
+
+ VkBufferCreateInfo buffer_info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
+ buffer_info.size = size;
+ buffer_info.usage = usage;
+ buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+ vkCreateBuffer(context->device.logical_device, &buffer_info, context->allocator,
+ &out_buffer->handle);
+
+ VkMemoryRequirements requirements;
+ vkGetBufferMemoryRequirements(context->device.logical_device, out_buffer->handle, &requirements);
+ out_buffer->memory_index =
+ find_memory_index(context, requirements.memoryTypeBits, out_buffer->memory_property_flags);
+
+ // Allocate
+ VkMemoryAllocateInfo allocate_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
+ allocate_info.allocationSize = requirements.size;
+ allocate_info.memoryTypeIndex = (u32)out_buffer->memory_index;
+
+ vkAllocateMemory(context->device.logical_device, &allocate_info, context->allocator,
+ &out_buffer->memory);
+
+ if (bind_on_create) {
+ vulkan_buffer_bind(context, out_buffer, 0);
+ }
+
+ DEBUG("Created buffer.");
+
+ return true;
+}
+
+// lock and unlock?
+
+void* vulkan_buffer_lock_memory(vulkan_context* context, vulkan_buffer* buffer, u64 offset,
+ u64 size, u32 flags) {
+ void* data;
+ vkMapMemory(context->device.logical_device, buffer->memory, offset, size, flags, &data);
+ return data;
+}
+void* vulkan_buffer_unlock_memory(vulkan_context* context, vulkan_buffer* buffer) {
+ vkUnmapMemory(context->device.logical_device, buffer->memory);
+}
+
+void vulkan_buffer_load_data(vulkan_context* context, vulkan_buffer* buffer, u64 offset, u64 size,
+ u32 flags, const void* data) {
+ void* data_ptr = 0;
+ VK_CHECK(
+ vkMapMemory(context->device.logical_device, buffer->memory, offset, size, flags, &data_ptr));
+ memcpy(data_ptr, data, size);
+ vkUnmapMemory(context->device.logical_device, buffer->memory);
+}
+
+// TODO: destroy
+
+bool create_shader_module(vulkan_context* context, const char* filename, const char* type_str,
+ VkShaderStageFlagBits flag, u32 stage_index,
+ vulkan_shader_stage* shader_stages) {
+ memset(&shader_stages[stage_index].create_info, 0, sizeof(VkShaderModuleCreateInfo));
+ memset(&shader_stages[stage_index].stage_create_info, 0, sizeof(VkPipelineShaderStageCreateInfo));
+
+ shader_stages[stage_index].create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+
+ // todo: file input
+ FileData file_contents = load_spv_file(filename);
+
+ shader_stages[stage_index].create_info.codeSize = file_contents.size;
+ shader_stages[stage_index].create_info.pCode = (u32*)file_contents.data;
+
+ vkCreateShaderModule(context->device.logical_device, &shader_stages[stage_index].create_info,
+ context->allocator, &shader_stages[stage_index].handle);
+
+ shader_stages[stage_index].stage_create_info.sType =
+ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ shader_stages[stage_index].stage_create_info.stage = flag;
+ shader_stages[stage_index].stage_create_info.module = shader_stages[stage_index].handle;
+ shader_stages[stage_index].stage_create_info.pName = "main";
+
+ free(file_contents.data);
+
+ // TODO: Descriptors
+
+ return true;
+}
+
+bool vulkan_object_shader_create(vulkan_context* context, vulkan_shader* out_shader) {
+ char stage_type_strs[SHADER_STAGE_COUNT][5] = { "vert", "frag" };
+ char stage_filenames[SHADER_STAGE_COUNT][256] = { "build/linux/x86_64/debug/object.vert.spv",
+ "build/linux/x86_64/debug/object.frag.spv" };
+ VkShaderStageFlagBits stage_types[SHADER_STAGE_COUNT] = { VK_SHADER_STAGE_VERTEX_BIT,
+ VK_SHADER_STAGE_FRAGMENT_BIT };
+ for (u8 i = 0; i < SHADER_STAGE_COUNT; i++) {
+ DEBUG("Loading %s", stage_filenames[i]);
+ create_shader_module(context, stage_filenames[i], stage_type_strs[i], stage_types[i], i,
+ out_shader->stages);
+ }
+
+ // descriptors
+ VkDescriptorSetLayoutBinding global_ubo_layout_binding;
+ global_ubo_layout_binding.binding = 0;
+ global_ubo_layout_binding.descriptorCount = 1;
+ global_ubo_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ global_ubo_layout_binding.pImmutableSamplers = 0;
+ global_ubo_layout_binding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+
+ VkDescriptorSetLayoutBinding sampler_layout_binding;
+ sampler_layout_binding.binding = 1;
+ sampler_layout_binding.descriptorCount = 1;
+ sampler_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ sampler_layout_binding.pImmutableSamplers = 0;
+ sampler_layout_binding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+ VkDescriptorSetLayoutCreateInfo global_layout_info = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO
+ };
+
+ VkDescriptorSetLayoutBinding bindings[2] = { global_ubo_layout_binding, sampler_layout_binding };
+
+ global_layout_info.bindingCount = 2;
+ global_layout_info.pBindings = bindings;
+
+ VK_CHECK(vkCreateDescriptorSetLayout(context->device.logical_device, &global_layout_info,
+ context->allocator, &out_shader->descriptor_set_layout));
+
+ VkDescriptorPoolSize global_pool_size;
+ global_pool_size.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ global_pool_size.descriptorCount = 3;
+
+ VkDescriptorPoolSize sampler_pool_size;
+ sampler_pool_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ sampler_pool_size.descriptorCount = 3;
+
+ VkDescriptorPoolSize pool_sizes[2] = { global_pool_size, sampler_pool_size };
+
+ VkDescriptorPoolCreateInfo pool_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
+ pool_info.poolSizeCount = 2;
+ pool_info.pPoolSizes = pool_sizes;
+ pool_info.maxSets = 3;
+
+ VK_CHECK(vkCreateDescriptorPool(context->device.logical_device, &pool_info, context->allocator,
+ &out_shader->descriptor_pool));
+
+ // Pipeline creation
+ VkViewport viewport;
+ viewport.x = 0;
+ viewport.y = 0;
+ viewport.width = (f32)context->framebuffer_width;
+ viewport.height = (f32)context->framebuffer_height;
+ viewport.minDepth = 0.0;
+ viewport.maxDepth = 1.0;
+
+ VkRect2D scissor;
+ scissor.offset.x = scissor.offset.y = 0;
+ scissor.extent.width = context->framebuffer_width;
+ scissor.extent.height = context->framebuffer_height;
+
+ // Attributes
+ u32 offset = 0;
+ const i32 attribute_count = 3;
+ VkVertexInputAttributeDescription attribute_descs[3];
+ // Position
+ VkFormat formats[3] = { VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_R32G32B32_SFLOAT,
+ VK_FORMAT_R32G32_SFLOAT };
+
+ u64 sizes[3] = { sizeof(vec3), sizeof(vec3), sizeof(vec2) };
+
+ for (u32 i = 0; i < attribute_count; i++) {
+ attribute_descs[i].binding = 0;
+ attribute_descs[i].location = i;
+ attribute_descs[i].format = formats[i];
+ attribute_descs[i].offset = offset;
+ offset += sizes[i];
+ }
+
+ // Descriptor set layouts
+ VkDescriptorSetLayout layouts[1] = { out_shader->descriptor_set_layout };
+
+ // Stages
+ VkPipelineShaderStageCreateInfo stage_create_infos[SHADER_STAGE_COUNT];
+ memset(stage_create_infos, 0, sizeof(stage_create_infos));
+ for (u32 i = 0; i < SHADER_STAGE_COUNT; i++) {
+ stage_create_infos[i].sType = out_shader->stages[i].stage_create_info.sType;
+ stage_create_infos[i] = out_shader->stages[i].stage_create_info;
+ }
+
+ vulkan_graphics_pipeline_create(
+ context, &context->main_renderpass, attribute_count, attribute_descs, 1, layouts,
+ SHADER_STAGE_COUNT, stage_create_infos, viewport, scissor, false, &out_shader->pipeline);
+ INFO("Graphics pipeline created!");
+
+ // Uniform buffer
+ if (!vulkan_buffer_create(context, sizeof(global_object_uniform),
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ true, &out_shader->global_uniforms_buffer)) {
+ ERROR("Couldnt create uniforms buffer");
+ return false;
+ }
+
+ VkDescriptorSetLayout global_layouts[3] = {
+ out_shader->descriptor_set_layout,
+ out_shader->descriptor_set_layout,
+ out_shader->descriptor_set_layout,
+ };
+
+ VkDescriptorSetAllocateInfo alloc_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
+ alloc_info.descriptorPool = out_shader->descriptor_pool;
+ alloc_info.descriptorSetCount = 3;
+ alloc_info.pSetLayouts = global_layouts;
+ VK_CHECK(vkAllocateDescriptorSets(context->device.logical_device, &alloc_info,
+ out_shader->descriptor_sets));
+
+ return true;
+}
+void vulkan_object_shader_destroy(vulkan_context* context, vulkan_shader* shader) {}
+void vulkan_object_shader_use(vulkan_context* context, vulkan_shader* shader) {
+ u32 image_index = context->image_index;
+ vulkan_pipeline_bind(&context->gfx_command_buffers->data[image_index],
+ VK_PIPELINE_BIND_POINT_GRAPHICS, &shader->pipeline);
+}
+void vulkan_object_shader_update_global_state(vulkan_context* context, vulkan_shader* shader) {
+ u32 image_index = context->image_index;
+ VkCommandBuffer cmd_buffer = context->gfx_command_buffers->data[image_index].handle;
+ VkDescriptorSet global_descriptors = shader->descriptor_sets[image_index];
+
+ u32 range = sizeof(global_object_uniform);
+ u64 offset = 0;
+
+ // copy data to buffer
+ vulkan_buffer_load_data(context, &shader->global_uniforms_buffer, offset, range, 0,
+ &shader->global_ubo);
+
+ VkDescriptorBufferInfo buffer_info;
+ buffer_info.buffer = shader->global_uniforms_buffer.handle;
+ buffer_info.offset = offset;
+ buffer_info.range = range;
+
+ VkDescriptorImageInfo image_info;
+ image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ image_info.imageView = shader->texture_data->image.view;
+ image_info.sampler = shader->texture_data->sampler;
+
+ VkWriteDescriptorSet uniform_write = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
+ uniform_write.dstSet = shader->descriptor_sets[image_index];
+ uniform_write.dstBinding = 0;
+ uniform_write.dstArrayElement = 0;
+ uniform_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ uniform_write.descriptorCount = 1;
+ uniform_write.pBufferInfo = &buffer_info;
+
+ VkWriteDescriptorSet texture_write = { VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET };
+ texture_write.dstSet = shader->descriptor_sets[image_index];
+ texture_write.dstBinding = 1;
+ texture_write.dstArrayElement = 0;
+ texture_write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ texture_write.descriptorCount = 1;
+ texture_write.pImageInfo = &image_info;
+
+ VkWriteDescriptorSet writes[2] = { uniform_write, texture_write };
+
+ vkUpdateDescriptorSets(context->device.logical_device, 2, writes, 0, 0);
+
+ vkCmdBindDescriptorSets(cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, shader->pipeline.layout, 0,
+ 1, &global_descriptors, 0, 0);
+}
+
+void vulkan_object_shader_update_object(vulkan_context* context, vulkan_shader* shader,
+ mat4 model) {
+ u32 image_index = context->image_index;
+ VkCommandBuffer cmd_buffer = context->gfx_command_buffers->data[image_index].handle;
+ // vulkan_command_buffer* cmd_buffer = &context->gfx_command_buffers->data[context.image_index];
+
+ vkCmdPushConstants(cmd_buffer, shader->pipeline.layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
+ sizeof(mat4), &model);
+
+ // vulkan_object_shader_use(context, &context->object_shader);
+ VkDeviceSize offsets[1] = { 0 };
+ vkCmdBindVertexBuffers(cmd_buffer, 0, 1, &context->object_vertex_buffer.handle,
+ (VkDeviceSize*)offsets);
+
+ vkCmdBindIndexBuffer(cmd_buffer, context->object_index_buffer.handle, 0, VK_INDEX_TYPE_UINT32);
+
+ vkCmdDrawIndexed(cmd_buffer, 36, 1, 0, 0, 0);
+ // vkCmdDraw(cmd_buffer, 36, 1, 0, 0);
+}
+
+bool select_physical_device(vulkan_context* ctx) {
+ u32 physical_device_count = 0;
+ VK_CHECK(vkEnumeratePhysicalDevices(ctx->instance, &physical_device_count, 0));
+ if (physical_device_count == 0) {
+ FATAL("No devices that support vulkan were found");
+ return false;
+ }
+ TRACE("Number of devices found %d", physical_device_count);
+
+ VkPhysicalDevice physical_devices[physical_device_count];
+ VK_CHECK(vkEnumeratePhysicalDevices(ctx->instance, &physical_device_count, physical_devices));
+
+ for (u32 i = 0; i < physical_device_count; i++) {
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(physical_devices[i], &properties);
+
+ VkPhysicalDeviceFeatures features;
+ vkGetPhysicalDeviceFeatures(physical_devices[i], &features);
+
+ VkPhysicalDeviceMemoryProperties memory;
+ vkGetPhysicalDeviceMemoryProperties(physical_devices[i], &memory);
+
+ vulkan_physical_device_requirements requirements = {};
+ requirements.graphics = true;
+ requirements.present = true;
+ requirements.compute = true;
+ requirements.transfer = true;
+
+ requirements.sampler_anistropy = true;
+ requirements.discrete_gpu = true;
+ requirements.device_ext_names[0] = str8lit(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
+ requirements.device_ext_name_count = 1;
+
+ vulkan_physical_device_queue_family_info queue_info = {};
+
+ bool result = physical_device_meets_requirements(physical_devices[i], ctx->surface, &properties,
+ &features, &requirements, &queue_info,
+ &ctx->device.swapchain_support);
+
+ if (result) {
+ INFO("GPU Driver version: %d.%d.%d", VK_VERSION_MAJOR(properties.driverVersion),
+ VK_VERSION_MINOR(properties.driverVersion), VK_VERSION_PATCH(properties.driverVersion));
+
+ INFO("Vulkan API version: %d.%d.%d", VK_VERSION_MAJOR(properties.apiVersion),
+ VK_VERSION_MINOR(properties.apiVersion), VK_VERSION_PATCH(properties.apiVersion));
+
+ // TODO: print gpu memory information -
+ // https://youtu.be/6Kj3O2Ov1RU?si=pXfP5NvXXcXjJsrG&t=2439
+
+ ctx->device.physical_device = physical_devices[i];
+ ctx->device.graphics_queue_index = queue_info.graphics_family_index;
+ ctx->device.present_queue_index = queue_info.present_family_index;
+ ctx->device.compute_queue_index = queue_info.compute_family_index;
+ ctx->device.transfer_queue_index = queue_info.transfer_family_index;
+ ctx->device.properties = properties;
+ ctx->device.features = features;
+ ctx->device.memory = memory;
+ break;
+ }
+ }
+
+ if (!ctx->device.physical_device) {
+ ERROR("No suitable physical devices were found :(");
+ return false;
+ }
+
+ INFO("Physical device selected: %s\n", ctx->device.properties.deviceName);
+ return true;
+}
+
+bool vulkan_device_create(vulkan_context* context) {
+ // Physical device - NOTE: mutates the context directly
+ if (!select_physical_device(context)) {
+ return false;
+ }
+
+// Logical device - NOTE: mutates the context directly
+
+// queues
+#define VULKAN_QUEUES_COUNT 2
+ const char* queue_names[VULKAN_QUEUES_COUNT] = {
+ "GRAPHICS",
+ "TRANSFER",
+ };
+ i32 indices[VULKAN_QUEUES_COUNT] = {
+ context->device.graphics_queue_index,
+ context->device.transfer_queue_index,
+ };
+ f32 prio_one = 1.0;
+ VkDeviceQueueCreateInfo queue_create_info[2]; // HACK: make 2 queues, graphics and transfer
+ // graphics
+ queue_create_info[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queue_create_info[0].queueFamilyIndex = 0;
+ queue_create_info[0].queueCount = 1;
+ queue_create_info[0].flags = 0;
+ queue_create_info[0].pNext = 0;
+ queue_create_info[0].pQueuePriorities = &prio_one;
+ // transfer
+ queue_create_info[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queue_create_info[1].queueFamilyIndex = 1;
+ queue_create_info[1].queueCount = 1;
+ queue_create_info[1].flags = 0;
+ queue_create_info[1].pNext = 0;
+ queue_create_info[1].pQueuePriorities = &prio_one;
+
+ // for (int i = 0; i < 2; i++) {
+ // TRACE("Configure %s queue", queue_names[i]);
+ // queue_create_info[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ // queue_create_info[i].queueFamilyIndex = indices[i];
+ // queue_create_info[i].queueCount = 1; // make just one of them
+ // queue_create_info[i].flags = 0;
+ // queue_create_info[i].pNext = 0;
+ // f32 priority = 1.0;
+ // queue_create_info[i].pQueuePriorities = &priority;
+ // }
+
+ // features
+ VkPhysicalDeviceFeatures device_features = {};
+ device_features.samplerAnisotropy = VK_TRUE; // request anistrophy
+
+ // device itself
+ VkDeviceCreateInfo device_create_info = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
+ device_create_info.queueCreateInfoCount = VULKAN_QUEUES_COUNT;
+ device_create_info.pQueueCreateInfos = queue_create_info;
+ device_create_info.pEnabledFeatures = &device_features;
+ device_create_info.enabledExtensionCount = 1;
+ const char* extension_names = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
+ device_create_info.ppEnabledExtensionNames = &extension_names;
+
+ // deprecated
+ device_create_info.enabledLayerCount = 0;
+ device_create_info.ppEnabledLayerNames = 0;
+
+ VkResult result = vkCreateDevice(context->device.physical_device, &device_create_info,
+ context->allocator, &context->device.logical_device);
+ if (result != VK_SUCCESS) {
+ printf("error creating logical device with status %u\n", result);
+ ERROR_EXIT("Bye bye");
+ }
+ INFO("Logical device created");
+
+ // get queues
+ vkGetDeviceQueue(context->device.logical_device, context->device.graphics_queue_index, 0,
+ &context->device.graphics_queue);
+ // vkGetDeviceQueue(context->device.logical_device, context->device.present_queue_index, 0,
+ // &context->device.present_queue);
+ // vkGetDeviceQueue(context->device.logical_device, context->device.compute_queue_index, 0,
+ // &context->device.compute_queue);
+ vkGetDeviceQueue(context->device.logical_device, context->device.transfer_queue_index, 0,
+ &context->device.transfer_queue);
+
+ // create command pool for graphics queue
+ VkCommandPoolCreateInfo pool_create_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
+ pool_create_info.queueFamilyIndex = context->device.graphics_queue_index;
+ pool_create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+ vkCreateCommandPool(context->device.logical_device, &pool_create_info, context->allocator,
+ &context->device.gfx_command_pool);
+ INFO("Created Command Pool")
+
+ return true;
+}
+void vulkan_device_destroy(vulkan_context* context) {
+ context->device.physical_device = 0; // release
+ // TODO: reset other memory
+}
+
+bool vulkan_device_detect_depth_format(vulkan_device* device) {
+ const size_t n_candidates = 3;
+ VkFormat candidates[3] = { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT,
+ VK_FORMAT_D24_UNORM_S8_UINT };
+ u32 flags = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ for (u64 i = 0; i < n_candidates; i++) {
+ VkFormatProperties properties;
+ vkGetPhysicalDeviceFormatProperties(device->physical_device, candidates[i], &properties);
+
+ if ((properties.linearTilingFeatures & flags) == flags) {
+ device->depth_format = candidates[i];
+ return true;
+ }
+ if ((properties.optimalTilingFeatures & flags) == flags) {
+ device->depth_format = candidates[i];
+ return true;
+ }
+ }
+ return false;
+}
+
+void vulkan_image_view_create(vulkan_context* context, VkFormat format, vulkan_image* image,
+ VkImageAspectFlags aspect_flags) {
+ VkImageViewCreateInfo view_create_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
+ view_create_info.image = image->handle;
+ view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ view_create_info.format = format;
+ view_create_info.subresourceRange.aspectMask = aspect_flags;
+
+ view_create_info.subresourceRange.baseMipLevel = 0;
+ view_create_info.subresourceRange.levelCount = 1;
+ view_create_info.subresourceRange.baseArrayLayer = 0;
+ view_create_info.subresourceRange.layerCount = 1;
+
+ vkCreateImageView(context->device.logical_device, &view_create_info, context->allocator,
+ &image->view);
+}
+
+void vulkan_image_transition_layout(vulkan_context* context, vulkan_command_buffer* command_buffer,
+ vulkan_image* image, VkFormat format, VkImageLayout old_layout,
+ VkImageLayout new_layout) {
+ VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
+void vulkan_image_transition_layout(vulkan_context* context, vulkan_command_buffer* command_buffer,
+ vulkan_image* image, VkFormat format, VkImageLayout old_layout,
+ VkImageLayout new_layout) {
+ VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
+ barrier.oldLayout = old_layout;
+ barrier.newLayout = new_layout;
+ barrier.srcQueueFamilyIndex = context->device.graphics_queue_index;
+ barrier.dstQueueFamilyIndex = context->device.graphics_queue_index;
+ barrier.image = image->handle;
+ barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ barrier.subresourceRange.baseMipLevel = 0;
+ barrier.subresourceRange.levelCount = 1;
+ barrier.subresourceRange.baseArrayLayer = 0;
+ barrier.subresourceRange.layerCount = 1;
+ barrier.srcAccessMask = 0;
+ barrier.dstAccessMask = 0;
+
+ VkPipelineStageFlags source_stage;
+ VkPipelineStageFlags dest_stage;
+
+ if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED &&
+ new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
+ barrier.srcAccessMask = 0;
+ barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+ source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+ dest_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+
+ } else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&
+ new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
+ barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+ source_stage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+ dest_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+ } else {
+ FATAL("Unsupported image layout transition");
+ return;
+ }
+
+ vkCmdPipelineBarrier(command_buffer->handle, source_stage, dest_stage, 0, 0, 0, 0, 0, 1,
+ &barrier);
+}
+
+void vulkan_image_copy_from_buffer(vulkan_image* image, VkBuffer buffer,
+ vulkan_command_buffer* command_buffer) {
+ VkBufferImageCopy region;
+ region.bufferOffset = 0;
+ region.bufferRowLength = 0;
+ region.bufferImageHeight = 0;
+ region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.imageSubresource.mipLevel = 0;
+ region.imageSubresource.baseArrayLayer = 0;
+ region.imageSubresource.layerCount = 1;
+ printf("Image details width: %d height %d\n", image->width, image->height);
+ region.imageOffset.x = 0;
+ region.imageOffset.y = 0;
+ region.imageOffset.z = 0;
+ region.imageExtent.width = image->width;
+ region.imageExtent.height = image->height;
+ region.imageExtent.depth = 1;
+
+ vkCmdCopyBufferToImage(command_buffer->handle, buffer, image->handle,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
+}
+
+void vulkan_image_create(vulkan_context* context, VkImageType image_type, u32 width, u32 height,
+ VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage,
+ VkMemoryPropertyFlags memory_flags, bool create_view,
+ VkImageAspectFlags aspect_flags, vulkan_image* out_image) {
+ // copy params
+ out_image->width = width;
+ out_image->height = height;
+
+ // create info
+ VkImageCreateInfo image_create_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
+ image_create_info.imageType = image_type;
+ image_create_info.extent.width = width;
+ image_create_info.extent.height = height;
+ image_create_info.extent.depth = 1;
+ image_create_info.mipLevels = 1;
+ image_create_info.arrayLayers = 1;
+ image_create_info.format = format;
+ image_create_info.tiling = tiling;
+ image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ image_create_info.usage = usage;
+ image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
+ image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+ VK_CHECK(vkCreateImage(context->device.logical_device, &image_create_info, context->allocator,
+ &out_image->handle));
+
+ VkMemoryRequirements memory_reqs;
+ vkGetImageMemoryRequirements(context->device.logical_device, out_image->handle, &memory_reqs);
+
+ i32 memory_type = -1;
+ VkPhysicalDeviceMemoryProperties memory_properties;
+ vkGetPhysicalDeviceMemoryProperties(context->device.physical_device, &memory_properties);
+
+ for (u32 i = 0; i < memory_properties.memoryTypeCount; i++) {
+ // typefilter = memoryTypeBits , prop filter = memory_flags
+ if (memory_reqs.memoryTypeBits & (1 << i) &&
+ (memory_properties.memoryTypes[i].propertyFlags & memory_flags)) {
+ memory_type = i;
+ break;
+ }
+ }
+
+ if (memory_type < 0) {
+ ERROR_EXIT("couldnt find a suitable memory type for the image");
+ }
+
+ // allocate memory
+ VkMemoryAllocateInfo memory_allocate_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
+ memory_allocate_info.allocationSize = memory_reqs.size;
+ memory_allocate_info.memoryTypeIndex = memory_type;
+ vkAllocateMemory(context->device.logical_device, &memory_allocate_info, context->allocator,
+ &out_image->memory);
+
+ // bind memory
+ // TODO: maybe bind context->device.logical_device to device at the top of the functions?
+ vkBindImageMemory(context->device.logical_device, out_image->handle, out_image->memory, 0);
+
+ if (create_view) {
+ out_image->view = 0;
+ vulkan_image_view_create(context, format, out_image, aspect_flags);
+ }
+}
+
+// TODO: vulkan_image_destroy
+
+void vulkan_framebuffer_create(vulkan_context* context, vulkan_renderpass* renderpass, u32 width,
+ u32 height, u32 attachment_count, VkImageView* attachments,
+ vulkan_framebuffer* out_framebuffer) {
+ out_framebuffer->attachments = malloc(sizeof(VkImageView) * attachment_count);
+ for (u32 i = 0; i < attachment_count; i++) {
+ out_framebuffer->attachments[i] = attachments[i];
+ }
+ out_framebuffer->attachment_count = attachment_count;
+ out_framebuffer->renderpass = renderpass;
+
+ VkFramebufferCreateInfo framebuffer_create_info = {
+ VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO
+ }; // TODO
+
+ framebuffer_create_info.renderPass = renderpass->handle;
+ framebuffer_create_info.attachmentCount = attachment_count;
+ framebuffer_create_info.pAttachments = out_framebuffer->attachments;
+ framebuffer_create_info.width = width;
+ framebuffer_create_info.height = height;
+ framebuffer_create_info.layers = 1;
+
+ vkCreateFramebuffer(context->device.logical_device, &framebuffer_create_info, context->allocator,
+ &out_framebuffer->handle);
+}
+
+// TODO: vulkan_framebuffer_destroy
+
+void vulkan_command_buffer_allocate(vulkan_context* context, VkCommandPool pool, bool is_primary,
+ vulkan_command_buffer* out_command_buffer) {
+ VkCommandBufferAllocateInfo allocate_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
+ allocate_info.commandPool = pool;
+ allocate_info.level =
+ is_primary ? VK_COMMAND_BUFFER_LEVEL_PRIMARY : VK_COMMAND_BUFFER_LEVEL_SECONDARY;
+ allocate_info.commandBufferCount = 1;
+ allocate_info.pNext = 0;
+
+ out_command_buffer->state = COMMAND_BUFFER_STATE_NOT_ALLOCATED;
+ vkAllocateCommandBuffers(context->device.logical_device, &allocate_info,
+ &out_command_buffer->handle);
+ out_command_buffer->state = COMMAND_BUFFER_STATE_READY;
+}
+
+void vulkan_command_buffer_free(vulkan_context* context, VkCommandPool pool,
+ vulkan_command_buffer* out_command_buffer) {
+ // TODO: implement freeing
+}
+
+void vulkan_command_buffer_begin(vulkan_command_buffer* command_buffer, bool is_single_use,
+ bool is_renderpass_continue, bool is_simultaneous_use) {
+ VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
+ begin_info.flags = 0;
+ if (is_single_use) {
+ begin_info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+ }
+ // TODO: RENDER_PASS_CONTINUE_BIT & SIMULTANEOUS_USE_BIT
+
+ begin_info.pNext = 0;
+ begin_info.pInheritanceInfo = 0;
+ vkBeginCommandBuffer(command_buffer->handle, &begin_info);
+
+ command_buffer->state = COMMAND_BUFFER_STATE_RECORDING;
+}
+
+void vulkan_command_buffer_end(vulkan_command_buffer* command_buffer) {
+ VK_CHECK(vkEndCommandBuffer(command_buffer->handle));
+ command_buffer->state = COMMAND_BUFFER_STATE_RECORDING_ENDED;
+}
+void vulkan_command_buffer_update_submitted(vulkan_command_buffer* command_buffer) {
+ command_buffer->state = COMMAND_BUFFER_STATE_SUBMITTED;
+}
+void vulkan_command_buffer_reset(vulkan_command_buffer* command_buffer) {
+ command_buffer->state = COMMAND_BUFFER_STATE_READY;
+}
+
+void vulkan_command_buffer_allocate_and_begin_oneshot(vulkan_context* context, VkCommandPool pool,
+ vulkan_command_buffer* out_command_buffer) {
+ vulkan_command_buffer_allocate(context, pool, true, out_command_buffer);
+ vulkan_command_buffer_begin(out_command_buffer, true, false, false);
+}
+
+void vulkan_command_buffer_end_oneshot(vulkan_context* context, VkCommandPool pool,
+ vulkan_command_buffer* command_buffer, VkQueue queue) {
+ vulkan_command_buffer_end(command_buffer);
+
+ // submit to queue
+ VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
+ submit_info.commandBufferCount = 1;
+ submit_info.pCommandBuffers = &command_buffer->handle;
+ VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, 0));
+ // wait for it to finish
+ VK_CHECK(vkQueueWaitIdle(queue));
+
+ vulkan_command_buffer_free(context, pool, command_buffer);
+}
+
+void vulkan_buffer_copy_to(vulkan_context* context, VkCommandPool pool, VkFence fence,
+ VkQueue queue, VkBuffer source, u64 source_offset, VkBuffer dest,
+ u64 dest_offset, u64 size) {
+ vkQueueWaitIdle(queue);
+
+ vulkan_command_buffer temp_cmd_buf;
+ vulkan_command_buffer_allocate_and_begin_oneshot(context, pool, &temp_cmd_buf);
+
+ VkBufferCopy copy_region;
+ copy_region.srcOffset = source_offset;
+ copy_region.dstOffset = dest_offset;
+ copy_region.size = size;
+
+ vkCmdCopyBuffer(temp_cmd_buf.handle, source, dest, 1, &copy_region);
+
+ vulkan_command_buffer_end_oneshot(context, pool, &temp_cmd_buf, queue);
+}
+
+void vulkan_swapchain_create(vulkan_context* context, u32 width, u32 height,
+ vulkan_swapchain* out_swapchain) {
+ VkExtent2D swapchain_extent = { width, height };
+ out_swapchain->max_frames_in_flight = 2; // support triple buffering
+
+ // find a format
+ bool found;
+ for (u32 i = 0; i < context->device.swapchain_support.format_count; i++) {
+ VkSurfaceFormatKHR format = context->device.swapchain_support.formats[i];
+ if (format.format == VK_FORMAT_B8G8R8A8_UNORM &&
+ format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+ out_swapchain->image_format = format;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ out_swapchain->image_format = context->device.swapchain_support.formats[0];
+ }
+
+ VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; // guaranteed to be implemented
+ // TODO: look for mailbox - https://youtu.be/jWKVb_QdSNM?si=bHcd3sEf-M0x3QwH&t=1687
+
+ // TODO: requery swapchain support
+
+ u32 image_count = context->device.swapchain_support.capabilities.minImageCount;
+
+ VkSwapchainCreateInfoKHR swapchain_create_info = { VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR };
+ swapchain_create_info.surface = context->surface;
+ swapchain_create_info.minImageCount = image_count;
+ swapchain_create_info.imageFormat = out_swapchain->image_format.format;
+ swapchain_create_info.imageColorSpace = out_swapchain->image_format.colorSpace;
+ DEBUG("Image extent %d %d\n", swapchain_extent.width, swapchain_extent.height);
+ swapchain_create_info.imageExtent = swapchain_extent;
+ swapchain_create_info.imageArrayLayers = 1;
+ swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ swapchain_create_info.queueFamilyIndexCount = 0;
+ swapchain_create_info.pQueueFamilyIndices = 0;
+
+ swapchain_create_info.preTransform =
+ context->device.swapchain_support.capabilities.currentTransform;
+ swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+ swapchain_create_info.presentMode = present_mode;
+ swapchain_create_info.clipped = VK_TRUE;
+ swapchain_create_info.oldSwapchain = 0;
+
+ TRACE("Create swapchain");
+ VK_CHECK(vkCreateSwapchainKHR(context->device.logical_device, &swapchain_create_info,
+ context->allocator, &out_swapchain->handle));
+
+ context->current_frame = 0;
+
+ // images
+ out_swapchain->image_count = 0;
+ vkGetSwapchainImagesKHR(context->device.logical_device, out_swapchain->handle,
+ &out_swapchain->image_count, 0);
+
+ if (!out_swapchain->images) {
+ out_swapchain->images = (VkImage*)malloc(sizeof(VkImage) * out_swapchain->image_count);
+ }
+ if (!out_swapchain->views) {
+ out_swapchain->views = (VkImageView*)malloc(sizeof(VkImage) * out_swapchain->image_count);
+ }
+ VK_CHECK(vkGetSwapchainImagesKHR(context->device.logical_device, out_swapchain->handle,
+ &out_swapchain->image_count, out_swapchain->images));
+
+ // views
+ for (int i = 0; i < out_swapchain->image_count; i++) {
+ VkImageViewCreateInfo view_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
+ view_info.image = out_swapchain->images[i];
+ view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ view_info.format = out_swapchain->image_format.format;
+ view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ view_info.subresourceRange.baseMipLevel = 0;
+ view_info.subresourceRange.levelCount = 1;
+ view_info.subresourceRange.baseArrayLayer = 0;
+ view_info.subresourceRange.layerCount = 1;
+
+ VK_CHECK(vkCreateImageView(context->device.logical_device, &view_info, context->allocator,
+ &out_swapchain->views[i]));
+ }
+
+ // depth attachment
+ if (!vulkan_device_detect_depth_format(&context->device)) {
+ ERROR_EXIT("Failed to find a supported depth format");
+ }
+ vulkan_image_create(context, VK_IMAGE_TYPE_2D, swapchain_extent.width, swapchain_extent.height,
+ context->device.depth_format, VK_IMAGE_TILING_OPTIMAL,
+ VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, true, VK_IMAGE_ASPECT_DEPTH_BIT,
+ &out_swapchain->depth_attachment);
+ INFO("Depth attachment created");
+
+ INFO("Swapchain created successfully");
+}
+
+// TODO: swapchain destroy
+void vulkan_swapchain_recreate(vulkan_context* context, u32 width, u32 height,
+ vulkan_swapchain* swapchain) {
+ // TODO
+}
+bool vulkan_swapchain_acquire_next_image_index(vulkan_context* context, vulkan_swapchain* swapchain,
+ u64 timeout_ns,
+ VkSemaphore image_available_semaphore, VkFence fence,
+ u32* out_image_index) {
+ VkResult result =
+ vkAcquireNextImageKHR(context->device.logical_device, swapchain->handle, timeout_ns,
+ image_available_semaphore, fence, out_image_index);
+
+ if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
+ FATAL("Failed to acquire swapchain image");
+ return false;
+ }
+
+ return true;
+}
+
+void vulkan_swapchain_present(vulkan_context* context, vulkan_swapchain* swapchain,
+ VkQueue graphics_queue, VkQueue present_queue,
+ VkSemaphore render_complete_semaphore, u32 present_image_index) {
+ // return image to swapchain for presentation
+ VkPresentInfoKHR present_info = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR };
+ present_info.waitSemaphoreCount = 1;
+ present_info.pWaitSemaphores = &render_complete_semaphore;
+ present_info.swapchainCount = 1;
+ present_info.pSwapchains = &swapchain->handle;
+ present_info.pImageIndices = &present_image_index;
+ present_info.pResults = 0;
+
+ VkResult result = vkQueuePresentKHR(present_queue, &present_info);
+ if (result != VK_SUCCESS) {
+ if (result == VK_SUBOPTIMAL_KHR) {
+ // WARN("Swapchain suboptimal - maybe resize needed?");
+ } else {
+ FATAL("Failed to present swapchain iamge");
+ }
+ }
+
+ // advance the current frame
+ context->current_frame = (context->current_frame + 1) % swapchain->max_frames_in_flight;
+}
+
+void vulkan_renderpass_create(vulkan_context* context, vulkan_renderpass* out_renderpass,
+ vec4 render_area, vec4 clear_colour, f32 depth, u32 stencil) {
+ out_renderpass->render_area = render_area;
+ out_renderpass->clear_colour = clear_colour;
+ out_renderpass->depth = depth;
+ out_renderpass->stencil = stencil;
+
+ // main subpass
+ VkSubpassDescription subpass = {};
+ subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+
+ // attachments
+ u32 attachment_desc_count = 2;
+ VkAttachmentDescription attachment_descriptions[2];
+
+ // Colour attachment
+ VkAttachmentDescription color_attachment;
+ color_attachment.format = context->swapchain.image_format.format;
+ color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+ color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+ color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+ color_attachment.flags = 0;
+
+ attachment_descriptions[0] = color_attachment;
+
+ VkAttachmentReference color_attachment_reference;
+ color_attachment_reference.attachment = 0;
+ color_attachment_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+ subpass.colorAttachmentCount = 1;
+ subpass.pColorAttachments = &color_attachment_reference;
+
+ // Depth attachment
+ VkAttachmentDescription depth_attachment;
+ depth_attachment.format = context->device.depth_format;
+ depth_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
+ depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ depth_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ depth_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ depth_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ depth_attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ depth_attachment.flags = 0;
+
+ attachment_descriptions[1] = depth_attachment;
+
+ VkAttachmentReference depth_attachment_reference;
+ depth_attachment_reference.attachment = 1;
+ depth_attachment_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+ subpass.pDepthStencilAttachment = &depth_attachment_reference;
+
+ // TODO: other attachment styles
+
+ subpass.inputAttachmentCount = 0;
+ subpass.pInputAttachments = 0;
+ subpass.pResolveAttachments = 0;
+ subpass.preserveAttachmentCount = 0;
+ subpass.preserveAttachmentCount = 0;
+
+ // renderpass dependencies
+ VkSubpassDependency dependency;
+ dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
+ dependency.dstSubpass = 0;
+ dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ dependency.srcAccessMask = 0;
+ dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ dependency.dstAccessMask =
+ VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+ dependency.dependencyFlags = 0;
+
+ VkRenderPassCreateInfo render_pass_create_info = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
+ render_pass_create_info.attachmentCount = attachment_desc_count;
+ render_pass_create_info.pAttachments = attachment_descriptions;
+ render_pass_create_info.subpassCount = 1;
+ render_pass_create_info.pSubpasses = &subpass;
+ render_pass_create_info.dependencyCount = 1;
+ render_pass_create_info.pDependencies = &dependency;
+ render_pass_create_info.pNext = 0;
+ render_pass_create_info.flags = 0;
+
+ VK_CHECK(vkCreateRenderPass(context->device.logical_device, &render_pass_create_info,
+ context->allocator, &out_renderpass->handle));
+}
+
+// TODO: renderpass destroy
+
+void vulkan_renderpass_begin(vulkan_command_buffer* command_buffer, vulkan_renderpass* renderpass,
+ VkFramebuffer framebuffer) {
+ VkRenderPassBeginInfo begin_info = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
+ begin_info.renderPass = renderpass->handle;
+ begin_info.framebuffer = framebuffer;
+ begin_info.renderArea.offset.x = renderpass->render_area.x;
+ begin_info.renderArea.offset.y = renderpass->render_area.y;
+ begin_info.renderArea.extent.width = renderpass->render_area.z;
+ begin_info.renderArea.extent.height = renderpass->render_area.w;
+
+ VkClearValue clear_values[2];
+ memset(&clear_values, 0, sizeof(VkClearValue) * 2);
+ clear_values[0].color.float32[0] = renderpass->clear_colour.x;
+ clear_values[0].color.float32[1] = renderpass->clear_colour.y;
+ clear_values[0].color.float32[2] = renderpass->clear_colour.z;
+ clear_values[0].color.float32[3] = renderpass->clear_colour.w;
+ clear_values[1].depthStencil.depth = renderpass->depth;
+ clear_values[1].depthStencil.stencil = renderpass->stencil;
+
+ begin_info.clearValueCount = 2;
+ begin_info.pClearValues = clear_values;
+
+ vkCmdBeginRenderPass(command_buffer->handle, &begin_info, VK_SUBPASS_CONTENTS_INLINE);
+ command_buffer->state = COMMAND_BUFFER_STATE_IN_RENDER_PASS;
+}
+
+void vulkan_renderpass_end(vulkan_command_buffer* command_buffer, vulkan_renderpass* renderpass) {
+ vkCmdEndRenderPass(command_buffer->handle);
+ command_buffer->state = COMMAND_BUFFER_STATE_RECORDING;
+}
+
+bool create_buffers(vulkan_context* context) {
+ VkMemoryPropertyFlagBits mem_prop_flags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+
+ const u64 vertex_buffer_size = sizeof(vertex_pos) * 1024 * 1024;
+ if (!vulkan_buffer_create(context, vertex_buffer_size,
+ VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ mem_prop_flags, true, &context->object_vertex_buffer)) {
+ ERROR("couldnt create vertex buffer");
+ return false;
+ }
+
+ context->geometry_vertex_offset = 0;
+
+ const u64 index1_buffer_size = sizeof(u32) * 1024 * 1024;
+ if (!vulkan_buffer_create(context, index1_buffer_size,
+ VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT,
+ mem_prop_flags, true, &context->object_index_buffer)) {
+ ERROR("couldnt create vertex buffer");
+ return false;
+ }
+ context->geometry_index_offset = 0;
+
+ return true;
+}
+
+void create_command_buffers(renderer* ren) {
+ if (!context.gfx_command_buffers) {
+ context.gfx_command_buffers = vulkan_command_buffer_darray_new(context.swapchain.image_count);
+ }
+
+ for (u32 i = 0; i < context.swapchain.image_count; i++) {
+ vulkan_command_buffer_allocate(&context, context.device.gfx_command_pool, true,
+ &context.gfx_command_buffers->data[i]);
+ }
+}
+
+void upload_data_range(vulkan_context* context, VkCommandPool pool, VkFence fence, VkQueue queue,
+ vulkan_buffer* buffer, u64 offset, u64 size, void* data) {
+ VkBufferUsageFlags flags =
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+ vulkan_buffer staging;
+ vulkan_buffer_create(context, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, flags, true, &staging);
+ // load data into staging buffer
+ printf("Size: %ld\n", size);
+ vulkan_buffer_load_data(context, &staging, 0, size, 0, data);
+
+ // copy
+ vulkan_buffer_copy_to(context, pool, fence, queue, staging.handle, 0, buffer->handle, offset,
+ size);
+
+ vkDestroyBuffer(context->device.logical_device, staging.handle, context->allocator);
+}
+
+void regenerate_framebuffers(renderer* ren, vulkan_swapchain* swapchain,
+ vulkan_renderpass* renderpass) {
+ for (u32 i = 0; i < swapchain->image_count; i++) {
+ u32 attachment_count = 2; // one for depth, one for colour
+
+ VkImageView attachments[2] = { swapchain->views[i], swapchain->depth_attachment.view };
+
+ vulkan_framebuffer_create(&context, renderpass, context.framebuffer_width,
+ context.framebuffer_height, 2, attachments,
+ &swapchain->framebuffers->data[i]);
+ }
+}
+
+void vulkan_fence_create(vulkan_context* context, bool create_signaled, vulkan_fence* out_fence) {
+ out_fence->is_signaled = create_signaled;
+ VkFenceCreateInfo fence_create_info = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
+ if (out_fence->is_signaled) {
+ fence_create_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+ }
+
+ vkCreateFence(context->device.logical_device, &fence_create_info, context->allocator,
+ &out_fence->handle);
+}
+
+// TODO: vulkan_fence_destroy
+
+bool vulkan_fence_wait(vulkan_context* context, vulkan_fence* fence, u64 timeout_ns) {
+ if (!fence->is_signaled) {
+ VkResult result =
+ vkWaitForFences(context->device.logical_device, 1, &fence->handle, true, timeout_ns);
+ switch (result) {
+ case VK_SUCCESS:
+ fence->is_signaled = true;
+ return true;
+ case VK_TIMEOUT:
+ WARN("vk_fence_wait - Timed out");
+ break;
+ default:
+ ERROR("vk_fence_wait - Unhanlded error type");
+ break;
+ }
+ } else {
+ return true;
+ }
+
+ return false;
+}
+void vulkan_fence_reset(vulkan_context* context, vulkan_fence* fence) {
+ if (fence->is_signaled) {
+ vkResetFences(context->device.logical_device, 1, &fence->handle);
+ fence->is_signaled = false;
+ }
+}
+
+bool gfx_backend_init(renderer* ren) {
+ INFO("loading Vulkan backend");
+
+ vulkan_state* internal = malloc(sizeof(vulkan_state));
+ ren->backend_state = (void*)internal;
+
+ context.allocator = 0; // TODO: custom allocator
+
+ context.framebuffer_width = SCR_WIDTH;
+ context.framebuffer_height = SCR_HEIGHT;
+
+ // Setup Vulkan instance
+ VkApplicationInfo app_info = { VK_STRUCTURE_TYPE_APPLICATION_INFO };
+ app_info.apiVersion = VK_API_VERSION_1_3;
+ app_info.pApplicationName = ren->config.window_name;
+ app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
+ app_info.pEngineName = "Celeritas Engine";
+ app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
+
+ VkInstanceCreateInfo create_info = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO };
+ create_info.pApplicationInfo = &app_info;
+
+ cstr_darray* required_extensions = cstr_darray_new(2);
+ cstr_darray_push(required_extensions, VK_KHR_SURFACE_EXTENSION_NAME);
+
+ plat_get_required_extension_names(required_extensions);
+
+#if defined(CDEBUG)
+ cstr_darray_push(required_extensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
+
+ DEBUG("Required extensions:");
+ for (u32 i = 0; i < cstr_darray_len(required_extensions); i++) {
+ DEBUG(" %s", required_extensions->data[i]);
+ }
+#endif
+
+ create_info.enabledExtensionCount = cstr_darray_len(required_extensions);
+ create_info.ppEnabledExtensionNames = required_extensions->data;
+
+ // Validation layers
+ create_info.enabledLayerCount = 0;
+ create_info.ppEnabledLayerNames = 0;
+#if defined(CDEBUG)
+ INFO("Validation layers enabled");
+ cstr_darray* desired_validation_layers = cstr_darray_new(1);
+ cstr_darray_push(desired_validation_layers, "VK_LAYER_KHRONOS_validation");
+
+ u32 n_available_layers = 0;
+ VK_CHECK(vkEnumerateInstanceLayerProperties(&n_available_layers, 0));
+ TRACE("%d available layers", n_available_layers);
+ VkLayerProperties_darray* available_layers = VkLayerProperties_darray_new(n_available_layers);
+ VK_CHECK(vkEnumerateInstanceLayerProperties(&n_available_layers, available_layers->data));
+
+ for (int i = 0; i < cstr_darray_len(desired_validation_layers); i++) {
+ // look through layers to make sure we can find the ones we want
+ bool found = false;
+ for (int j = 0; j < n_available_layers; j++) {
+ if (str8_equals(str8_cstr_view(desired_validation_layers->data[i]),
+ str8_cstr_view(available_layers->data[j].layerName))) {
+ found = true;
+ TRACE("Found layer %s", desired_validation_layers->data[i]);
+ break;
+ }
+ }
+
+ if (!found) {
+ FATAL("Required validation is missing %s", desired_validation_layers->data[i]);
+ return false;
+ }
+ }
+ INFO("All validation layers are present");
+ create_info.enabledLayerCount = cstr_darray_len(desired_validation_layers);
+ create_info.ppEnabledLayerNames = desired_validation_layers->data;
+#endif
+
+ VkResult result = vkCreateInstance(&create_info, NULL, &context.instance);
+ if (result != VK_SUCCESS) {
+ ERROR("vkCreateInstance failed with result: %u", result);
+ return false;
+ }
+
+ // Debugger
+#if defined(CDEBUG)
+ DEBUG("Creating Vulkan debugger")
+ u32 log_severity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
+ VkDebugUtilsMessengerCreateInfoEXT debug_create_info = {
+ VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT
+ };
+ debug_create_info.messageSeverity = log_severity;
+ debug_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
+ debug_create_info.pfnUserCallback = vk_debug_callback;
+
+ PFN_vkCreateDebugUtilsMessengerEXT func =
+ (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(context.instance,
+ "vkCreateDebugUtilsMessengerEXT");
+ assert(func);
+ VK_CHECK(func(context.instance, &debug_create_info, context.allocator, &context.vk_debugger));
+ DEBUG("Vulkan debugger created");
+
+#endif
+
+ // Surface creation
+ DEBUG("Create SurfaceKHR")
+ VkSurfaceKHR surface;
+ VK_CHECK(glfwCreateWindowSurface(context.instance, ren->window, NULL, &surface));
+ context.surface = surface;
+ DEBUG("Vulkan surface created")
+
+ // Device creation
+ if (!vulkan_device_create(&context)) {
+ FATAL("device creation failed");
+ return false;
+ }
+
+ // Swapchain creation
+ vulkan_swapchain_create(&context, SCR_WIDTH, SCR_HEIGHT, &context.swapchain);
+
+ // Renderpass creation
+ vulkan_renderpass_create(&context, &context.main_renderpass,
+ vec4(0, 0, context.framebuffer_width, context.framebuffer_height),
+ rgba_to_vec4(COLOUR_SEA_GREEN), 1.0, 0);
+
+ // Framebiffers creation
+ context.swapchain.framebuffers = vulkan_framebuffer_darray_new(context.swapchain.image_count);
+ regenerate_framebuffers(ren, &context.swapchain, &context.main_renderpass);
+ INFO("Framebuffers created");
+
+ // Command buffers creation
+ create_command_buffers(ren);
+ INFO("Command buffers created");
+
+ // Sync objects
+ context.image_available_semaphores =
+ calloc(context.swapchain.max_frames_in_flight, sizeof(VkSemaphore));
+ context.queue_complete_semaphores =
+ calloc(context.swapchain.max_frames_in_flight, sizeof(VkSemaphore));
+ context.in_flight_fences = calloc(context.swapchain.max_frames_in_flight, sizeof(vulkan_fence));
+
+ for (u8 i = 0; i < context.swapchain.max_frames_in_flight; i++) {
+ VkSemaphoreCreateInfo semaphore_create_info = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
+ vkCreateSemaphore(context.device.logical_device, &semaphore_create_info, context.allocator,
+ &context.image_available_semaphores[i]);
+ vkCreateSemaphore(context.device.logical_device, &semaphore_create_info, context.allocator,
+ &context.queue_complete_semaphores[i]);
+
+ // create the fence in a signaled state
+ vulkan_fence_create(&context, true, &context.in_flight_fences[i]);
+ }
+
+ context.images_in_flight = malloc(sizeof(vulkan_fence*) * context.swapchain.max_frames_in_flight);
+ for (u8 i = 0; i < context.swapchain.max_frames_in_flight; i++) {
+ context.images_in_flight[i] = 0;
+ }
+ INFO("Sync objects created");
+
+ // Shader modules
+ vulkan_object_shader_create(&context, &context.object_shader);
+ INFO("Compiled shader modules")
+
+ create_buffers(&context);
+ INFO("Created buffers");
+
+ // TODO: temporary test code
+
+ mesh cube = prim_cube_mesh_create();
+
+ vertex* verts = malloc(sizeof(vertex) * cube.vertices->len);
+
+ f32 scale = 3.0;
+ for (size_t i = 0; i < cube.vertices->len; i++) {
+ verts[i].position = vec3_mult(cube.vertices->data[i].position, scale);
+ verts[i].normal = cube.vertices->data[i].normal;
+ verts[i].uv = cube.vertices->data[i].uv;
+ }
+
+ // const f32 s = 1.0;
+ // const u32 vert_count = 4;
+ // vertex_pos verts[4] = { 0 };
+
+ // verts[0].pos.x = -0.5 * s;
+ // verts[0].pos.y = -0.5 * s;
+
+ // verts[1].pos.x = 0.5 * s;
+ // verts[1].pos.y = 0.5 * s;
+
+ // verts[2].pos.x = -0.5 * s;
+ // verts[2].pos.y = 0.5 * s;
+
+ // verts[3].pos.x = 0.5 * s;
+ // verts[3].pos.y = -0.5 * s;
+
+ // const u32 index_count = 6;
+ // u32 indices[6] = { 0, 1, 2, 0, 3, 1 };
+
+ upload_data_range(&context, context.device.gfx_command_pool, 0, context.device.graphics_queue,
+ &context.object_vertex_buffer, 0, sizeof(vertex) * cube.vertices->len, verts);
+ TRACE("Uploaded vertex data");
+ upload_data_range(&context, context.device.gfx_command_pool, 0, context.device.graphics_queue,
+ &context.object_index_buffer, 0, sizeof(u32) * cube.indices_len, cube.indices);
+ TRACE("Uploaded index data");
+ vertex_darray_free(cube.vertices);
+ free(cube.indices);
+
+ // upload texture
+
+ // --- End test code
+
+ INFO("Vulkan renderer initialisation succeeded");
+ return true;
+}
+
+void gfx_backend_shutdown(renderer* ren) {
+ DEBUG("Destroying Vulkan debugger");
+ if (context.vk_debugger) {
+ PFN_vkDestroyDebugUtilsMessengerEXT func =
+ (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(
+ context.instance, "vkDestroyDebugUtilsMessengerEXT");
+ func(context.instance, context.vk_debugger, context.allocator);
+ }
+
+ DEBUG("Destroying Vulkan instance...");
+ vkDestroyInstance(context.instance, context.allocator);
+}
+
+void backend_begin_frame(renderer* ren, f32 delta_time) {
+ vulkan_device* device = &context.device;
+
+ // TODO: resize gubbins
+
+ if (!vulkan_fence_wait(&context, &context.in_flight_fences[context.current_frame], UINT64_MAX)) {
+ WARN("In-flight fence wait failure");
+ }
+
+ if (!vulkan_swapchain_acquire_next_image_index(
+ &context, &context.swapchain, UINT64_MAX,
+ context.image_available_semaphores[context.current_frame], 0, &context.image_index)) {
+ WARN("couldnt acquire swapchain next image");
+ }
+
+ vulkan_command_buffer* command_buffer = &context.gfx_command_buffers->data[context.image_index];
+ vulkan_command_buffer_reset(command_buffer);
+ vulkan_command_buffer_begin(command_buffer, false, false, false);
+
+ VkViewport viewport;
+ viewport.x = 0.0;
+ viewport.y = 0;
+ viewport.width = (f32)context.framebuffer_width;
+ viewport.height = (f32)context.framebuffer_height;
+ viewport.minDepth = 0.0;
+ viewport.maxDepth = 1.0;
+
+ VkRect2D scissor;
+ scissor.offset.x = scissor.offset.y = 0;
+ scissor.extent.width = context.framebuffer_width;
+ scissor.extent.height = context.framebuffer_height;
+
+ vkCmdSetViewport(command_buffer->handle, 0, 1, &viewport);
+ vkCmdSetScissor(command_buffer->handle, 0, 1, &scissor);
+
+ context.main_renderpass.render_area.z = context.framebuffer_width;
+ context.main_renderpass.render_area.w = context.framebuffer_height;
+
+ vulkan_renderpass_begin(command_buffer, &context.main_renderpass,
+ context.swapchain.framebuffers->data[context.image_index].handle);
+}
+
+void texture_data_upload(texture* tex) {
+ printf("Texture name %s\n", tex->name);
+ tex->backend_data = malloc(sizeof(vulkan_texture_data));
+ vulkan_texture_data* data = (vulkan_texture_data*)tex->backend_data;
+ printf("Texture (%s) details: \n width %d\n height %d\n channel count %d\n", tex->name,
+ tex->width, tex->height, tex->channel_count);
+ VkDeviceSize image_size = tex->width * tex->height * max(tex->channel_count, 4);
+
+ TRACE("Creating buffer of size %ld", image_size);
+
+ VkFormat image_format = VK_FORMAT_R8G8B8A8_SRGB;
+
+ VkBufferUsageFlags usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
+ VkMemoryPropertyFlags memory_prop_flags =
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
+ vulkan_buffer staging;
+ vulkan_buffer_create(&context, image_size, usage, memory_prop_flags, true, &staging);
+ DEBUG("Uploading image data");
+ vulkan_buffer_load_data(&context, &staging, 0, image_size, 0, tex->image_data);
+ INFO("Loaded iamge data!");
+
+ vulkan_image_create(
+ &context, VK_IMAGE_TYPE_2D, tex->width, tex->height, image_format, VK_IMAGE_TILING_OPTIMAL,
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, true, VK_IMAGE_ASPECT_COLOR_BIT, &data->image);
+
+ vulkan_command_buffer temp_buffer;
+ vulkan_command_buffer_allocate_and_begin_oneshot(&context, context.device.gfx_command_pool,
+ &temp_buffer);
+
+ vulkan_image_transition_layout(&context, &temp_buffer, &data->image, image_format,
+ VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+
+ vulkan_image_copy_from_buffer(&data->image, staging.handle, &temp_buffer);
+
+ vulkan_image_transition_layout(&context, &temp_buffer, &data->image, image_format,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+ vulkan_command_buffer_end_oneshot(&context, context.device.gfx_command_pool, &temp_buffer,
+ context.device.graphics_queue);
+
+ VkSamplerCreateInfo sampler_info = { VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO };
+ sampler_info.magFilter = VK_FILTER_LINEAR;
+ sampler_info.minFilter = VK_FILTER_LINEAR;
+ sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+ sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+ sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+ sampler_info.anisotropyEnable = VK_TRUE;
+ sampler_info.maxAnisotropy = 16;
+ sampler_info.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
+ sampler_info.unnormalizedCoordinates = VK_FALSE;
+ sampler_info.compareEnable = VK_FALSE;
+ sampler_info.compareOp = VK_COMPARE_OP_ALWAYS;
+ sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+ sampler_info.mipLodBias = 0.0;
+ sampler_info.minLod = 0.0;
+ sampler_info.maxLod = 0.0;
+
+ VkResult res = vkCreateSampler(context.device.logical_device, &sampler_info, context.allocator,
+ &data->sampler);
+ if (res != VK_SUCCESS) {
+ ERROR("Error creating texture sampler for image %s", tex->name);
+ return;
+ }
+
+ tex->image_data = (void*)data;
+}
+
+// TODO: destroy texture
+
+void backend_end_frame(renderer* ren, f32 delta_time) {
+ vulkan_command_buffer* command_buffer = &context.gfx_command_buffers->data[context.image_index];
+
+ vulkan_renderpass_end(command_buffer, &context.main_renderpass);
+
+ vulkan_command_buffer_end(command_buffer);
+
+ // TODO: wait on fence - https://youtu.be/hRL71D1f3pU?si=nLJx-ZsemDBeQiQ1&t=1037
+
+ context.images_in_flight[context.image_index] = &context.in_flight_fences[context.current_frame];
+
+ vulkan_fence_reset(&context, &context.in_flight_fences[context.current_frame]);
+
+ VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
+ submit_info.commandBufferCount = 1;
+ submit_info.pCommandBuffers = &command_buffer->handle;
+ submit_info.signalSemaphoreCount = 1;
+ submit_info.pSignalSemaphores = &context.queue_complete_semaphores[context.current_frame];
+ submit_info.waitSemaphoreCount = 1;
+ submit_info.pWaitSemaphores = &context.image_available_semaphores[context.current_frame];
+
+ VkPipelineStageFlags flags[1] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
+ submit_info.pWaitDstStageMask = flags;
+
+ VkResult result = vkQueueSubmit(context.device.graphics_queue, 1, &submit_info,
+ context.in_flight_fences[context.current_frame].handle);
+
+ if (result != VK_SUCCESS) {
+ ERROR("queue submission failed. fark.");
+ }
+
+ vulkan_command_buffer_update_submitted(command_buffer);
+
+ vulkan_swapchain_present(
+ &context, &context.swapchain, context.device.graphics_queue, context.device.graphics_queue,
+ context.queue_complete_semaphores[context.current_frame], context.image_index);
+}
+
+void gfx_backend_draw_frame(renderer* ren, camera* cam, mat4 model, texture* tex) {
+ backend_begin_frame(ren, 16.0);
+
+ mat4 proj;
+ mat4 view;
+
+ camera_view_projection(cam, SCR_HEIGHT, SCR_WIDTH, &view, &proj);
+
+ context.object_shader.texture_data = (vulkan_texture_data*)tex->image_data;
+ gfx_backend_update_global_state(proj, view, cam->position, vec4(1.0, 1.0, 1.0, 1.0), 0);
+
+ vulkan_object_shader_update_object(&context, &context.object_shader, model);
+
+ backend_end_frame(ren, 16.0);
+}
+
+void gfx_backend_update_global_state(mat4 projection, mat4 view, vec3 view_pos, vec4 ambient_colour,
+ i32 mode) {
+ vulkan_object_shader_use(&context, &context.object_shader);
+
+ vulkan_object_shader_update_global_state(&context, &context.object_shader);
+ context.object_shader.global_ubo.projection = projection;
+ context.object_shader.global_ubo.view = view;
+ // TODO: other UBO properties
+}
+
+void clear_screen(vec3 colour) {}
+
+void bind_texture(shader s, texture* tex, u32 slot) {}
+void bind_mesh_vertex_buffer(void* backend, mesh* mesh) {}
+void draw_primitives(cel_primitive_topology primitive, u32 start_index, u32 count) {}
+
+shader shader_create_separate(const char* vert_shader, const char* frag_shader) {}
+void set_shader(shader s) {}
+
+void uniform_vec3f(u32 program_id, const char* uniform_name, vec3* value) {}
+void uniform_f32(u32 program_id, const char* uniform_name, f32 value) {}
+void uniform_i32(u32 program_id, const char* uniform_name, i32 value) {}
+void uniform_mat4f(u32 program_id, const char* uniform_name, mat4* value) {}
+
+VKAPI_ATTR VkBool32 VKAPI_CALL vk_debug_callback(
+ VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT flags,
+ const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) {
+ switch (severity) {
+ default:
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
+ ERROR("%s", callback_data->pMessage);
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
+ WARN("%s", callback_data->pMessage);
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
+ INFO("%s", callback_data->pMessage);
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
+ TRACE("%s", callback_data->pMessage);
+ break;
+ }
+ return VK_FALSE;
+}
+
+#endif \ No newline at end of file
diff --git a/src/renderer/backends/vulkan/vulkan_glossary.md b/src/renderer/backends/vulkan/vulkan_glossary.md
new file mode 100644
index 0000000..4214f9d
--- /dev/null
+++ b/src/renderer/backends/vulkan/vulkan_glossary.md
@@ -0,0 +1,18 @@
+# Vulkan Glossary
+
+*from https://vkguide.dev/docs/introduction/vulkan_execution/*
+
+- **VkInstance**: The Vulkan context, used to access drivers.
+- **VkPhysicalDevice**: A GPU. Used to query physical GPU details, like features, capabilities, memory size, etc.
+- **VkDevice**: The “logical” GPU context that you actually execute things on.
+- **VkBuffer**: A chunk of GPU visible memory.
+- **VkImage**: A texture you can write to and read from.
+- **VkPipeline**: Holds the state of the gpu needed to draw. For example: shaders, rasterization options, depth settings.
+- **VkRenderPass**: Holds information about the images you are rendering into. All drawing commands have to be done inside a renderpass. Only used in legacy vkguide.
+- **VkFrameBuffer**: Holds the target images for a renderpass. Only used in legacy vkguide.
+- **VkCommandBuffer**: Encodes GPU commands. All execution that is performed on the GPU itself (not in the driver) has to be encoded in a VkCommandBuffer.
+- **VkQueue**: Execution “port” for commands. GPUs will have a set of queues with different properties. Some allow only graphics commands, others only allow memory commands, etc. Command buffers are executed by submitting them into a queue, which will copy the rendering commands onto the GPU for execution.
+- **VkDescriptorSet**: Holds the binding information that connects shader inputs to data such as VkBuffer resources and VkImage textures. Think of it as a set of gpu-side pointers that you bind once.
+- **VkSwapchainKHR**: Holds the images for the screen. It allows you to render things into a visible window. The KHR suffix shows that it comes from an extension, which in this case is VK_KHR_swapchain.
+- **VkSemaphore**: Synchronizes GPU to GPU execution of commands. Used for syncing multiple command buffer submissions one after another.
+- **VkFence**: Synchronizes GPU to CPU execution of commands. Used to know if a command buffer has finished being executed on the GPU.
diff --git a/src/renderer/backends/vulkan_helpers.h b/src/renderer/backends/vulkan_helpers.h
new file mode 100644
index 0000000..3465aed
--- /dev/null
+++ b/src/renderer/backends/vulkan_helpers.h
@@ -0,0 +1,166 @@
+#pragma once
+
+#include <assert.h>
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_core.h>
+
+#include "darray.h"
+#include "defines.h"
+#include "log.h"
+#include "str.h"
+
+#define VULKAN_PHYS_DEVICE_MAX_EXTENSION_NAMES 36
+
+DECL_TYPED_ARRAY(const char*, cstr)
+
+static void plat_get_required_extension_names(cstr_darray* extensions) {
+#ifdef CEL_PLATFORM_LINUX
+ cstr_darray_push(extensions, "VK_KHR_xcb_surface");
+#endif
+}
+
+// TODO(omni): port to using internal assert functions
+#define VK_CHECK(vulkan_expr) \
+ { assert(vulkan_expr == VK_SUCCESS); }
+
+// TODO: typedef struct vk_debugger {} vk_debugger;
+
+typedef struct vulkan_physical_device_requirements {
+ bool graphics;
+ bool present;
+ bool compute;
+ bool transfer;
+ str8 device_ext_names[VULKAN_PHYS_DEVICE_MAX_EXTENSION_NAMES];
+ size_t device_ext_name_count;
+ bool sampler_anistropy;
+ bool discrete_gpu;
+} vulkan_physical_device_requirements;
+
+typedef struct vulkan_physical_device_queue_family_info {
+ u32 graphics_family_index;
+ u32 present_family_index;
+ u32 compute_family_index;
+ u32 transfer_family_index;
+} vulkan_physical_device_queue_family_info;
+
+#define VULKAN_MAX_DEFAULT 32
+
+typedef struct vulkan_swapchain_support_info {
+ VkSurfaceCapabilitiesKHR capabilities;
+ VkSurfaceFormatKHR formats[VULKAN_MAX_DEFAULT];
+ u32 format_count;
+ VkPresentModeKHR present_modes[VULKAN_MAX_DEFAULT];
+ u32 mode_count;
+} vulkan_swapchain_support_info;
+
+VKAPI_ATTR VkBool32 VKAPI_CALL vk_debug_callback(
+ VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT flags,
+ const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data);
+
+void vulkan_device_query_swapchain_support(VkPhysicalDevice device, VkSurfaceKHR surface,
+ vulkan_swapchain_support_info* out_support_info) {
+ // TODO: add VK_CHECK to these calls!
+
+ // Surface capabilities
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &out_support_info->capabilities);
+
+ // Surface formats
+ vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &out_support_info->format_count,
+ 0); // Get number of formats
+ if (out_support_info->format_count > 0) {
+ vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &out_support_info->format_count,
+ out_support_info->formats);
+ }
+
+ // Present Modes
+ vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &out_support_info->mode_count,
+ 0); // Get number of formats
+ if (out_support_info->mode_count > 0) {
+ vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &out_support_info->mode_count,
+ out_support_info->present_modes);
+ }
+}
+
+static bool physical_device_meets_requirements(
+ VkPhysicalDevice device, VkSurfaceKHR surface, const VkPhysicalDeviceProperties* properties,
+ const VkPhysicalDeviceFeatures* features,
+ const vulkan_physical_device_requirements* requirements,
+ vulkan_physical_device_queue_family_info* out_queue_info,
+ vulkan_swapchain_support_info* out_swapchain_support) {
+ // TODO: pass in an arena
+
+ out_queue_info->graphics_family_index = -1;
+ out_queue_info->present_family_index = -1;
+ out_queue_info->compute_family_index = -1;
+ out_queue_info->transfer_family_index = -1;
+
+ if (requirements->discrete_gpu) {
+ if (properties->deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
+ TRACE("Device is not a physical GPU. Skipping.");
+ return false;
+ }
+ }
+
+ u32 queue_family_count = 0;
+ vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, 0);
+ VkQueueFamilyProperties queue_families[queue_family_count];
+ vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families);
+
+ INFO("Graphics | Present | Compute | Transfer | Name");
+ u8 min_transfer_score = 255;
+ for (u32 i = 0; i < queue_family_count; i++) {
+ u8 current_transfer_score = 0;
+
+ // Graphics queue
+ if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ out_queue_info->graphics_family_index = i;
+ current_transfer_score++;
+ }
+
+ // Compute queue
+ if (queue_families[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
+ out_queue_info->compute_family_index = i;
+ current_transfer_score++;
+ }
+
+ // Transfer queue
+ if (queue_families[i].queueFlags & VK_QUEUE_TRANSFER_BIT) {
+ // always take the lowest score transfer index
+ if (current_transfer_score <= min_transfer_score) {
+ min_transfer_score = current_transfer_score;
+ out_queue_info->transfer_family_index = i;
+ }
+ }
+
+ // Present Queue
+ VkBool32 supports_present = VK_FALSE;
+ vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &supports_present);
+ if (supports_present) {
+ out_queue_info->present_family_index = i;
+ }
+ }
+
+ INFO(" %d | %d | %d | %d | %s",
+ out_queue_info->graphics_family_index != -1, out_queue_info->present_family_index != -1,
+ out_queue_info->compute_family_index != -1, out_queue_info->transfer_family_index != -1,
+ properties->deviceName);
+ TRACE("Graphics Family queue index: %d", out_queue_info->graphics_family_index);
+ TRACE("Present Family queue index: %d", out_queue_info->present_family_index);
+ TRACE("Compute Family queue index: %d", out_queue_info->compute_family_index);
+ TRACE("Transfer Family queue index: %d", out_queue_info->transfer_family_index);
+
+ if ((!requirements->graphics ||
+ (requirements->graphics && out_queue_info->graphics_family_index != -1))) {
+ INFO("Physical device meets our requirements! Proceed.");
+
+ vulkan_device_query_swapchain_support(
+ device, surface, out_swapchain_support
+
+ // TODO: error handling i.e. format count = 0 or present mode = 0
+
+ );
+ return true;
+ }
+
+ return false;
+} \ No newline at end of file
diff --git a/src/renderer/cleanroom/README.md b/src/renderer/cleanroom/README.md
new file mode 100644
index 0000000..d510f16
--- /dev/null
+++ b/src/renderer/cleanroom/README.md
@@ -0,0 +1 @@
+# Cleanroom / Re-jig of the renderer structure \ No newline at end of file
diff --git a/src/renderer/cleanroom/backend_vulkan.c b/src/renderer/cleanroom/backend_vulkan.c
new file mode 100644
index 0000000..71a09f3
--- /dev/null
+++ b/src/renderer/cleanroom/backend_vulkan.c
@@ -0,0 +1,65 @@
+#include <stdlib.h>
+#include "ral.h"
+#include "types.h"
+// #include "render_types.h"
+
+#define VULKAN_QUEUES_COUNT 2
+const char* queue_names[VULKAN_QUEUES_COUNT] = { "GRAPHICS", "TRANSFER" };
+
+typedef struct gpu_device {
+} gpu_device;
+
+typedef struct vulkan_context {
+ gpu_device device;
+
+ VkInstance instance;
+
+} vulkan_context;
+
+static vulkan_context context;
+
+static bool select_physical_device(gpu_device* out_device) {}
+
+bool gpu_device_create(gpu_device* out_device) {
+ // Physical device
+ if (!select_physical_device(out_device)) {
+ return false;
+ }
+ INFO("Physical device selected");
+
+ // Logical device
+ VkDeviceQueueCreateInfo queue_create_info[2];
+ //..
+ VkDeviceCreateInfo device_create_info = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
+
+ VkResult result = vkCreateDevice();
+ if (result != VK_SUCCESS) {
+ FATAL("Error creating logical device with status %u\n", result);
+ exit(1);
+ }
+ INFO("Logical device created");
+
+ // Queues
+
+ // Create the command pool
+}
+
+gpu_renderpass* gpu_renderpass_create() {
+ // Allocate it
+ // sets everything up
+ // return pointer to it
+}
+
+void encode_set_pipeline(gpu_cmd_encoder* encoder, pipeline_type kind, gpu_pipeline* pipeline) {
+ // VK_PIPELINE_BIND_POINT_GRAPHICS, &shader->pipeline);
+ if (kind == PIPELINE_GRAPHICS) {
+ // ...
+ } else {
+ // ...
+ }
+}
+
+// --- Drawing
+inline void encode_draw_indexed(gpu_cmd_encoder* encoder, u64 index_count) {
+ vkCmdDrawIndexed(encoder->cmd_buffer, index_count, 1, 0, 0, 0);
+} \ No newline at end of file
diff --git a/src/renderer/cleanroom/backend_vulkan.h b/src/renderer/cleanroom/backend_vulkan.h
new file mode 100644
index 0000000..6798b13
--- /dev/null
+++ b/src/renderer/cleanroom/backend_vulkan.h
@@ -0,0 +1,27 @@
+#pragma once
+#include "cleanroom/ral.h"
+
+#define GPU_SWAPCHAIN_IMG_COUNT 2
+
+typedef struct gpu_swapchain {} gpu_swapchain;
+typedef struct gpu_device {
+ // In Vulkan we store both physical and logical device here
+ VkPhysicalDevice physical_device;
+ VkDevice logical_device;
+ VkPhysicalDeviceProperties properties;
+ VkPhysicalDeviceFeatures features;
+ VkPhysicalDeviceMemoryProperties memory;
+ VkCommandPool pool;
+} gpu_device;
+typedef struct gpu_pipeline {} gpu_pipeline;
+
+typedef struct gpu_renderpass {
+ VkRenderPass vk_handle;
+ VkFramebuffer framebuffers[GPU_SWAPCHAIN_IMG_COUNT];
+ u32
+} gpu_renderpass;
+
+
+typedef struct gpu_cmd_encoder {
+ VkCommandBuffer cmd_buffer;
+} gpu_cmd_encoder; \ No newline at end of file
diff --git a/src/renderer/cleanroom/ral.h b/src/renderer/cleanroom/ral.h
new file mode 100644
index 0000000..a1e9929
--- /dev/null
+++ b/src/renderer/cleanroom/ral.h
@@ -0,0 +1,86 @@
+/**
+ * @file ral.h
+ * @author your name (you@domain.com)
+ * @brief Render Abstraction Layer
+ * @details API that a graphics backend *must* implement
+ * @version 0.1
+ * @date 2024-03-31
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
+#pragma once
+
+#include "cleanroom/types.h"
+#include "defines.h"
+
+// Forward declare structs
+typedef struct gpu_swapchain gpu_swapchain;
+typedef struct gpu_device gpu_device;
+typedef struct gpu_pipeline gpu_pipeline;
+typedef struct gpu_renderpass gpu_renderpass;
+typedef struct gpu_cmd_encoder gpu_cmd_encoder; // Recording
+typedef struct gpu_cmd_buffer gpu_cmd_buffer; // Ready for submission
+
+enum pipeline_kind {
+ GRAPHICS,
+ COMPUTE,
+} pipeline_kind;
+
+typedef struct shader_desc {
+ const char* debug_name;
+ str8 filepath; // where it came from
+ str8 glsl; // contents
+} shader_desc;
+
+struct pipeline_desc {
+ shader_desc vs; /** @brief Vertex shader stage */
+ shader_desc fs; /** @brief Fragment shader stage */
+};
+
+// lifecycle functions
+gpu_device* gpu_device_create();
+void gpu_device_destroy();
+
+gpu_renderpass* gpu_renderpass_create();
+void gpu_renderpass_destroy(gpu_renderpass* pass);
+
+gpu_pipeline* gpu_pipeline_create(enum pipeline_kind kind, struct pipeline_desc description);
+void gpu_pipeline_destroy(gpu_pipeline* pipeline);
+
+void gpu_cmd_encoder_begin();
+void gpu_cmd_encoder_begin_render();
+void gpu_cmd_encoder_begin_compute();
+
+/* Actual commands that we can encode */
+void encode_buffer_copy(gpu_cmd_encoder* encoder, buffer_handle src, u64 src_offset,
+ buffer_handle dst, u64 dst_offset, u64 copy_size);
+void encode_clear_buffer(gpu_cmd_encoder* encoder, buffer_handle buf);
+void encode_set_pipeline(gpu_cmd_encoder* encoder, gpu_pipeline* pipeline);
+// render pass
+void encode_set_vertex_buffer(gpu_cmd_encoder* encoder, buffer_handle buf);
+void encode_set_index_buffer(gpu_cmd_encoder* encoder, buffer_handle buf);
+void encode_set_bind_group();
+void encode_draw(gpu_cmd_encoder* encoder);
+void encode_draw_indexed(gpu_cmd_encoder* encoder, u64 index_count);
+
+// FUTURE: compute passes
+
+/** @brief Finish recording and return a command buffer that can be submitted to a queue */
+gpu_cmd_buffer gpu_cmd_encoder_finish(gpu_cmd_encoder* encoder);
+
+void gpu_queue_submit(gpu_cmd_buffer* buffer);
+
+// Buffers
+void gpu_buffer_create(u64 size);
+void gpu_buffer_destroy(buffer_handle buffer);
+void gpu_buffer_upload();
+void gpu_buffer_bind(buffer_handle buffer);
+
+// Textures
+void gpu_texture_create();
+void gpu_texture_destroy();
+void gpu_texture_upload();
+
+// Samplers
+void gpu_sampler_create(); \ No newline at end of file
diff --git a/src/renderer/cleanroom/renderer.c b/src/renderer/cleanroom/renderer.c
new file mode 100644
index 0000000..a874664
--- /dev/null
+++ b/src/renderer/cleanroom/renderer.c
@@ -0,0 +1,4 @@
+#include "defines.h"
+#include "render_types.h"
+
+bool renderer_init() {} \ No newline at end of file
diff --git a/src/renderer/cleanroom/renderer.h b/src/renderer/cleanroom/renderer.h
new file mode 100644
index 0000000..8012b49
--- /dev/null
+++ b/src/renderer/cleanroom/renderer.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include "cleanroom/ral.h"
+#include "cleanroom/backend_vulkan.h"
+
+typedef struct renderer2 {
+ void* backend_state;
+ gpu_device* device;
+ gpu_pipeline* static_opaque_pipeline;
+} renderer2;
+
+// mesh
+// model
+// material \ No newline at end of file
diff --git a/src/renderer/cleanroom/simda.h b/src/renderer/cleanroom/simda.h
new file mode 100644
index 0000000..d0b4794
--- /dev/null
+++ b/src/renderer/cleanroom/simda.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "maths_types.h"
+
+// 3. SIMA (simplified immediate mode api) / render.h
+// - dont need to worry about uploading mesh data
+// - very useful for debugging
+void imm_draw_cuboid();
+void imm_draw_sphere(vec3 pos, f32 radius, vec4 colour);
+void imm_draw_camera_frustum();
+static void imm_draw_model(
+ const char* model_filepath); // tracks internally whether the model is loaded
+
+static void imm_draw_model(const char* model_filepath) {
+ // check that model is loaded
+ // if not loaded, load model and upload to gpu - LRU cache for models
+ // else submit draw call
+} \ No newline at end of file
diff --git a/src/renderer/cleanroom/types.h b/src/renderer/cleanroom/types.h
new file mode 100644
index 0000000..98c2e21
--- /dev/null
+++ b/src/renderer/cleanroom/types.h
@@ -0,0 +1,182 @@
+#pragma once
+#include "darray.h"
+#include "defines.h"
+#include "maths_types.h"
+#include "str.h"
+
+CORE_DEFINE_HANDLE(buffer_handle);
+CORE_DEFINE_HANDLE(texture_handle);
+CORE_DEFINE_HANDLE(sampler_handle);
+CORE_DEFINE_HANDLE(shader_handle);
+CORE_DEFINE_HANDLE(model_handle);
+
+typedef struct transform_hierarchy {} transform_hierarchy;
+
+/** @brief Texture Description - used by texture creation functions */
+typedef struct texture_desc {
+ // gpu_texture_type tex_type;
+ // gpu_texture_format format;
+ // u32x2 extents;
+} texture_desc;
+
+/*
+ - render_types.h
+ - ral_types.h
+ - ral.h
+ - render.h ?
+*/
+
+// gpu types
+typedef enum gpu_primitive_topology {
+ CEL_PRIMITIVE_TOPOLOGY_POINT,
+ CEL_PRIMITIVE_TOPOLOGY_LINE,
+ CEL_PRIMITIVE_TOPOLOGY_LINE_STRIP,
+ CEL_PRIMITIVE_TOPOLOGY_TRIANGLE,
+ CEL_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
+ CEL_PRIMITIVE_TOPOLOGY_COUNT
+} cel_primitive_topology;
+
+typedef enum gpu_texture_type {
+ CEL_TEXTURE_TYPE_2D,
+ CEL_TEXTURE_TYPE_3D,
+ CEL_TEXTURE_TYPE_2D_ARRAY,
+ CEL_TEXTURE_TYPE_CUBE_MAP,
+ CEL_TEXTURE_TYPE_COUNT
+} gpu_texture_type;
+
+typedef enum gpu_texture_format {
+ CEL_TEXTURE_FORMAT_8_8_8_8_RGBA_UNORM,
+ CEL_TEXTURE_FORMAT_DEPTH_DEFAULT,
+ CEL_TEXTURE_FORMAT_COUNT
+} gpu_texture_format;
+
+/* render_types */
+typedef struct model pbr_material;
+typedef struct model bp_material; // blinn-phong
+
+#include "maths_types.h"
+
+typedef enum vertex_format { VERTEX_STATIC_3D, VERTEX_SPRITE, VERTEX_SKINNED, VERTEX_COUNT } vertex_format;
+
+typedef union vertex {
+ struct {
+ vec3 position;
+ vec4 colour;
+ vec2 tex_coords;
+ vec3 normal;
+ } static_3d; /** @brief standard vertex format for static geometry in 3D */
+
+ struct {
+ vec2 position;
+ vec4 colour;
+ vec2 tex_coords;
+ } sprite; /** @brief vertex format for 2D sprites or quads */
+
+ struct {
+ vec3 position;
+ vec4 colour;
+ vec2 tex_coords;
+ vec3 normal;
+ vec4i bone_ids; // Integer vector for bone IDs
+ vec4 bone_weights; // Weight of each bone's influence
+ } skinned_3d; /** @brief vertex format for skeletal (animated) geometry in 3D */
+} vertex;
+
+KITC_DECL_TYPED_ARRAY(vertex)
+KITC_DECL_TYPED_ARRAY(u32)
+
+typedef struct geometry_data {
+ vertex_format format;
+ vertex_darray vertices;
+ bool has_indices;
+ u32_darray indices;
+} geometry_data;
+
+typedef struct mesh {
+ buffer_handle vertex_buffer;
+ buffer_handle index_buffer;
+ u32 index_count;
+ bool has_indices;
+ geometry_data* vertices; // NULL means it has been freed
+} mesh;
+
+/* Hot reloading:
+C side - reload_model():
+ - load model from disk using existing loader
+ - remove from transform graph so it isnt tried to be drawn
+ -
+
+*/
+
+// TODO: move to some sort of render layer (not inside the abstraction layer)
+typedef struct model {
+ str8 debug_name;
+ mesh* meshes;
+ u32 mesh_count;
+} model;
+
+// ? How to tie together materials and shaders
+
+// Three registers
+// 1. low level graphics api calls "ral"
+// 2. higher level render calls
+// 3. simplified immediate mode API
+
+// 3 - you don't need to know how the renderer works at all
+// 2 - you need to know how the overall renderer is designed
+// 1 - you need to understand graphics API specifics
+
+/* ral.h */
+
+
+// command buffer gubbins
+
+/* --- Backends */
+
+// struct vulkan_backend {
+// gpu_pipeline static_opaque_pipeline;
+// gpu_pipeline skinned_opaque_pipeline;
+// };
+
+/* --- Renderer layer */
+/* render.h */
+
+typedef struct renderer {
+ void* backend_context;
+} renderer;
+
+bool renderer_init(renderer* ren);
+void renderer_shutdown(renderer* ren);
+
+// frontend -- these can be called from say a loop in an example, or via FFI
+texture_handle texture_create(const char* debug_name, texture_desc description, const u8* data);
+
+// Frontend Resources
+void texture_data_upload(texture_handle texture);
+buffer_handle buffer_create(const char* debug_name, u64 size);
+bool buffer_destroy(buffer_handle buffer);
+sampler_handle sampler_create();
+
+void shader_hot_reload(const char* filepath);
+
+// models and meshes are implemented **in terms of the above**
+mesh mesh_create(geometry_data* geometry);
+model_handle model_load(const char* debug_name, const char* filepath);
+
+// Drawing
+
+// void draw_mesh(gpu_cmd_encoder* encoder, mesh* mesh) {
+// encode_set_vertex_buffer(encoder, mesh->vertex_buffer);
+// encode_set_index_buffer(encoder, mesh->index_buffer);
+// encode_draw_indexed(encoder, mesh->index_count)
+// // vkCmdDrawIndexed
+// }
+
+// void draw_scene(arena* frame, model_darray* models, renderer* ren, camera* camera,
+// transform_hierarchy* tfh, scene* scene) {
+// // set the pipeline first
+// encode_set_pipeline()
+// // in open this sets the shader
+// // in vulkan it sets the whole pipeline
+
+// } \ No newline at end of file
diff --git a/src/renderer/render.c b/src/renderer/render.c
index 4e9ad89..b1e2a46 100644
--- a/src/renderer/render.c
+++ b/src/renderer/render.c
@@ -1,21 +1,54 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include "animation.h"
+#include "maths_types.h"
+#include "mem.h"
+#include "transform_hierarchy.h"
+#define STB_IMAGE_IMPLEMENTATION
+#include <stb_image.h>
+
+#define STB_TRUETYPE_IMPLEMENTATION
+#include <stb_truetype.h>
+
#include "render.h"
+#include "render_types.h"
+#include <glad/glad.h>
#include <glfw3.h>
+#include "defines.h"
#include "log.h"
+#include "maths.h"
#include "render_backend.h"
+// FIXME: get rid of these and store dynamic screen realestate
+// in renderer
+#define SCR_WIDTH 1000
+#define SCR_HEIGHT 1000
+
+material DEFAULT_MATERIAL = { 0 };
+
bool renderer_init(renderer* ren) {
INFO("Renderer init");
// NOTE: all platforms use GLFW at the moment but thats subject to change
glfwInit();
+#if defined(CEL_REND_BACKEND_OPENGL)
+ 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);
+#elif defined(CEL_REND_BACKEND_VULKAN)
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+#endif
+
// 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");
+ ERROR("Failed to create GLFW window\n");
glfwTerminate();
return false;
}
@@ -23,14 +56,25 @@ bool renderer_init(renderer* ren) {
glfwMakeContextCurrent(ren->window);
+ DEBUG("init graphics api backend");
if (!gfx_backend_init(ren)) {
FATAL("Couldnt load graphics api backend");
return false;
}
+ ren->blinn_phong =
+ shader_create_separate("assets/shaders/blinn_phong.vert", "assets/shaders/blinn_phong.frag");
+
+ ren->skinned =
+ shader_create_separate("assets/shaders/skinned.vert", "assets/shaders/blinn_phong.frag");
+
+ default_material_init();
+
return true;
}
+void renderer_shutdown(renderer* ren) {}
+
void render_frame_begin(renderer* ren) {
vec3 color = ren->config.clear_colour;
clear_screen(color);
@@ -39,4 +83,331 @@ void render_frame_end(renderer* ren) {
// present frame
glfwSwapBuffers(ren->window);
glfwPollEvents();
+}
+
+void default_material_init() {
+ INFO("Load default material")
+ DEFAULT_MATERIAL.ambient_colour = (vec3){ 0.5, 0.5, 0.5 };
+ DEFAULT_MATERIAL.diffuse = (vec3){ 0.8, 0.8, 0.8 };
+ DEFAULT_MATERIAL.specular = (vec3){ 1.0, 1.0, 1.0 };
+ DEFAULT_MATERIAL.diffuse_texture = texture_data_load("assets/textures/white1x1.png", false);
+ DEFAULT_MATERIAL.specular_texture = texture_data_load("assets/textures/black1x1.png", false);
+ DEFAULT_MATERIAL.spec_exponent = 32.0;
+ strcpy(DEFAULT_MATERIAL.name, "Default");
+ texture_data_upload(&DEFAULT_MATERIAL.diffuse_texture);
+ texture_data_upload(&DEFAULT_MATERIAL.specular_texture);
+}
+
+void model_destroy(model* model) {
+ TRACE("Freeing all data for model %s", model->name);
+ arena_free_all(&model->animation_data_arena);
+ arena_free_storage(&model->animation_data_arena);
+ mesh_darray_free(model->meshes);
+ material_darray_free(model->materials);
+ if (model->is_uploaded) {
+ // Delete gpu buffer data
+ for (u32 i = 0; i < mesh_darray_len(model->meshes); i++) {
+ // FIXME: dont leak Opengl
+ glDeleteBuffers(1, &model->meshes->data[i].vbo);
+ glDeleteVertexArrays(1, &model->meshes->data[i].vao);
+ }
+ }
+}
+
+typedef struct draw_ctx {
+ model_darray* models;
+ renderer* ren;
+ camera* cam;
+ scene* scene;
+} draw_ctx;
+bool draw_scene_node(transform_node* node, void* ctx_data) {
+ if (!node || !node->parent) return true;
+ draw_ctx* ctx = ctx_data;
+ model* m = &ctx->models->data[node->model.raw];
+ draw_model(ctx->ren, ctx->cam, m, &node->world_matrix_tf, ctx->scene);
+ return true;
+}
+
+void draw_scene(arena* frame, model_darray* models, renderer* ren, camera* camera,
+ transform_hierarchy* tfh, scene* scene) {
+ draw_ctx* ctx = arena_alloc(frame, sizeof(draw_ctx));
+ ctx->models = models;
+ ctx->ren = ren;
+ ctx->cam = camera;
+ ctx->scene = scene;
+ transform_hierarchy_dfs(transform_hierarchy_root_node(tfh), draw_scene_node, true, ctx);
+}
+
+void draw_model(renderer* ren, camera* camera, model* model, mat4* model_tf, scene* scene) {
+ // TRACE("Drawing model: %s", model->name);
+ mat4 view;
+ mat4 proj;
+ camera_view_projection(camera, SCR_HEIGHT, SCR_WIDTH, &view, &proj);
+
+ set_shader(ren->blinn_phong);
+
+ // set camera uniform
+ uniform_vec3f(ren->blinn_phong.program_id, "viewPos", &camera->position);
+ // set light uniforms
+ dir_light_upload_uniforms(ren->blinn_phong, &scene->dir_light);
+ for (int i = 0; i < scene->n_point_lights; i++) {
+ point_light_upload_uniforms(ren->blinn_phong, &scene->point_lights[i], '0' + i);
+ }
+
+ for (size_t i = 0; i < mesh_darray_len(model->meshes); i++) {
+ mesh* m = &model->meshes->data[i];
+ if (vertex_darray_len(m->vertices) == 0) {
+ continue;
+ }
+ // TRACE("Drawing mesh %d", i);
+ material* mat = &model->materials->data[m->material_index];
+ draw_mesh(ren, m, model_tf, mat, &view, &proj);
+ }
+}
+
+void draw_mesh(renderer* ren, mesh* mesh, mat4* model_tf, material* mat, mat4* view, mat4* proj) {
+ shader lighting_shader = ren->blinn_phong;
+
+ // bind buffer
+ bind_mesh_vertex_buffer(ren->backend_state, mesh);
+
+ // bind textures
+ bind_texture(lighting_shader, &mat->diffuse_texture, 0); // bind to slot 0
+ bind_texture(lighting_shader, &mat->specular_texture, 1); // bind to slot 1
+ uniform_f32(lighting_shader.program_id, "material.shininess", 32.);
+
+ // upload model, view, and projection matrices
+ uniform_mat4f(lighting_shader.program_id, "model", model_tf);
+ uniform_mat4f(lighting_shader.program_id, "view", view);
+ uniform_mat4f(lighting_shader.program_id, "projection", proj);
+
+ // draw triangles
+ u32 num_vertices = vertex_darray_len(mesh->vertices);
+ draw_primitives(CEL_PRIMITIVE_TOPOLOGY_TRIANGLE, 0, num_vertices);
+}
+
+void draw_skinned_mesh(renderer* ren, mesh* mesh, transform tf, material* mat, mat4* view,
+ mat4* proj) {
+ shader lighting_shader = ren->skinned;
+
+ // bind buffer
+ bind_mesh_vertex_buffer(ren->backend_state, mesh);
+
+ // bind textures
+ bind_texture(lighting_shader, &mat->diffuse_texture, 0); // bind to slot 0
+ bind_texture(lighting_shader, &mat->specular_texture, 1); // bind to slot 1
+
+ // Uniforms
+ uniform_f32(lighting_shader.program_id, "material.shininess", 32.);
+ mat4 trans = mat4_translation(tf.position);
+ mat4 rot = mat4_rotation(tf.rotation);
+ mat4 scale = mat4_scale(tf.scale);
+ mat4 model_tf = mat4_mult(trans, mat4_mult(rot, scale));
+ uniform_mat4f(lighting_shader.program_id, "model", &model_tf);
+ uniform_mat4f(lighting_shader.program_id, "view", view);
+ uniform_mat4f(lighting_shader.program_id, "projection", proj);
+
+ // bone transforms
+ size_t n_bones = mesh->bones->len;
+
+ // for now assume correct ordering
+ mat4* bone_transforms = malloc(n_bones * sizeof(mat4));
+ mat4 parent = mat4_ident();
+ for (int bone_i = 0; bone_i < n_bones; bone_i++) {
+ joint j = mesh->bones->data[bone_i];
+ transform tf = mesh->bones->data[bone_i].transform_components;
+ tf.position.y = -tf.position.y;
+ mat4 local = transform_to_mat(&tf);
+ mat4 inverse = j.inverse_bind_matrix;
+ inverse.data[13] = -inverse.data[13];
+ mat4 intemediate = mat4_mult(local, inverse);
+
+ bone_transforms[bone_i] = intemediate;
+ parent = bone_transforms[bone_i];
+ }
+
+ // premultiply the inverses
+ // for (int bone_i = 0; bone_i < n_bones; bone_i++) {
+ // joint j = mesh->bones->data[bone_i];
+ // // bone_transforms[bone_i] = mat4_mult(bone_transforms[bone_i], j.inverse_bind_matrix);
+ // bone_transforms[bone_i] = mat4_mult(bone_transforms[bone_i], j.inverse_bind_matrix);
+ // }
+
+ glUniformMatrix4fv(glGetUniformLocation(lighting_shader.program_id, "boneMatrices"), n_bones,
+ GL_FALSE, &bone_transforms->data[0]);
+
+ free(bone_transforms);
+
+ // draw triangles
+ u32 num_vertices = vertex_darray_len(mesh->vertices);
+ draw_primitives(CEL_PRIMITIVE_TOPOLOGY_TRIANGLE, 0, num_vertices);
+}
+
+void draw_skinned_model(renderer* ren, camera* cam, model* model, transform tf, scene* scene) {
+ mat4 view;
+ mat4 proj;
+ camera_view_projection(cam, SCR_HEIGHT, SCR_WIDTH, &view, &proj);
+
+ set_shader(ren->skinned);
+
+ // set camera uniform
+ uniform_vec3f(ren->skinned.program_id, "viewPos", &cam->position);
+ // set light uniforms
+ dir_light_upload_uniforms(ren->skinned, &scene->dir_light);
+ for (int i = 0; i < scene->n_point_lights; i++) {
+ point_light_upload_uniforms(ren->skinned, &scene->point_lights[i], '0' + i);
+ }
+
+ for (size_t i = 0; i < mesh_darray_len(model->meshes); i++) {
+ mesh* m = &model->meshes->data[i];
+ if (vertex_darray_len(m->vertices) == 0) {
+ continue;
+ }
+ // material* mat = &model->materials->data[m->material_index];
+ material* mat = &DEFAULT_MATERIAL;
+ draw_skinned_mesh(ren, m, tf, mat, &view, &proj);
+ }
+}
+
+void model_upload_meshes(renderer* ren, model* model) {
+ INFO("Upload mesh vertex data to GPU for model %s", model->name);
+
+ size_t num_meshes = mesh_darray_len(model->meshes);
+ u32 VBOs[num_meshes];
+ u32 VAOs[num_meshes];
+ glGenBuffers(num_meshes, VBOs);
+ glGenVertexArrays(num_meshes, VAOs);
+
+ u64 total_verts = 0;
+
+ TRACE("num meshes %d", num_meshes);
+
+ // upload each mesh to the GPU
+ for (int mesh_i = 0; mesh_i < num_meshes; mesh_i++) {
+ mesh mesh = model->meshes->data[mesh_i];
+ model->meshes->data[mesh_i].vao = VAOs[mesh_i];
+ model->meshes->data[mesh_i].vbo = VBOs[mesh_i];
+ // 3. bind buffers
+ glBindBuffer(GL_ARRAY_BUFFER, VBOs[mesh_i]);
+
+ size_t num_vertices = vertex_darray_len(model->meshes->data[mesh_i].vertices);
+ // TRACE("Uploading vertex array data: %d verts", num_vertices);
+ total_verts += num_vertices;
+
+ size_t static_vertex_size = 2 * sizeof(vec3) + sizeof(vec2);
+ size_t skinned_vertex_size = 2 * sizeof(vec3) + sizeof(vec2) + 4 * sizeof(u32) + sizeof(vec4);
+ size_t vertex_size = mesh.is_skinned ? skinned_vertex_size : static_vertex_size;
+
+ // TRACE("sizeof(vertex) -> %ld, vertex_size -> %ld\n", sizeof(vertex), vertex_size);
+ if (mesh.is_skinned) {
+ assert(vertex_size == (12 + 12 + 8 + 16 + 16));
+ } else {
+ assert(vertex_size == sizeof(vertex));
+ assert(vertex_size == 8 * sizeof(float));
+ }
+
+ size_t buffer_size = vertex_size * num_vertices;
+ u8* bytes = malloc(buffer_size);
+
+ for (int i = 0; i < num_vertices; i++) {
+ u8* p = bytes + vertex_size * i;
+ memcpy(p, &mesh.vertices->data[i], static_vertex_size);
+ if (mesh.is_skinned) {
+ u8* bone_data_offset = p + static_vertex_size;
+ memcpy(bone_data_offset, &mesh.vertex_bone_data->data[i], sizeof(vertex_bone_data));
+ }
+ }
+
+ // 4. upload data
+ glBufferData(GL_ARRAY_BUFFER, buffer_size, bytes, GL_STATIC_DRAW);
+
+ // 5. cont. set mesh vertex layout
+ glBindVertexArray(model->meshes->data[mesh_i].vao);
+ // position attribute
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertex_size, (void*)0);
+ glEnableVertexAttribArray(0);
+ // normal vector attribute
+ glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, vertex_size, (void*)(3 * sizeof(float)));
+ glEnableVertexAttribArray(1);
+ // tex coords
+ glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, vertex_size, (void*)(6 * sizeof(float)));
+ glEnableVertexAttribArray(2);
+
+ // skinning (optional)
+ if (mesh.is_skinned) {
+ glEnableVertexAttribArray(3);
+ glVertexAttribIPointer(3, 4, GL_INT, vertex_size, (void*)(8 * sizeof(float)));
+
+ glEnableVertexAttribArray(4);
+ glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, vertex_size, (void*)(12 * sizeof(float)));
+ }
+ }
+
+ INFO("Uploaded %d submeshes with a total of %d vertices\n", num_meshes, total_verts);
+
+ // 6. reset buffer
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+texture texture_data_load(const char* path, bool invert_y) {
+ TRACE("Load texture %s", path);
+
+ // load the file data
+ // texture loading
+ int width, height, num_channels;
+ stbi_set_flip_vertically_on_load(invert_y);
+
+#pragma GCC diagnostic ignored "-Wpointer-sign"
+ char* data = stbi_load(path, &width, &height, &num_channels, 0); // STBI_rgb_alpha);
+ if (data) {
+ DEBUG("loaded texture: %s", path);
+ } else {
+ WARN("failed to load texture");
+ }
+
+ unsigned int channel_type;
+ if (num_channels == 4) {
+ channel_type = GL_RGBA;
+ } else {
+ channel_type = GL_RGB;
+ }
+
+ return (texture){ .texture_id = 0,
+ .width = width,
+ .height = height,
+ .channel_count = num_channels,
+ .channel_type = channel_type,
+ .name = "TODO: Texture names",
+ .image_data = data };
+}
+
+void dir_light_upload_uniforms(shader shader, directional_light* light) {
+ uniform_vec3f(shader.program_id, "dirLight.direction", &light->direction);
+ uniform_vec3f(shader.program_id, "dirLight.ambient", &light->ambient);
+ uniform_vec3f(shader.program_id, "dirLight.diffuse", &light->diffuse);
+ uniform_vec3f(shader.program_id, "dirLight.specular", &light->specular);
+}
+
+void point_light_upload_uniforms(shader shader, point_light* light, char index) {
+ char position_str[] = "pointLights[x].position";
+ position_str[12] = (char)index;
+ char ambient_str[] = "pointLights[x].ambient";
+ ambient_str[12] = (char)index;
+ char diffuse_str[] = "pointLights[x].diffuse";
+ diffuse_str[12] = (char)index;
+ char specular_str[] = "pointLights[x].specular";
+ specular_str[12] = (char)index;
+ char constant_str[] = "pointLights[x].constant";
+ constant_str[12] = (char)index;
+ char linear_str[] = "pointLights[x].linear";
+ linear_str[12] = (char)index;
+ char quadratic_str[] = "pointLights[x].quadratic";
+ quadratic_str[12] = (char)index;
+ uniform_vec3f(shader.program_id, position_str, &light->position);
+ uniform_vec3f(shader.program_id, ambient_str, &light->ambient);
+ uniform_vec3f(shader.program_id, diffuse_str, &light->diffuse);
+ uniform_vec3f(shader.program_id, specular_str, &light->specular);
+ uniform_f32(shader.program_id, constant_str, light->constant);
+ uniform_f32(shader.program_id, linear_str, light->linear);
+ uniform_f32(shader.program_id, quadratic_str, light->quadratic);
} \ No newline at end of file
diff --git a/src/renderer/render.h b/src/renderer/render.h
index c89c364..31cf3b0 100644
--- a/src/renderer/render.h
+++ b/src/renderer/render.h
@@ -1,6 +1,19 @@
+/**
+ * @file render.h
+ * @author your name (you@domain.com)
+ * @brief Renderer frontend
+ * @version 0.1
+ * @date 2024-03-21
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
#pragma once
+#include "camera.h"
+#include "loaders.h"
#include "render_types.h"
+#include "transform_hierarchy.h"
// --- Lifecycle
/** @brief initialise the render system frontend */
@@ -8,9 +21,26 @@ bool renderer_init(renderer* ren);
/** @brief shutdown the render system frontend */
void renderer_shutdown(renderer* ren);
+void renderer_on_resize(renderer* ren);
+
+struct render_packet;
+
// --- Frame
void render_frame_begin(renderer* ren);
void render_frame_end(renderer* ren);
+void render_frame_draw(renderer* ren);
+
+// --- models meshes
+void model_upload_meshes(renderer* ren, model* model);
+void draw_model(renderer* ren, camera* camera, model* model, mat4* tf, scene* scene);
+void draw_mesh(renderer* ren, mesh* mesh, mat4* tf, material* mat, mat4* view, mat4* proj);
+void draw_scene(arena* frame, model_darray* models, renderer* ren, camera* camera,
+ transform_hierarchy* tfh, scene* scene);
+
+void draw_skinned_model(renderer* ren, camera* cam, model* model, transform tf, scene* scene);
+
+void model_destroy(model* model);
-// --- \ No newline at end of file
+// ---
+texture texture_data_load(const char* path, bool invert_y); // #frontend
diff --git a/src/renderer/render_backend.h b/src/renderer/render_backend.h
index 61c7ab5..da30bcc 100644
--- a/src/renderer/render_backend.h
+++ b/src/renderer/render_backend.h
@@ -1,15 +1,39 @@
/**
- * @brief
+ * @brief Renderer backend
*/
#pragma once
+#include "camera.h"
#include "maths_types.h"
#include "render_types.h"
/// --- Lifecycle
+
+/** @brief Initialise the graphics API backend */
bool gfx_backend_init(renderer* ren);
void gfx_backend_shutdown(renderer* ren);
+void gfx_backend_draw_frame(renderer* ren, camera* camera, mat4 model, texture* tex);
+void gfx_backend_update_global_state(mat4 projection, mat4 view, vec3 view_pos, vec4 ambient_colour,
+ i32 mode);
+
void clear_screen(vec3 colour);
+void texture_data_upload(texture* tex);
+void bind_texture(shader s, texture* tex, u32 slot);
+void bind_mesh_vertex_buffer(void* backend, mesh* mesh);
+void draw_primitives(cel_primitive_topology primitive, u32 start_index, u32 count);
+
+shader shader_create_separate(const char* vert_shader, const char* frag_shader);
+void set_shader(shader s);
+
// --- Uniforms
+
+/** @brief upload a vec3 of f32 to a uniform */
+void uniform_vec3f(u32 program_id, const char* uniform_name, vec3* value);
+/** @brief upload a single f32 to a uniform */
+void uniform_f32(u32 program_id, const char* uniform_name, f32 value);
+/** @brief upload a integer to a uniform */
+void uniform_i32(u32 program_id, const char* uniform_name, i32 value);
+/** @brief upload a mat4 of f32 to a uniform */
+void uniform_mat4f(u32 program_id, const char* uniform_name, mat4* value); \ No newline at end of file
diff --git a/src/renderer/render_types.h b/src/renderer/render_types.h
index e24fc24..387ac81 100644
--- a/src/renderer/render_types.h
+++ b/src/renderer/render_types.h
@@ -1,75 +1,209 @@
-/**
- * @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
+// /**
+// * @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 "animation.h"
#include "darray.h"
+#include "maths.h"
#include "maths_types.h"
#include "str.h"
-struct GLFWwindow;
+// struct GLFWwindow;
-#ifndef RESOURCE_HANDLE_DEFS
-CORE_DEFINE_HANDLE(texture_handle);
-#define RESOURCE_HANDLE_DEFS
-#endif
+// #define MAX_MATERIAL_NAME_LEN 256
+// #define MAX_TEXTURE_NAME_LEN 256
+
+// #ifndef RESOURCE_HANDLE_DEFS
+// // CORE_DEFINE_HANDLE(model_handle);
+// #define ABSENT_MODEL_HANDLE 999999999
+// // CORE_DEFINE_HANDLE(texture_handle);
+// #define RESOURCE_HANDLE_DEFS
+// #endif
-/* @brief Opaque wrapper around a shader program */
-typedef struct shader {
- u32 program_id;
-} shader;
+// /* @brief Opaque wrapper around a shader program */
+// typedef struct shader {
+// u32 program_id;
+// } shader;
-/** @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;
+// /** @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 frame_stats {
+// u64 last_time;
+// } frame_stats;
typedef struct renderer {
struct GLFWwindow *window; /** Currently all platforms use GLFW*/
void *backend_state; /** Graphics API-specific state */
renderer_config config;
+ // shaders
+ shader blinn_phong;
+ shader skinned;
} renderer;
-/** @brief Vertex format for a static mesh */
-typedef struct vertex {
- vec3 position;
- vec3 normal;
- vec2 uv;
-} vertex;
+// // --- Lighting & Materials
+
+typedef struct texture {
+ u32 texture_id;
+ char name[MAX_TEXTURE_NAME_LEN];
+ void *image_data;
+ void *backend_data;
+ u32 width;
+ u32 height;
+ u8 channel_count;
+ u32 channel_type;
+} texture;
+
+// typedef struct blinn_phong_material {
+// char name[MAX_MATERIAL_NAME_LEN];
+// texture diffuse_texture;
+// char diffuse_tex_path[256];
+// texture specular_texture;
+// char specular_tex_path[256];
+// vec3 ambient_colour;
+// vec3 diffuse;
+// vec3 specular;
+// f32 spec_exponent;
+// bool is_loaded;
+// bool is_uploaded;
+// } blinn_phong_material;
+// typedef blinn_phong_material material; // when we start using PBR, this will no longer be the case
+
+// // the default blinn-phong material. MUST be initialised with the function below
+// extern material DEFAULT_MATERIAL;
+// void default_material_init();
+
+#ifndef TYPED_MATERIAL_ARRAY
+KITC_DECL_TYPED_ARRAY(material) // creates "material_darray"
+#define TYPED_MATERIAL_ARRAY
+#endif
+
+#ifndef TYPED_ANIMATION_CLIP_ARRAY
+KITC_DECL_TYPED_ARRAY(animation_clip) // creates "material_darray"
+#define TYPED_ANIMATION_CLIP_ARRAY
+#endif
+
+// // lights
+// typedef struct point_light {
+// vec3 position;
+// f32 constant, linear, quadratic;
+// vec3 ambient;
+// vec3 diffuse;
+// vec3 specular;
+// } point_light;
+
+// typedef struct directional_light {
+// vec3 direction;
+// vec3 ambient;
+// vec3 diffuse;
+// vec3 specular;
+// } directional_light;
+
+// void point_light_upload_uniforms(shader shader, point_light *light, char index);
+// void dir_light_upload_uniforms(shader shader, directional_light *light);
+// // --- Models & Meshes
+
+// /** @brief Vertex format for a static mesh */
+// typedef struct vertex {
+// vec3 position;
+// vec3 normal;
+// vec2 uv;
+// } vertex;
+
+typedef struct vertex_bone_data {
+ vec4u joints; /** @brief 4 indices of joints that influence vectors position */
+ vec4 weights; /** @brief weight (0,1) of each joint */
+} vertex_bone_data;
+
+#include "animation.h"
#ifndef TYPED_VERTEX_ARRAY
-KITC_DECL_TYPED_ARRAY(vertex) // creates "vertex_darray"
+KITC_DECL_TYPED_ARRAY(vertex) // creates "vertex_darray"
+KITC_DECL_TYPED_ARRAY(vertex_bone_data) // creates "skinned_vertex_darray"
+KITC_DECL_TYPED_ARRAY(joint)
#define TYPED_VERTEX_ARRAY
#endif
-// --- Models & Meshes
-
typedef struct mesh {
vertex_darray *vertices;
+ vertex_bone_data_darray *vertex_bone_data; // only used if model needs it
+ joint_darray *bones;
+ bool is_skinned;
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 */
+ u32 vbo, vao; /** OpenGL data. TODO: dont leak OpenGL details */
} mesh;
-#ifndef TYPED_MESH_ARRAY
-KITC_DECL_TYPED_ARRAY(mesh) // creates "mesh_darray"
-#define TYPED_MESH_ARRAY
-#endif
+// #ifndef TYPED_MESH_ARRAY
+// KITC_DECL_TYPED_ARRAY(mesh) // creates "mesh_darray"
+// #define TYPED_MESH_ARRAY
+// #endif
typedef struct model {
str8 name;
+ mesh_darray *meshes;
+ aabb_3d bbox;
+ material_darray *materials;
+ animation_clip_darray *animations;
+ arena animation_data_arena;
+ bool is_loaded;
+ bool is_uploaded;
} model;
-#ifndef TYPED_MODEL_ARRAY
-KITC_DECL_TYPED_ARRAY(model) // creates "model_darray"
-#define TYPED_MODEL_ARRAY
-#endif \ No newline at end of file
+// #ifndef TYPED_MODEL_ARRAY
+// KITC_DECL_TYPED_ARRAY(model) // creates "model_darray"
+// #define TYPED_MODEL_ARRAY
+// #endif
+
+// // --- Scene
+
+// // NOTE: This struct won't stay like this for a long time. It's somewhat temporary
+// // in order to get a basic scene working without putting burden on the caller of
+// // draw_model()
+// typedef struct scene {
+// directional_light dir_light;
+// point_light point_lights[4];
+// size_t n_point_lights;
+// } scene;
+
+// // --- Graphics API related
+
+// // typedef enum cel_primitive_topology {
+// // CEL_PRIMITIVE_TOPOLOGY_POINT,
+// // CEL_PRIMITIVE_TOPOLOGY_LINE,
+// // CEL_PRIMITIVE_TOPOLOGY_LINE_STRIP,
+// // CEL_PRIMITIVE_TOPOLOGY_TRIANGLE,
+// // CEL_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
+// // CEL_PRIMITIVE_TOPOLOGY_COUNT
+// // } cel_primitive_topology;
+
+// // typedef enum gpu_texture_type {
+// // TEXTURE_TYPE_2D,
+// // TEXTURE_TYPE_3D,
+// // TEXTURE_TYPE_2D_ARRAY,
+// // TEXTURE_TYPE_CUBE_MAP,
+// // TEXTURE_TYPE_COUNT
+// // } gpu_texture_type;
+
+// // typedef enum gpu_texture_format {
+// // TEXTURE_FORMAT_8_8_8_8_RGBA_UNORM,
+// // TEXTURE_FORMAT_DEPTH_DEFAULT,
+// // TEXTURE_FORMAT_COUNT
+// // } gpu_texture_format;
+
+// // typedef enum pipeline_kind {
+// // GRAPHICS,
+// // COMPUTE,
+// // } pipeline_kind; \ No newline at end of file
diff --git a/src/resources/gltf.c b/src/resources/gltf.c
index b646f58..81992d1 100644
--- a/src/resources/gltf.c
+++ b/src/resources/gltf.c
@@ -1 +1,764 @@
-// TODO: Port code from old repo \ No newline at end of file
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include "animation.h"
+#include "core.h"
+#include "defines.h"
+#include "file.h"
+#include "loaders.h"
+#include "log.h"
+#include "maths.h"
+#include "maths_types.h"
+#include "mem.h"
+#include "path.h"
+#include "render.h"
+#include "render_backend.h"
+#include "render_types.h"
+#include "str.h"
+
+#define CGLTF_IMPLEMENTATION
+#include <cgltf.h>
+
+struct face {
+ cgltf_uint indices[3];
+};
+typedef struct face face;
+
+KITC_DECL_TYPED_ARRAY(vec3)
+KITC_DECL_TYPED_ARRAY(vec2)
+KITC_DECL_TYPED_ARRAY(u32)
+KITC_DECL_TYPED_ARRAY(vec4u)
+KITC_DECL_TYPED_ARRAY(vec4)
+KITC_DECL_TYPED_ARRAY(face)
+// KITC_DECL_TYPED_ARRAY(joint)
+
+bool model_load_gltf_str(const char *file_string, const char *filepath, str8 relative_path,
+ model *out_model, bool invert_textures_y);
+
+model_handle model_load_gltf(struct core *core, const char *path, bool invert_texture_y) {
+ size_t arena_size = 1024;
+ arena scratch = arena_create(malloc(arena_size), arena_size);
+
+ TRACE("Loading model at Path %s\n", path);
+ path_opt relative_path = path_parent(&scratch, path);
+ if (!relative_path.has_value) {
+ WARN("Couldnt get a relative path for the path to use for loading materials & textures later");
+ }
+ const char *file_string = string_from_file(path);
+
+ model model = { 0 };
+ model.name = str8_cstr_view(path);
+ model.meshes = mesh_darray_new(1);
+ model.materials = material_darray_new(1);
+
+ bool success =
+ model_load_gltf_str(file_string, path, relative_path.path, &model, invert_texture_y);
+
+ if (!success) {
+ FATAL("Couldnt load OBJ file at path %s", path);
+ ERROR_EXIT("Load fails are considered crash-worthy right now. This will change later.\n");
+ }
+
+ u32 index = model_darray_len(core->models);
+ model_darray_push(core->models, model);
+
+ arena_free_all(&scratch);
+ arena_free_storage(&scratch);
+ return (model_handle){ .raw = index };
+}
+
+void assert_path_type_matches_component_type(cgltf_animation_path_type target_path,
+ cgltf_accessor *output) {
+ if (target_path == cgltf_animation_path_type_rotation) {
+ assert(output->component_type == cgltf_component_type_r_32f);
+ assert(output->type == cgltf_type_vec4);
+ }
+}
+
+// TODO: Brainstorm how I can make this simpler and break it up into more testable pieces
+
+bool model_load_gltf_str(const char *file_string, const char *filepath, str8 relative_path,
+ model *out_model, bool invert_textures_y) {
+ TRACE("Load GLTF from string");
+
+ // Setup temps
+ vec3_darray *tmp_positions = vec3_darray_new(1000);
+ vec3_darray *tmp_normals = vec3_darray_new(1000);
+ vec2_darray *tmp_uvs = vec2_darray_new(1000);
+ vec4u_darray *tmp_joint_indices = vec4u_darray_new(1000);
+ vec4_darray *tmp_weights = vec4_darray_new(1000);
+ joint_darray *tmp_joints = joint_darray_new(256);
+ vertex_bone_data_darray *tmp_vertex_bone_data = vertex_bone_data_darray_new(1000);
+
+ cgltf_options options = { 0 };
+ cgltf_data *data = NULL;
+ cgltf_result result = cgltf_parse_file(&options, filepath, &data);
+ if (result != cgltf_result_success) {
+ WARN("gltf load failed");
+ // TODO: cleanup arrays(allocate all from arena ?)
+ return false;
+ }
+
+ cgltf_load_buffers(&options, data, filepath);
+ DEBUG("loaded buffers");
+
+ // --- Skin
+ size_t num_skins = data->skins_count;
+ bool is_skinned = false;
+ if (num_skins == 1) {
+ is_skinned = true;
+ } else if (num_skins > 1) {
+ WARN("GLTF files with more than 1 skin are not supported");
+ return false;
+ }
+
+ if (is_skinned) {
+ cgltf_skin *gltf_skin = data->skins;
+ DEBUG("loading skin %s", gltf_skin->name);
+ size_t num_joints = gltf_skin->joints_count;
+ DEBUG("# Joints %d", num_joints);
+
+ cgltf_accessor *gltf_inverse_bind_matrices = gltf_skin->inverse_bind_matrices;
+
+ // for each one we'll spit out a joint
+ for (size_t i = 0; i < num_joints; i++) {
+ cgltf_node *joint_node = gltf_skin->joints[i];
+
+ joint joint_i = { .name = "testjoint" };
+ if (joint_node->children_count > 0 && !joint_node->has_translation &&
+ !joint_node->has_rotation) {
+ WARN("joint Node with index %d is the root node", i);
+ joint_i.transform_components = TRANSFORM_DEFAULT;
+ } else {
+ TRACE("Storing joint transform");
+ joint_i.transform_components = TRANSFORM_DEFAULT;
+ if (joint_node->has_translation) {
+ memcpy(&joint_i.transform_components.position, &joint_node->translation, 3 * sizeof(f32));
+ }
+ if (joint_node->has_rotation) {
+ memcpy(&joint_i.transform_components.rotation, &joint_node->rotation, 4 * sizeof(f32));
+ }
+ // TODO: support scaling as vec instead of float
+ }
+ joint_i.local_transform = transform_to_mat(&joint_i.transform_components);
+ cgltf_accessor_read_float(gltf_inverse_bind_matrices, i, &joint_i.inverse_bind_matrix.data[0],
+ 16);
+ joint_darray_push(tmp_joints, joint_i);
+ }
+ }
+
+ // --- Materials
+ TRACE("Num materials %d", data->materials_count);
+ size_t num_materials = data->materials_count;
+ for (size_t m = 0; m < num_materials; m++) {
+ cgltf_material gltf_material = data->materials[m];
+ material our_material = DEFAULT_MATERIAL;
+
+ strcpy(our_material.name, gltf_material.name);
+
+ cgltf_pbr_metallic_roughness pbr = gltf_material.pbr_metallic_roughness;
+ if (gltf_material.has_pbr_metallic_roughness) {
+ // we will use base color texture like blinn phong
+ cgltf_texture_view diff_tex_view = pbr.base_color_texture;
+
+ char diffuse_map_path[1024];
+ snprintf(diffuse_map_path, sizeof(diffuse_map_path), "%s/%s", relative_path.buf,
+ diff_tex_view.texture->image->uri);
+
+ strcpy(our_material.diffuse_tex_path, diffuse_map_path);
+ texture diffuse_texture = texture_data_load(our_material.diffuse_tex_path, false);
+ texture_data_upload(&diffuse_texture);
+ our_material.diffuse_texture = diffuse_texture;
+
+ cgltf_texture_view specular_tex_view = pbr.metallic_roughness_texture;
+
+ char specular_map_path[1024];
+ snprintf(specular_map_path, sizeof(specular_map_path), "%s/%s", relative_path.buf,
+ specular_tex_view.texture->image->uri);
+
+ strcpy(our_material.specular_tex_path, specular_map_path);
+ texture specular_texture = texture_data_load(our_material.specular_tex_path, false);
+ texture_data_upload(&specular_texture);
+ our_material.specular_texture = specular_texture;
+ }
+
+ material_darray_push(out_model->materials, our_material);
+ }
+
+ // --- Meshes
+ TRACE("Num meshes %d", data->meshes_count);
+ size_t num_meshes = data->meshes_count;
+ for (size_t m = 0; m < num_meshes; m++) {
+ cgltf_primitive primitive = data->meshes[m].primitives[0];
+ DEBUG("Found %d attributes", primitive.attributes_count);
+ // DEBUG("Number of this primitive %d", primitive.)
+
+ for (int a = 0; a < data->meshes[m].primitives[0].attributes_count; a++) {
+ cgltf_attribute attribute = data->meshes[m].primitives[0].attributes[a];
+ if (attribute.type == cgltf_attribute_type_position) {
+ TRACE("Load positions from accessor");
+
+ cgltf_accessor *accessor = attribute.data;
+ assert(accessor->component_type == cgltf_component_type_r_32f);
+ // CASSERT_MSG(accessor->type == cgltf_type_vec3, "Vertex positions should be a vec3");
+
+ TRACE("Loading %d vec3 components", accessor->count);
+
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ vec3 pos;
+ cgltf_accessor_read_float(accessor, v, &pos.x, 3);
+ vec3_darray_push(tmp_positions, pos);
+ }
+
+ } else if (attribute.type == cgltf_attribute_type_normal) {
+ TRACE("Load normals from accessor");
+
+ cgltf_accessor *accessor = attribute.data;
+ assert(accessor->component_type == cgltf_component_type_r_32f);
+ // CASSERT_MSG(accessor->type == cgltf_type_vec3, "Normal vectors should be a vec3");
+
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ vec3 pos;
+ cgltf_accessor_read_float(accessor, v, &pos.x, 3);
+ vec3_darray_push(tmp_normals, pos);
+ }
+
+ } else if (attribute.type == cgltf_attribute_type_texcoord) {
+ TRACE("Load texture coordinates from accessor");
+ cgltf_accessor *accessor = attribute.data;
+ assert(accessor->component_type == cgltf_component_type_r_32f);
+ // CASSERT_MSG(accessor->type == cgltf_type_vec2, "Texture coordinates should be a vec2");
+
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ vec2 tex;
+ bool success = cgltf_accessor_read_float(accessor, v, &tex.x, 2);
+ if (!success) {
+ ERROR("Error loading tex coord");
+ }
+ vec2_darray_push(tmp_uvs, tex);
+ }
+ } else if (attribute.type == cgltf_attribute_type_joints) {
+ TRACE("Load joint indices from accessor");
+ cgltf_accessor *accessor = attribute.data;
+ assert(accessor->component_type == cgltf_component_type_r_16u);
+ assert(accessor->type == cgltf_type_vec4);
+ vec4u joint_indices;
+ vec4 joints_as_floats;
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ cgltf_accessor_read_float(accessor, v, &joints_as_floats.x, 4);
+ joint_indices.x = (u32)joints_as_floats.x;
+ joint_indices.y = (u32)joints_as_floats.y;
+ joint_indices.z = (u32)joints_as_floats.z;
+ joint_indices.w = (u32)joints_as_floats.w;
+ printf("Joints affecting vertex %d : %d %d %d %d\n", v, joint_indices.x, joint_indices.y,
+ joint_indices.z, joint_indices.w);
+ vec4u_darray_push(tmp_joint_indices, joint_indices);
+ }
+
+ } else if (attribute.type == cgltf_attribute_type_weights) {
+ TRACE("Load joint weights from accessor");
+ cgltf_accessor *accessor = attribute.data;
+ assert(accessor->component_type == cgltf_component_type_r_32f);
+ assert(accessor->type == cgltf_type_vec4);
+
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ vec4 weights;
+ cgltf_accessor_read_float(accessor, v, &weights.x, 4);
+ printf("Weights affecting vertex %d : %f %f %f %f\n", v, weights.x, weights.y, weights.z,
+ weights.w);
+ vec4_darray_push(tmp_weights, weights);
+ }
+ } else {
+ WARN("Unhandled cgltf_attribute_type: %s. skipping..", attribute.name);
+ }
+ }
+
+ mesh mesh = { 0 };
+ mesh.vertices = vertex_darray_new(10);
+ mesh.vertex_bone_data = vertex_bone_data_darray_new(1);
+
+ if (primitive.material != NULL) {
+ for (int i = 0; i < material_darray_len(out_model->materials); i++) {
+ printf("%s vs %s \n", primitive.material->name, out_model->materials->data[i].name);
+ if (strcmp(primitive.material->name, out_model->materials->data[i].name) == 0) {
+ TRACE("Found material");
+ mesh.material_index = i;
+ break;
+ }
+ }
+ }
+
+ if (is_skinned) {
+ mesh.is_skinned = true;
+ // mesh.vertex_bone_data = vertex_bone_data_darray_new(tmp_joint_indices->len);
+ mesh.bones = joint_darray_new(tmp_joints->len);
+ for (int i = 0; i < tmp_joint_indices->len; i++) {
+ vertex_bone_data data;
+ data.joints = tmp_joint_indices->data[i];
+ data.weights = tmp_weights->data[i];
+ vertex_bone_data_darray_push(tmp_vertex_bone_data,
+ data); // Push the temp data that aligns with raw vertices
+ }
+ for (int i = 0; i < tmp_joints->len; i++) {
+ joint data = tmp_joints->data[i];
+ joint_darray_push(mesh.bones, data);
+ }
+ }
+
+ cgltf_accessor *indices = primitive.indices;
+ if (primitive.indices > 0) {
+ WARN("indices!");
+ mesh.has_indices = true;
+
+ mesh.indices = malloc(indices->count * sizeof(u32));
+ mesh.indices_len = indices->count;
+
+ // store indices
+ for (cgltf_size i = 0; i < indices->count; ++i) {
+ cgltf_uint ei;
+ cgltf_accessor_read_uint(indices, i, &ei, 1);
+ mesh.indices[i] = ei;
+ }
+
+ // fetch and store vertices for each index
+ for (cgltf_size i = 0; i < indices->count; ++i) {
+ vertex vert;
+ cgltf_uint index = mesh.indices[i];
+ vert.position = tmp_positions->data[index];
+ vert.normal = tmp_normals->data[index];
+ vert.uv = tmp_uvs->data[index];
+ vertex_darray_push(mesh.vertices, vert);
+
+ if (is_skinned) {
+ vertex_bone_data vbd = tmp_vertex_bone_data->data[index]; // create a copy
+ vertex_bone_data_darray_push(mesh.vertex_bone_data, vbd);
+ }
+ // for each vertex do the bone data
+ }
+ } else {
+ mesh.has_indices = false;
+ return false; // TODO
+ }
+
+ mesh_darray_push(out_model->meshes, mesh);
+
+ // clear data for each mesh
+ vec3_darray_clear(tmp_positions);
+ vec3_darray_clear(tmp_normals);
+ vec2_darray_free(tmp_uvs);
+ vec4u_darray_clear(tmp_joint_indices);
+ vec4_darray_clear(tmp_weights);
+ joint_darray_clear(tmp_joints);
+ }
+
+ for (int i = 0; i < out_model->meshes->len; i++) {
+ u32 mat_idx = out_model->meshes->data[i].material_index;
+ printf("Mesh %d Mat index %d Mat name %s\n", i, mat_idx,
+ out_model->materials->data[mat_idx].name);
+ }
+
+ // Animations
+ TRACE("Num animations %d", data->animations_count);
+ size_t num_animations = data->animations_count;
+ if (num_animations > 0) {
+// Create an arena for all animation related data
+#define ANIMATION_STORAGE_ARENA_SIZE (1024 * 1024 * 1024)
+ char *animation_backing_storage = malloc(ANIMATION_STORAGE_ARENA_SIZE);
+ // We'll store data on this arena so we can easily free it all at once later
+ out_model->animation_data_arena =
+ arena_create(animation_backing_storage, ANIMATION_STORAGE_ARENA_SIZE);
+ arena *arena = &out_model->animation_data_arena;
+
+ if (!out_model->animations) {
+ out_model->animations = animation_clip_darray_new(num_animations);
+ }
+
+ for (int anim_idx = 0; anim_idx < data->animations_count; anim_idx++) {
+ cgltf_animation animation = data->animations[anim_idx];
+ animation_clip clip = { 0 };
+
+ for (size_t c = 0; c < animation.channels_count; c++) {
+ cgltf_animation_channel channel = animation.channels[c];
+
+ animation_sampler *sampler = arena_alloc(arena, sizeof(animation_sampler));
+
+ animation_sampler **target_property;
+ keyframe_kind data_type;
+
+ switch (channel.target_path) {
+ case cgltf_animation_path_type_rotation:
+ target_property = &clip.rotation;
+ data_type = KEYFRAME_ROTATION;
+ break;
+ case cgltf_animation_path_type_translation:
+ target_property = &clip.translation;
+ data_type = KEYFRAME_TRANSLATION;
+ break;
+ case cgltf_animation_path_type_scale:
+ target_property = &clip.scale;
+ data_type = KEYFRAME_SCALE;
+ break;
+ case cgltf_animation_path_type_weights:
+ target_property = &clip.weights;
+ data_type = KEYFRAME_WEIGHTS;
+ WARN("Morph target weights arent supported yet");
+ return false;
+ default:
+ WARN("unsupported animation type");
+ return false;
+ }
+ *target_property = sampler;
+
+ sampler->current_index = 0;
+ printf("1 %d index\n", sampler->current_index);
+ sampler->animation.interpolation = INTERPOLATION_LINEAR;
+
+ // keyframe times
+ size_t n_frames = channel.sampler->input->count;
+ assert(channel.sampler->input->component_type == cgltf_component_type_r_32f);
+ // FIXME: CASSERT_MSG function "Expected animation sampler input component to be type f32
+ // (keyframe times)");
+ f32 *times = arena_alloc(arena, n_frames * sizeof(f32));
+ sampler->animation.n_timestamps = n_frames;
+ sampler->animation.timestamps = times;
+ cgltf_accessor_unpack_floats(channel.sampler->input, times, n_frames);
+
+ assert_path_type_matches_component_type(channel.target_path, channel.sampler->output);
+
+ // keyframe values
+ size_t n_values = channel.sampler->output->count;
+ assert(n_frames == n_values);
+
+ keyframes keyframes = { 0 };
+ keyframes.kind = KEYFRAME_ROTATION;
+ keyframes.count = n_values;
+ keyframes.values = arena_alloc(arena, n_values * sizeof(keyframe));
+ for (cgltf_size v = 0; v < channel.sampler->output->count; ++v) {
+ switch (data_type) {
+ case KEYFRAME_ROTATION: {
+ quat rot;
+ cgltf_accessor_read_float(channel.sampler->output, v, &rot.x, 4);
+ // printf("Quat %f %f %f %f\n", rot.x, rot.y, rot.z, rot.w);
+ keyframes.values[v].rotation = rot;
+ break;
+ }
+ case KEYFRAME_TRANSLATION: {
+ vec3 trans;
+ cgltf_accessor_read_float(channel.sampler->output, v, &trans.x, 3);
+ keyframes.values[v].translation = trans;
+ break;
+ }
+ case KEYFRAME_SCALE: {
+ vec3 scale;
+ cgltf_accessor_read_float(channel.sampler->output, v, &scale.x, 3);
+ keyframes.values[v].scale = scale;
+ break;
+ }
+ case KEYFRAME_WEIGHTS: {
+ // TODO
+ break;
+ }
+ }
+ }
+ sampler->animation.values = keyframes;
+
+ sampler->min = channel.sampler->input->min[0];
+ sampler->max = channel.sampler->input->max[0];
+
+ // clip.rotation = sampler;
+ // printf("%d timestamps\n", sampler->animation.n_timestamps);
+ // printf("%d index\n", sampler->current_index);
+ }
+
+ WARN("stuff %ld", clip.rotation->animation.n_timestamps);
+ animation_clip_darray_push(out_model->animations, clip);
+ }
+ }
+
+ return true;
+}
+
+/*
+bool model_load_gltf(const char *path, model *out_model) {
+ TRACE("Load GLTF %s", path);
+
+ // Setup temp arrays
+ kitc_darray *tmp_positions = kitc_darray_new(sizeof(vec3), 1000);
+ kitc_darray *tmp_normals = kitc_darray_new(sizeof(vec3), 1000);
+ kitc_darray *tmp_uvs = kitc_darray_new(sizeof(vec2), 1000);
+
+ // may as well just init with max capacity as we're just gonna free at end of this function anyway
+ bh_material_darray *materials = bh_material_darray_new(MAX_MATERIALS);
+ CASSERT(materials->len == 0);
+
+ cgltf_options options = {0};
+ cgltf_data *data = NULL;
+ cgltf_result result = cgltf_parse_file(&options, path, &data);
+ if (result == cgltf_result_success) {
+ DEBUG("gltf loaded succesfully");
+
+ cgltf_load_buffers(&options, data, path);
+ DEBUG("loaded buffers");
+
+ // -- Load materials.
+ // Each mesh will be handed a material
+ TRACE("Num materials %d", data->materials_count);
+ out_model->num_materials = data->materials_count;
+
+ for (int m = 0; m < data->materials_count; m++) {
+ cgltf_material gltf_material = data->materials[m];
+ bh_material our_material = {0};
+
+ str8 name = str8_copy(gltf_material.name);
+ printf("Material name %s\n", name.buf);
+ our_material.name = name;
+
+ cgltf_pbr_metallic_roughness pbr = gltf_material.pbr_metallic_roughness;
+ if (gltf_material.has_pbr_metallic_roughness) {
+ // we will use base color texture like blinn phong
+ cgltf_texture_view diff_tex = pbr.base_color_texture;
+ strcpy(our_material.diffuse_tex_path, diff_tex.texture->image->uri);
+ }
+
+ bh_material_darray_push(materials, our_material);
+ }
+
+ // -- Load animations.
+ TRACE("Num animations %d", data->animations_count);
+ out_model->num_animations = data->animations_count;
+ for (int anim_idx = 0; anim_idx < data->animations_count; anim_idx++) {
+ cgltf_animation animation = data->animations[anim_idx];
+ animation_clip our_animation = {0};
+
+ // loop through each channel (track)
+ for (int c = 0; c < animation.channels_count; c++) {
+ // each channel (track) has a target and a sampler
+ // for the time being we assume the target is the model itself
+ cgltf_animation_channel channel = animation.channels[c];
+ animation_track our_track = {0};
+ our_track.interpolation = interpolation_fn_from_gltf(channel.sampler->interpolation);
+ our_track.property = anim_prop_from_gltf(channel.target_path);
+
+ // get the actual data out via the "accessor"
+ // input will be the times
+
+ // Keyframe times
+ size_t n_frames = channel.sampler->input->count;
+ our_track.num_keyframes = n_frames;
+ f32 *times = malloc(sizeof(f32) * n_frames);
+ our_track.keyframe_times = times;
+ CASSERT_MSG(channel.sampler->input->component_type == cgltf_component_type_r_32f,
+ "Expected animation sampler input component to be type f32 (keyframe times)");
+ cgltf_accessor_unpack_floats(channel.sampler->input, times, channel.sampler->input->count);
+
+ // printf("keyframe times[\n");
+ // for (int i = 0; i < n_frames; i++) {
+ // printf(" %f\n", times[i]);
+ // }
+ // printf("]\n");
+
+ // Data!
+ if (channel.target_path == cgltf_animation_path_type_rotation) {
+ CASSERT(channel.sampler->output->component_type == cgltf_component_type_r_32f);
+ CASSERT(channel.sampler->output->type == cgltf_type_vec4);
+ }
+
+ our_track.keyframes = malloc(sizeof(keyframe_data) * n_frames);
+ for (cgltf_size v = 0; v < channel.sampler->output->count; ++v) {
+ quat rot;
+ cgltf_accessor_read_float(channel.sampler->output, v, &rot.x, 4);
+ // vectors[v] = rot;
+ // printf("Quat %f %f %f %f\n", rot.x, rot.y, rot.z, rot.w);
+ our_track.keyframes[v].rotation = rot;
+ }
+
+ our_track.min_time = channel.sampler->input->min[0];
+ our_track.max_time = channel.sampler->input->max[0];
+
+ // printf("min time: %f max time %f\n", our_track.min_time, our_track.max_time);
+
+ animation_track_darray_push(&our_animation.tracks, our_track);
+ }
+
+ out_model->animations[anim_idx] = our_animation;
+ }
+
+ // Load meshes
+ TRACE("Num meshes %d", data->meshes_count);
+ out_model->num_meshes = data->meshes_count;
+
+ for (int m = 0; m < data->meshes_count; m++) {
+ // at the moment we only handle one primitives per mesh
+ // CASSERT(data->meshes[m].primitives_count == 1);
+
+ // Load vertex data from FIRST primitive only
+ cgltf_primitive primitive = data->meshes[m].primitives[0];
+ DEBUG("Found %d attributes", primitive.attributes_count);
+ for (int a = 0; a < data->meshes[m].primitives[0].attributes_count; a++) {
+ cgltf_attribute attribute = data->meshes[m].primitives[0].attributes[a];
+ if (attribute.type == cgltf_attribute_type_position) {
+ TRACE("Load positions from accessor");
+
+ cgltf_accessor *accessor = attribute.data;
+ CASSERT(accessor->component_type == cgltf_component_type_r_32f);
+ CASSERT_MSG(accessor->type == cgltf_type_vec3, "Vertex positions should be a vec3");
+
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ vec3 pos;
+ cgltf_accessor_read_float(accessor, v, &pos.x, 3);
+ kitc_darray_push(tmp_positions, &pos);
+ }
+
+ } else if (attribute.type == cgltf_attribute_type_normal) {
+ TRACE("Load normals from accessor");
+
+ cgltf_accessor *accessor = attribute.data;
+ CASSERT(accessor->component_type == cgltf_component_type_r_32f);
+ CASSERT_MSG(accessor->type == cgltf_type_vec3, "Normal vectors should be a vec3");
+
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ vec3 pos;
+ cgltf_accessor_read_float(accessor, v, &pos.x, 3);
+ kitc_darray_push(tmp_normals, &pos);
+ }
+
+ } else if (attribute.type == cgltf_attribute_type_texcoord) {
+ TRACE("Load texture coordinates from accessor");
+ cgltf_accessor *accessor = attribute.data;
+ CASSERT(accessor->component_type == cgltf_component_type_r_32f);
+ CASSERT_MSG(accessor->type == cgltf_type_vec2, "Texture coordinates should be a vec2");
+
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ vec2 tex;
+ bool success = cgltf_accessor_read_float(accessor, v, &tex.x, 2);
+ if (!success) {
+ ERROR("Error loading tex coord");
+ }
+ kitc_darray_push(tmp_uvs, &tex);
+ }
+ } else if (attribute.type == cgltf_attribute_type_joints) {
+ // handle joints
+
+ } else {
+ WARN("Unhandled cgltf_attribute_type: %s. skipping..", attribute.name);
+ }
+ }
+
+ // Create mesh
+ mesh mesh;
+ mesh.vertices =
+ kitc_darray_new(sizeof(mesh_vertex), data->meshes[m].primitives[0].attributes_count);
+
+ // Flatten faces from indices if present otherwise push vertices verbatim
+ cgltf_accessor *indices = primitive.indices;
+ if (primitive.indices > 0) {
+ mesh.has_indices = true;
+
+ kitc_darray *element_indexes = kitc_darray_new(sizeof(cgltf_uint), indices->count);
+ TRACE("Indices count %ld\n", indices->count);
+ for (cgltf_size i = 0; i < indices->count; ++i) {
+ cgltf_uint ei;
+ cgltf_accessor_read_uint(indices, i, &ei, 1);
+ kitc_darray_push(element_indexes, &ei);
+ }
+
+ kitc_darray_iter indices_iter = kitc_darray_iter_new(element_indexes);
+ cgltf_uint *cur;
+ while ((cur = kitc_darray_iter_next(&indices_iter))) {
+ mesh_vertex vert;
+ memcpy(&vert.position, &((vec3 *)tmp_positions->data)[*cur], sizeof(vec3));
+ memcpy(&vert.normal, &((vec3 *)tmp_normals->data)[*cur], sizeof(vec3));
+ memcpy(&vert.tex_coord, &((vec2 *)tmp_uvs->data)[*cur], sizeof(vec2));
+ kitc_darray_push(mesh.vertices, &vert);
+ // mesh_vertex_debug_print(vert);
+ }
+ // printf("indices: %ld, positions: %ld\n", kitc_darray_len(element_indexes),
+ kitc_darray_free(element_indexes);
+ } else {
+ mesh.has_indices = false;
+
+ bool calc_normals = false;
+ if (kitc_darray_len(tmp_normals) == 0) {
+ TRACE("No normals data is present. Normals will be calculated for you.");
+ calc_normals = true;
+ }
+ for (int v = 0; v < kitc_darray_len(tmp_positions); v++) {
+ mesh_vertex vert;
+ memcpy(&vert.position, &((vec3 *)tmp_positions->data)[v], sizeof(vec3));
+ if (!calc_normals) {
+ memcpy(&vert.normal, &((vec3 *)tmp_normals->data)[v], sizeof(vec3));
+ }
+ memcpy(&vert.tex_coord, &((vec2 *)tmp_uvs->data)[v], sizeof(vec2));
+ kitc_darray_push(mesh.vertices, &vert);
+ }
+
+ if (calc_normals) {
+ if (mesh.has_indices) {
+ // generate_normals_nonindexed(mesh.vertices);
+ } else {
+ generate_normals_nonindexed(mesh.vertices);
+ }
+ }
+ }
+
+ // Material
+ if (primitive.material != NULL) {
+ for (int i = 0; i < bh_material_darray_len(materials); i++) {
+ if (strcmp(primitive.material->name, cstr(materials->data->name))) {
+ TRACE("Found material");
+ mesh.material_index = i;
+ break;
+ }
+ }
+ }
+
+ // mesh.material_index = 0; // TODO: make sure DEFAULT_MATERIAL is added at material index 0
+ // TODO: material handling
+ mesh.material_index = bh_material_darray_len(materials) - 1;
+
+ calc_mesh_bounding_box(&mesh);
+ // out_model->meshes.data[m] = mesh;
+ mesh_darray_push(&out_model->meshes, mesh);
+
+ kitc_darray_clear(tmp_positions);
+ kitc_darray_clear(tmp_normals);
+ kitc_darray_clear(tmp_uvs);
+ }
+ // End Load meshes
+
+ // Load animations
+ DEBUG("Num animations %d", data->animations_count);
+ out_model->num_animations = data->animations_count;
+
+ // End Load animations
+
+ cgltf_free(data);
+ } else {
+ ERROR("Load failed");
+ kitc_darray_free(tmp_positions);
+ kitc_darray_free(tmp_normals);
+ kitc_darray_free(tmp_uvs);
+ return false;
+ }
+
+ for (int i = 0; i < materials->len; i++) {
+ out_model->materials[i] = materials->data[i];
+ }
+
+ calc_model_bounding_box(out_model);
+
+ DEBUG("Num meshes %d", out_model->num_meshes);
+ DEBUG("Num materials %d", out_model->num_materials);
+ DEBUG("Num animations %d", out_model->num_animations);
+
+ CASSERT(out_model->num_materials == 1);
+
+ kitc_darray_free(tmp_positions);
+ kitc_darray_free(tmp_normals);
+ kitc_darray_free(tmp_uvs);
+ bh_material_darray_free(materials);
+
+ TRACE("Finished loading GLTF");
+ return true;
+}
+*/ \ No newline at end of file
diff --git a/src/resources/loaders.h b/src/resources/loaders.h
index ba38ec4..858e4d1 100644
--- a/src/resources/loaders.h
+++ b/src/resources/loaders.h
@@ -1,9 +1,9 @@
#pragma once
#include "defines.h"
+#include "render_types.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
+model_handle model_load_gltf(struct core *core, const char *path, bool invert_texture_y);
diff --git a/src/resources/obj.c b/src/resources/obj.c
index b646f58..ea73ffa 100644
--- a/src/resources/obj.c
+++ b/src/resources/obj.c
@@ -1 +1,389 @@
-// TODO: Port code from old repo \ No newline at end of file
+/**
+ * @file obj.c
+ * @brief Wavefront OBJ loader.
+ * @copyright Copyright (c) 2024
+ */
+#include <ctype.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "core.h"
+#include "darray.h"
+#include "file.h"
+#include "log.h"
+#include "maths.h"
+#include "mem.h"
+#include "path.h"
+#include "render.h"
+#include "render_backend.h"
+#include "render_types.h"
+#include "str.h"
+
+struct face {
+ u32 vertex_indices[3];
+ u32 normal_indices[3];
+ u32 uv_indices[3];
+};
+typedef struct face face;
+
+KITC_DECL_TYPED_ARRAY(vec3)
+KITC_DECL_TYPED_ARRAY(vec2)
+KITC_DECL_TYPED_ARRAY(face)
+
+// Forward declarations
+void create_submesh(mesh_darray *meshes, vec3_darray *tmp_positions, vec3_darray *tmp_normals,
+ vec2_darray *tmp_uvs, face_darray *tmp_faces, material_darray *materials,
+ bool material_loaded, char current_material_name[256]);
+bool load_material_lib(const char *path, str8 relative_path, material_darray *materials);
+bool model_load_obj_str(const char *file_string, str8 relative_path, model *out_model,
+ bool invert_textures_y);
+
+model_handle model_load_obj(core *core, const char *path, bool invert_textures_y) {
+ size_t arena_size = 1024;
+ arena scratch = arena_create(malloc(arena_size), arena_size);
+
+ TRACE("Loading model at Path %s\n", path);
+ path_opt relative_path = path_parent(&scratch, path);
+ if (!relative_path.has_value) {
+ WARN("Couldnt get a relative path for the path to use for loading materials & textures later");
+ }
+ const char *file_string = string_from_file(path);
+
+ model model = { 0 };
+ model.name = str8_cstr_view(path);
+ model.meshes = mesh_darray_new(1);
+ model.materials = material_darray_new(1);
+
+ bool success = model_load_obj_str(file_string, relative_path.path, &model, invert_textures_y);
+
+ if (!success) {
+ FATAL("Couldnt load OBJ file at path %s", path);
+ ERROR_EXIT("Load fails are considered crash-worthy right now. This will change later.\n");
+ }
+
+ u32 index = model_darray_len(core->models);
+ model_darray_push(core->models, model);
+
+ arena_free_all(&scratch);
+ arena_free_storage(&scratch);
+ return (model_handle){ .raw = index };
+}
+
+bool model_load_obj_str(const char *file_string, str8 relative_path, model *out_model,
+ bool invert_textures_y) {
+ TRACE("Load OBJ from string");
+
+ // Setup temps
+ vec3_darray *tmp_positions = vec3_darray_new(1000);
+ vec3_darray *tmp_normals = vec3_darray_new(1000);
+ vec2_darray *tmp_uvs = vec2_darray_new(1000);
+ face_darray *tmp_faces = face_darray_new(1000);
+ // TODO: In the future I'd like these temporary arrays to be allocated from an arena provided
+ // by the function one level up, model_load_obj. That way we can just `return false;` anywhere in
+ // this code to indicate an error, and be sure that all that memory will be cleaned up without
+ // having to call vec3_darray_free in every single error case before returning.
+
+ // Other state
+ bool object_set = false;
+ bool material_loaded = false;
+ char current_material_name[64];
+
+ char *pch;
+ char *rest = file_string;
+ pch = strtok_r((char *)file_string, "\n", &rest);
+
+ int line_num = 0;
+ char last_char_type = 'a';
+
+ while (pch != NULL) {
+ line_num++;
+ char line_header[128];
+ int offset = 0;
+
+ // skip whitespace
+ char *p = pch;
+
+ skip_space(pch);
+
+ if (*p == '\0') {
+ /* the string is empty */
+ } else {
+ // read the first word of the line
+ int res = sscanf(pch, "%s %n", line_header, &offset);
+ /* printf("header: %s, offset : %d res: %d\n",line_header, offset, res); */
+ if (res != 1) {
+ break;
+ }
+
+ if (strcmp(line_header, "o") == 0 || strcmp(line_header, "g") == 0) {
+ // if we're currently parsing one
+ if (!object_set) {
+ object_set = true;
+ } else {
+ create_submesh(out_model->meshes, tmp_positions, tmp_normals, tmp_uvs, tmp_faces,
+ out_model->materials, material_loaded, current_material_name);
+ object_set = false;
+ }
+ } else if (strcmp(line_header, "v") == 0) {
+ // special logic: if we went from faces back to vertices trigger a mesh output.
+ // PS: I hate OBJ
+ if (last_char_type == 'f') {
+ create_submesh(out_model->meshes, tmp_positions, tmp_normals, tmp_uvs, tmp_faces,
+ out_model->materials, material_loaded, current_material_name);
+ object_set = false;
+ }
+
+ last_char_type = 'v';
+ vec3 vertex;
+ sscanf(pch + offset, "%f %f %f", &vertex.x, &vertex.y, &vertex.z);
+
+ vec3_darray_push(tmp_positions, vertex);
+ } else if (strcmp(line_header, "vt") == 0) {
+ last_char_type = 't';
+ vec2 uv;
+ char copy[1024];
+ memcpy(copy, pch + offset, strlen(pch + offset) + 1);
+ char *p = pch + offset;
+ while (isspace((unsigned char)*p)) ++p;
+
+ // I can't remember what is going on here
+ memset(copy, 0, 1024);
+ memcpy(copy, pch + offset, strlen(pch + offset) + 1);
+ int res = sscanf(copy, "%f %f", &uv.x, &uv.y);
+ memset(copy, 0, 1024);
+ memcpy(copy, pch + offset, strlen(pch + offset) + 1);
+ if (res != 1) {
+ // da frick? some .obj files have 3 uvs instead of 2
+ f32 dummy;
+ int res2 = sscanf(copy, "%f %f %f", &uv.x, &uv.y, &dummy);
+ }
+
+ if (invert_textures_y) {
+ uv.y = -uv.y; // flip Y axis to be consistent with how other PNGs are being handled
+ // `texture_load` will flip it again
+ }
+ vec2_darray_push(tmp_uvs, uv);
+ } else if (strcmp(line_header, "vn") == 0) {
+ last_char_type = 'n';
+ vec3 normal;
+ sscanf(pch + offset, "%f %f %f", &normal.x, &normal.y, &normal.z);
+ vec3_darray_push(tmp_normals, normal);
+ } else if (strcmp(line_header, "f") == 0) {
+ last_char_type = 'f';
+ struct face f;
+ sscanf(pch + offset, "%d/%d/%d %d/%d/%d %d/%d/%d", &f.vertex_indices[0], &f.uv_indices[0],
+ &f.normal_indices[0], &f.vertex_indices[1], &f.uv_indices[1], &f.normal_indices[1],
+ &f.vertex_indices[2], &f.uv_indices[2], &f.normal_indices[2]);
+ // printf("f %d/%d/%d %d/%d/%d %d/%d/%d\n", f.vertex_indices[0], f.uv_indices[0],
+ // f.normal_indices[0],
+ // f.vertex_indices[1], f.uv_indices[1], f.normal_indices[1],
+ // f.vertex_indices[2], f.uv_indices[2], f.normal_indices[2]);
+ face_darray_push(tmp_faces, f);
+ } else if (strcmp(line_header, "mtllib") == 0) {
+ char filename[1024];
+ sscanf(pch + offset, "%s", filename);
+ char mtllib_path[1024];
+ snprintf(mtllib_path, sizeof(mtllib_path), "%s/%s", relative_path.buf, filename);
+ if (!load_material_lib(mtllib_path, relative_path, out_model->materials)) {
+ ERROR("couldnt load material lib");
+ return false;
+ }
+ } else if (strcmp(line_header, "usemtl") == 0) {
+ material_loaded = true;
+ sscanf(pch + offset, "%s", current_material_name);
+ }
+ }
+
+ pch = strtok_r(NULL, "\n", &rest);
+ }
+
+ // last mesh or if one wasnt created with 'o' directive
+ if (face_darray_len(tmp_faces) > 0) {
+ TRACE("Last leftover mesh");
+ create_submesh(out_model->meshes, tmp_positions, tmp_normals, tmp_uvs, tmp_faces,
+ out_model->materials, material_loaded, current_material_name);
+ }
+
+ // Free data
+ free((char *)file_string);
+ vec3_darray_free(tmp_positions);
+ vec3_darray_free(tmp_normals);
+ vec2_darray_free(tmp_uvs);
+ face_darray_free(tmp_faces);
+ TRACE("Freed temporary OBJ loading data");
+
+ if (mesh_darray_len(out_model->meshes) > 256) {
+ printf("num meshes: %ld\n", mesh_darray_len(out_model->meshes));
+ }
+
+ // TODO: bounding box calculation for each mesh
+ // TODO: bounding box calculation for model
+
+ return true;
+}
+
+/**
+ * @brief Takes the current positions, normals, uvs arrays and constructs the vertex array
+ * from those indices.
+ */
+void create_submesh(mesh_darray *meshes, vec3_darray *tmp_positions, vec3_darray *tmp_normals,
+ vec2_darray *tmp_uvs, face_darray *tmp_faces, material_darray *materials,
+ bool material_loaded, char current_material_name[256]) {
+ size_t num_verts = face_darray_len(tmp_faces) * 3;
+ vertex_darray *out_vertices = vertex_darray_new(num_verts);
+
+ face_darray_iter face_iter = face_darray_iter_new(tmp_faces);
+ struct face *f;
+
+ while ((f = face_darray_iter_next(&face_iter))) {
+ for (int j = 0; j < 3; j++) {
+ vertex vert = { 0 };
+ vert.position = tmp_positions->data[f->vertex_indices[j] - 1];
+ if (vec3_darray_len(tmp_normals) == 0) {
+ vert.normal = vec3_create(0.0, 0.0, 0.0);
+ } else {
+ vert.normal = tmp_normals->data[f->normal_indices[j] - 1];
+ }
+ vert.uv = tmp_uvs->data[f->uv_indices[j] - 1];
+ vertex_darray_push(out_vertices, vert);
+ }
+ }
+
+ DEBUG("Loaded submesh\n vertices: %zu\n uvs: %zu\n normals: %zu\n faces: %zu",
+ vec3_darray_len(tmp_positions), vec2_darray_len(tmp_uvs), vec3_darray_len(tmp_normals),
+ face_darray_len(tmp_faces));
+
+ // Clear current object faces
+ face_darray_clear(tmp_faces);
+
+ mesh m = { .vertices = out_vertices };
+ if (material_loaded) {
+ // linear scan to find material
+ bool found = false;
+ DEBUG("Num of materials : %ld", material_darray_len(materials));
+ material_darray_iter mat_iter = material_darray_iter_new(materials);
+ blinn_phong_material *cur_material;
+ while ((cur_material = material_darray_iter_next(&mat_iter))) {
+ if (strcmp(cur_material->name, current_material_name) == 0) {
+ DEBUG("Found match");
+ m.material_index = mat_iter.current_idx - 1;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // TODO: default material
+ m.material_index = 0;
+ DEBUG("Set default material");
+ }
+ }
+ mesh_darray_push(meshes, m);
+}
+
+bool load_material_lib(const char *path, str8 relative_path, material_darray *materials) {
+ TRACE("BEGIN load material lib at %s", path);
+
+ const char *file_string = string_from_file(path);
+ if (file_string == NULL) {
+ ERROR("couldnt load %s", path);
+ return false;
+ }
+
+ char *pch;
+ char *saveptr;
+ pch = strtok_r((char *)file_string, "\n", &saveptr);
+
+ material current_material = DEFAULT_MATERIAL;
+
+ bool material_set = false;
+
+ while (pch != NULL) {
+ char line_header[128];
+ int offset = 0;
+ // read the first word of the line
+ int res = sscanf(pch, "%s %n", line_header, &offset);
+ if (res != 1) {
+ break;
+ }
+
+ // When we see "newmtl", start a new material, or flush the previous one
+ if (strcmp(line_header, "newmtl") == 0) {
+ if (material_set) {
+ // a material was being parsed, so flush that one and start a new one
+ material_darray_push(materials, current_material);
+ DEBUG("pushed material with name %s", current_material.name);
+ WARN("Reset current material");
+ current_material = DEFAULT_MATERIAL;
+ } else {
+ material_set = true;
+ }
+ // scan the new material name
+ char material_name[64];
+ sscanf(pch + offset, "%s", current_material.name);
+ DEBUG("material name %s\n", current_material.name);
+ // current_material.name = material_name;
+ } else if (strcmp(line_header, "Ka") == 0) {
+ // ambient
+ sscanf(pch + offset, "%f %f %f", &current_material.ambient_colour.x,
+ &current_material.ambient_colour.y, &current_material.ambient_colour.z);
+ } else if (strcmp(line_header, "Kd") == 0) {
+ // diffuse
+ sscanf(pch + offset, "%f %f %f", &current_material.diffuse.x, &current_material.diffuse.y,
+ &current_material.diffuse.z);
+ } else if (strcmp(line_header, "Ks") == 0) {
+ // specular
+ sscanf(pch + offset, "%f %f %f", &current_material.specular.x, &current_material.specular.y,
+ &current_material.specular.z);
+ } else if (strcmp(line_header, "Ns") == 0) {
+ // specular exponent
+ sscanf(pch + offset, "%f", &current_material.spec_exponent);
+ } else if (strcmp(line_header, "map_Kd") == 0) {
+ char diffuse_map_filename[1024];
+ sscanf(pch + offset, "%s", diffuse_map_filename);
+ char diffuse_map_path[1024];
+ snprintf(diffuse_map_path, sizeof(diffuse_map_path), "%s/%s", relative_path.buf,
+ diffuse_map_filename);
+ printf("load from %s\n", diffuse_map_path);
+
+ // --------------
+ texture diffuse_texture = texture_data_load(diffuse_map_path, true);
+ current_material.diffuse_texture = diffuse_texture;
+ strcpy(current_material.diffuse_tex_path, diffuse_map_path);
+ texture_data_upload(&current_material.diffuse_texture);
+ // --------------
+ } else if (strcmp(line_header, "map_Ks") == 0) {
+ // char specular_map_path[1024] = "assets/";
+ // sscanf(pch + offset, "%s", specular_map_path + 7);
+ char specular_map_filename[1024];
+ sscanf(pch + offset, "%s", specular_map_filename);
+ char specular_map_path[1024];
+ snprintf(specular_map_path, sizeof(specular_map_path), "%s/%s", relative_path.buf,
+ specular_map_filename);
+ printf("load from %s\n", specular_map_path);
+ // --------------
+ texture specular_texture = texture_data_load(specular_map_path, true);
+ current_material.specular_texture = specular_texture;
+ strcpy(current_material.specular_tex_path, specular_map_path);
+ texture_data_upload(&current_material.specular_texture);
+ // --------------
+ } else if (strcmp(line_header, "map_Bump") == 0) {
+ // TODO
+ }
+
+ pch = strtok_r(NULL, "\n", &saveptr);
+ }
+
+ TRACE("end load material lib");
+
+ // last mesh or if one wasnt created with 'o' directive
+ // TRACE("Last leftover material");
+ material_darray_push(materials, current_material);
+
+ INFO("Loaded %ld materials", material_darray_len(materials));
+ TRACE("END load material lib");
+ return true;
+}
diff --git a/src/std/containers/darray.h b/src/std/containers/darray.h
index 729b4cf..b15d269 100644
--- a/src/std/containers/darray.h
+++ b/src/std/containers/darray.h
@@ -5,6 +5,11 @@
*/
// COPIED FROM KITC WITH SOME MINOR ADJUSTMENTS
+/* TODO:
+ - a 'find' function that takes a predicate (maybe wrap with a macro so we dont have to define a
+ new function?)
+*/
+
#ifndef KITC_TYPED_ARRAY_H
#define KITC_TYPED_ARRAY_H
@@ -33,14 +38,24 @@
#define PREFIX static
-#define KITC_DECL_TYPED_ARRAY(T) \
- typedef typed_array(T) T##_darray; \
- typedef typed_array_iterator(T) T##_darray_iter; \
+/* if (arena != NULL) {\ */
+/* d = arena_alloc(arena, sizeof(T##_darray));\ */
+/* data = arena_alloc(arena, starting_capacity * sizeof(T));\ */
+/* } else {\ */
+/* }\ */
+
+#define KITC_DECL_TYPED_ARRAY(T) DECL_TYPED_ARRAY(T, T)
+
+#define DECL_TYPED_ARRAY(T, Type) \
+ typedef typed_array(T) Type##_darray; \
+ typedef typed_array_iterator(Type) Type##_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)); \
+ PREFIX Type##_darray *Type##_darray_new(size_t starting_capacity) { \
+ Type##_darray *d; \
+ T *data; \
+ d = malloc(sizeof(Type##_darray)); \
+ data = malloc(starting_capacity * sizeof(T)); \
\
d->len = 0; \
d->capacity = starting_capacity; \
@@ -49,14 +64,14 @@
return d; \
} \
\
- PREFIX void T##_darray_free(T##_darray *d) { \
+ PREFIX void Type##_darray_free(Type##_darray *d) { \
if (d != NULL) { \
free(d->data); \
free(d); \
} \
} \
\
- PREFIX T *T##_darray_resize(T##_darray *d, size_t capacity) { \
+ PREFIX T *Type##_darray_resize(Type##_darray *d, size_t capacity) { \
/* resize the internal data block */ \
T *new_data = realloc(d->data, sizeof(T) * capacity); \
/* TODO: handle OOM error */ \
@@ -66,22 +81,22 @@
return new_data; \
} \
\
- PREFIX void T##_darray_push(T##_darray *d, T value) { \
+ PREFIX void Type##_darray_push(Type##_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); \
+ T *resized = Type##_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) { \
+ PREFIX void Type##_darray_push_copy(Type##_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 *resized = Type##_darray_resize(d, new_capacity); \
} \
\
T *place = d->data + d->len; \
@@ -89,18 +104,18 @@
memcpy(place, value, sizeof(T)); \
} \
\
- PREFIX void T##_darray_pop(T##_darray *d, T *dest) { \
+ PREFIX void Type##_darray_pop(Type##_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) { \
+ PREFIX void Type##_darray_ins(Type##_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); \
+ T *resized = Type##_darray_resize(d, new_capacity); \
} \
\
/* shift existing data after index */ \
@@ -114,14 +129,14 @@
memcpy(insert_dest, value, sizeof(T)); \
} \
\
- PREFIX void T##_darray_clear(T##_darray *d) { \
+ PREFIX void Type##_darray_clear(Type##_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 size_t Type##_darray_len(Type##_darray *d) { return d->len; } \
\
- PREFIX void T##_darray_print(T##_darray *d) { \
+ PREFIX void Type##_darray_print(Type##_darray *d) { \
printf("len: %zu ", d->len); \
printf("capacity: %zu\n", d->capacity); \
for (int i = 0; i < d->len; i++) { \
@@ -129,14 +144,14 @@
} \
} \
\
- PREFIX T##_darray_iter T##_darray_iter_new(T##_darray *d) { \
- T##_darray_iter iterator; \
+ PREFIX Type##_darray_iter Type##_darray_iter_new(Type##_darray *d) { \
+ Type##_darray_iter iterator; \
iterator.array = d; \
iterator.current_idx = 0; \
return iterator; \
} \
\
- PREFIX void *T##_darray_iter_next(T##_darray_iter *iterator) { \
+ PREFIX void *Type##_darray_iter_next(Type##_darray_iter *iterator) { \
if (iterator->current_idx < iterator->array->len) { \
return &iterator->array->data[iterator->current_idx++]; \
} else { \
diff --git a/src/std/containers/ring_queue.c b/src/std/containers/ring_queue.c
index a9d3506..8bfc10b 100644
--- a/src/std/containers/ring_queue.c
+++ b/src/std/containers/ring_queue.c
@@ -1,5 +1,7 @@
#include "ring_queue.h"
+
#include <stdlib.h>
+#include <string.h>
#include "defines.h"
ring_queue* ring_queue_new(size_t type_size, size_t capacity, void* memory) {
diff --git a/src/std/mem.c b/src/std/mem.c
index f5b92d4..5468898 100644
--- a/src/std/mem.c
+++ b/src/std/mem.c
@@ -11,12 +11,12 @@
void* arena_alloc_align(arena* a, size_t size, size_t align) {
ptrdiff_t padding = -(uintptr_t)a->curr & (align - 1);
ptrdiff_t available = a->end - a->curr - padding;
- TRACE("Padding %td available %td", padding, available);
+ // TRACE("Padding %td available %td", padding, available);
if (available < 0 || (ptrdiff_t)size > available) {
ERROR_EXIT("Arena ran out of memory\n");
}
- void* p = a->begin + padding;
- a->begin += padding + size;
+ void* p = a->curr + padding;
+ a->curr += padding + size;
return memset(p, 0, size);
}
void* arena_alloc(arena* a, size_t size) { return arena_alloc_align(a, size, DEFAULT_ALIGNMENT); }
@@ -29,4 +29,6 @@ arena arena_create(void* backing_buffer, size_t capacity) {
void arena_free_all(arena* a) {
a->curr = a->begin; // pop everything at once and reset to the start.
-} \ No newline at end of file
+}
+
+void arena_free_storage(arena* a) { free(a->begin); } \ No newline at end of file
diff --git a/src/std/mem.h b/src/std/mem.h
index c3ec61d..2f92894 100644
--- a/src/std/mem.h
+++ b/src/std/mem.h
@@ -22,4 +22,5 @@ arena arena_create(void* backing_buffer, size_t capacity);
void* arena_alloc(arena* a, size_t size);
void* arena_alloc_align(arena* a, size_t size, size_t align);
void arena_free_all(arena* a);
+void arena_free_storage(arena* a);
// TODO: arena_resize \ No newline at end of file
diff --git a/src/std/str.c b/src/std/str.c
index 7dafef1..07a8e73 100644
--- a/src/std/str.c
+++ b/src/std/str.c
@@ -1,10 +1,12 @@
#include "str.h"
-#include <string.h>
#include <assert.h>
+#include <string.h>
#include "mem.h"
str8 str8_create(u8* buf, size_t len) { return (str8){ .buf = buf, .len = len }; }
+str8 str8_cstr_view(char* string) { return str8_create((u8*)string, strlen(string)); }
+
bool str8_equals(str8 a, str8 b) {
if (a.len != b.len) {
return false;
@@ -48,25 +50,15 @@ str8 str8_substr(str8 s, u64 min, u64 max) {
assert(min < s.len);
assert(max >= 0);
assert(max <= s.len);
- uint8_t * start = s.buf + (ptrdiff_t)min;
+ uint8_t* start = s.buf + (ptrdiff_t)min;
size_t new_len = max - min;
- return (str8) {.buf = start, .len = new_len };
+ return (str8){ .buf = start, .len = new_len };
}
-str8 str8_take(str8 s, u64 first_n) {
- return str8_substr(s, 0, first_n);
-}
+str8 str8_take(str8 s, u64 first_n) { return str8_substr(s, 0, first_n); }
-str8 str8_drop(str8 s, u64 last_n) {
- return str8_substr(s, s.len - last_n, s.len);
-}
-
-str8 str8_skip(str8 s, u64 n) {
- return str8_substr(s, n, s.len);
-}
-
-str8 str8_chop(str8 s, u64 n) {
- return str8_substr(s, 0, s.len - n);
-}
+str8 str8_drop(str8 s, u64 last_n) { return str8_substr(s, s.len - last_n, s.len); }
+str8 str8_skip(str8 s, u64 n) { return str8_substr(s, n, s.len); }
+str8 str8_chop(str8 s, u64 n) { return str8_substr(s, 0, s.len - n); }
diff --git a/src/std/str.h b/src/std/str.h
index 735b88e..1ebecac 100644
--- a/src/std/str.h
+++ b/src/std/str.h
@@ -10,6 +10,8 @@
*/
#pragma once
+#include <ctype.h>
+
#include "defines.h"
#include "mem.h"
@@ -36,6 +38,11 @@ char* str8_to_cstr(arena* a, str8 s);
#define cstr(a, s) (str8_to_cstr(a, s)) // Shorthand
+/** @brief Return a str8 that references a statically allocated string.
+ `string` therefore must already be null-terminated.
+ @note The backing `string` cannot be modified. */
+str8 str8_cstr_view(char* string);
+
// --- Comparisons
/** @brief Compare two strings for exact equality */
@@ -70,4 +77,9 @@ str8 str8_concat(arena* a, str8 left, str8 right);
static inline bool str8_is_null_term(str8 a) {
return a.buf[a.len] == 0; // This doesn't seem safe. YOLO
-} \ No newline at end of file
+}
+
+// TODO: move or delete this and replace with handling using our internal type
+static void skip_space(char* p) {
+ while (isspace((unsigned char)*p)) ++p;
+}
diff --git a/src/systems/input.c b/src/systems/input.c
index 292d438..fc62db8 100644
--- a/src/systems/input.c
+++ b/src/systems/input.c
@@ -1,11 +1,17 @@
#include "input.h"
+#include <assert.h>
#include <glfw3.h>
+#include <string.h>
#include "log.h"
+static input_state *g_input; // Use a global to simplify caller code
+
bool input_system_init(input_state *input, GLFWwindow *window) {
INFO("Input init");
+ memset(input, 0, sizeof(input_state));
+
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++) {
@@ -14,9 +20,16 @@ bool input_system_init(input_state *input, GLFWwindow *window) {
input->just_released_keys[i] = false;
}
+ g_input = input;
+
+ assert(input->mouse.x_delta == 0);
+ assert(input->mouse.y_delta == 0);
+
return true;
}
+void input_system_shutdown(input_state *input) {}
+
void input_update(input_state *input) {
// --- update keyboard input
@@ -75,3 +88,9 @@ void input_update(input_state *input) {
input->mouse = new_mouse_state;
}
+
+bool key_is_pressed(keycode key) { return g_input->depressed_keys[key]; }
+
+bool key_just_pressed(keycode key) { return g_input->just_pressed_keys[key]; }
+
+bool key_just_released(keycode key) { return g_input->just_released_keys[key]; } \ No newline at end of file
diff --git a/src/systems/keys.h b/src/systems/keys.h
index 090bb49..a76e101 100644
--- a/src/systems/keys.h
+++ b/src/systems/keys.h
@@ -2,5 +2,18 @@
typedef enum keycode {
// TODO: add all keycodes
- KEYCODE_MAX
+ KEYCODE_SPACE = 32,
+ KEYCODE_A = 65,
+ KEYCODE_D = 68,
+ KEYCODE_S = 83,
+ KEYCODE_W = 87,
+ KEYCODE_ESCAPE = 256,
+ KEYCODE_ENTER = 257,
+ KEYCODE_TAB = 258,
+ KEYCODE_BACKSPACE = 259,
+ KEYCODE_KEY_RIGHT = 262,
+ KEYCODE_KEY_LEFT = 263,
+ KEYCODE_KEY_DOWN = 264,
+ KEYCODE_KEY_UP = 265,
+ KEYCODE_MAX = 348
} keycode; \ No newline at end of file
diff --git a/src/systems/physics.c b/src/systems/physics.c
new file mode 100644
index 0000000..299c0c1
--- /dev/null
+++ b/src/systems/physics.c
@@ -0,0 +1 @@
+#include "physics.h" \ No newline at end of file
diff --git a/src/systems/physics.h b/src/systems/physics.h
new file mode 100644
index 0000000..5c96c6e
--- /dev/null
+++ b/src/systems/physics.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "maths_types.h"
+
+// 'system' means that it gets called per frame
+
+typedef struct physics_settings {
+ f32 gravity_strength;
+} physics_settings;
+
+enum collider_type {
+ cuboid_collider,
+ sphere_collider,
+};
+
+/** @brief generic collider structure */
+typedef struct physics_collider {
+ u64 id; // ? Replace with handle?
+ enum collider_type shape;
+ transform transform;
+ u8 layer;
+ bool on_ground;
+} physics_collider;
+
+typedef struct physics_world {
+ physics_settings settings;
+} physics_world;
+
+physics_world physics_init(physics_settings settings);
+void physics_shutdown(physics_world* phys_world);
+
+/** @brief perform one or more simulation steps */
+void physics_system_update(physics_world* phys_world, f64 deltatime); \ No newline at end of file
diff --git a/src/systems/screenspace.h b/src/systems/screenspace.h
index 2250847..f513148 100644
--- a/src/systems/screenspace.h
+++ b/src/systems/screenspace.h
@@ -37,7 +37,7 @@ KITC_DECL_TYPED_ARRAY(draw_cmd)
typedef struct screenspace_state {
u32 rect_vbo;
u32 rect_vao;
- shader rect_shader;
+ // shader rect_shader;
draw_cmd_darray* draw_cmd_buf;
} screenspace_state;
diff --git a/src/systems/text.h b/src/systems/text.h
index 19248a6..4fac0b8 100644
--- a/src/systems/text.h
+++ b/src/systems/text.h
@@ -5,9 +5,11 @@
#include <stb_truetype.h>
+#include "cleanroom/types.h"
#include "darray.h"
#include "defines.h"
#include "render_types.h"
+#include "ral.h"
struct core;
@@ -29,7 +31,7 @@ KITC_DECL_TYPED_ARRAY(draw_text_packet)
typedef struct text_system_state {
font default_font;
- shader glyph_shader;
+ shader_handle glyph_shader;
u32 glyph_vbo;
u32 glyph_vao;
draw_text_packet_darray *draw_cmd_buf;
diff --git a/src/transform_hierarchy.c b/src/transform_hierarchy.c
new file mode 100644
index 0000000..2f2ff01
--- /dev/null
+++ b/src/transform_hierarchy.c
@@ -0,0 +1,184 @@
+
+/**
+ * @file transform_hierarchy.h
+ */
+#pragma once
+#include "transform_hierarchy.h"
+#include <stdlib.h>
+#include <string.h>
+
+#include "core.h"
+#include "log.h"
+#include "maths.h"
+#include "maths_types.h"
+// #include "render_types.h"
+
+struct transform_hierarchy {
+ transform_node root;
+};
+
+transform_hierarchy* transform_hierarchy_create() {
+ transform_hierarchy* tfh = malloc(sizeof(struct transform_hierarchy));
+
+ tfh->root = (transform_node){ .model = { ABSENT_MODEL_HANDLE },
+ .tf = TRANSFORM_DEFAULT,
+ .local_matrix_tf = mat4_ident(),
+ .world_matrix_tf = mat4_ident(),
+ .parent = NULL,
+ .children = { 0 },
+ .n_children = 0,
+ .tfh = tfh };
+ return tfh;
+}
+
+bool free_node(transform_node* node, void* _ctx_data) {
+ if (!node) return true; // leaf node
+ if (node == &node->tfh->root) {
+ WARN("You can't free the root node!");
+ return false;
+ }
+
+ printf("Freed node\n");
+ free(node);
+ return true;
+}
+
+void transform_hierarchy_free(transform_hierarchy* tfh) {
+ transform_hierarchy_dfs(&tfh->root, free_node, false, NULL);
+ free(tfh);
+}
+
+transform_node* transform_hierarchy_root_node(transform_hierarchy* tfh) { return &tfh->root; }
+
+transform_node* transform_hierarchy_add_node(transform_node* parent, model_handle model,
+ transform tf) {
+ if (!parent) {
+ WARN("You tried to add a node to a bad parent (NULL?)");
+ return NULL;
+ }
+ transform_node* node = malloc(sizeof(transform_node));
+ node->model = model;
+ node->tf = tf;
+ node->local_matrix_tf = mat4_ident();
+ node->world_matrix_tf = mat4_ident();
+ node->parent = parent;
+ memset(node->children, 0, sizeof(node->children));
+ node->n_children = 0;
+ node->tfh = parent->tfh;
+
+ // push into parent's children array
+ u32 next_index = parent->n_children;
+ if (next_index == MAX_TF_NODE_CHILDREN) {
+ ERROR("This transform hierarchy node already has MAX children. Dropping.");
+ free(node);
+ } else {
+ parent->children[next_index] = node;
+ parent->n_children++;
+ }
+
+ return node;
+}
+
+void transform_hierarchy_delete_node(transform_node* node) {
+ // delete all children
+ for (u32 i = 0; i < node->n_children; i++) {
+ transform_node* child = node->children[i];
+ transform_hierarchy_dfs(child, free_node, false, NULL);
+ }
+
+ if (node->parent) {
+ for (u32 i = 0; i < node->parent->n_children; i++) {
+ transform_node* child = node->parent->children[i];
+ if (child == node) {
+ node->parent->children[i] = NULL; // HACK: this will leave behind empty slots in the
+ // children array of the parent. oh well.
+ }
+ }
+ }
+
+ free(node);
+}
+
+void transform_hierarchy_dfs(transform_node* start_node,
+ bool (*visit_node)(transform_node* node, void* ctx_data),
+ bool is_pre_order, void* ctx_data) {
+ if (!start_node) return;
+
+ bool continue_traversal = true;
+ if (is_pre_order) {
+ continue_traversal = visit_node(start_node, ctx_data);
+ }
+
+ if (continue_traversal) {
+ for (u32 i = 0; i < start_node->n_children; i++) {
+ transform_node* child = start_node->children[i];
+ transform_hierarchy_dfs(child, visit_node, is_pre_order, ctx_data);
+ }
+ }
+
+ if (!is_pre_order) {
+ // post-order
+ visit_node(start_node, ctx_data);
+ }
+}
+
+// Update matrix for the current node
+bool update_matrix(transform_node* node, void* _ctx_data) {
+ if (!node) return true; // leaf node
+
+ if (node->parent && node->parent->tf.is_dirty) {
+ node->tf.is_dirty = true;
+ }
+
+ if (node->tf.is_dirty) {
+ // invalidates children
+ mat4 updated_local_transform = transform_to_mat(&node->tf);
+ node->local_matrix_tf = updated_local_transform;
+ if (node->parent) {
+ mat4 updated_world_transform =
+ mat4_mult(node->parent->world_matrix_tf, updated_local_transform);
+ node->world_matrix_tf = updated_world_transform;
+ }
+ }
+
+ return true;
+}
+
+void transform_hierarchy_propagate_transforms(transform_hierarchy* tfh) {
+ // kickoff traversal
+ transform_hierarchy_dfs(&tfh->root, update_matrix, false, NULL);
+}
+
+struct print_ctx {
+ core* core;
+ u32 indentation_lvl;
+};
+
+bool print_node(transform_node* node, void* ctx_data) {
+ struct print_ctx* ctx = (struct print_ctx*)ctx_data;
+
+ if (!node) return true;
+ if (!node->parent) {
+ printf("Root Node\n");
+ ctx->indentation_lvl++;
+ return true;
+ }
+
+ // Grab the model
+ model m = ctx->core->models->data[node->model.raw];
+ for (int i = 0; i < ctx->indentation_lvl; i++) {
+ printf(" ");
+ }
+ printf("Node %s\n", m.name.buf);
+ ctx->indentation_lvl++;
+
+ return true;
+}
+
+void transform_hierarchy_debug_print(transform_node* start_node, core* core) {
+ struct print_ctx* ctx = malloc(sizeof(struct print_ctx));
+ ctx->core = core;
+ ctx->indentation_lvl = 0;
+ transform_hierarchy_dfs(start_node, print_node, true, (void*)ctx);
+ free(ctx);
+} \ No newline at end of file
diff --git a/src/transform_hierarchy.h b/src/transform_hierarchy.h
new file mode 100644
index 0000000..d77b846
--- /dev/null
+++ b/src/transform_hierarchy.h
@@ -0,0 +1,77 @@
+/**
+ * @file transform_hierarchy.h
+ */
+#pragma once
+
+#include "maths_types.h"
+#include "render_types.h"
+#include "ral.h"
+
+#define MAX_TF_NODE_CHILDREN \
+ 32 /** TEMP: Make it simpler to manage children in `transform_node`s */
+
+typedef struct transform_hierarchy transform_hierarchy;
+
+struct transform_node {
+ model_handle model; /** A handle back to what model this node represents */
+ transform tf;
+ mat4 local_matrix_tf; /** cached local affine transform */
+ mat4 world_matrix_tf; /** cached world-space affine transform */
+
+ struct transform_node* parent;
+ struct transform_node* children[MAX_TF_NODE_CHILDREN];
+ u32 n_children;
+ struct transform_hierarchy* tfh;
+};
+typedef struct transform_node transform_node;
+
+// --- Lifecycle
+
+/** @brief Allocates and returns an empty transform hierarchy with a root node */
+transform_hierarchy* transform_hierarchy_create();
+
+/**
+ * @brief recursively frees all the children and then finally itself
+ * @note in the future we can use an object pool for the nodes
+ */
+void transform_hierarchy_free(transform_hierarchy* tfh);
+
+// --- Main usecase
+
+/** @brief Updates matrices of any invalidated nodes based on the `is_dirty` flag inside `transform`
+ */
+void transform_hierarchy_propagate_transforms(transform_hierarchy* tfh);
+
+// --- Queries
+
+/** @brief Get a pointer to the root node */
+transform_node* transform_hierarchy_root_node(transform_hierarchy* tfh);
+
+// --- Mutations
+transform_node* transform_hierarchy_add_node(transform_node* parent, model_handle model,
+ transform tf);
+void transform_hierarchy_delete_node(transform_node* node);
+
+// --- Traversal
+
+/**
+ * @brief Perform a depth-first search traversal starting from `start_node`.
+ * @param start_node The starting node of the traversal.
+ * @param visit_node The function to call for each node visited. The callback should return false to
+ stop the traversal early.
+ * @param is_pre_order Indicates whether to do pre-order or post-order traversal i.e. when to call
+ the `visit_node` function.
+ * @param ctx_data An optional pointer to data that is be passed on each call to `visit_node`. Can
+ be used to carry additional information or context.
+ *
+ * @note The main use-cases are:
+ 1. traversing the whole tree to update cached 4x4 affine transform matrices (post-order)
+ 2. freeing child nodes after deleting a node in the tree (post-order)
+ 3. debug pretty printing the whole tree (post-order)
+ */
+void transform_hierarchy_dfs(transform_node* start_node,
+ bool (*visit_node)(transform_node* node, void* ctx_data),
+ bool is_pre_order, void* ctx_data);
+
+struct core;
+void transform_hierarchy_debug_print(transform_node* start_node, struct core* core); \ No newline at end of file