diff options
Diffstat (limited to 'src')
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 }) @@ -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 @@ -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, ®ion); +} + +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, ©_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", ¤t_material.ambient_colour.x, + ¤t_material.ambient_colour.y, ¤t_material.ambient_colour.z); + } else if (strcmp(line_header, "Kd") == 0) { + // diffuse + sscanf(pch + offset, "%f %f %f", ¤t_material.diffuse.x, ¤t_material.diffuse.y, + ¤t_material.diffuse.z); + } else if (strcmp(line_header, "Ks") == 0) { + // specular + sscanf(pch + offset, "%f %f %f", ¤t_material.specular.x, ¤t_material.specular.y, + ¤t_material.specular.z); + } else if (strcmp(line_header, "Ns") == 0) { + // specular exponent + sscanf(pch + offset, "%f", ¤t_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(¤t_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(¤t_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 |