summaryrefslogtreecommitdiff
path: root/archive
diff options
context:
space:
mode:
authoromniscient <17525998+omnisci3nce@users.noreply.github.com>2024-10-05 12:48:05 +1000
committeromniscient <17525998+omnisci3nce@users.noreply.github.com>2024-10-05 12:48:05 +1000
commitdfb6adbcbcc7d50b770b6d5ea82efdd8f8c32e25 (patch)
treea470b91a90716d7ea46fde53ed395449c24583a2 /archive
parent54354e32c6498cc7f8839ab4deb1208d37216cc5 (diff)
delete documentation workflow
Diffstat (limited to 'archive')
-rw-r--r--archive/src/animation.c90
-rw-r--r--archive/src/animation.h122
-rw-r--r--archive/src/apidocs/coldark_prism.css317
-rw-r--r--archive/src/apidocs/doc_styles.css76
-rw-r--r--archive/src/apidocs/gen_apidocs.py119
-rw-r--r--archive/src/apidocs/index.html304
-rw-r--r--archive/src/apidocs/modules.md18
-rw-r--r--archive/src/apidocs/prism.css3
-rw-r--r--archive/src/apidocs/prism.js6
-rw-r--r--archive/src/apidocs/template.html91
-rw-r--r--archive/src/collision.c9
-rw-r--r--archive/src/colours.h89
-rw-r--r--archive/src/core/README.md3
-rw-r--r--archive/src/core/animation.c0
-rw-r--r--archive/src/core/camera.c90
-rw-r--r--archive/src/core/camera.h39
-rw-r--r--archive/src/core/core.c81
-rw-r--r--archive/src/core/core.h43
-rw-r--r--archive/src/core/input.c0
-rw-r--r--archive/src/core/vfs.h38
-rw-r--r--archive/src/empty.c3
-rw-r--r--archive/src/log.c56
-rw-r--r--archive/src/log.h84
-rw-r--r--archive/src/logos/README.md6
-rw-r--r--archive/src/logos/tasks.h74
-rw-r--r--archive/src/logos/threadpool.c141
-rw-r--r--archive/src/logos/threadpool.h96
-rw-r--r--archive/src/maths/geometry.h50
-rw-r--r--archive/src/maths/maths.c35
-rw-r--r--archive/src/maths/maths.h321
-rw-r--r--archive/src/maths/maths_types.h84
-rw-r--r--archive/src/maths/primitives.c343
-rw-r--r--archive/src/maths/primitives.h29
-rw-r--r--archive/src/physics.h44
-rw-r--r--archive/src/platform/file.c93
-rw-r--r--archive/src/platform/file.h26
-rw-r--r--archive/src/platform/platform.h37
-rw-r--r--archive/src/platform/platform_unix.c16
-rw-r--r--archive/src/platform/platform_windows.c22
-rw-r--r--archive/src/ral/README.md5
-rw-r--r--archive/src/ral/backends/metal/backend_metal.h0
-rw-r--r--archive/src/ral/backends/opengl/backend_opengl.c449
-rw-r--r--archive/src/ral/backends/opengl/backend_opengl.h109
-rw-r--r--archive/src/ral/backends/opengl/opengl_helpers.h159
-rw-r--r--archive/src/ral/backends/vulkan/backend_vulkan.c0
-rw-r--r--archive/src/ral/backends/vulkan/backend_vulkan.h44
-rw-r--r--archive/src/ral/backends/vulkan/vulkan_glossary.md18
-rw-r--r--archive/src/ral/backends/vulkan/vulkan_helpers.h199
-rw-r--r--archive/src/ral/ral.h5
-rw-r--r--archive/src/ral/ral_common.c70
-rw-r--r--archive/src/ral/ral_common.h61
-rw-r--r--archive/src/ral/ral_impl.h102
-rw-r--r--archive/src/ral/ral_types.h168
-rw-r--r--archive/src/render/archive/backends/backend_test.c1
-rw-r--r--archive/src/render/archive/backends/metal/README.md1
-rw-r--r--archive/src/render/archive/backends/metal/backend_metal.h74
-rw-r--r--archive/src/render/archive/backends/metal/backend_metal.m285
-rw-r--r--archive/src/render/archive/backends/opengl/backend_opengl.c521
-rw-r--r--archive/src/render/archive/backends/opengl/backend_opengl.h68
-rw-r--r--archive/src/render/archive/backends/vulkan/README.md1
-rw-r--r--archive/src/render/archive/backends/vulkan/backend_vulkan.c1705
-rw-r--r--archive/src/render/archive/backends/vulkan/backend_vulkan.h118
-rw-r--r--archive/src/render/immdraw.c176
-rw-r--r--archive/src/render/immdraw.h63
-rw-r--r--archive/src/render/pbr.c266
-rw-r--r--archive/src/render/pbr.h70
-rw-r--r--archive/src/render/render.c359
-rw-r--r--archive/src/render/render.h151
-rw-r--r--archive/src/render/render_types.h138
-rw-r--r--archive/src/render/shader_layouts.h70
-rw-r--r--archive/src/render/shadows.c211
-rw-r--r--archive/src/render/shadows.h48
-rw-r--r--archive/src/render/skybox.c161
-rw-r--r--archive/src/render/skybox.h41
-rw-r--r--archive/src/resources/gltf.c596
-rw-r--r--archive/src/resources/loaders.h17
-rw-r--r--archive/src/resources/obj.c398
-rw-r--r--archive/src/std/buf.h11
-rw-r--r--archive/src/std/containers/container_utils.h17
-rw-r--r--archive/src/std/containers/darray.h151
-rw-r--r--archive/src/std/containers/graphs.h14
-rw-r--r--archive/src/std/containers/hashmap.h27
-rw-r--r--archive/src/std/containers/hashset.h29
-rw-r--r--archive/src/std/containers/ring_queue.c68
-rw-r--r--archive/src/std/containers/ring_queue.h35
-rw-r--r--archive/src/std/containers/stack_array.h19
-rw-r--r--archive/src/std/mem.c135
-rw-r--r--archive/src/std/mem.h96
-rw-r--r--archive/src/std/str.c74
-rw-r--r--archive/src/std/str.h89
-rw-r--r--archive/src/std/utils.h4
-rw-r--r--archive/src/systems/grid.c84
-rw-r--r--archive/src/systems/grid.h21
-rw-r--r--archive/src/systems/input.c105
-rw-r--r--archive/src/systems/input.h53
-rw-r--r--archive/src/systems/keys.h59
-rw-r--r--archive/src/systems/screenspace.h53
-rw-r--r--archive/src/systems/terrain.c204
-rw-r--r--archive/src/systems/terrain.h72
-rw-r--r--archive/src/systems/text.c1
-rw-r--r--archive/src/systems/text.h53
-rw-r--r--archive/src/transform_hierarchy.c185
-rw-r--r--archive/src/transform_hierarchy.h80
103 files changed, 11765 insertions, 0 deletions
diff --git a/archive/src/animation.c b/archive/src/animation.c
new file mode 100644
index 0000000..d7973dc
--- /dev/null
+++ b/archive/src/animation.c
@@ -0,0 +1,90 @@
+#include "animation.h"
+#include "immdraw.h"
+#include "log.h"
+#include "maths.h"
+#include "maths_types.h"
+#include "ral_types.h"
+
+Keyframe Animation_Sample(AnimationSampler* sampler, f32 t) {
+ size_t previous_index = 0;
+ f32 previous_time = 0.0;
+ // look forwards
+ DEBUG("%d\n", keyframe_kind_strings[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;
+
+ switch (sampler->animation.values.kind) {
+ case KEYFRAME_ROTATION:
+ return (Keyframe){ .rotation = quat_slerp(
+ sampler->animation.values.values[previous_index].rotation,
+ sampler->animation.values.values[next_index].rotation, percent) };
+ case KEYFRAME_TRANSLATION:
+ case KEYFRAME_SCALE:
+ case KEYFRAME_WEIGHTS:
+ WARN("TODO: other keyframe kind interpolation");
+ return prev_value;
+ }
+}
+
+void Animation_Tick(AnimationClip* clip, Armature* armature, f32 time) {
+ TRACE("Ticking animation %s", clip->clip_name);
+
+ for (u32 c_i = 0; c_i < clip->channels->len; c_i++) {
+ AnimationSampler* sampler = clip->channels->data;
+
+ // Interpolated keyframe based on time
+ Keyframe k = Animation_Sample(sampler, time);
+
+ // Get the joint in the armature
+ Joint* joint = &armature->joints->data[sampler->target_joint_idx];
+ if (sampler->animation.values.kind == KEYFRAME_ROTATION) {
+ // Update the joints rotation
+ joint->transform_components.rotation = k.rotation;
+ } else {
+ WARN("not yet implemented animation kind");
+ }
+ }
+}
+
+void Animation_VisualiseJoints(Armature* armature) {
+ for (int j = 0; j < armature->joints->len; j++) {
+ Joint joint = armature->joints->data[j];
+ Transform tf = joint.transform_components;
+ tf.scale = vec3(0.05, 0.05, 0.05);
+ Immdraw_Sphere(tf, vec4(0, 1, 1, 1), true);
+ }
+}
+
+ShaderDataLayout AnimData_GetLayout(void* data) {
+ AnimDataUniform* d = data;
+ bool has_data = data != NULL;
+
+ ShaderBinding b1 = { .label = "AnimData",
+ .kind = BINDING_BYTES,
+ .data.bytes.size = sizeof(AnimDataUniform) };
+
+ if (has_data) {
+ b1.data.bytes.data = d;
+ }
+ return (ShaderDataLayout){ .bindings = { b1 }, .binding_count = 1 };
+}
diff --git a/archive/src/animation.h b/archive/src/animation.h
new file mode 100644
index 0000000..5883f13
--- /dev/null
+++ b/archive/src/animation.h
@@ -0,0 +1,122 @@
+#pragma once
+
+#include "cgltf.h"
+#include "darray.h"
+#include "defines.h"
+#include "maths_types.h"
+#include "ral_types.h"
+
+typedef enum Interpolation {
+ INTERPOLATION_STEP,
+ INTERPOLATION_LINEAR,
+ INTERPOLATION_CUBIC, /** @brief Cubic spline interpolation */
+ INTERPOLATION_COUNT
+} Interpolation;
+
+typedef enum KeyframeKind {
+ KEYFRAME_ROTATION,
+ KEYFRAME_TRANSLATION,
+ KEYFRAME_SCALE,
+ KEYFRAME_WEIGHTS,
+} KeyframeKind;
+
+static const char* keyframe_kind_strings[4] = { "ROTATION", "TRANSLATION", "SCALE", "WEIGHTS"};
+
+typedef union Keyframe {
+ Quat rotation;
+ Vec3 translation;
+ Vec3 scale;
+ f32 weights[4];
+} Keyframe;
+
+typedef struct Keyframes {
+ KeyframeKind kind;
+ Keyframe* values;
+ size_t count;
+} Keyframes;
+
+typedef struct Joint {
+ // used instead of pointers later to update correct joints
+ size_t node_idx;
+ ssize_t parent; // parent bone. -1 means its the root
+ size_t children[8]; // children bones, upto 8
+ u8 children_count;
+ char* debug_label; // optional
+ Mat4 inverse_bind_matrix;
+ Mat4 local_transform;
+ /** @brief holds position, rotation, and scale that will be written to by animation
+ samplers every tick of the animation system. */
+ Transform transform_components;
+} Joint;
+#ifndef TYPED_JOINT_ARRAY
+KITC_DECL_TYPED_ARRAY(Joint);
+#define TYPED_JOINT_ARRAY
+#endif
+
+typedef u32 JointIdx;
+
+typedef struct Armature {
+ char* label;
+ Joint_darray* joints;
+} Armature;
+
+// NOTE: I think we will need to topologically sort the joints to store them in array if we want to
+// do linear array traversal
+// when calculating transforms.
+
+typedef struct AnimationSpline {
+ f32* timestamps;
+ size_t n_timestamps;
+ Keyframes values;
+ Interpolation interpolation;
+} AnimationSpline;
+
+// combines a sampler and a channel in gltf
+typedef struct AnimationSampler {
+ int current_index;
+ f32 min;
+ f32 max;
+ AnimationSpline animation;
+ u32 target_joint_idx; // index into the array of joints in an armature
+} AnimationSampler;
+#ifndef TYPED_ANIM_SAMPLER_ARRAY
+KITC_DECL_TYPED_ARRAY(AnimationSampler);
+#define TYPED_ANIM_SAMPLER_ARRAY
+#endif
+
+/** @brief Sample an animation at a given time `t` returning an interpolated keyframe */
+PUB Keyframe Animation_Sample(AnimationSampler* sampler, f32 t);
+
+/** @brief A clip contains one or more animation curves. */
+typedef struct AnimationClip {
+ const char* clip_name;
+ bool is_playing;
+ f32 time;
+ AnimationSampler_darray* channels;
+} AnimationClip;
+#ifndef TYPED_ANIM_CLIP_ARRAY
+KITC_DECL_TYPED_ARRAY(AnimationClip);
+#define TYPED_ANIM_CLIP_ARRAY
+#endif
+
+// typedef struct SkinnedAnimation {
+// Mat4* joint_matrices;
+// size_t n_joints;
+// } SkinnedAnimation;
+
+PUB void Animation_Play(AnimationClip* clip);
+
+void Animation_Tick(AnimationClip* clip, Armature* armature, f32 delta_time);
+
+void Animation_VisualiseJoints(Armature* armature);
+
+#define MAX_BONES 100
+
+typedef struct AnimDataUniform {
+ Mat4 bone_matrices[MAX_BONES];
+} AnimDataUniform;
+ShaderDataLayout AnimData_GetLayout(void* data);
+
+// Animation Targets:
+// - Mesh
+// - Joint
diff --git a/archive/src/apidocs/coldark_prism.css b/archive/src/apidocs/coldark_prism.css
new file mode 100644
index 0000000..39dd470
--- /dev/null
+++ b/archive/src/apidocs/coldark_prism.css
@@ -0,0 +1,317 @@
+/**
+ * Coldark Theme for Prism.js
+ * Theme variation: Dark
+ * Tested with HTML, CSS, JS, JSON, PHP, YAML, Bash script
+ * @author Armand Philippot <contact@armandphilippot.com>
+ * @homepage https://github.com/ArmandPhilippot/coldark-prism
+ * @license MIT
+ */
+code[class*="language-"],
+pre[class*="language-"] {
+ color: #e3eaf2;
+ background: none;
+ font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
+ text-align: left;
+ white-space: pre;
+ word-spacing: normal;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 1.5;
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ tab-size: 4;
+ -webkit-hyphens: none;
+ -moz-hyphens: none;
+ -ms-hyphens: none;
+ hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection,
+pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection,
+code[class*="language-"] ::-moz-selection {
+ background: #3c526d;
+}
+
+pre[class*="language-"]::selection,
+pre[class*="language-"] ::selection,
+code[class*="language-"]::selection,
+code[class*="language-"] ::selection {
+ background: #3c526d;
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+ padding: 1em;
+ margin: 0.5em 0;
+ overflow: auto;
+}
+
+:not(pre) > code[class*="language-"],
+pre[class*="language-"] {
+ background: #111b27;
+}
+
+/* Inline code */
+:not(pre) > code[class*="language-"] {
+ padding: 0.1em 0.3em;
+ border-radius: 0.3em;
+ white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+ color: #8da1b9;
+}
+
+.token.punctuation {
+ color: #e3eaf2;
+}
+
+.token.delimiter.important,
+.token.selector .parent,
+.token.tag,
+.token.tag .token.punctuation {
+ color: #66cccc;
+}
+
+.token.attr-name,
+.token.boolean,
+.token.boolean.important,
+.token.number,
+.token.constant,
+.token.selector .token.attribute {
+ color: #e6d37a;
+}
+
+.token.class-name,
+.token.key,
+.token.parameter,
+.token.property,
+.token.property-access,
+.token.variable {
+ color: #6cb8e6;
+}
+
+.token.attr-value,
+.token.inserted,
+.token.color,
+.token.selector .token.value,
+.token.string,
+.token.string .token.url-link {
+ color: #91d076;
+}
+
+.token.builtin,
+.token.keyword-array,
+.token.package,
+.token.regex {
+ color: #f4adf4;
+}
+
+.token.function,
+.token.selector .token.class,
+.token.selector .token.id {
+ color: #c699e3;
+}
+
+.token.atrule .token.rule,
+.token.combinator,
+.token.keyword,
+.token.operator,
+.token.pseudo-class,
+.token.pseudo-element,
+.token.selector,
+.token.unit {
+ color: #e9ae7e;
+}
+
+.token.deleted,
+.token.important {
+ color: #cd6660;
+}
+
+.token.keyword-this,
+.token.this {
+ color: #6cb8e6;
+}
+
+.token.important,
+.token.keyword-this,
+.token.this,
+.token.bold {
+ font-weight: bold;
+}
+
+.token.delimiter.important {
+ font-weight: inherit;
+}
+
+.token.italic {
+ font-style: italic;
+}
+
+.token.entity {
+ cursor: help;
+}
+
+.language-markdown .token.title,
+.language-markdown .token.title .token.punctuation {
+ color: #6cb8e6;
+ font-weight: bold;
+}
+
+.language-markdown .token.blockquote.punctuation {
+ color: #f4adf4;
+}
+
+.language-markdown .token.code {
+ color: #66cccc;
+}
+
+.language-markdown .token.hr.punctuation {
+ color: #6cb8e6;
+}
+
+.language-markdown .token.url .token.content {
+ color: #91d076;
+}
+
+.language-markdown .token.url-link {
+ color: #e6d37a;
+}
+
+.language-markdown .token.list.punctuation {
+ color: #f4adf4;
+}
+
+.language-markdown .token.table-header {
+ color: #e3eaf2;
+}
+
+.language-json .token.operator {
+ color: #e3eaf2;
+}
+
+.language-scss .token.variable {
+ color: #66cccc;
+}
+
+/* overrides color-values for the Show Invisibles plugin
+ * https://prismjs.com/plugins/show-invisibles/
+ */
+.token.token.tab:not(:empty):before,
+.token.token.cr:before,
+.token.token.lf:before,
+.token.token.space:before {
+ color: #8da1b9;
+}
+
+/* overrides color-values for the Toolbar plugin
+ * https://prismjs.com/plugins/toolbar/
+ */
+div.code-toolbar > .toolbar.toolbar > .toolbar-item > a,
+div.code-toolbar > .toolbar.toolbar > .toolbar-item > button {
+ color: #111b27;
+ background: #6cb8e6;
+}
+
+div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover,
+div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus,
+div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover,
+div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus {
+ color: #111b27;
+ background: #6cb8e6da;
+ text-decoration: none;
+}
+
+div.code-toolbar > .toolbar.toolbar > .toolbar-item > span,
+div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover,
+div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus {
+ color: #111b27;
+ background: #8da1b9;
+}
+
+/* overrides color-values for the Line Highlight plugin
+ * http://prismjs.com/plugins/line-highlight/
+ */
+.line-highlight.line-highlight {
+ background: #3c526d5f;
+ background: linear-gradient(to right, #3c526d5f 70%, #3c526d55);
+}
+
+.line-highlight.line-highlight:before,
+.line-highlight.line-highlight[data-end]:after {
+ background-color: #8da1b9;
+ color: #111b27;
+ box-shadow: 0 1px #3c526d;
+}
+
+pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows > span:hover:before {
+ background-color: #8da1b918;
+}
+
+/* overrides color-values for the Line Numbers plugin
+ * http://prismjs.com/plugins/line-numbers/
+ */
+.line-numbers.line-numbers .line-numbers-rows {
+ border-right: 1px solid #0b121b;
+ background: #0b121b7a;
+}
+
+.line-numbers .line-numbers-rows > span:before {
+ color: #8da1b9da;
+}
+
+/* overrides color-values for the Match Braces plugin
+ * https://prismjs.com/plugins/match-braces/
+ */
+.rainbow-braces .token.token.punctuation.brace-level-1,
+.rainbow-braces .token.token.punctuation.brace-level-5,
+.rainbow-braces .token.token.punctuation.brace-level-9 {
+ color: #e6d37a;
+}
+
+.rainbow-braces .token.token.punctuation.brace-level-2,
+.rainbow-braces .token.token.punctuation.brace-level-6,
+.rainbow-braces .token.token.punctuation.brace-level-10 {
+ color: #f4adf4;
+}
+
+.rainbow-braces .token.token.punctuation.brace-level-3,
+.rainbow-braces .token.token.punctuation.brace-level-7,
+.rainbow-braces .token.token.punctuation.brace-level-11 {
+ color: #6cb8e6;
+}
+
+.rainbow-braces .token.token.punctuation.brace-level-4,
+.rainbow-braces .token.token.punctuation.brace-level-8,
+.rainbow-braces .token.token.punctuation.brace-level-12 {
+ color: #c699e3;
+}
+
+/* overrides color-values for the Diff Highlight plugin
+ * https://prismjs.com/plugins/diff-highlight/
+ */
+pre.diff-highlight > code .token.token.deleted:not(.prefix),
+pre > code.diff-highlight .token.token.deleted:not(.prefix) {
+ background-color: #cd66601f;
+}
+
+pre.diff-highlight > code .token.token.inserted:not(.prefix),
+pre > code.diff-highlight .token.token.inserted:not(.prefix) {
+ background-color: #91d0761f;
+}
+
+/* overrides color-values for the Command Line plugin
+ * https://prismjs.com/plugins/command-line/
+ */
+.command-line .command-line-prompt {
+ border-right: 1px solid #0b121b;
+}
+
+.command-line .command-line-prompt > span:before {
+ color: #8da1b9da;
+}
diff --git a/archive/src/apidocs/doc_styles.css b/archive/src/apidocs/doc_styles.css
new file mode 100644
index 0000000..ee0851b
--- /dev/null
+++ b/archive/src/apidocs/doc_styles.css
@@ -0,0 +1,76 @@
+/*
+ Josh's Custom CSS Reset
+ https://www.joshwcomeau.com/css/custom-css-reset/
+*/
+*, *::before, *::after {
+ box-sizing: border-box;
+}
+* {
+ margin: 0;
+}
+body {
+ line-height: 1.5;
+ -webkit-font-smoothing: antialiased;
+}
+img, picture, video, canvas, svg {
+ display: block;
+ max-width: 100%;
+}
+input, button, textarea, select {
+ font: inherit;
+}
+p, h1, h2, h3, h4, h5, h6 {
+ overflow-wrap: break-word;
+}
+#root, #__next {
+ isolation: isolate;
+}
+
+/* Styles */
+
+html {
+ -webkit-text-size-adjust: 100%;
+ -moz-tab-size: 4;
+ -o-tab-size: 4;
+ tab-size: 4;
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */
+ font-feature-settings: normal;
+ font-variation-settings: normal;
+}
+
+pre {
+ margin: 0;
+ padding: 0;
+}
+code {
+ margin: 0;
+ padding: 6px 0 !important
+}
+
+:root {
+ /* TODO: create basic greyscale colours */
+}
+
+main {
+ padding: 12px;
+}
+
+h1 {
+ font-size: 18px;
+ margin-bottom: 20px;
+ /* font-weight: 700; */
+}
+
+h3 {
+ font-size: 16px;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.category-list {
+ padding: 10px 20px;
+ list-style-type: none;
+}
+
+.signature {
+}
diff --git a/archive/src/apidocs/gen_apidocs.py b/archive/src/apidocs/gen_apidocs.py
new file mode 100644
index 0000000..d01e441
--- /dev/null
+++ b/archive/src/apidocs/gen_apidocs.py
@@ -0,0 +1,119 @@
+# Generates a static webpage for the public C-API of `celeritas-core`
+#
+# TODO:
+# - remove prefixes like 'static' and 'inline'
+# - parse docstrings from source
+
+import re
+import os
+from pathlib import Path
+
+# --- HTML Fragments
+page_start = """
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <link rel="stylesheet" href="doc_styles.css">
+ <!-- <link rel="stylesheet" href="prism.css"> -->
+ <!-- <script src="prism.js"></script> -->
+
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
+ <!-- and it's easy to individually load additional languages -->
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js"></script>
+ <script>hljs.highlightAll();</script>
+
+ <title>Celeritas core API</title>
+</head>
+<body>
+<main>
+"""
+
+page_header = """
+<header>
+ <h1>CELERITAS CORE API DOCS</h1>
+</header>
+"""
+
+page_footer = """
+<footer>
+</footer>
+"""
+
+page_end = """
+</main>
+</body>
+</html>
+"""
+
+def emit_function_sig(signature: str) -> str:
+ return f"""
+ <li class="signature">
+ <pre><code class="language-c">{signature}</code></pre>
+ </li>
+ """
+
+categories = {
+ "Core": "src/core",
+ "Render": "src/new_render",
+ "Maths": "src/maths",
+ "RAL": "src/ral",
+ "Systems": "src/systems",
+}
+
+def find_pub_functions_in_folder(folder_path):
+ functions = []
+ for filename in os.listdir(folder_path):
+ filepath = os.path.join(folder_path, filename)
+ if os.path.isfile(filepath):
+ file_funcs = find_pub_functions_in_file(filepath)
+ functions.extend(file_funcs)
+
+ return functions
+
+def find_pub_functions_in_file(file_path):
+ pattern = r'PUB\s+(\w+\s+)*(\w+)\s+(\w+)\s*\((.*?)\)'
+
+ with open(file_path, 'r') as file:
+ content = file.read()
+
+ matches = re.finditer(pattern, content, re.MULTILINE)
+
+ # Collect all the functions into an array
+ functions = []
+ for match in matches:
+ signature = match.group(0)
+ if signature.startswith("PUB "):
+ signature = signature[4:]
+ if signature.startswith("c_static_inline "):
+ signature = signature[16:]
+
+ print(signature)
+ functions.append(signature)
+
+ return functions
+
+def generate_html():
+ html_filepath = "index.html"
+
+ script_dir = Path(__file__).resolve().parent
+ grandparent_dir = script_dir.parents[1]
+
+ with open(html_filepath, 'w') as export_file:
+ export_file.write(page_start)
+ export_file.write(page_header)
+ # TODO: make the actual content
+ for category in categories.keys():
+ folder = os.path.join(grandparent_dir, categories[category])
+ category_funcs = find_pub_functions_in_folder(folder)
+ export_file.write(f"<h3>{category}</h3>")
+ export_file.write("<ul class=\"category-list\">")
+ for func in category_funcs:
+ export_file.write(emit_function_sig(func))
+ export_file.write("</ul>")
+ export_file.write(page_end)
+
+if __name__ == "__main__":
+ generate_html()
diff --git a/archive/src/apidocs/index.html b/archive/src/apidocs/index.html
new file mode 100644
index 0000000..a72dbf3
--- /dev/null
+++ b/archive/src/apidocs/index.html
@@ -0,0 +1,304 @@
+
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="description" content="">
+ <link rel="stylesheet" href="doc_styles.css">
+ <!-- <link rel="stylesheet" href="prism.css"> -->
+ <!-- <script src="prism.js"></script> -->
+
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
+ <!-- and it's easy to individually load additional languages -->
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js"></script>
+ <script>hljs.highlightAll();</script>
+
+ <title>Celeritas core API</title>
+</head>
+<body>
+<main>
+
+<header>
+ <h1>CELERITAS CORE API DOCS</h1>
+</header>
+<h3>Core</h3><ul class="category-list">
+ <li class="signature">
+ <pre><code class="language-c">Camera Camera_Create(Vec3 pos, Vec3 front, Vec3 up, f32 fov)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">Mat4 Camera_View2D(Camera* c)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void FlyCamera_Update(Camera* camera)</code></pre>
+ </li>
+ </ul><h3>Render</h3><ul class="category-list">
+ <li class="signature">
+ <pre><code class="language-c">Skybox Skybox_Create(const char** face_paths, int n)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Skybox_Draw(Skybox* skybox, Camera camera)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void SetCamera(Camera camera)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void SetMainLight(DirectionalLight light)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void PBR_Init(PBR_Storage* storage)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">Material PBRMaterialDefault()</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">ShaderDataLayout PBRMaterial_GetLayout(void* data)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Shadow_Init(Shadow_Storage* storage, u32 shadowmap_width, u32 shadowmap_height)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Shadow_Run(RenderEnt* entities, size_t entity_count)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Shadow_DrawDebugQuad()</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">TextureHandle Shadow_GetShadowMapTexture(Shadow_Storage* storage)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Immdraw_Init(Immdraw_Storage* storage)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Immdraw_Shutdown(Immdraw_Storage* storage)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Immdraw_Plane(Transform tf, Vec4 colour, bool wireframe)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Immdraw_Cuboid(Transform tf, Vec4 colour, bool wireframe)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Immdraw_Sphere(Transform tf, f32 size, Vec4 colour, bool wireframe)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Immdraw_TransformGizmo(Transform tf, f32 size)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void EncodeDrawModel(Handle model, Mat4 transform)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void EncodeDrawMesh(Mesh* mesh, Material* material, Mat4 affine)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Renderer_Shutdown(Renderer* renderer)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">size_t Renderer_GetMemReqs()</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Render_FrameBegin(Renderer* renderer)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Render_FrameEnd(Renderer* renderer)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Render_RenderEntities(RenderEnt* entities, size_t entity_count)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">TextureData TextureDataLoad(const char* path, bool invert_y)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void TextureUpload(TextureHandle handle, size_t n_bytes, const void* data)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">TextureHandle TextureLoadFromFile(const char* path)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">ModelHandle ModelLoad(const char* debug_name, const char* filepath)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">Mesh Mesh_Create(Geometry* geometry, bool free_on_upload)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Mesh_Delete(Mesh* mesh)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void DrawMesh(Mesh* mesh, Material* material, Mat4 model)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void Render_DrawTerrain()</code></pre>
+ </li>
+ </ul><h3>Maths</h3><ul class="category-list">
+ <li class="signature">
+ <pre><code class="language-c">Vec3 vec3_normalise(Vec3 a)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">Vec3 vec3_create(f32 x, f32 y, f32 z)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">Vec3 vec3_add(Vec3 a, Vec3 b)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">Vec3 vec3_sub(Vec3 a, Vec3 b)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">Vec3 vec3_mult(Vec3 a, f32 s)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">Vec3 vec3_div(Vec3 a, f32 s)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">f32 vec3_len_squared(Vec3 a)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">f32 vec3_len(Vec3 a)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">Vec3 vec3_negate(Vec3 a)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">Vec3 vec3_normalise(Vec3 a)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">f32 vec3_dot(Vec3 a, Vec3 b)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">Vec3 vec3_cross(Vec3 a, Vec3 b)</code></pre>
+ </li>
+ </ul><h3>RAL</h3><ul class="category-list">
+ <li class="signature">
+ <pre><code class="language-c">void GPU_Renderpass_Destroy(GPU_Renderpass* pass)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GraphicsPipeline_Destroy(GPU_Pipeline* pipeline)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">GPU_CmdEncoder GPU_CmdEncoder_Create()</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_CmdEncoder_Destroy(GPU_CmdEncoder* encoder)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_CmdEncoder_Begin(GPU_CmdEncoder* encoder)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_CmdEncoder_Finish(GPU_CmdEncoder* encoder)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_CmdEncoder_BeginRender(GPU_CmdEncoder* encoder, GPU_Renderpass* renderpass)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_CmdEncoder_EndRender(GPU_CmdEncoder* encoder)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_QueueSubmit(GPU_CmdBuffer* cmd_buffer)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_BufferDestroy(BufferHandle handle)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_BufferUpload(BufferHandle buffer, size_t n_bytes, const void* data)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">TextureHandle GPU_TextureCreate(TextureDesc desc, bool create_view, const void* data)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_TextureDestroy(TextureHandle handle)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_TextureUpload(TextureHandle handle, size_t n_bytes, const void* data)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_EncodeBindPipeline(GPU_CmdEncoder* encoder, GPU_Pipeline* pipeline)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_EncodeBindShaderData(GPU_CmdEncoder* encoder, u32 group, ShaderData data)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_EncodeSetVertexBuffer(GPU_CmdEncoder* encoder, BufferHandle buf)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_EncodeSetIndexBuffer(GPU_CmdEncoder* encoder, BufferHandle buf)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_EncodeDraw(GPU_CmdEncoder* encoder, u64 count)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_EncodeDrawIndexed(GPU_CmdEncoder* encoder, u64 index_count)</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">bool GPU_Backend_BeginFrame()</code></pre>
+ </li>
+
+ <li class="signature">
+ <pre><code class="language-c">void GPU_Backend_EndFrame()</code></pre>
+ </li>
+ </ul>
+</main>
+</body>
+</html>
diff --git a/archive/src/apidocs/modules.md b/archive/src/apidocs/modules.md
new file mode 100644
index 0000000..7298844
--- /dev/null
+++ b/archive/src/apidocs/modules.md
@@ -0,0 +1,18 @@
+
+
+- core lifecycle
+- memory
+ - arena
+ - pool
+- containers
+ - darray
+ - hashtbl
+ - ring_queue
+- maths
+- physics
+- platform
+ - file
+ - path
+ - mutex
+ - thread
+- threadpool \ No newline at end of file
diff --git a/archive/src/apidocs/prism.css b/archive/src/apidocs/prism.css
new file mode 100644
index 0000000..333e985
--- /dev/null
+++ b/archive/src/apidocs/prism.css
@@ -0,0 +1,3 @@
+/* PrismJS 1.29.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike */
+code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
diff --git a/archive/src/apidocs/prism.js b/archive/src/apidocs/prism.js
new file mode 100644
index 0000000..d0b4c05
--- /dev/null
+++ b/archive/src/apidocs/prism.js
@@ -0,0 +1,6 @@
+/* PrismJS 1.29.0
+https://prismjs.com/download.html#themes=prism&languages=markup+css+clike */
+var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function e(n,t){var r,i;switch(t=t||{},a.util.type(n)){case"Object":if(i=a.util.objId(n),t[i])return t[i];for(var l in r={},t[i]=r,n)n.hasOwnProperty(l)&&(r[l]=e(n[l],t));return r;case"Array":return i=a.util.objId(n),t[i]?t[i]:(r=[],t[i]=r,n.forEach((function(n,a){r[a]=e(n,t)})),r);default:return n}},getLanguage:function(e){for(;e;){var t=n.exec(e.className);if(t)return t[1].toLowerCase();e=e.parentElement}return"none"},setLanguage:function(e,t){e.className=e.className.replace(RegExp(n,"gi"),""),e.classList.add("language-"+t)},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(r){var e=(/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(r.stack)||[])[1];if(e){var n=document.getElementsByTagName("script");for(var t in n)if(n[t].src==e)return n[t]}return null}},isActive:function(e,n,t){for(var r="no-"+n;e;){var a=e.classList;if(a.contains(n))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!t}},languages:{plain:r,plaintext:r,text:r,txt:r,extend:function(e,n){var t=a.util.clone(a.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(e,n,t,r){var i=(r=r||a.languages)[e],l={};for(var o in i)if(i.hasOwnProperty(o)){if(o==n)for(var s in t)t.hasOwnProperty(s)&&(l[s]=t[s]);t.hasOwnProperty(o)||(l[o]=i[o])}var u=r[e];return r[e]=l,a.languages.DFS(a.languages,(function(n,t){t===u&&n!=e&&(this[n]=l)})),l},DFS:function e(n,t,r,i){i=i||{};var l=a.util.objId;for(var o in n)if(n.hasOwnProperty(o)){t.call(n,o,n[o],r||o);var s=n[o],u=a.util.type(s);"Object"!==u||i[l(s)]?"Array"!==u||i[l(s)]||(i[l(s)]=!0,e(s,t,o,i)):(i[l(s)]=!0,e(s,t,null,i))}}},plugins:{},highlightAll:function(e,n){a.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};a.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),a.hooks.run("before-all-elements-highlight",r);for(var i,l=0;i=r.elements[l++];)a.highlightElement(i,!0===n,r.callback)},highlightElement:function(n,t,r){var i=a.util.getLanguage(n),l=a.languages[i];a.util.setLanguage(n,i);var o=n.parentElement;o&&"pre"===o.nodeName.toLowerCase()&&a.util.setLanguage(o,i);var s={element:n,language:i,grammar:l,code:n.textContent};function u(e){s.highlightedCode=e,a.hooks.run("before-insert",s),s.element.innerHTML=s.highlightedCode,a.hooks.run("after-highlight",s),a.hooks.run("complete",s),r&&r.call(s.element)}if(a.hooks.run("before-sanity-check",s),(o=s.element.parentElement)&&"pre"===o.nodeName.toLowerCase()&&!o.hasAttribute("tabindex")&&o.setAttribute("tabindex","0"),!s.code)return a.hooks.run("complete",s),void(r&&r.call(s.element));if(a.hooks.run("before-highlight",s),s.grammar)if(t&&e.Worker){var c=new Worker(a.filename);c.onmessage=function(e){u(e.data)},c.postMessage(JSON.stringify({language:s.language,code:s.code,immediateClose:!0}))}else u(a.highlight(s.code,s.grammar,s.language));else u(a.util.encode(s.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};if(a.hooks.run("before-tokenize",r),!r.grammar)throw new Error('The language "'+r.language+'" has no grammar.');return r.tokens=a.tokenize(r.code,r.grammar),a.hooks.run("after-tokenize",r),i.stringify(a.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new s;return u(a,a.head,e),o(e,a,n,a.head,0),function(e){for(var n=[],t=e.head.next;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=a.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=a.hooks.all[e];if(t&&t.length)for(var r,i=0;r=t[i++];)r(n)}},Token:i};function i(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||"").length}function l(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function o(e,n,t,r,s,g){for(var f in t)if(t.hasOwnProperty(f)&&t[f]){var h=t[f];h=Array.isArray(h)?h:[h];for(var d=0;d<h.length;++d){if(g&&g.cause==f+","+d)return;var v=h[d],p=v.inside,m=!!v.lookbehind,y=!!v.greedy,k=v.alias;if(y&&!v.pattern.global){var x=v.pattern.toString().match(/[imsuy]*$/)[0];v.pattern=RegExp(v.pattern.source,x+"g")}for(var b=v.pattern||v,w=r.next,A=s;w!==n.tail&&!(g&&A>=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(j<O||"string"==typeof C.value);C=C.next)L++,j+=C.value.length;L--,E=e.slice(A,j),P.index-=A}else if(!(P=l(b,0,E,m)))continue;S=P.index;var N=P[0],_=E.slice(0,S),M=E.slice(S+N.length),W=A+E.length;g&&W>g.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a<t&&r!==e.tail;a++)r=r.next;n.next=r,r.prev=n,e.length-=a}if(e.Prism=a,i.stringify=function e(n,t){if("string"==typeof n)return n;if(Array.isArray(n)){var r="";return n.forEach((function(n){r+=e(n,t)})),r}var i={type:n.type,content:e(n.content,t),tag:"span",classes:["token",n.type],attributes:{},language:t},l=n.alias;l&&(Array.isArray(l)?Array.prototype.push.apply(i.classes,l):i.classes.push(l)),a.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=" "+s+'="'+(i.attributes[s]||"").replace(/"/g,"&quot;")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+o+">"+i.content+"</"+i.tag+">"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
+Prism.languages.markup={comment:{pattern:/<!--(?:(?!<!--)[\s\S])*?-->/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&amp;/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^<!\[CDATA\[|\]\]>$/i;var t={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml;
+!function(s){var e=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|"+e.source+")*?(?:;|(?=\\s*\\{))"),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism);
+Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
diff --git a/archive/src/apidocs/template.html b/archive/src/apidocs/template.html
new file mode 100644
index 0000000..1e1c7a7
--- /dev/null
+++ b/archive/src/apidocs/template.html
@@ -0,0 +1,91 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <script src="https://cdn.tailwindcss.com"></script>
+</head>
+<body class="flex p-4 bg-neutral-800 text-neutral-200">
+ <nav class="pr-8">
+ <h3 class="text-sm font-semibold">Modules</h3>
+ <br />
+ <ul class="flex flex-col">
+ <li class="text-neutral-400 text-sm"><span class="pr-1">1.1</span> core lifecycle</li>
+ <li class="text-neutral-400 text-sm"><span class="pr-1">1.2</span> threadpool</li>
+ <h5 class="text-xs text-neutral-300 tracking-wide pt-2">maths</h5>
+ <li class="text-neutral-400 text-sm"><span class="pr-1">1.3</span> vector</li>
+ <li class="text-neutral-400 text-sm"><span class="pr-1">1.4</span> quaternion</li>
+ <li class="text-neutral-400 text-sm"><span class="pr-1">1.5</span> matrix</li>
+ <!-- <li class="text-neutral-400 text-sm"><span class="pr-1">3.0</span> memory</li> -->
+ <h5 class="text-xs text-neutral-300 tracking-wide pt-2">memory</h5>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.6</span> arena</li>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.7</span> pool</li>
+ <!-- <li class="text-neutral-400 text-sm"><span class="pr-1">4.0</span> containers</li> -->
+ <h5 class="text-xs text-neutral-300 tracking-wide pt-2">containers</h5>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.8</span> darray</li>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.9</span> hashtbl</li>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.10</span> ring_queue</li>
+
+ <h5 class="text-xs text-neutral-300 tracking-wide pt-2">std</h5>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.11</span> str8</li>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.12</span> bytebuffer</li>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.13</span> utils</li>
+
+ <h5 class="text-xs text-neutral-300 tracking-wide pt-2">render</h5>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.14</span> ral</li>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.15</span> render</li>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.16</span> immdraw</li>
+
+ <h5 class="text-xs text-neutral-300 tracking-wide pt-2">platform</h5>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.17</span> file</li>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.18</span> path</li>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.19</span> mutex</li>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.20</span> thread</li>
+
+ <h5 class="text-xs text-neutral-300 tracking-wide pt-2"></h5>
+ <li class="text-neutral-400 text-sm "><span class="pr-1">1.21</span> scene</li>
+
+<!--
+- core lifecycle
+- memory
+ - arena
+ - pool
+- containers
+ - darray
+ - hashtbl
+ - ring_queue
+- maths
+- physics
+- platform
+ - file
+ - path
+ - mutex
+ - thread
+- threadpool
+ -->
+ </ul>
+ </nav>
+ <main class="px-8">
+ <h2 class="font-semibold text-amber-400">Celeritas Core API</h2>
+
+ <div class="my-8"></div>
+ <div class="bg-neutral-700 px-2 py-1 min-w-[600px]" style="font-family: monospace;">
+ <span class="text-[#ECBE7B]">arena</span>
+ <span class="text-[#80A0C2]">arena_create</span>(<span class="text-[#D2876D]">void*</span>
+ <span class="text-neutral-300">backing_buffer</span>,
+ <span class="text-[#D2876D]">size_t</span>
+ <span class="text-neutral-300">capacity</span>);
+ </div>
+ <div class="bg-neutral-700 px-2 py-1 min-w-[600px]" style="font-family: monospace;">
+ <span class="text-[#ECBE7B]">void*</span>
+ <span class="text-[#80A0C2]">arena_alloc</span>(<span class="text-[#D2876D]">arena*</span>
+ <span class="text-neutral-300">a</span>,
+ <span class="text-[#D2876D]">size_t</span>
+ <span class="text-neutral-300">size</span>);
+ </div>
+<!-- void* arena_alloc(arena* a, size_t size); -->
+ </main>
+
+
+</body>
+</html> \ No newline at end of file
diff --git a/archive/src/collision.c b/archive/src/collision.c
new file mode 100644
index 0000000..81cbcfc
--- /dev/null
+++ b/archive/src/collision.c
@@ -0,0 +1,9 @@
+#include "immdraw.h"
+#include "maths.h"
+#include "maths_types.h"
+#include "physics.h"
+
+PUB void Debug_DrawOBB(OBB obb) {
+ Transform t = transform_create(obb.center, obb.rotation, vec3_sub(obb.bbox.max, obb.bbox.min));
+ Immdraw_Cuboid(t, vec4(0.0, 0.8, 0.1, 1.0), true);
+}
diff --git a/archive/src/colours.h b/archive/src/colours.h
new file mode 100644
index 0000000..ac8996a
--- /dev/null
+++ b/archive/src/colours.h
@@ -0,0 +1,89 @@
+#pragma once
+
+#include "defines.h"
+
+typedef struct rgba {
+ f32 r, g, b, a;
+} rgba;
+
+#define COLOUR_BLACK ((rgba){ 0.02, 0.02, 0.0, 1.0 })
+#define COLOUR_IMPERIAL_RED ((rgba){ 0.97, 0.09, 0.21, 1.0 })
+#define COLOUR_TRUE_BLUE ((rgba){ 0.11, 0.41, 0.77, 1.0 })
+#define COLOUR_SEA_GREEN ((rgba){ 0.18, 0.77, 0.71, 1.0 })
+#define COLOUR_WHITE ((rgba){ 1.0, 1.0, 1.0, 1.0 })
+
+#define rgba_to_vec4(color) (vec4(color.r, color.g, color.b, color.a))
+#define rgba_to_vec3(color) (vec3(color.r, color.g, color.b))
+
+// Thanks ChatGPT
+#define STONE_50 ((rgba){ 0.980, 0.980, 0.976, 1.0 })
+#define STONE_100 ((rgba){ 0.961, 0.961, 0.957, 1.0 })
+#define STONE_200 ((rgba){ 0.906, 0.898, 0.894, 1.0 })
+#define STONE_300 ((rgba){ 0.839, 0.827, 0.819, 1.0 })
+#define STONE_400 ((rgba){ 0.659, 0.635, 0.620, 1.0 })
+#define STONE_500 ((rgba){ 0.471, 0.443, 0.424, 1.0 })
+#define STONE_600 ((rgba){ 0.341, 0.325, 0.306, 1.0 })
+#define STONE_700 ((rgba){ 0.267, 0.251, 0.235, 1.0 })
+#define STONE_800 ((rgba){ 0.161, 0.145, 0.141, 1.0 })
+#define STONE_900 ((rgba){ 0.110, 0.098, 0.090, 1.0 })
+#define STONE_950 ((rgba){ 0.047, 0.043, 0.035, 1.0 })
+
+#define CYAN_50 ((rgba){ 0.930, 1.000, 1.000, 1.0 })
+#define CYAN_100 ((rgba){ 0.810, 0.980, 1.000, 1.0 })
+#define CYAN_200 ((rgba){ 0.650, 0.953, 0.988, 1.0 })
+#define CYAN_300 ((rgba){ 0.404, 0.910, 0.976, 1.0 })
+#define CYAN_400 ((rgba){ 0.133, 0.827, 0.933, 1.0 })
+#define CYAN_500 ((rgba){ 0.023, 0.714, 0.831, 1.0 })
+#define CYAN_600 ((rgba){ 0.031, 0.569, 0.698, 1.0 })
+#define CYAN_700 ((rgba){ 0.055, 0.455, 0.565, 1.0 })
+#define CYAN_800 ((rgba){ 0.082, 0.369, 0.459, 1.0 })
+#define CYAN_900 ((rgba){ 0.086, 0.306, 0.388, 1.0 })
+#define CYAN_950 ((rgba){ 0.033, 0.200, 0.263, 1.0 })
+
+#define GRAY_50 ((rgba){ 0.976, 0.980, 0.984, 1.0 })
+#define GRAY_100 ((rgba){ 0.953, 0.957, 0.965, 1.0 })
+#define GRAY_200 ((rgba){ 0.898, 0.906, 0.922, 1.0 })
+#define GRAY_300 ((rgba){ 0.820, 0.835, 0.859, 1.0 })
+#define GRAY_400 ((rgba){ 0.612, 0.639, 0.686, 1.0 })
+#define GRAY_500 ((rgba){ 0.420, 0.447, 0.502, 1.0 })
+#define GRAY_600 ((rgba){ 0.294, 0.333, 0.388, 1.0 })
+#define GRAY_700 ((rgba){ 0.216, 0.255, 0.318, 1.0 })
+#define GRAY_800 ((rgba){ 0.122, 0.161, 0.216, 1.0 })
+#define GRAY_900 ((rgba){ 0.067, 0.094, 0.153, 1.0 })
+#define GRAY_950 ((rgba){ 0.012, 0.027, 0.071, 1.0 })
+
+#define RED_50 ((rgba){ 0.996, 0.949, 0.949, 1.0 })
+#define RED_100 ((rgba){ 0.996, 0.886, 0.886, 1.0 })
+#define RED_200 ((rgba){ 0.996, 0.792, 0.792, 1.0 })
+#define RED_300 ((rgba){ 0.988, 0.647, 0.647, 1.0 })
+#define RED_400 ((rgba){ 0.973, 0.443, 0.443, 1.0 })
+#define RED_500 ((rgba){ 0.937, 0.267, 0.267, 1.0 })
+#define RED_600 ((rgba){ 0.863, 0.149, 0.149, 1.0 })
+#define RED_700 ((rgba){ 0.725, 0.110, 0.110, 1.0 })
+#define RED_800 ((rgba){ 0.600, 0.106, 0.106, 1.0 })
+#define RED_900 ((rgba){ 0.498, 0.114, 0.114, 1.0 })
+#define RED_950 ((rgba){ 0.271, 0.039, 0.039, 1.0 })
+
+#define ORANGE_50 ((rgba){ 1.000, 0.969, 0.929, 1.0 })
+#define ORANGE_100 ((rgba){ 1.000, 0.929, 0.835, 1.0 })
+#define ORANGE_200 ((rgba){ 0.996, 0.843, 0.667, 1.0 })
+#define ORANGE_300 ((rgba){ 0.992, 0.729, 0.455, 1.0 })
+#define ORANGE_400 ((rgba){ 0.984, 0.573, 0.235, 1.0 })
+#define ORANGE_500 ((rgba){ 0.976, 0.451, 0.086, 1.0 })
+#define ORANGE_600 ((rgba){ 0.918, 0.345, 0.047, 1.0 })
+#define ORANGE_700 ((rgba){ 0.761, 0.255, 0.047, 1.0 })
+#define ORANGE_800 ((rgba){ 0.604, 0.204, 0.071, 1.0 })
+#define ORANGE_900 ((rgba){ 0.486, 0.176, 0.071, 1.0 })
+#define ORANGE_950 ((rgba){ 0.263, 0.078, 0.027, 1.0 })
+
+#define AMBER_50 ((rgba){ 1.000, 0.984, 0.922, 1.0 })
+#define AMBER_100 ((rgba){ 0.996, 0.953, 0.780, 1.0 })
+#define AMBER_200 ((rgba){ 0.992, 0.902, 0.541, 1.0 })
+#define AMBER_300 ((rgba){ 0.988, 0.827, 0.302, 1.0 })
+#define AMBER_400 ((rgba){ 0.984, 0.749, 0.141, 1.0 })
+#define AMBER_500 ((rgba){ 0.961, 0.620, 0.043, 1.0 })
+#define AMBER_600 ((rgba){ 0.851, 0.467, 0.024, 1.0 })
+#define AMBER_700 ((rgba){ 0.706, 0.325, 0.035, 1.0 })
+#define AMBER_800 ((rgba){ 0.573, 0.251, 0.055, 1.0 })
+#define AMBER_900 ((rgba){ 0.471, 0.208, 0.059, 1.0 })
+#define AMBER_950 ((rgba){ 0.271, 0.102, 0.012, 1.0 })
diff --git a/archive/src/core/README.md b/archive/src/core/README.md
new file mode 100644
index 0000000..19cc1d0
--- /dev/null
+++ b/archive/src/core/README.md
@@ -0,0 +1,3 @@
+# Core
+
+Core engine facilities
diff --git a/archive/src/core/animation.c b/archive/src/core/animation.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/archive/src/core/animation.c
diff --git a/archive/src/core/camera.c b/archive/src/core/camera.c
new file mode 100644
index 0000000..77ddad6
--- /dev/null
+++ b/archive/src/core/camera.c
@@ -0,0 +1,90 @@
+#include "camera.h"
+
+#include "input.h"
+#include "keys.h"
+#include "maths.h"
+
+#define CAMERA_SPEED 0.2
+#define CAMERA_SENSITIVITY 0.5
+
+Camera Camera_Create(Vec3 pos, Vec3 front, Vec3 up, f32 fov) {
+ Camera c = { .position = pos, .front = front, .up = up, .fov = fov };
+ return c;
+}
+
+Mat4 Camera_ViewProj(Camera* c, f32 lens_height, f32 lens_width, Mat4* out_view, Mat4* out_proj) {
+ Mat4 proj = mat4_perspective(c->fov, lens_width / lens_height, 0.1, 1000.0);
+ Vec3 camera_direction = vec3_add(c->position, c->front);
+ Mat4 view = mat4_look_at(c->position, camera_direction, c->up);
+ if (out_view) {
+ *out_view = view;
+ }
+ if (out_proj) {
+ *out_proj = proj;
+ }
+ return mat4_mult(view, proj);
+}
+
+void FlyCamera_Update(Camera* camera) {
+ static f32 yaw = 0.0;
+ static f32 pitch = 0.0;
+
+ // Keyboard
+ f32 speed = CAMERA_SPEED;
+ Vec3 horizontal = vec3_cross(camera->front, camera->up);
+ if (key_is_pressed(KEYCODE_A) || key_is_pressed(KEYCODE_KEY_LEFT)) {
+ Vec3 displacement = vec3_mult(horizontal, -speed);
+ camera->position = vec3_add(camera->position, displacement);
+ }
+ if (key_is_pressed(KEYCODE_D) || key_is_pressed(KEYCODE_KEY_RIGHT)) {
+ Vec3 displacement = vec3_mult(horizontal, speed);
+ camera->position = vec3_add(camera->position, displacement);
+ }
+ if (key_is_pressed(KEYCODE_W) || key_is_pressed(KEYCODE_KEY_UP)) {
+ Vec3 displacement = vec3_mult(camera->front, speed);
+ camera->position = vec3_add(camera->position, displacement);
+ }
+ if (key_is_pressed(KEYCODE_S) || key_is_pressed(KEYCODE_KEY_DOWN)) {
+ Vec3 displacement = vec3_mult(camera->front, -speed);
+ camera->position = vec3_add(camera->position, displacement);
+ }
+ if (key_is_pressed(KEYCODE_Q)) {
+ Vec3 displacement = vec3_mult(camera->up, speed);
+ camera->position = vec3_add(camera->position, displacement);
+ }
+ if (key_is_pressed(KEYCODE_E)) {
+ Vec3 displacement = vec3_mult(camera->up, -speed);
+ camera->position = vec3_add(camera->position, displacement);
+ }
+
+ // Mouse
+ if (MouseBtn_Held(MOUSEBTN_LEFT)) {
+ mouse_state mouse = Input_GetMouseState();
+ // printf("Delta x: %d Delta y %d\n",mouse.x_delta, mouse.y_delta );
+
+ f32 x_offset = mouse.x_delta;
+ f32 y_offset = -mouse.y_delta;
+
+ f32 sensitivity = CAMERA_SENSITIVITY; // change this value to your liking
+ x_offset *= sensitivity;
+ y_offset *= sensitivity;
+
+ yaw += x_offset;
+ pitch += y_offset;
+
+ // make sure that when pitch is out of bounds, screen doesn't get flipped
+ if (pitch > 89.0f) pitch = 89.0f;
+ if (pitch < -89.0f) pitch = -89.0f;
+
+ Vec3 front;
+ front.x = cos(deg_to_rad(yaw) * cos(deg_to_rad(pitch)));
+ front.y = sin(deg_to_rad(pitch));
+ front.z = sin(deg_to_rad(yaw)) * cos(deg_to_rad(pitch));
+ front = vec3_normalise(front);
+ camera->front.x = front.x;
+ camera->front.y = front.y;
+ camera->front.z = front.z;
+ }
+
+ // TODO: Right mouse => pan in screen space
+}
diff --git a/archive/src/core/camera.h b/archive/src/core/camera.h
new file mode 100644
index 0000000..4300f87
--- /dev/null
+++ b/archive/src/core/camera.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include "defines.h"
+#include "maths_types.h"
+
+// TODO: swap to position + quaternion
+
+typedef struct Camera {
+ Vec3 position;
+ Vec3 front;
+ Vec3 up;
+ f32 fov;
+} Camera;
+
+/** @brief create a camera */
+PUB Camera Camera_Create(Vec3 pos, Vec3 front, Vec3 up, f32 fov);
+
+/**
+ * @brief Get 3D camera transform matrix
+ * @param out_view optionally stores just the view matrix
+ * @param out_proj optionally stores just the projection matrix
+ * @returns the camera's view projection matrix pre-multiplied
+ */
+PUB Mat4 Camera_ViewProj(Camera* c, f32 lens_height, f32 lens_width, Mat4* out_view,
+ Mat4* out_proj);
+
+/** @brief Get 2D camera transform matrix */
+PUB Mat4 Camera_View2D(Camera* c); // TODO: 2D cameras
+
+struct Input_State;
+
+PUB void FlyCamera_Update(Camera* camera);
+
+// TODO: (HIGH) Basic reusable camera controls
+/*
+Right click + move = pan
+Left click = orbit camera
+WASD = forward/backward/left/right
+*/
diff --git a/archive/src/core/core.c b/archive/src/core/core.c
new file mode 100644
index 0000000..64f59f3
--- /dev/null
+++ b/archive/src/core/core.c
@@ -0,0 +1,81 @@
+#include "core.h"
+
+#include <stdlib.h>
+
+#include "glfw3.h"
+#include "input.h"
+#include "keys.h"
+#include "log.h"
+#include "mem.h"
+#include "render.h"
+#include "render_types.h"
+
+// These are only the initial window dimensions
+#define SCR_WIDTH 1000
+#define SCR_HEIGHT 1000
+
+Core g_core; /** @brief global `Core` that other files can use */
+
+/** @brief Gets the global `Core` singleton */
+inline Core* GetCore() { return &g_core; }
+
+void Core_Bringup(const char* window_name, struct GLFWwindow* optional_window) {
+ INFO("Initiate Core bringup");
+ memset(&g_core, 0, sizeof(Core));
+
+ RendererConfig conf = { .window_name = window_name,
+ .scr_width = SCR_WIDTH,
+ .scr_height = SCR_HEIGHT,
+ .clear_colour = (Vec3){ .08, .08, .1 } };
+
+ g_core.renderer = malloc(Renderer_GetMemReqs());
+
+ // Initialise all subsystems
+
+ // renderer config, renderer ptr, ptr to store a window, and optional preexisting glfw window
+ if (!Renderer_Init(conf, g_core.renderer, &g_core.window, optional_window)) {
+ // FATAL("Failed to start renderer");
+ ERROR_EXIT("Failed to start renderer\n");
+ }
+ if (optional_window != NULL) {
+ g_core.window = optional_window;
+ }
+
+ if (!Input_Init(&g_core.input, g_core.window)) {
+ // the input system needs the glfw window which is created by the renderer
+ // hence the order here is important
+ ERROR_EXIT("Failed to start input system\n");
+ }
+
+ size_t model_data_max = 1024 * 1024 * 1024;
+ arena model_arena = arena_create(malloc(model_data_max), model_data_max);
+
+ Model_pool model_pool = Model_pool_create(&model_arena, 256, sizeof(Model));
+ g_core.models = model_pool;
+ INFO("Created model pool allocator");
+}
+
+void Core_Shutdown() {
+ Input_Shutdown(&g_core.input);
+ Renderer_Shutdown(g_core.renderer);
+ free(g_core.renderer);
+}
+
+bool ShouldExit() {
+ return key_just_released(KEYCODE_ESCAPE) || glfwWindowShouldClose(g_core.window);
+}
+
+void Frame_Begin() {
+ Input_Update(&g_core.input);
+ Render_FrameBegin(g_core.renderer);
+}
+void Frame_Draw() {}
+void Frame_End() { Render_FrameEnd(g_core.renderer); }
+
+Core* get_global_core() { return &g_core; }
+
+GLFWwindow* Core_GetGlfwWindowPtr(Core* core) { return g_core.window; }
+
+struct Renderer* Core_GetRenderer(Core* core) { return core->renderer; }
+
+Model* Model_Get(ModelHandle h) { return Model_pool_get(&g_core.models, h); }
diff --git a/archive/src/core/core.h b/archive/src/core/core.h
new file mode 100644
index 0000000..14ba65d
--- /dev/null
+++ b/archive/src/core/core.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "input.h"
+#include "mem.h"
+#include "render_types.h"
+#include "screenspace.h"
+#include "terrain.h"
+#include "text.h"
+
+TYPED_POOL(Model, Model)
+#define MODEL_GET(h) (Model_pool_get(&g_core.models, h))
+Model* Model_Get(ModelHandle h);
+
+typedef struct GLFWwindow GLFWwindow;
+
+typedef struct Core {
+ const char* app_name;
+ GLFWwindow* window;
+ Renderer* renderer;
+ Input_State input;
+ // Model_pool models;
+} Core;
+extern Core g_core;
+
+struct Renderer;
+
+Core* get_global_core();
+
+/**
+ @brief Throws error if the core cannot be instantiated
+ @param [in] optional_window - Leave NULL if you want Celeritas to instantiate its own window with
+ GLFW, if you want to provide the glfw window then pass it in here.
+*/
+void Core_Bringup(const char* window_name, GLFWwindow* optional_window);
+void Core_Shutdown();
+bool ShouldExit();
+
+GLFWwindow* Core_GetGlfwWindowPtr(Core* core);
+struct Renderer* Core_GetRenderer(Core* core);
+
+void Frame_Begin();
+void Frame_Draw();
+void Frame_End();
diff --git a/archive/src/core/input.c b/archive/src/core/input.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/archive/src/core/input.c
diff --git a/archive/src/core/vfs.h b/archive/src/core/vfs.h
new file mode 100644
index 0000000..41033f5
--- /dev/null
+++ b/archive/src/core/vfs.h
@@ -0,0 +1,38 @@
+#pragma once
+#include "defines.h"
+
+#define MAX_VIRTUAL_FILENAME_LEN 256
+
+typedef struct VFS_Pack VFS_Pack;
+
+const char VFS_OpenErr_DoesNotExist[] = "PATH DOES NOT EXIST";
+
+typedef struct VFS_File {
+ size_t n_bytes;
+ void* data;
+} VFS_File;
+
+// virtual file open result
+typedef struct VFS_FileRes {
+ bool success;
+ const char* error_reason;
+ VFS_File file;
+} VFS_FileRes;
+
+VFS_Pack* VFS_Open(const char* filepath);
+
+bool VFS_Close(VFS_Pack*);
+
+VFS_FileRes VFS_VirtualRead(VFS_Pack* vfs, const char* unique_path);
+
+typedef struct VFS_PackBuilder {
+ const char* pack_filename;
+} VFS_PackBuilder;
+
+typedef struct VFS_FileEntry {
+ char filename[MAX_VIRTUAL_FILENAME_LEN];
+ size_t offset;
+ size_t size;
+} VFS_FileEntry;
+
+VFS_PackBuilder VFS_Pack_Create(); \ No newline at end of file
diff --git a/archive/src/empty.c b/archive/src/empty.c
new file mode 100644
index 0000000..b40cc85
--- /dev/null
+++ b/archive/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/archive/src/log.c b/archive/src/log.c
new file mode 100644
index 0000000..8bb7a65
--- /dev/null
+++ b/archive/src/log.c
@@ -0,0 +1,56 @@
+#include "log.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "defines.h"
+
+// Regular text
+#define BLK "\e[0;30m"
+#define RED "\e[0;31m"
+#define GRN "\e[0;32m"
+#define YEL "\e[0;33m"
+#define BLU "\e[0;34m"
+#define MAG "\e[0;35m"
+#define CYN "\e[0;36m"
+#define WHT "\e[0;37m"
+#define CRESET "\e[0m"
+
+static const char* level_strings[6] = { "[FATAL]: ", "[ERROR]: ", "[WARN]: ",
+ "[INFO]: ", "[DEBUG]: ", "[TRACE]: " };
+static const char* level_colours[6] = { RED, RED, YEL, BLU, CYN, MAG };
+
+bool logger_init() {
+ // TODO: create log file
+ return true;
+}
+
+void logger_shutdown() {
+ // does nothing right now
+}
+
+void log_output(log_level level, const char* message, ...) {
+ char out_message[32000];
+ memset(out_message, 0, sizeof(out_message));
+
+ // format original message
+ __builtin_va_list arg_ptr;
+ va_start(arg_ptr, message);
+ vsnprintf(out_message, 32000, message, arg_ptr);
+ va_end(arg_ptr);
+
+ char out_message2[32006];
+ // prepend log level string
+ sprintf(out_message2, "%s%s%s%s\n", level_colours[level], level_strings[level], out_message,
+ CRESET);
+
+ // print message to console
+ printf("%s", out_message2);
+}
+
+void report_assertion_failure(const char* expression, const char* message, const char* file,
+ int line) {
+ log_output(LOG_LEVEL_FATAL, "Assertion failure: %s, message: '%s', in file: %s, on line %d\n",
+ expression, message, file, line);
+}
diff --git a/archive/src/log.h b/archive/src/log.h
new file mode 100644
index 0000000..537cb6e
--- /dev/null
+++ b/archive/src/log.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define ERROR_EXIT(...) \
+ { \
+ fprintf(stderr, __VA_ARGS__); \
+ exit(1); \
+ }
+
+#define TODO(msg) \
+ do { \
+ ERROR_EXIT("TODO: %s", msg); \
+ } while (0)
+
+#define LOG_WARN_ENABLED 1
+#define LOG_INFO_ENABLED 1
+
+#ifdef CRELEASE
+#define LOG_DEBUG_ENABLED 0
+#define LOG_TRACE_ENABLED 0
+#else
+#define LOG_DEBUG_ENABLED 1
+#define LOG_TRACE_ENABLED 1
+#endif
+
+typedef enum log_level {
+ LOG_LEVEL_FATAL = 0,
+ LOG_LEVEL_ERROR = 1,
+ LOG_LEVEL_WARN = 2,
+ LOG_LEVEL_INFO = 3,
+ LOG_LEVEL_DEBUG = 4,
+ LOG_LEVEL_TRACE = 5,
+} log_level;
+
+bool logger_init();
+void logger_shutdown();
+
+// TODO: macro that outputs logger macros for a specific subsystem or string prefix e.g. "MEMORY" ->
+// logs now have more context potentially have line numbers too?
+
+void log_output(log_level level, const char* message, ...);
+
+#define FATAL(message, ...) log_output(LOG_LEVEL_FATAL, message, ##__VA_ARGS__)
+#define ERROR(message, ...) log_output(LOG_LEVEL_ERROR, message, ##__VA_ARGS__)
+#define WARN(message, ...) log_output(LOG_LEVEL_WARN, message, ##__VA_ARGS__)
+#define INFO(message, ...) log_output(LOG_LEVEL_INFO, message, ##__VA_ARGS__)
+
+#if LOG_DEBUG_ENABLED == 1
+#define DEBUG(message, ...) log_output(LOG_LEVEL_DEBUG, message, ##__VA_ARGS__)
+#else
+#define DEBUG(message, ...)
+#endif
+
+#if LOG_TRACE_ENABLED == 1
+#define TRACE(message, ...) log_output(LOG_LEVEL_TRACE, message, ##__VA_ARGS__)
+#else
+#define TRACE(message, ...)
+#endif
+
+// TODO: Move this to an asserts file
+
+void report_assertion_failure(const char* expression, const char* message, const char* file,
+ int line);
+
+#define CASSERT(expr) \
+ { \
+ if (expr) { \
+ } else { \
+ report_assertion_failure(#expr, "", __FILE__, __LINE__); \
+ __builtin_trap(); \
+ } \
+ }
+
+#define CASSERT_MSG(expr, msg) \
+ { \
+ if (expr) { \
+ } else { \
+ report_assertion_failure(#expr, msg, __FILE__, __LINE__); \
+ __builtin_trap(); \
+ } \
+ } \ No newline at end of file
diff --git a/archive/src/logos/README.md b/archive/src/logos/README.md
new file mode 100644
index 0000000..25b7bef
--- /dev/null
+++ b/archive/src/logos/README.md
@@ -0,0 +1,6 @@
+# Logos
+
+Logos is the namespace for threadpool & job system code. This is not a 'system' as it is underlying core unit of the engine.
+
+Threadpool currently gets initialised at core bringup with a set number of threads and results are processed once per frame
+on the main thread. This is subject to change but multithreading is not the highest priority right now. \ No newline at end of file
diff --git a/archive/src/logos/tasks.h b/archive/src/logos/tasks.h
new file mode 100644
index 0000000..2e3dc53
--- /dev/null
+++ b/archive/src/logos/tasks.h
@@ -0,0 +1,74 @@
+/**
+ * Common jobs that get run
+ */
+#pragma once
+#include "defines.h"
+#include "logos/threadpool.h"
+#include "render_types.h"
+#include "str.h"
+
+typedef enum TaskLifetime {
+ /** ephemeral tasks must be finished by the end of the frame and thus we use a leak and clear
+ allocation strategy */
+ TASK_EPHEMERAL,
+ /** multi-frame tasks have a more complex lifetime and must be cleaned up by the caller or in a
+ separate cleanup callback */
+ TASK_MULTIFRAME,
+ TASK_COUNT
+} TaskLifetime;
+
+typedef enum TaskKind {
+ TASK_RENDER,
+ TASK_PHYSICS,
+ TASK_GAMEPLAY,
+ TASK_ASSET,
+ TASK_USERLAND,
+ TASKKIND_COUNT
+} TaskKind;
+
+typedef struct Task {
+ char* debug_name;
+ void* params;
+ bool is_done;
+} Task;
+
+// Macro : give Params and Result structs and it creates a function that knows
+// correct sizes
+
+typedef struct Task_ModelLoad_Params {
+ Str8 filepath; // filepath to the model on disk
+} Task_ModelLoad_Params;
+typedef struct Task_ModelLoad_Result {
+ Model model;
+} Task_ModelLoad_Result;
+
+// Internally it will allocate data for each
+
+static bool Task_ModelLoad_Typed(Task_ModelLoad_Params* params, Task_ModelLoad_Result* result,
+ tpool_task_start run_task, tpool_task_on_complete on_success,
+ tpool_task_on_complete on_failure) {
+ threadpool_add_task(pool, , tpool_task_on_complete on_success, tpool_task_on_complete on_fail,
+ bool buffer_result_for_main_thread, void* param_data, u32 param_data_size,
+ u32 result_data_size)
+}
+
+// do task
+// success
+void model_load_success(task_globals* globals, void* result) {
+ Task_ModelLoad_Result* load_res = result;
+
+ // push into render -> renderables ?
+}
+// fail
+
+// we can define our custom task here that wraps the more verbose function pointers
+static Task Task_ModelLoad(Task_ModelLoad_Params* params, Task_ModelLoad_Result* result) {
+ Task task;
+ task.debug_name = "Load Model";
+ task.params = params;
+
+ Task_ModelLoad_Typed(params, result, tpool_task_start run_task, tpool_task_on_complete on_success,
+ tpool_task_on_complete on_failure)
+
+ return task;
+}
diff --git a/archive/src/logos/threadpool.c b/archive/src/logos/threadpool.c
new file mode 100644
index 0000000..0e82d98
--- /dev/null
+++ b/archive/src/logos/threadpool.c
@@ -0,0 +1,141 @@
+#include "threadpool.h"
+
+#include <pthread.h>
+
+#include "defines.h"
+#include "log.h"
+#include "ring_queue.h"
+
+static void* worker_factory(void* arg) {
+ threadpool_worker* worker = arg;
+ // INFO("Starting job thread %d", worker->id);
+
+ // Run forever, waiting for jobs.
+ while (true) {
+ pthread_mutex_lock(&worker->pool->mutex);
+ pthread_cond_wait(&worker->pool->has_tasks, &worker->pool->mutex); // wait for work to be ready
+
+ task t;
+ if (ring_queue_dequeue(worker->pool->task_queue, &t)) {
+ DEBUG("Job thread %d picked up task %d", worker->id, t.task_id);
+ } else {
+ WARN("Job thread %d didnt pick up a task as queue was empty", worker->id);
+ pthread_mutex_unlock(&worker->pool->mutex);
+ break;
+ }
+
+ pthread_mutex_unlock(&worker->pool->mutex);
+
+ // Do the work
+ bool result = t.do_task(t.params, t.result_data);
+
+ // INFO("Task result was %s", result ? "success" : "failure");
+ if (result) {
+ pthread_mutex_lock(&worker->pool->mutex);
+ if (t.buffer_result_for_main_thread) {
+ deferred_task_result dtr = { .task_id = t.task_id,
+ .callback = t.on_success,
+ .result_data = t.result_data,
+ .result_data_size = t.result_data_size };
+ deferred_task_result_darray_push(worker->pool->results, dtr);
+ } else {
+ // call on complete from here.
+ }
+ } else {
+ // TODO
+ }
+ pthread_mutex_unlock(&worker->pool->mutex);
+ }
+
+ return NULL;
+}
+
+bool threadpool_create(threadpool* pool, u8 thread_count, u32 queue_size) {
+ INFO("Threadpool init");
+ pool->next_task_id = 0;
+ pool->context = NULL;
+
+ u8 num_worker_threads = thread_count;
+ if (thread_count > MAX_NUM_THREADS) {
+ ERROR_EXIT("Threadpool has a hard limit of %d threads, you tried to start one with %d",
+ MAX_NUM_THREADS, thread_count)
+ num_worker_threads = MAX_NUM_THREADS;
+ }
+
+ DEBUG("creating task queue with max length %d", queue_size);
+ pool->task_queue = ring_queue_new(sizeof(task), queue_size, NULL);
+
+ DEBUG("creating mutex and condition");
+ pthread_mutex_init(&pool->mutex, NULL);
+ pthread_cond_init(&pool->has_tasks, NULL);
+
+ pool->results = deferred_task_result_darray_new(256);
+
+ DEBUG("Spawning %d threads for the threadpool", thread_count);
+ for (u8 i = 0; i < num_worker_threads; i++) {
+ pool->workers[i].id = i;
+ pool->workers[i].pool = pool;
+ if (pthread_create(&pool->workers[i].thread, NULL, worker_factory, &pool->workers[i]) != 0) {
+ FATAL("OS error creating job thread");
+ return false;
+ };
+ }
+
+ return true;
+}
+
+bool threadpool_add_task(threadpool* pool, tpool_task_start do_task,
+ tpool_task_on_complete on_success, tpool_task_on_complete on_fail,
+ bool buffer_result_for_main_thread, void* param_data, u32 param_data_size,
+ u32 result_data_size) {
+ void* result_data = malloc(result_data_size);
+
+ task* work_task = malloc(sizeof(task));
+ work_task->task_id = 0;
+ work_task->do_task = do_task;
+ work_task->on_success = on_success;
+ work_task->on_failure = on_fail;
+ work_task->buffer_result_for_main_thread = buffer_result_for_main_thread;
+ work_task->param_size = param_data_size;
+ work_task->params = param_data;
+ work_task->result_data_size = result_data_size;
+ work_task->result_data = result_data;
+
+ // START critical section
+ if (pthread_mutex_lock(&pool->mutex) != 0) {
+ ERROR("Unable to get threadpool lock.");
+ return false;
+ }
+
+ work_task->task_id = pool->next_task_id;
+ pool->next_task_id++;
+
+ ring_queue_enqueue(pool->task_queue, work_task);
+ DEBUG("Enqueued job");
+ pthread_cond_broadcast(&pool->has_tasks);
+
+ if (pthread_mutex_unlock(&pool->mutex) != 0) {
+ ERROR("couldnt unlock threadpool after adding task.");
+ return false; // ?
+ }
+ // END critical section
+
+ return true;
+}
+
+void threadpool_process_results(threadpool* pool, int _num_to_process) {
+ pthread_mutex_lock(&pool->mutex);
+ size_t num_results = deferred_task_result_darray_len(pool->results);
+ if (num_results > 0) {
+ u32 _size = ((deferred_task_result*)pool->results->data)[num_results].result_data_size;
+ deferred_task_result res;
+ deferred_task_result_darray_pop(pool->results, &res);
+ pthread_mutex_unlock(&pool->mutex);
+ task_globals globals = { .pool = pool, .ctx = pool->context };
+ res.callback(&globals, res.result_data);
+ } else {
+ pthread_mutex_unlock(&pool->mutex);
+ }
+}
+
+void threadpool_set_ctx(threadpool* pool, void* ctx) { pool->context = ctx; } \ No newline at end of file
diff --git a/archive/src/logos/threadpool.h b/archive/src/logos/threadpool.h
new file mode 100644
index 0000000..6390a38
--- /dev/null
+++ b/archive/src/logos/threadpool.h
@@ -0,0 +1,96 @@
+/**
+ A Threadpool has a number of "workers", each which process "tasks"
+*/
+#pragma once
+
+#include <pthread.h>
+
+#include "darray.h"
+#include "defines.h"
+#include "ring_queue.h"
+
+#define MAX_NUM_THREADS 16
+
+struct threadpool;
+typedef struct threadpool threadpool;
+
+typedef struct task_globals {
+ threadpool* pool;
+ void* ctx;
+} task_globals;
+
+/* function pointer */
+typedef bool (*tpool_task_start)(void*, void*);
+
+/* function pointer */
+typedef void (*tpool_task_on_complete)(task_globals*, void*);
+
+typedef struct threadpool_worker {
+ u16 id;
+ pthread_t thread;
+ threadpool* pool; // pointer back to the pool so we can get the mutex and cond
+} threadpool_worker;
+
+typedef enum tpool_task_status {
+ TASK_STATUS_READY,
+} task_status;
+
+typedef struct tpool_task {
+ u64 task_id;
+ tpool_task_start do_task;
+ tpool_task_on_complete on_success;
+ tpool_task_on_complete on_failure;
+ bool buffer_result_for_main_thread;
+ /** @brief a pointer to the parameters data that will be passed into the task. */
+ void* params;
+ u32 param_size;
+ void* result_data;
+ u32 result_data_size;
+} task;
+
+typedef struct deferred_task_result {
+ u64 task_id;
+ tpool_task_on_complete callback;
+ u32 result_data_size;
+ // this gets passed to the void* argument of `tpool_task_on_complete`
+ void* result_data;
+} deferred_task_result;
+
+#ifndef TYPED_TASK_RESULT_ARRAY
+KITC_DECL_TYPED_ARRAY(deferred_task_result) // creates "deferred_task_result_darray"
+#define TYPED_TASK_RESULT_ARRAY
+#endif
+
+struct threadpool {
+ ring_queue* task_queue;
+ pthread_mutex_t mutex;
+ pthread_cond_t has_tasks;
+ threadpool_worker workers[MAX_NUM_THREADS];
+ deferred_task_result_darray* results;
+ u64 next_task_id;
+
+ void* context;
+};
+
+/**
+ * @param pool where to store the created threadpool
+ * @param thread_count how many threads to spawn
+ * @param queue_size max size of task queue
+ */
+bool threadpool_create(threadpool* pool, u8 thread_count, u32 queue_size);
+void threadpool_destroy(threadpool* pool);
+
+/** @brief set a context variable for the threadpool that task data has access to */
+void threadpool_set_ctx(threadpool* pool, void* ctx);
+
+/**
+ * @brief Add a task to the threadpool
+ */
+bool threadpool_add_task(threadpool* pool, tpool_task_start do_task,
+ tpool_task_on_complete on_success, tpool_task_on_complete on_fail,
+ bool buffer_result_for_main_thread, void* param_data, u32 param_data_size,
+ u32 result_data_size);
+
+void threadpool_process_results(threadpool* pool, int num_to_process);
+
+u32 Tpool_GetNumWorkers(); // how many threads are we using \ No newline at end of file
diff --git a/archive/src/maths/geometry.h b/archive/src/maths/geometry.h
new file mode 100644
index 0000000..532651c
--- /dev/null
+++ b/archive/src/maths/geometry.h
@@ -0,0 +1,50 @@
+/**
+ * @file geometry.h
+ * @author your name (you@domain.com)
+ * @brief Shapes and intersections between them
+ * @version 0.1
+ * @date 2024-02-24
+ *
+ * @copyright Copyright (c) 2024
+ */
+#pragma once
+
+#include "maths.h"
+
+// typedef struct line_3d {
+// vec3 start, end;
+// } line_3d;
+
+// typedef struct plane {
+// vec3 normal;
+// } plane;
+
+typedef struct Cuboid {
+ Vec3 half_extents;
+} Cuboid;
+
+typedef struct Sphere {
+ f32 radius;
+} Sphere;
+
+// typedef struct cylinder {
+// f32 radius;
+// f32 half_height;
+// } cylinder;
+
+// typedef struct cone {
+// f32 radius;
+// f32 half_height;
+// } cone;
+
+// TODO:
+// capsule
+// torus
+// ray
+// frustum
+// conical frustum
+// wedge
+
+// 2d...
+// line
+// circle
diff --git a/archive/src/maths/maths.c b/archive/src/maths/maths.c
new file mode 100644
index 0000000..19052fe
--- /dev/null
+++ b/archive/src/maths/maths.c
@@ -0,0 +1,35 @@
+#include "maths.h"
+
+#define c_static_inline
+
+c_static_inline Vec3 vec3_create(f32 x, f32 y, f32 z) { return (Vec3){ x, y, z }; }
+c_static_inline Vec3 vec3_add(Vec3 a, Vec3 b) { return (Vec3){ a.x + b.x, a.y + b.y, a.z + b.z }; }
+c_static_inline Vec3 vec3_sub(Vec3 a, Vec3 b) { return (Vec3){ a.x - b.x, a.y - b.y, a.z - b.z }; }
+c_static_inline Vec3 vec3_mult(Vec3 a, f32 s) { return (Vec3){ a.x * s, a.y * s, a.z * s }; }
+c_static_inline Vec3 vec3_div(Vec3 a, f32 s) { return (Vec3){ a.x / s, a.y / s, a.z / s }; }
+
+c_static_inline f32 vec3_len_squared(Vec3 a) { return (a.x * a.x) + (a.y * a.y) + (a.z * a.z); }
+c_static_inline f32 vec3_len(Vec3 a) { return sqrtf(vec3_len_squared(a)); }
+c_static_inline Vec3 vec3_negate(Vec3 a) { return (Vec3){ -a.x, -a.y, -a.z }; }
+PUB c_static_inline Vec3 vec3_normalise(Vec3 a) {
+ f32 length = vec3_len(a);
+ return vec3_div(a, length);
+}
+
+c_static_inline f32 vec3_dot(Vec3 a, Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
+c_static_inline Vec3 vec3_cross(Vec3 a, Vec3 b) {
+ return (
+ Vec3){ .x = a.y * b.z - a.z * b.y, .y = a.z * b.x - a.x * b.z, .z = a.x * b.y - a.y * b.x };
+}
+
+Mat4 mat4_ident() {
+ return (Mat4){ .data = { 1.0, 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.0 } };
+}
+
+Mat4 transform_to_mat(Transform* tf) {
+ 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));
+ return mat4_mult(mat4_mult(scale, rotation), translation);
+} \ No newline at end of file
diff --git a/archive/src/maths/maths.h b/archive/src/maths/maths.h
new file mode 100644
index 0000000..e77b81a
--- /dev/null
+++ b/archive/src/maths/maths.h
@@ -0,0 +1,321 @@
+/**
+ * @file maths.h
+ * @author your name (you@domain.com)
+ * @brief
+ * @version 0.1
+ * @date 2024-02-24
+ * @copyright Copyright (c) 2024
+ */
+#pragma once
+
+#include <math.h>
+#include <stdio.h>
+#include "defines.h"
+#include "maths_types.h"
+
+// #undef c_static_inline
+// #define c_static_inline static
+
+// --- 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
+PUB c_static_inline Vec3 vec3_create(f32 x, f32 y, f32 z);
+#define vec3(x, y, z) ((Vec3){ x, y, z })
+PUB c_static_inline Vec3 vec3_add(Vec3 a, Vec3 b);
+PUB c_static_inline Vec3 vec3_sub(Vec3 a, Vec3 b);
+PUB c_static_inline Vec3 vec3_mult(Vec3 a, f32 s);
+PUB c_static_inline Vec3 vec3_div(Vec3 a, f32 s);
+
+PUB c_static_inline f32 vec3_len_squared(Vec3 a);
+PUB c_static_inline f32 vec3_len(Vec3 a);
+PUB c_static_inline Vec3 vec3_negate(Vec3 a);
+PUB c_static_inline Vec3 vec3_normalise(Vec3 a);
+
+PUB c_static_inline f32 vec3_dot(Vec3 a, Vec3 b);
+PUB c_static_inline Vec3 vec3_cross(Vec3 a, Vec3 b);
+
+static const Vec3 VEC3_X = vec3(1.0, 0.0, 0.0);
+static const Vec3 VEC3_NEG_X = vec3(-1.0, 0.0, 0.0);
+static const Vec3 VEC3_Y = vec3(0.0, 1.0, 0.0);
+static const Vec3 VEC3_NEG_Y = vec3(0.0, -1.0, 0.0);
+static const Vec3 VEC3_Z = vec3(0.0, 0.0, 1.0);
+static const Vec3 VEC3_NEG_Z = vec3(0.0, 0.0, -1.0);
+static const Vec3 VEC3_ZERO = vec3(0.0, 0.0, 0.0);
+static const Vec3 VEC3_ONES = vec3(1.0, 1.0, 1.0);
+
+static void print_vec3(Vec3 v) {
+ printf("{ x: %f, y: %f, z: %f )\n", (f64)v.x, (f64)v.y, (f64)v.z);
+}
+
+// TODO: Dimension 2
+static Vec2 vec2_create(f32 x, f32 y) { return (Vec2){ x, y }; }
+#define vec2(x, y) ((Vec2){ x, y })
+static Vec2 vec2_div(Vec2 a, f32 s) { return (Vec2){ a.x / s, a.y / s }; }
+
+// TODO: Dimension 4
+static 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 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 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 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 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);
+ }
+
+ // TODO: Are there math functions that take floats instead of doubles?
+
+ // Since dot is in range [0, DOT_THRESHOLD], acos is safe
+ f64 theta_0 = cos((f64)dot); // theta_0 = angle between input vectors
+ f64 theta = theta_0 * (f64)percentage; // theta = angle between v0 and result
+ f64 sin_theta = sin((f64)theta); // compute this value only once
+ f64 sin_theta_0 = sin((f64)theta_0); // compute this value only once
+
+ f32 s0 =
+ cos(theta) - (f64)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
+
+Mat4 mat4_ident();
+
+static Mat4 mat4_translation(Vec3 position) {
+ Mat4 out_matrix = mat4_ident();
+ out_matrix.data[12] = position.x;
+ out_matrix.data[13] = position.y;
+ out_matrix.data[14] = position.z;
+ return out_matrix;
+}
+
+static Mat4 mat4_scale(Vec3 scale) {
+ Mat4 out_matrix = mat4_ident();
+ out_matrix.data[0] = scale.x;
+ out_matrix.data[5] = scale.y;
+ out_matrix.data[10] = scale.z;
+ return out_matrix;
+}
+
+// TODO: double check this
+static 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 Mat4 mat4_mult(Mat4 lhs, Mat4 rhs) {
+ Mat4 out_matrix = mat4_ident();
+
+ const f32* m1_ptr = lhs.data;
+ const f32* m2_ptr = rhs.data;
+ f32* dst_ptr = out_matrix.data;
+
+ for (i32 i = 0; i < 4; ++i) {
+ for (i32 j = 0; j < 4; ++j) {
+ *dst_ptr = m1_ptr[0] * m2_ptr[0 + j] + m1_ptr[1] * m2_ptr[4 + j] + m1_ptr[2] * m2_ptr[8 + j] +
+ m1_ptr[3] * m2_ptr[12 + j];
+ dst_ptr++;
+ }
+ m1_ptr += 4;
+ }
+
+ return out_matrix;
+}
+
+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 */
+c_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) {
+ f32 half_tan_fov = tanf(fov_radians * 0.5f);
+ Mat4 out_matrix = { .data = { 0 } };
+ out_matrix.data[0] = 1.0f / (aspect_ratio * half_tan_fov);
+ out_matrix.data[5] = 1.0f / half_tan_fov;
+ out_matrix.data[10] = -((far_clip + near_clip) / (far_clip - near_clip));
+ out_matrix.data[11] = -1.0f;
+ out_matrix.data[14] = -((2.0f * far_clip * near_clip) / (far_clip - near_clip));
+ return out_matrix;
+}
+#endif
+
+/** @brief Creates an orthographic projection matrix */
+static inline Mat4 mat4_orthographic(f32 left, f32 right, f32 bottom, f32 top, f32 near_clip,
+ f32 far_clip) {
+ // source: kohi game engine.
+ Mat4 out_matrix = mat4_ident();
+
+ f32 lr = 1.0f / (left - right);
+ f32 bt = 1.0f / (bottom - top);
+ f32 nf = 1.0f / (near_clip - far_clip);
+
+ out_matrix.data[0] = -2.0f * lr;
+ out_matrix.data[5] = -2.0f * bt;
+ out_matrix.data[10] = 2.0f * nf;
+
+ out_matrix.data[12] = (left + right) * lr;
+ out_matrix.data[13] = (top + bottom) * bt;
+ out_matrix.data[14] = (far_clip + near_clip) * nf;
+
+ return out_matrix;
+}
+
+static inline Mat4 mat4_look_at(Vec3 position, Vec3 target, Vec3 up) {
+ Mat4 out_matrix;
+ Vec3 z_axis;
+ z_axis.x = target.x - position.x;
+ z_axis.y = target.y - position.y;
+ z_axis.z = target.z - position.z;
+
+ z_axis = vec3_normalise(z_axis);
+ Vec3 x_axis = vec3_normalise(vec3_cross(z_axis, up));
+ Vec3 y_axis = vec3_cross(x_axis, z_axis);
+
+ out_matrix.data[0] = x_axis.x;
+ out_matrix.data[1] = y_axis.x;
+ out_matrix.data[2] = -z_axis.x;
+ out_matrix.data[3] = 0;
+ out_matrix.data[4] = x_axis.y;
+ out_matrix.data[5] = y_axis.y;
+ out_matrix.data[6] = -z_axis.y;
+ out_matrix.data[7] = 0;
+ out_matrix.data[8] = x_axis.z;
+ out_matrix.data[9] = y_axis.z;
+ out_matrix.data[10] = -z_axis.z;
+ out_matrix.data[11] = 0;
+ out_matrix.data[12] = -vec3_dot(x_axis, position);
+ out_matrix.data[13] = -vec3_dot(y_axis, position);
+ out_matrix.data[14] = vec3_dot(z_axis, position);
+ out_matrix.data[15] = 1.0f;
+
+ return out_matrix;
+}
+
+// ...
+
+// --- Transform Implementations
+
+#define TRANSFORM_DEFAULT \
+ ((Transform){ .position = VEC3_ZERO, \
+ .rotation = (Quat){ .x = 0., .y = 0., .z = 0., .w = 1. }, \
+ .scale = 1.0, \
+ .is_dirty = false })
+
+static Transform transform_create(Vec3 pos, Quat rot, Vec3 scale) {
+ return (Transform){ .position = pos, .rotation = rot, .scale = scale, .is_dirty = true };
+}
+
+Mat4 transform_to_mat(Transform* tf);
+
+// --- Sizing asserts
+
+_Static_assert(alignof(Vec3) == 4, "Vec3 is 4 byte aligned");
+_Static_assert(sizeof(Vec3) == 12, "Vec3 is 12 bytes so has no padding");
+
+_Static_assert(alignof(Vec4) == 4, "Vec4 is 4 byte aligned");
diff --git a/archive/src/maths/maths_types.h b/archive/src/maths/maths_types.h
new file mode 100644
index 0000000..66d260d
--- /dev/null
+++ b/archive/src/maths/maths_types.h
@@ -0,0 +1,84 @@
+/**
+ * @file maths_types.h
+ * @author Omniscient
+ * @brief Maths types
+ * @date 2024-02-24
+ * @copyright Copyright (c) 2024
+ */
+#pragma once
+
+#include "defines.h"
+
+// --- Constants
+#define PI 3.14159265358979323846
+#define HALF_PI 1.57079632679489661923
+#define TAU (2.0 * PI)
+
+// --- Types
+
+/** @brief 2D Vector */
+typedef struct Vec2 {
+ f32 x, y;
+} Vec2;
+
+/** @brief 4x4 Matrix */
+typedef struct Mat4 {
+ // TODO: use this format for more readable code: vec4 x_axis, y_axis, z_axis, w_axis;
+ f32 data[16];
+} Mat4;
+
+/** @brief Three dimensional bounding box */
+typedef struct Bbox_3D {
+ Vec3 min; // minimum point of the box
+ Vec3 max; // maximum point of the box
+} Bbox_3D;
+
+/** @brief 3D Axis-aligned bounding box */
+typedef Bbox_3D Aabb_3D;
+
+/** @brief 3D affine transformation */
+typedef struct Transform {
+ Vec3 position;
+ Quat rotation;
+ Vec3 scale;
+ bool is_dirty;
+} Transform;
+
+typedef struct Vec4i {
+ i32 x, y, z, w;
+} Vec4i;
+
+typedef struct Vec4u {
+ u32 x, y, z, w;
+} Vec4u;
+
+// --- Some other types
+typedef struct u32x3 {
+ union {
+ struct {
+ u32 x;
+ u32 y;
+ u32 z;
+ };
+ struct {
+ u32 r;
+ u32 g;
+ u32 b;
+ };
+ };
+} u32x3;
+#define u32x3(x, y, z) ((u32x3){ x, y, z })
+
+typedef struct u32x2 {
+ u32 x;
+ u32 y;
+} u32x2;
+#define u32x2(x, y) ((u32x2){ x, y })
+
+// Type aliass
+
+typedef struct Vec2 f32x2;
+#define f32x2(x, y) ((f32x2){ x, y })
+
+typedef struct Vec3 f32x3;
+#define f32x3(x, y, z) ((f32x3){ x, y, z })
diff --git a/archive/src/maths/primitives.c b/archive/src/maths/primitives.c
new file mode 100644
index 0000000..c24d1e2
--- /dev/null
+++ b/archive/src/maths/primitives.c
@@ -0,0 +1,343 @@
+#include "primitives.h"
+#include "colours.h"
+#include "log.h"
+#include "maths.h"
+#include "maths_types.h"
+#include "ral_types.h"
+#include "render_types.h"
+
+// --- Helpers
+
+void push_triangle(u32_darray* arr, u32 i0, u32 i1, u32 i2) {
+ u32_darray_push(arr, i0);
+ u32_darray_push(arr, i1);
+ u32_darray_push(arr, i2);
+}
+
+Vec3 plane_vertex_positions[] = {
+ (Vec3){ -0.5, 0, -0.5 },
+ (Vec3){ 0.5, 0, -0.5 },
+ (Vec3){ -0.5, 0, 0.5 },
+ (Vec3){ 0.5, 0, 0.5 },
+};
+
+Geometry Geo_CreatePlane(f32x2 extents, u32 tiling_u, u32 tiling_v) {
+ CASSERT(tiling_u >= 1 && tiling_v >= 1);
+ Vertex_darray* vertices = Vertex_darray_new(4);
+ u32_darray* indices = u32_darray_new(vertices->len);
+
+ Vec3 vert_pos[4];
+ memcpy(&vert_pos, plane_vertex_positions, sizeof(plane_vertex_positions));
+ for (int i = 0; i < 4; i++) {
+ vert_pos[i].x *= extents.x;
+ vert_pos[i].z *= extents.y;
+ }
+ VERT_3D(vertices, vert_pos[0], VEC3_Y, vec2(0, 0)); // back left
+ VERT_3D(vertices, vert_pos[1], VEC3_Y, vec2(1 * tiling_u, 0 * tiling_v)); // back right
+ VERT_3D(vertices, vert_pos[2], VEC3_Y, vec2(0, 1 * tiling_v)); // front left
+ VERT_3D(vertices, vert_pos[3], VEC3_Y, vec2(1 * tiling_u, 1 * tiling_v)); // front right
+
+ // push_triangle(indices, 0, 1, 2);
+ // push_triangle(indices, 2, 1, 3);
+ push_triangle(indices, 2, 1, 0);
+ push_triangle(indices, 1, 2, 3);
+
+ for (int i = 0; i < 4; i++) {
+ printf("Vertex %d: (%f, %f, %f)\n", i, vert_pos[i].x, vert_pos[i].y, vert_pos[i].z);
+ }
+
+ Geometry geo = { .format = VERTEX_STATIC_3D,
+ .vertices = vertices,
+ .has_indices = true,
+ .index_count = indices->len,
+ .indices = indices };
+
+ return geo;
+}
+
+Geometry Geo_CreateCuboid(f32x3 extents) {
+ Vertex_darray* vertices = Vertex_darray_new(36);
+
+ // back faces
+ VERT_3D(vertices, BACK_TOP_RIGHT, VEC3_NEG_Z, vec2(1, 0));
+ VERT_3D(vertices, BACK_BOT_LEFT, VEC3_NEG_Z, vec2(0, 1));
+ VERT_3D(vertices, BACK_TOP_LEFT, VEC3_NEG_Z, vec2(0, 0));
+ VERT_3D(vertices, BACK_TOP_RIGHT, VEC3_NEG_Z, vec2(1, 0));
+ VERT_3D(vertices, BACK_BOT_RIGHT, VEC3_NEG_Z, vec2(1, 1));
+ VERT_3D(vertices, BACK_BOT_LEFT, VEC3_NEG_Z, vec2(0, 1));
+
+ // front faces
+ VERT_3D(vertices, FRONT_BOT_LEFT, VEC3_Z, vec2(0, 1));
+ VERT_3D(vertices, FRONT_TOP_RIGHT, VEC3_Z, vec2(1, 0));
+ VERT_3D(vertices, FRONT_TOP_LEFT, VEC3_Z, vec2(0, 0));
+ VERT_3D(vertices, FRONT_BOT_LEFT, VEC3_Z, vec2(0, 1));
+ VERT_3D(vertices, FRONT_BOT_RIGHT, VEC3_Z, vec2(1, 1));
+ VERT_3D(vertices, FRONT_TOP_RIGHT, VEC3_Z, vec2(1, 0));
+
+ // top faces
+ VERT_3D(vertices, BACK_TOP_LEFT, VEC3_Y, vec2(0, 0));
+ VERT_3D(vertices, FRONT_TOP_LEFT, VEC3_Y, vec2(0, 1));
+ VERT_3D(vertices, FRONT_TOP_RIGHT, VEC3_Y, vec2(1, 1));
+ VERT_3D(vertices, BACK_TOP_LEFT, VEC3_Y, vec2(0, 0));
+ VERT_3D(vertices, FRONT_TOP_RIGHT, VEC3_Y, vec2(1, 1));
+ VERT_3D(vertices, BACK_TOP_RIGHT, VEC3_Y, vec2(1, 0));
+
+ // bottom faces
+ VERT_3D(vertices, BACK_BOT_LEFT, VEC3_NEG_Y, vec2(0, 1));
+ VERT_3D(vertices, FRONT_BOT_RIGHT, VEC3_NEG_Y, vec2(1, 1));
+ VERT_3D(vertices, FRONT_BOT_LEFT, VEC3_NEG_Y, vec2(0, 1));
+ VERT_3D(vertices, BACK_BOT_LEFT, VEC3_NEG_Y, vec2(0, 1));
+ VERT_3D(vertices, BACK_BOT_RIGHT, VEC3_NEG_Y, vec2(1, 1));
+ VERT_3D(vertices, FRONT_BOT_RIGHT, VEC3_NEG_Y, vec2(0, 1));
+
+ // right faces
+ VERT_3D(vertices, FRONT_TOP_RIGHT, VEC3_X, vec2(0, 0));
+ VERT_3D(vertices, BACK_BOT_RIGHT, VEC3_X, vec2(1, 1));
+ VERT_3D(vertices, BACK_TOP_RIGHT, VEC3_X, vec2(1, 0));
+ VERT_3D(vertices, BACK_BOT_RIGHT, VEC3_X, vec2(1, 1));
+ VERT_3D(vertices, FRONT_TOP_RIGHT, VEC3_X, vec2(0, 0));
+ VERT_3D(vertices, FRONT_BOT_RIGHT, VEC3_X, vec2(0, 1));
+
+ // left faces
+ VERT_3D(vertices, FRONT_TOP_LEFT, VEC3_NEG_X, vec2(0, 0));
+ VERT_3D(vertices, BACK_TOP_LEFT, VEC3_NEG_X, vec2(0, 0));
+ VERT_3D(vertices, BACK_BOT_LEFT, VEC3_NEG_X, vec2(0, 0));
+ VERT_3D(vertices, BACK_BOT_LEFT, VEC3_NEG_X, vec2(0, 0));
+ VERT_3D(vertices, FRONT_BOT_LEFT, VEC3_NEG_X, vec2(0, 0));
+ VERT_3D(vertices, FRONT_TOP_LEFT, VEC3_NEG_X, vec2(0, 0));
+
+ u32_darray* indices = u32_darray_new(vertices->len);
+
+ for (u32 i = 0; i < vertices->len; i++) {
+ u32_darray_push(indices, i);
+ vertices->data[i].static_3d.position =
+ vec3_sub(vertices->data[i].static_3d.position,
+ vec3(0.5, 0.5, 0.5)); // make center of the cube is the origin of mesh space
+ }
+
+ Geometry geo = {
+ .format = VERTEX_STATIC_3D,
+ .vertices = vertices,
+ .has_indices = true,
+ .index_count = indices->len,
+ .indices = indices, // FIXME: make darray methods that return stack allocated struct
+ };
+
+ return geo;
+}
+
+// --- Spheres
+
+Vec3 spherical_to_cartesian_coords(f32 rho, f32 theta, f32 phi) {
+ f32 x = rho * sin(phi) * cos(theta);
+ f32 y = rho * cos(phi);
+ f32 z = rho * sin(phi) * sin(theta);
+ return vec3(x, y, z);
+}
+
+Geometry Geo_CreateUVsphere(f32 radius, u32 north_south_lines, u32 east_west_lines) {
+ assert(east_west_lines >= 3); // sphere will be degenerate and look gacked without at least 3
+ assert(north_south_lines >= 3);
+
+ Vertex_darray* vertices = Vertex_darray_new(2 + (east_west_lines - 1) * north_south_lines);
+
+ // Create a UV sphere with spherical coordinates
+ // a point P on the unit sphere can be represented P(r, theta, phi)
+ // for each vertex we must convert that to a cartesian R3 coordinate
+
+ // Top point
+ Vertex top = { .static_3d = { .position = vec3(0, radius, 0),
+ .normal = vec3_normalise(vec3(0, radius, 0)),
+ .tex_coords = vec2(0, 0) } };
+ Vertex_darray_push(vertices, top);
+
+ // parallels
+ for (u32 i = 0; i < (east_west_lines - 1); i++) {
+ // phi should range from 0 to pi
+ f32 phi = PI * (((f32)i + 1) / (f32)east_west_lines);
+
+ // meridians
+ for (u32 j = 0; j < east_west_lines; j++) {
+ // theta should range from 0 to 2PI
+ f32 theta = TAU * ((f32)j / (f32)north_south_lines);
+ Vec3 position = spherical_to_cartesian_coords(radius, theta, phi);
+ // f32 d = vec3_len(position);
+ // print_vec3(position);
+ // printf("Phi %f Theta %f d %d\n", phi, theta, d);
+ // assert(d == radius); // all points on the sphere should be 'radius' away from the origin
+ Vertex v = { .static_3d = {
+ .position = position,
+ .normal =
+ vec3_normalise(position), // normal vector on sphere is same as position
+ .tex_coords = vec2(0, 0) // TODO
+ } };
+ Vertex_darray_push(vertices, v);
+ }
+ }
+
+ // Bottom point
+ Vertex bot = { .static_3d = { .position = vec3(0, -radius, 0),
+ .normal = vec3_normalise(vec3(0, -radius, 0)),
+ .tex_coords = vec2(0, 0) } };
+ Vertex_darray_push(vertices, bot);
+
+ u32_darray* indices = u32_darray_new(1);
+
+ // top bottom rings
+ for (u32 i = 0; i < north_south_lines; i++) {
+ u32 i1 = i + 1;
+ u32 i2 = (i + 1) % north_south_lines + 1;
+ push_triangle(indices, 0, i2, i1);
+ /* TRACE("Push triangle (%.2f %.2f %.2f)->(%.2f %.2f %.2f)->(%.2f %.2f %.2f)\n", */
+ /* vertices->data[0].static_3d.position.x, vertices->data[0].static_3d.position.y, */
+ /* vertices->data[0].static_3d.position.z, vertices->data[i1].static_3d.position.x, */
+ /* vertices->data[i1].static_3d.position.y, vertices->data[i1].static_3d.position.z, */
+ /* vertices->data[i2].static_3d.position.x, vertices->data[i2].static_3d.position.y, */
+ /* vertices->data[i2].static_3d.position.z); */
+ u32 bot = vertices->len - 1;
+ u32 i3 = i + north_south_lines * (east_west_lines - 2) + 1;
+ u32 i4 = (i + 1) % north_south_lines + north_south_lines * (east_west_lines - 2) + 1;
+ push_triangle(indices, bot, i3, i4);
+ }
+
+ // quads
+ for (u32 i = 0; i < east_west_lines - 2; i++) {
+ u32 ring_start = i * north_south_lines + 1;
+ u32 next_ring_start = (i + 1) * north_south_lines + 1;
+ /* printf("ring start %d next ring start %d\n", ring_start, next_ring_start); */
+ /* print_vec3(vertices->data[ring_start].static_3d.position); */
+ /* print_vec3(vertices->data[next_ring_start].static_3d.position); */
+ for (u32 j = 0; j < north_south_lines; j++) {
+ u32 i0 = ring_start + j;
+ u32 i1 = next_ring_start + j;
+ u32 i2 = ring_start + (j + 1) % north_south_lines;
+ u32 i3 = next_ring_start + (j + 1) % north_south_lines;
+ push_triangle(indices, i0, i2, i1);
+ /* TRACE("Push triangle (%.2f %.2f %.2f)->(%.2f %.2f %.2f)->(%.2f %.2f %.2f)\n", */
+ /* vertices->data[i0].static_3d.position.x, vertices->data[i0].static_3d.position.y, */
+ /* vertices->data[i0].static_3d.position.z, vertices->data[i1].static_3d.position.x, */
+ /* vertices->data[i1].static_3d.position.y, vertices->data[i1].static_3d.position.z, */
+ /* vertices->data[i2].static_3d.position.x, vertices->data[i2].static_3d.position.y, */
+ /* vertices->data[i2].static_3d.position.z); */
+ push_triangle(indices, i1, i2, i3);
+ }
+ }
+
+ Geometry geo = {
+ .format = VERTEX_STATIC_3D,
+ .vertices = vertices,
+ .has_indices = true,
+ .index_count = indices->len,
+ .indices = indices,
+ };
+
+ return geo;
+}
+
+Geometry Geo_CreateCone(f32 radius, f32 height, u32 resolution) {
+ Vertex_darray* vertices = Vertex_darray_new((resolution + 1) * 2);
+ u32_darray* indices = u32_darray_new(resolution * 2 * 3);
+
+ // TODO: decide how UVs are unwrapped
+
+ // tip
+ VERT_3D(vertices, vec3(0.0, height, 0.0), VEC3_Y, vec2(0, 0));
+
+ // sides
+ f32 step = TAU / resolution;
+
+ for (u32 i = 0; i < resolution; i++) {
+ f32 x = cos(step * i) * radius;
+ f32 z = sin(step * i) * radius;
+ Vec3 pos = vec3(x, 0.0, z);
+ Vec3 tip_to_vertex = vec3_sub(pos, vertices->data[0].static_3d.position);
+ Vec3 center_to_vertex = pos;
+ Vec3 tangent = vec3_cross(VEC3_Y, center_to_vertex);
+ Vec3 normal_dir = vec3_cross(tangent, tip_to_vertex);
+ Vec3 normal = vec3_normalise(normal_dir);
+ VERT_3D(vertices, pos, normal, vec2(0, 0));
+ }
+ for (u32 i = 1; i < resolution; i++) {
+ push_triangle(indices, 0, i + 1, i);
+ }
+ push_triangle(indices, 0, 1, resolution);
+
+ // base center
+ u32 center_idx = vertices->len;
+ VERT_3D(vertices, VEC3_ZERO, VEC3_NEG_Y, vec2(0, 0));
+
+ // base circle
+ for (u32 i = 0; i < resolution; i++) {
+ f32 x = cos(step * i) * radius;
+ f32 z = sin(step * i) * radius;
+ VERT_3D(vertices, vec3(x, 0.0, z), VEC3_NEG_Z, vec2(0, 0));
+ }
+ for (u32 i = 1; i < resolution; i++) {
+ push_triangle(indices, center_idx, center_idx + i, center_idx + i + 1);
+ }
+ push_triangle(indices, center_idx, center_idx + resolution, center_idx + 1);
+
+ Geometry geo = {
+ .format = VERTEX_STATIC_3D,
+ .vertices = vertices,
+ .has_indices = true,
+ .index_count = indices->len,
+ .indices = indices,
+ };
+ return geo;
+}
+
+Geometry Geo_CreateCylinder(f32 radius, f32 height, u32 resolution) {
+ Vertex_darray* vertices = Vertex_darray_new(1);
+ u32_darray* indices = u32_darray_new(1);
+
+ f32 step = TAU / resolution;
+
+ // bot cap
+ VERT_3D(vertices, VEC3_ZERO, VEC3_NEG_Y, vec2(0, 0));
+ for (u32 i = 0; i < resolution; i++) {
+ VERT_3D(vertices, vec3(cos(step * i) * radius, 0.0, sin(step * i) * radius), VEC3_NEG_Y,
+ vec2(0, 0));
+ }
+ for (u32 i = 1; i < resolution; i++) {
+ push_triangle(indices, 0, i, i + 1);
+ }
+ push_triangle(indices, 0, resolution, 1);
+
+ // top cap
+ u32 center_idx = vertices->len;
+ VERT_3D(vertices, vec3(0.0, height, 0.0), VEC3_Y, vec2(0, 0));
+ for (u32 i = 0; i < resolution; i++) {
+ VERT_3D(vertices, vec3(cos(step * i) * radius, height, sin(step * i) * radius), VEC3_Y,
+ vec2(0, 0));
+ }
+ for (u32 i = 1; i < resolution; i++) {
+ push_triangle(indices, center_idx, center_idx + i + 1, center_idx + i);
+ }
+ push_triangle(indices, center_idx, center_idx + 1, center_idx + resolution);
+
+ // sides
+ u32 sides_start = vertices->len;
+ for (u32 i = 0; i < resolution; i++) {
+ f32 x = cos(step * i) * radius;
+ f32 z = sin(step * i) * radius;
+ // top then bottom
+ VERT_3D(vertices, vec3(x, height, z), vec3_normalise(vec3(x, 0.0, z)), vec2(0, 0));
+ VERT_3D(vertices, vec3(x, 0.0, z), vec3_normalise(vec3(x, 0.0, z)), vec2(0, 0));
+ }
+ for (u32 i = 0; i < resolution; i++) {
+ u32 current = sides_start + i * 2;
+ u32 next = sides_start + ((i + 1) % resolution) * 2;
+ push_triangle(indices, current, next, current + 1);
+ push_triangle(indices, current + 1, next, next + 1);
+ }
+
+ Geometry geo = {
+ .format = VERTEX_STATIC_3D,
+ .vertices = vertices,
+ .has_indices = true,
+ .index_count = indices->len,
+ .indices = indices,
+ };
+ return geo;
+}
diff --git a/archive/src/maths/primitives.h b/archive/src/maths/primitives.h
new file mode 100644
index 0000000..4965545
--- /dev/null
+++ b/archive/src/maths/primitives.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <assert.h>
+#include <stdlib.h>
+#include "core.h"
+#include "maths_types.h"
+#include "render_types.h"
+
+Geometry Geo_CreatePlane(f32x2 extents, u32 tiling_u, u32 tiling_v);
+Geometry Geo_CreateCuboid(f32x3 extents);
+Geometry Geo_CreateCylinder(f32 radius, f32 height, u32 resolution);
+Geometry Geo_CreateCone(f32 radius, f32 height, u32 resolution);
+Geometry Geo_CreateUVsphere(f32 radius, u32 north_south_lines, u32 east_west_lines);
+Geometry Geo_CreateIcosphere(f32 radius, f32 n_subdivisions);
+
+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 };
+
+#define VERT_3D(arr, pos, norm, uv) \
+ { \
+ Vertex v = { .static_3d = { .position = pos, .normal = norm, .tex_coords = uv } }; \
+ Vertex_darray_push(arr, v); \
+ } \ No newline at end of file
diff --git a/archive/src/physics.h b/archive/src/physics.h
new file mode 100644
index 0000000..134f08b
--- /dev/null
+++ b/archive/src/physics.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "geometry.h"
+#include "maths_types.h"
+
+// 'system' means that it gets called per frame
+
+typedef struct physics_settings {
+ f32 gravity_strength;
+} physics_settings;
+
+// What else do I need?
+// intersection methods
+
+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);
+
+// enum ColliderType {
+// CuboidCollider,
+// SphereCollider,
+// };
+
+/** @brief Oriented Bounding Box */
+typedef struct OBB {
+ Vec3 center;
+ Bbox_3D bbox;
+ Quat rotation;
+} OBB;
+
+PUB void Debug_DrawOBB(OBB obb);
+
+/** @brief generic collider structure */
+typedef struct Collider {
+ u64 id; // ? Replace with handle?
+ OBB shape; // NOTE: We're only supporting the one collider type for now
+ bool on_ground;
+} Collider;
diff --git a/archive/src/platform/file.c b/archive/src/platform/file.c
new file mode 100644
index 0000000..91daa4f
--- /dev/null
+++ b/archive/src/platform/file.c
@@ -0,0 +1,93 @@
+#include "file.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "log.h"
+#include "mem.h"
+#include "str.h"
+
+const char* string_from_file(const char* path) {
+ FILE* f = fopen(path, "rb");
+ if (f == NULL) {
+ ERROR("Error reading file: %s. errno: %d", path, errno);
+ return NULL;
+ }
+ if (ferror(f)) {
+ ERROR("Error reading file: %s. errno: %d", path, errno);
+ return NULL;
+ }
+ fseek(f, 0, SEEK_END);
+ long fsize = ftell(f);
+ rewind(f);
+
+ char* string = malloc(fsize + 1);
+ fread(string, fsize, 1, f);
+ fclose(f);
+
+ string[fsize] = '\0';
+
+ return string;
+}
+
+str8_opt str8_from_file(arena* a, Str8 path) {
+ char* p = cstr(a, path);
+ str8_opt result = { .has_value = false };
+
+ FILE* f = fopen(p, "rb");
+ if (f == NULL) {
+ ERROR("Error reading file: %s. errno: %d", path, errno);
+ return result;
+ }
+ if (ferror(f)) {
+ ERROR("Error reading file: %s. errno: %d", path, errno);
+ return result;
+ }
+ fseek(f, 0, SEEK_END);
+ long fsize = ftell(f);
+ rewind(f);
+
+ u8* raw = arena_alloc(a, fsize + 1);
+ Str8 contents = Str8_create(raw, fsize);
+ contents.buf[contents.len] = '\0';
+
+ fread(raw, fsize, 1, f);
+ fclose(f);
+ result.contents = contents;
+ 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 };
+}
diff --git a/archive/src/platform/file.h b/archive/src/platform/file.h
new file mode 100644
index 0000000..5e5e1e1
--- /dev/null
+++ b/archive/src/platform/file.h
@@ -0,0 +1,26 @@
+/**
+ * @file file.h
+ * @brief File I/O utilities
+ * @date 2024-02-24
+ * @copyright Copyright (c) 2024
+ */
+#pragma once
+
+#include "defines.h"
+#include "str.h"
+
+typedef struct str8_opt {
+ Str8 contents;
+ bool has_value;
+} str8_opt;
+
+const char* string_from_file(const char* path);
+
+str8_opt str8_from_file(arena* a, Str8 path);
+
+typedef struct {
+ char* data;
+ size_t size;
+} FileData;
+
+FileData load_spv_file(const char* path);
diff --git a/archive/src/platform/platform.h b/archive/src/platform/platform.h
new file mode 100644
index 0000000..c2be630
--- /dev/null
+++ b/archive/src/platform/platform.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "defines.h"
+#include "str.h"
+
+// -- Paths
+typedef struct path_opt {
+ Str8 path;
+ bool has_value;
+} path_opt;
+
+// TODO: convert to using str8
+// TODO: use uppercase code style
+path_opt path_parent(arena* a, const char* path);
+
+// --- Threads
+typedef struct CelThread CelThread;
+
+CelThread Thread_Create();
+void Thread_Destroy(CelThread* thread);
+
+// --- Mutexes
+typedef struct CelMutex CelMutex;
+
+CelMutex Mutex_Create();
+void Mutex_Destroy(CelMutex* mutex);
+
+/** @brief Blocks until the mutex can be acquired. if returns false then an error occurred and can
+ * be checked (TODO) */
+bool Mutex_Lock(CelMutex* mutex);
+
+/** @brief Tries to acquire the mutex like `mutex_lock` but returns immediately if the mutex has
+ * already been locked */
+bool Mutex_TryLock(CelMutex* mutex);
+
+/** @brief Releases a mutex. If it is already unlocked then does nothing */
+void Mutex_Unlock(CelMutex* mutex);
diff --git a/archive/src/platform/platform_unix.c b/archive/src/platform/platform_unix.c
new file mode 100644
index 0000000..86ac170
--- /dev/null
+++ b/archive/src/platform/platform_unix.c
@@ -0,0 +1,16 @@
+#include "platform.h"
+
+#if defined(CEL_PLATFORM_LINUX) || defined(CEL_PLATFORM_MAC)
+
+#include <libgen.h>
+#include <string.h>
+
+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
diff --git a/archive/src/platform/platform_windows.c b/archive/src/platform/platform_windows.c
new file mode 100644
index 0000000..21ef359
--- /dev/null
+++ b/archive/src/platform/platform_windows.c
@@ -0,0 +1,22 @@
+#include "platform.h"
+
+#if defined(CEL_PLATFORM_WINDOWS)
+
+#include <shlwapi.h>
+#include <windows.h>
+#pragma comment(lib, "Shlwapi.lib")
+
+path_opt path_parent(arena* a, const char* path) {
+ // Duplicate the string because PathRemoveFileSpec mutates in-place
+ size_t len = strlen(path) + 1;
+ char* path_copy = arena_alloc(a, len);
+ strcpy_s(path_copy, len, path);
+
+ if (PathRemoveFileSpecA(path_copy)) {
+ return (path_opt){ .path = Str8_cstr_view(path_copy), .has_value = true };
+ } else {
+ return (path_opt){ .has_value = false };
+ }
+}
+
+#endif
diff --git a/archive/src/ral/README.md b/archive/src/ral/README.md
new file mode 100644
index 0000000..f66b95a
--- /dev/null
+++ b/archive/src/ral/README.md
@@ -0,0 +1,5 @@
+# RAL
+
+**Render Abstraction Layer** is a thin abstraction over graphics APIs. Everything in `render` builds on top of the code in
+this folder in order to be API-agnostic. It also makes writing graphics code easier as it smooths over some of the discrepancies
+between APIs like texture/buffer creation and updating shader values. \ No newline at end of file
diff --git a/archive/src/ral/backends/metal/backend_metal.h b/archive/src/ral/backends/metal/backend_metal.h
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/archive/src/ral/backends/metal/backend_metal.h
diff --git a/archive/src/ral/backends/opengl/backend_opengl.c b/archive/src/ral/backends/opengl/backend_opengl.c
new file mode 100644
index 0000000..613d7e1
--- /dev/null
+++ b/archive/src/ral/backends/opengl/backend_opengl.c
@@ -0,0 +1,449 @@
+#include "backend_opengl.h"
+#include "colours.h"
+#include "maths_types.h"
+#if defined(CEL_REND_BACKEND_OPENGL)
+#include <assert.h>
+#include "log.h"
+#include "mem.h"
+#include "opengl_helpers.h"
+#include "ral_common.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+
+#include <glad/glad.h>
+#include <glfw3.h>
+
+typedef struct OpenglCtx {
+ GLFWwindow* window;
+ arena pool_arena;
+ GPU_Swapchain swapchain;
+ GPU_CmdEncoder main_encoder;
+ GPU_BackendPools gpu_pools;
+ ResourcePools* resource_pools;
+} OpenglCtx;
+
+static OpenglCtx context;
+
+bool GPU_Backend_Init(const char* window_name, struct GLFWwindow* window,
+ struct ResourcePools* res_pools) {
+ INFO("loading OpenGL backend");
+
+ memset(&context, 0, sizeof(context));
+ context.window = window;
+
+ size_t pool_buffer_size = 1024 * 1024;
+ context.pool_arena = arena_create(malloc(pool_buffer_size), pool_buffer_size);
+
+ BackendPools_Init(&context.pool_arena, &context.gpu_pools);
+ context.resource_pools = res_pools;
+
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+
+ // glad: load all opengl function pointers
+ if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
+ ERROR("Failed to initialise GLAD \n");
+ return false;
+ }
+
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_CULL_FACE);
+
+ context.swapchain = (GPU_Swapchain){ .dimensions = u32x2(1000, 1000) };
+
+ return true;
+}
+
+// All of these are no-ops in OpenGL
+void GPU_Backend_Shutdown() { /* TODO */ }
+bool GPU_Device_Create(GPU_Device* out_device) { return true; }
+void GPU_Device_Destroy(GPU_Device* device) {}
+bool GPU_Swapchain_Create(GPU_Swapchain* out_swapchain) { return true; }
+void GPU_Swapchain_Destroy(GPU_Swapchain* swapchain) {}
+void GPU_CmdEncoder_Destroy(GPU_CmdEncoder* encoder) {}
+
+void GPU_CmdEncoder_BeginRender(GPU_CmdEncoder* encoder, GPU_Renderpass* renderpass) {
+ glBindFramebuffer(GL_FRAMEBUFFER, renderpass->fbo);
+ // rgba clear_colour = STONE_800;
+ // glClearColor(clear_colour.r, clear_colour.g, clear_colour.b, 1.0f);
+ // if (renderpass->description.has_depth_stencil) {
+ // glClear(GL_DEPTH_BUFFER_BIT);
+ // } else {
+ // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ // }
+}
+
+void GPU_CmdEncoder_EndRender(GPU_CmdEncoder* encoder) { glBindFramebuffer(GL_FRAMEBUFFER, 0); }
+
+GPU_CmdEncoder* GPU_GetDefaultEncoder() { return &context.main_encoder; }
+void GPU_QueueSubmit(GPU_CmdBuffer* cmd_buffer) {}
+
+void GPU_Swapchain_Resize(i32 new_width, i32 new_height) {
+ context.swapchain.dimensions = u32x2(new_width, new_height);
+}
+
+u32x2 GPU_Swapchain_GetDimensions() { return context.swapchain.dimensions; }
+
+GPU_Renderpass* GPU_Renderpass_Create(GPU_RenderpassDesc description) {
+ // allocate new pass
+ GPU_Renderpass* renderpass = Renderpass_pool_alloc(&context.gpu_pools.renderpasses, NULL);
+ renderpass->description = description;
+
+ if (!description.default_framebuffer) {
+ // If we're not using the default framebuffer we need to generate a new one
+ GLuint gl_fbo_id;
+ glGenFramebuffers(1, &gl_fbo_id);
+ renderpass->fbo = gl_fbo_id;
+ } else {
+ renderpass->fbo = OPENGL_DEFAULT_FRAMEBUFFER;
+ assert(!description.has_color_target);
+ assert(!description.has_depth_stencil);
+ }
+ glBindFramebuffer(GL_FRAMEBUFFER, renderpass->fbo);
+
+ if (description.has_color_target && !description.default_framebuffer) {
+ GPU_Texture* colour_attachment = TEXTURE_GET(description.color_target);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ colour_attachment->id, 0);
+ }
+ if (description.has_depth_stencil && !description.default_framebuffer) {
+ GPU_Texture* depth_attachment = TEXTURE_GET(description.depth_stencil);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_attachment->id,
+ 0);
+ }
+
+ if (description.has_depth_stencil && !description.has_color_target) {
+ glDrawBuffer(GL_NONE);
+ glReadBuffer(GL_NONE);
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0); // reset to default framebuffer
+
+ return renderpass;
+}
+
+void GPU_Renderpass_Destroy(GPU_Renderpass* pass) { glDeleteFramebuffers(1, &pass->fbo); }
+
+GPU_Pipeline* GPU_GraphicsPipeline_Create(GraphicsPipelineDesc description,
+ GPU_Renderpass* renderpass) {
+ GPU_Pipeline* pipeline = Pipeline_pool_alloc(&context.gpu_pools.pipelines, NULL);
+
+ // Create shader program
+ u32 shader_id = shader_create_separate(description.vs.filepath.buf, description.fs.filepath.buf);
+ pipeline->shader_id = shader_id;
+
+ // Vertex format
+ pipeline->vertex_desc = description.vertex_desc;
+
+ // Allocate uniform buffers if needed
+ u32 ubo_count = 0;
+ // printf("data layouts %d\n", description.data_layouts_count);
+ for (u32 layout_i = 0; layout_i < description.data_layouts_count; layout_i++) {
+ ShaderDataLayout sdl = description.data_layouts[layout_i];
+ TRACE("Got shader data layout %d's bindings! . found %d", layout_i, sdl.binding_count);
+
+ for (u32 binding_j = 0; binding_j < sdl.binding_count; binding_j++) {
+ u32 binding_id = binding_j;
+ assert(binding_id < MAX_PIPELINE_UNIFORM_BUFFERS);
+ ShaderBinding binding = sdl.bindings[binding_j];
+ // Do I want Buffer vs Bytes?
+ if (binding.kind == BINDING_BYTES) {
+ static u32 s_binding_point = 0;
+ BufferHandle ubo_handle = GPU_BufferCreate(binding.data.bytes.size, BUFFER_UNIFORM,
+ BUFFER_FLAG_GPU, NULL); // no data right now
+ pipeline->uniform_bindings[ubo_count++] = ubo_handle;
+ GPU_Buffer* ubo_buf = BUFFER_GET(ubo_handle);
+
+ i32 blockIndex = glGetUniformBlockIndex(pipeline->shader_id, binding.label);
+ printf("Block index for %s: %d", binding.label, blockIndex);
+ if (blockIndex < 0) {
+ WARN("Couldn't retrieve block index for uniform block '%s'", binding.label);
+ } else {
+ // DEBUG("Retrived block index %d for %s", blockIndex, binding.label);
+ }
+ u32 blocksize;
+ glGetActiveUniformBlockiv(pipeline->shader_id, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE,
+ &blocksize);
+ printf("\t with size %d bytes\n", blocksize);
+
+ glBindBufferBase(GL_UNIFORM_BUFFER, s_binding_point, ubo_buf->id.ubo);
+ if (blockIndex != GL_INVALID_INDEX) {
+ glUniformBlockBinding(pipeline->shader_id, blockIndex, s_binding_point);
+ }
+ ubo_buf->ubo_binding_point = s_binding_point++;
+ ubo_buf->name = binding.label;
+ assert(s_binding_point < GL_MAX_UNIFORM_BUFFER_BINDINGS);
+ }
+ }
+ }
+ pipeline->uniform_count = ubo_count;
+
+ pipeline->renderpass = renderpass;
+ pipeline->wireframe = description.wireframe;
+
+ return pipeline;
+}
+
+void GraphicsPipeline_Destroy(GPU_Pipeline* pipeline) {}
+
+GPU_CmdEncoder GPU_CmdEncoder_Create() {
+ GPU_CmdEncoder encoder = { 0 };
+ return encoder;
+}
+
+BufferHandle GPU_BufferCreate(u64 size, GPU_BufferType buf_type, GPU_BufferFlags flags,
+ const void* data) {
+ // "allocating" the cpu-side buffer struct
+ BufferHandle handle;
+ GPU_Buffer* buffer = Buffer_pool_alloc(&context.resource_pools->buffers, &handle);
+ buffer->size = size;
+ buffer->vao = 0;
+
+ // Opengl buffer
+ GLuint gl_buffer_id;
+ glGenBuffers(1, &gl_buffer_id);
+
+ GLenum gl_buf_type;
+ GLenum gl_buf_usage = GL_STATIC_DRAW;
+
+ switch (buf_type) {
+ case BUFFER_UNIFORM:
+ DEBUG("Creating Uniform buffer");
+ gl_buf_type = GL_UNIFORM_BUFFER;
+ /* gl_buf_usage = GL_DYNAMIC_DRAW; */
+ buffer->id.ubo = gl_buffer_id;
+ break;
+ case BUFFER_DEFAULT:
+ case BUFFER_VERTEX:
+ DEBUG("Creating Vertex buffer");
+ gl_buf_type = GL_ARRAY_BUFFER;
+ buffer->id.vbo = gl_buffer_id;
+ break;
+ case BUFFER_INDEX:
+ DEBUG("Creating Index buffer");
+ gl_buf_type = GL_ELEMENT_ARRAY_BUFFER;
+ buffer->id.ibo = gl_buffer_id;
+ break;
+ default:
+ WARN("Unimplemented gpu_buffer_type provided %s", buffer_type_names[buf_type]);
+ break;
+ }
+ // bind buffer
+ glBindBuffer(gl_buf_type, gl_buffer_id);
+
+ if (data) {
+ TRACE("Upload data (%d bytes) as part of buffer creation", size);
+ glBufferData(gl_buf_type, buffer->size, data, gl_buf_usage);
+ } else {
+ TRACE("Allocating but not uploading (%d bytes)", size);
+ glBufferData(gl_buf_type, buffer->size, NULL, gl_buf_usage);
+ }
+
+ glBindBuffer(gl_buf_type, 0);
+
+ return handle;
+}
+
+void GPU_BufferDestroy(BufferHandle handle) { glDeleteBuffers(1, &handle.raw); }
+
+TextureHandle GPU_TextureCreate(TextureDesc desc, bool create_view, const void* data) {
+ // "allocating" the cpu-side struct
+ TextureHandle handle;
+ GPU_Texture* texture = Texture_pool_alloc(&context.resource_pools->textures, &handle);
+ DEBUG("Allocated texture with handle %d", handle.raw);
+
+ GLuint gl_texture_id;
+ glGenTextures(1, &gl_texture_id);
+ texture->id = gl_texture_id;
+
+ GLenum gl_tex_type = opengl_tex_type(desc.tex_type);
+ texture->type = desc.tex_type;
+ printf("Creating texture of type %s\n", texture_type_names[desc.tex_type]);
+ glBindTexture(gl_tex_type, gl_texture_id);
+
+ GLint internal_format;
+ if (desc.format == TEXTURE_FORMAT_DEPTH_DEFAULT) {
+ internal_format = GL_DEPTH_COMPONENT;
+ } else if (desc.format == TEXTURE_FORMAT_8_8_8_8_RGBA_UNORM) {
+ internal_format = GL_RGBA;
+ } else {
+ internal_format = GL_RGB;
+ }
+
+ GLint format = internal_format;
+ // FIXME: GLint format = desc.format == TEXTURE_FORMAT_DEPTH_DEFAULT ? GL_DEPTH_COMPONENT :
+ // GL_RGBA;
+ GLenum data_type = desc.format == TEXTURE_FORMAT_DEPTH_DEFAULT ? GL_FLOAT : GL_UNSIGNED_BYTE;
+
+ if (desc.format == TEXTURE_FORMAT_DEPTH_DEFAULT) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+ } else {
+ // 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);
+ }
+
+ if (data) {
+ glTexImage2D(GL_TEXTURE_2D, 0, internal_format, desc.extents.x, desc.extents.y, 0, format,
+ data_type, data);
+ if (desc.tex_type == TEXTURE_TYPE_2D) {
+ glGenerateMipmap(GL_TEXTURE_2D);
+ }
+ } else {
+ WARN("No image data provided");
+ glTexImage2D(GL_TEXTURE_2D, 0, internal_format, desc.extents.x, desc.extents.y, 0, format,
+ data_type, NULL);
+ }
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ return handle;
+}
+
+GPU_Texture* GPU_TextureAlloc(TextureHandle* out_handle) {
+ TextureHandle handle;
+ GPU_Texture* texture = Texture_pool_alloc(&context.resource_pools->textures, &handle);
+ DEBUG("Allocated texture with handle %d", handle.raw);
+
+ GLuint gl_texture_id;
+ glGenTextures(1, &gl_texture_id);
+ texture->id = gl_texture_id;
+
+ if (out_handle != NULL) {
+ *out_handle = handle;
+ }
+
+ return texture;
+}
+
+void GPU_TextureDestroy(TextureHandle handle) { glDeleteTextures(1, &handle.raw); }
+
+// TODO: void GPU_TextureUpload(TextureHandle handle, size_t n_bytes, const void* data)
+
+void GPU_EncodeBindPipeline(GPU_CmdEncoder* encoder, GPU_Pipeline* pipeline) {
+ encoder->pipeline = pipeline;
+
+ // In OpenGL binding a pipeline is more or less equivalent to just setting the shader
+ glUseProgram(pipeline->shader_id);
+
+ if (pipeline->wireframe) {
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ } else {
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ }
+}
+
+void GPU_EncodeBindShaderData(GPU_CmdEncoder* encoder, u32 group, ShaderDataLayout layout) {
+ for (u32 binding_i = 0; binding_i < layout.binding_count; binding_i++) {
+ ShaderBinding binding = layout.bindings[binding_i];
+
+ switch (binding.kind) {
+ case BINDING_BYTES: {
+#ifdef RAL_ASSERTS
+ CASSERT_MSG(binding.data.bytes.data, "void* data pointer should be non null");
+ CASSERT_MSG(binding.data.bytes.size > 0, "size should be greater than 0 bytes");
+#endif
+ BufferHandle b;
+ GPU_Buffer* ubo_buf;
+ bool found = false;
+ for (u32 i = 0; i < encoder->pipeline->uniform_count; i++) {
+ b = encoder->pipeline->uniform_bindings[i];
+ ubo_buf = BUFFER_GET(b);
+ assert(ubo_buf->name != NULL);
+ if (strcmp(ubo_buf->name, binding.label) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ ERROR("Couldnt find uniform buffer object for %s!!", binding.label);
+ break;
+ }
+
+ i32 blockIndex = glGetUniformBlockIndex(encoder->pipeline->shader_id, binding.label);
+ if (blockIndex < 0) {
+ WARN("Couldn't retrieve block index for uniform block '%s'", binding.label);
+ break;
+ }
+
+ glBindBuffer(GL_UNIFORM_BUFFER, ubo_buf->id.ubo);
+ glBufferSubData(GL_UNIFORM_BUFFER, 0, ubo_buf->size, binding.data.bytes.data);
+ break;
+ }
+ case BINDING_TEXTURE: {
+ GPU_Texture* tex = TEXTURE_GET(binding.data.texture.handle);
+ GLint tex_slot = glGetUniformLocation(encoder->pipeline->shader_id, binding.label);
+ if (tex_slot == GL_INVALID_VALUE || tex_slot < 0) {
+ WARN("Invalid binding label for texture %s - couldn't fetch texture slot uniform",
+ binding.label);
+ }
+ glUniform1i(tex_slot, binding_i);
+ glActiveTexture(GL_TEXTURE0 + binding_i);
+ glBindTexture(opengl_tex_type(tex->type), tex->id);
+ break;
+ }
+ default:
+ WARN("Unsupported binding kind");
+ }
+ }
+}
+
+void GPU_EncodeSetDefaults(GPU_CmdEncoder* encoder) {}
+
+void GPU_EncodeSetVertexBuffer(GPU_CmdEncoder* encoder, BufferHandle buf) {
+ GPU_Buffer* buffer = BUFFER_GET(buf);
+ if (buffer->vao == 0) { // if no VAO for this vertex buffer, create it
+ INFO("Setting up VAO");
+ buffer->vao = opengl_bindcreate_vao(buffer, encoder->pipeline->vertex_desc);
+ }
+ glBindVertexArray(buffer->vao);
+}
+void GPU_EncodeSetIndexBuffer(GPU_CmdEncoder* encoder, BufferHandle buf) {
+ GPU_Buffer* buffer = BUFFER_GET(buf);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->id.ibo);
+}
+void GPU_EncodeDrawTris(GPU_CmdEncoder* encoder, u64 count) {
+ glDrawArrays(GL_TRIANGLES, 0, count);
+}
+void GPU_EncodeDrawIndexedTris(GPU_CmdEncoder* encoder, u64 index_count) {
+ glDrawElements(GL_TRIANGLES, index_count, GL_UNSIGNED_INT, 0);
+}
+
+PUB void GPU_EncodeDraw(GPU_CmdEncoder* encoder, PrimitiveTopology topology, u64 count) {
+ glDrawArrays(opengl_prim_topology(topology), 0, count);
+}
+PUB void GPU_EncodeDrawIndexed(GPU_CmdEncoder* encoder, PrimitiveTopology topology,
+ u64 index_count) {
+ glDrawElements(opengl_prim_topology(topology), index_count, GL_UNSIGNED_INT, 0);
+}
+
+PUB void GPU_WriteTextureRegion(GPU_CmdEncoder* encoder, TextureHandle dst, u32 x_offset,
+ u32 y_offset, u32 width, u32 height, const void* data) {
+ CASSERT_MSG(data, "const void* data must not be NULL");
+
+ GPU_Texture* tex = TEXTURE_GET(dst);
+
+ glBindTexture(GL_TEXTURE_2D, tex->id);
+ glTexSubImage2D(GL_TEXTURE_2D, 0, x_offset, y_offset, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
+ data);
+}
+
+bool GPU_Backend_BeginFrame() {
+ glViewport(0, 0, context.swapchain.dimensions.x * 2, context.swapchain.dimensions.y * 2);
+ glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ return true;
+}
+
+void GPU_Backend_EndFrame() { glfwSwapBuffers(context.window); }
+
+#endif
diff --git a/archive/src/ral/backends/opengl/backend_opengl.h b/archive/src/ral/backends/opengl/backend_opengl.h
new file mode 100644
index 0000000..7bd1b81
--- /dev/null
+++ b/archive/src/ral/backends/opengl/backend_opengl.h
@@ -0,0 +1,109 @@
+#pragma once
+#include "defines.h"
+
+#if defined(CEL_REND_BACKEND_OPENGL)
+
+#include "maths_types.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+
+#define MAX_PIPELINE_UNIFORM_BUFFERS 32
+
+#define OPENGL_DEFAULT_FRAMEBUFFER 0
+
+typedef struct GPU_Swapchain {
+ u32x2 dimensions;
+} GPU_Swapchain;
+
+typedef struct GPU_Device {
+ u32 pad;
+} GPU_Device;
+
+typedef struct GPU_PipelineLayout {
+ void* pad;
+} GPU_PipelineLayout;
+
+typedef struct GPU_Pipeline {
+ u32 shader_id;
+ GPU_Renderpass* renderpass;
+ VertexDescription vertex_desc;
+ BufferHandle uniform_bindings[MAX_PIPELINE_UNIFORM_BUFFERS];
+ u32 uniform_count;
+ bool wireframe;
+} GPU_Pipeline;
+
+typedef struct GPU_Renderpass {
+ u32 fbo;
+ GPU_RenderpassDesc description;
+} GPU_Renderpass;
+
+typedef struct GPU_CmdEncoder {
+ GPU_Pipeline* pipeline;
+} GPU_CmdEncoder; // Recording
+
+typedef struct GPU_CmdBuffer {
+ void* pad;
+} GPU_CmdBuffer; // Ready for submission
+
+typedef struct GPU_Buffer {
+ union {
+ u32 vbo;
+ u32 ibo;
+ u32 ubo;
+ } id;
+ union {
+ u32 vao;
+ u32 ubo_binding_point;
+ }; // Optional
+ char* name;
+ u64 size;
+} GPU_Buffer;
+
+typedef struct GPU_Texture {
+ u32 id;
+ GPU_TextureType type;
+} GPU_Texture;
+
+typedef struct opengl_support {
+ u32 pad;
+} opengl_support;
+
+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);
+
+typedef enum GlCommandType {
+ GLCMD_DRAW,
+ GLCMD_DRAW_INDEXED,
+ GLCMD_BIND_VBUF,
+ GLCMD_BIND_IBUF,
+ GLCMD_SET_PROGRAM,
+} GlCommandType;
+
+typedef struct GlCommand {
+ GlCommandType cmd_type;
+ union {
+ struct {
+ PrimitiveTopology topology;
+ u32 start_vertex;
+ u32 vertex_count;
+ // TODO: instance
+ } draw;
+ struct {
+ PrimitiveTopology topology;
+ u32 index_count;
+ } draw_indexed;
+ struct {
+ u32 buffer_id;
+ } bind_vbuf;
+ struct {
+ u32 buffer_id;
+ } bind_ibuf;
+ struct {
+ u32 program_id;
+ } set_program;
+ } data;
+} GlCommand;
+
+#endif
diff --git a/archive/src/ral/backends/opengl/opengl_helpers.h b/archive/src/ral/backends/opengl/opengl_helpers.h
new file mode 100644
index 0000000..706e2a0
--- /dev/null
+++ b/archive/src/ral/backends/opengl/opengl_helpers.h
@@ -0,0 +1,159 @@
+#pragma once
+#include "defines.h"
+#include "ral_common.h"
+#include "ral_impl.h"
+#if defined(CEL_REND_BACKEND_OPENGL)
+#include "backend_opengl.h"
+#include "file.h"
+#include "log.h"
+#include "ral_types.h"
+
+#include <glad/glad.h>
+#include <glfw3.h>
+#include "ral_types.h"
+
+typedef struct opengl_vertex_attr {
+ u32 count;
+ GLenum data_type;
+} opengl_vertex_attr;
+
+static opengl_vertex_attr format_from_vertex_attr(VertexAttribType attr) {
+ switch (attr) {
+ case ATTR_F32:
+ return (opengl_vertex_attr){ .count = 1, .data_type = GL_FLOAT };
+ case ATTR_U32:
+ return (opengl_vertex_attr){ .count = 1, .data_type = GL_UNSIGNED_INT };
+ case ATTR_I32:
+ return (opengl_vertex_attr){ .count = 1, .data_type = GL_INT };
+ case ATTR_F32x2:
+ return (opengl_vertex_attr){ .count = 2, .data_type = GL_FLOAT };
+ case ATTR_U32x2:
+ // return VK_FORMAT_R32G32_UINT;
+ case ATTR_I32x2:
+ // return VK_FORMAT_R32G32_UINT;
+ case ATTR_F32x3:
+ return (opengl_vertex_attr){ .count = 3, .data_type = GL_FLOAT };
+ case ATTR_U32x3:
+ // return VK_FORMAT_R32G32B32_UINT;
+ case ATTR_I32x3:
+ // return VK_FORMAT_R32G32B32_SINT;
+ case ATTR_F32x4:
+ return (opengl_vertex_attr){ .count = 4, .data_type = GL_FLOAT };
+ case ATTR_U32x4:
+ // return VK_FORMAT_R32G32B32A32_UINT;
+ case ATTR_I32x4:
+ return (opengl_vertex_attr){ .count = 4, .data_type = GL_INT };
+ }
+}
+
+static u32 opengl_bindcreate_vao(GPU_Buffer* buf, VertexDescription desc) {
+ DEBUG("Vertex format name %s", desc.debug_label);
+ // 1. Bind the buffer
+ glBindBuffer(GL_ARRAY_BUFFER, buf->id.vbo);
+ // 2. Create new VAO
+ u32 vao;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ // Attributes
+ u32 attr_count = desc.attributes_count;
+ // printf("N attributes %d\n", attr_count);
+ u64 offset = 0;
+ size_t vertex_size = desc.use_full_vertex_size ? sizeof(Vertex) : VertexDesc_CalcStride(&desc);
+ for (u32 i = 0; i < desc.attributes_count; i++) {
+ opengl_vertex_attr format = format_from_vertex_attr(desc.attributes[i]);
+ glVertexAttribPointer(i, format.count, format.data_type, GL_FALSE, vertex_size, (void*)offset);
+ TRACE(" %d %d %d %d %d %s", i, format.count, format.data_type, vertex_size, offset,
+ desc.attr_names[i]);
+ glEnableVertexAttribArray(i); // nth index
+ size_t this_offset = VertexAttribSize(desc.attributes[i]);
+ // printf("offset total %lld this attr %zu\n", offset, this_offset);
+ offset += this_offset;
+ }
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+
+ return vao;
+}
+
+static u32 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);
+
+ return shader_prog;
+}
+
+static GLenum opengl_tex_type(GPU_TextureType tex_type) {
+ switch (tex_type) {
+ case TEXTURE_TYPE_2D:
+ return GL_TEXTURE_2D;
+ case TEXTURE_TYPE_CUBE_MAP:
+ return GL_TEXTURE_CUBE_MAP;
+ default:
+ return GL_TEXTURE_2D;
+ }
+}
+
+static GLenum opengl_prim_topology(PrimitiveTopology t) {
+ switch (t) {
+ case CEL_POINT:
+ return GL_POINT;
+ case CEL_LINE:
+ return GL_LINES;
+ case CEL_LINE_STRIP:
+ return GL_LINE_STRIP;
+ case CEL_TRI:
+ return GL_TRIANGLES;
+ case CEL_TRI_STRIP:
+ return GL_TRIANGLE_STRIP;
+ case PRIMITIVE_TOPOLOGY_COUNT:
+ WARN("Invalid PrimitiveTopology value");
+ break;
+ }
+}
+
+#endif
diff --git a/archive/src/ral/backends/vulkan/backend_vulkan.c b/archive/src/ral/backends/vulkan/backend_vulkan.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/archive/src/ral/backends/vulkan/backend_vulkan.c
diff --git a/archive/src/ral/backends/vulkan/backend_vulkan.h b/archive/src/ral/backends/vulkan/backend_vulkan.h
new file mode 100644
index 0000000..f31bed2
--- /dev/null
+++ b/archive/src/ral/backends/vulkan/backend_vulkan.h
@@ -0,0 +1,44 @@
+#pragma once
+
+#ifdef CEL_REND_BACKEND_VULKAN
+#include "defines.h"
+#include "maths_types.h"
+#include "ral.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+
+#include <vulkan/vk_platform.h>
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_core.h>
+
+// Provide definitions for RAL structs
+
+struct GPU_Swapchain {
+ VkSwapchainKHR handle;
+};
+
+struct GPU_Device {
+ VkPhysicalDevice physical_device;
+ VkDevice logical_device;
+};
+
+struct GPU_PipelineLayout {};
+struct GPU_Pipeline {};
+struct GPU_Renderpass {};
+struct GPU_CmdEncoder {};
+struct GPU_CmdBuffer {};
+struct GPU_Buffer {
+ VkBuffer handle;
+ VkDeviceMemory memory;
+ u64 size;
+};
+struct GPU_Texture {
+ VkImage handle;
+ VkDeviceMemory memory;
+ u64 size;
+ VkImageView view;
+ VkSampler sampler;
+ char* debug_label;
+};
+
+#endif
diff --git a/archive/src/ral/backends/vulkan/vulkan_glossary.md b/archive/src/ral/backends/vulkan/vulkan_glossary.md
new file mode 100644
index 0000000..4214f9d
--- /dev/null
+++ b/archive/src/ral/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/archive/src/ral/backends/vulkan/vulkan_helpers.h b/archive/src/ral/backends/vulkan/vulkan_helpers.h
new file mode 100644
index 0000000..23666c6
--- /dev/null
+++ b/archive/src/ral/backends/vulkan/vulkan_helpers.h
@@ -0,0 +1,199 @@
+#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) \
+ do { \
+ VkResult res = vulkan_expr; \
+ if (res != VK_SUCCESS) { \
+ ERROR_EXIT("Vulkan error: %u (%s:%d)", res, __FILE__, __LINE__); \
+ } \
+ } while (0)
+
+// 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;
+
+#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);
+
+static 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 VkSurfaceFormatKHR choose_swapchain_format(
+ vulkan_swapchain_support_info* swapchain_support) {
+ assert(swapchain_support->format_count > 0);
+ // find a format
+ for (u32 i = 0; i < swapchain_support->format_count; i++) {
+ VkSurfaceFormatKHR format = swapchain_support->formats[i];
+ if (format.format == VK_FORMAT_B8G8R8A8_SRGB &&
+ format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+ return format;
+ }
+ }
+ return swapchain_support->formats[0];
+}
+
+// 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;
+// }
+
+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;
+} \ No newline at end of file
diff --git a/archive/src/ral/ral.h b/archive/src/ral/ral.h
new file mode 100644
index 0000000..fdbadd3
--- /dev/null
+++ b/archive/src/ral/ral.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "ral_common.h"
+#include "ral_impl.h"
+#include "ral_types.h" \ No newline at end of file
diff --git a/archive/src/ral/ral_common.c b/archive/src/ral/ral_common.c
new file mode 100644
index 0000000..d921ac4
--- /dev/null
+++ b/archive/src/ral/ral_common.c
@@ -0,0 +1,70 @@
+#include "ral_common.h"
+#include "ral_impl.h"
+
+void BackendPools_Init(arena* a, GPU_BackendPools* backend_pools) {
+ PipelineLayout_pool pipeline_layout_pool =
+ PipelineLayout_pool_create(a, MAX_PIPELINES, sizeof(GPU_PipelineLayout));
+ backend_pools->pipeline_layouts = pipeline_layout_pool;
+ Pipeline_pool pipeline_pool = Pipeline_pool_create(a, MAX_PIPELINES, sizeof(GPU_Pipeline));
+ backend_pools->pipelines = pipeline_pool;
+ Renderpass_pool rpass_pool = Renderpass_pool_create(a, MAX_RENDERPASSES, sizeof(GPU_Renderpass));
+ backend_pools->renderpasses = rpass_pool;
+}
+
+void ResourcePools_Init(arena* a, struct ResourcePools* res_pools) {
+ Buffer_pool buf_pool = Buffer_pool_create(a, MAX_BUFFERS, sizeof(GPU_Buffer));
+ res_pools->buffers = buf_pool;
+ Texture_pool tex_pool = Texture_pool_create(a, MAX_TEXTURES, sizeof(GPU_Texture));
+ res_pools->textures = tex_pool;
+}
+
+VertexDescription static_3d_vertex_description() {
+ VertexDescription builder = { .debug_label = "Standard static 3d vertex format" };
+ VertexDesc_AddAttr(&builder, "inPosition", ATTR_F32x3);
+ VertexDesc_AddAttr(&builder, "inNormal", ATTR_F32x3);
+ VertexDesc_AddAttr(&builder, "inTexCoords", ATTR_F32x2);
+ builder.use_full_vertex_size = true;
+ return builder;
+}
+
+void VertexDesc_AddAttr(VertexDescription* builder, const char* name, VertexAttribType type) {
+ u32 i = builder->attributes_count;
+
+ size_t size = VertexAttribSize(type);
+ builder->attributes[i] = type;
+ // builder->stride += size;
+ builder->attr_names[i] = name;
+
+ builder->attributes_count++;
+}
+
+size_t VertexAttribSize(VertexAttribType attr) {
+ switch (attr) {
+ case ATTR_F32:
+ case ATTR_U32:
+ case ATTR_I32:
+ return 4;
+ case ATTR_F32x2:
+ case ATTR_U32x2:
+ case ATTR_I32x2:
+ return 8;
+ case ATTR_F32x3:
+ case ATTR_U32x3:
+ case ATTR_I32x3:
+ return 12;
+ case ATTR_F32x4:
+ case ATTR_U32x4:
+ case ATTR_I32x4:
+ return 16;
+ break;
+ }
+}
+
+size_t VertexDesc_CalcStride(VertexDescription* desc) {
+ size_t stride = 0;
+ for (int i = 0; i < desc->attributes_count; i++) {
+ size_t size = VertexAttribSize(desc->attributes[i]);
+ stride += size;
+ }
+ return stride;
+}
diff --git a/archive/src/ral/ral_common.h b/archive/src/ral/ral_common.h
new file mode 100644
index 0000000..5a797e5
--- /dev/null
+++ b/archive/src/ral/ral_common.h
@@ -0,0 +1,61 @@
+/**
+ * @brief Common functions that don't actually depend on the specific backend
+ */
+#pragma once
+#include "buf.h"
+#include "defines.h"
+#include "mem.h"
+#include "ral_types.h"
+// #include "ral_impl.h"
+
+// Concrete implementation
+#if defined(CEL_REND_BACKEND_OPENGL)
+#include "backend_opengl.h"
+#endif
+
+TYPED_POOL(GPU_Buffer, Buffer);
+TYPED_POOL(GPU_Texture, Texture);
+TYPED_POOL(GPU_PipelineLayout, PipelineLayout);
+TYPED_POOL(GPU_Pipeline, Pipeline);
+TYPED_POOL(GPU_Renderpass, Renderpass);
+
+// --- Handy macros
+#define BUFFER_GET(h) (Buffer_pool_get(&context.resource_pools->buffers, h))
+#define TEXTURE_GET(h) (Texture_pool_get(&context.resource_pools->textures, h))
+
+// --- Views
+typedef struct GPU_BufferView {
+ BufferHandle buf;
+ size_t offset;
+ size_t bytes;
+} GPU_BufferView;
+
+// --- Pools
+typedef struct GPU_BackendPools {
+ Pipeline_pool pipelines;
+ PipelineLayout_pool pipeline_layouts;
+ Renderpass_pool renderpasses;
+} GPU_BackendPools;
+void BackendPools_Init(arena* a, GPU_BackendPools* backend_pools);
+
+struct ResourcePools {
+ Buffer_pool buffers;
+ Texture_pool textures;
+};
+typedef struct ResourcePools ResourcePools;
+void ResourcePools_Init(arena* a, struct ResourcePools* res_pools);
+
+PUB GPU_Renderpass* GPU_GetDefaultRenderpass(); // returns a renderpass that draws directly to
+ // default framebuffer with default depth
+
+// --
+// window resize callback
+void GPU_WindowResizedCallback(u32 x, u32 y);
+
+// --- Vertex formats
+VertexDescription static_3d_vertex_description();
+
+void VertexDesc_AddAttr(VertexDescription* builder, const char* name, VertexAttribType type);
+size_t VertexDesc_CalcStride(VertexDescription* desc);
+
+size_t VertexAttribSize(VertexAttribType attr);
diff --git a/archive/src/ral/ral_impl.h b/archive/src/ral/ral_impl.h
new file mode 100644
index 0000000..16c9767
--- /dev/null
+++ b/archive/src/ral/ral_impl.h
@@ -0,0 +1,102 @@
+/**
+ * @brief
+ */
+#pragma once
+#include "buf.h"
+#include "defines.h"
+#include "ral_types.h"
+
+struct GLFWwindow;
+struct ResourcePools;
+
+// Forward declare structs - these must be defined in the backend implementation
+typedef struct GPU_Swapchain GPU_Swapchain;
+typedef struct GPU_Device GPU_Device;
+typedef struct GPU_PipelineLayout GPU_PipelineLayout;
+typedef struct GPU_Pipeline GPU_Pipeline;
+typedef struct GPU_Renderpass GPU_Renderpass;
+typedef struct GPU_CmdEncoder GPU_CmdEncoder; // Recording
+typedef struct GPU_CmdBuffer GPU_CmdBuffer; // Ready for submission
+typedef struct GPU_Buffer GPU_Buffer;
+typedef struct GPU_Texture GPU_Texture;
+
+bool GPU_Backend_Init(const char* window_name, struct GLFWwindow* window,
+ struct ResourcePools* res_pools);
+void GPU_Backend_Shutdown();
+
+bool GPU_Device_Create(GPU_Device* out_device);
+void GPU_Device_Destroy(GPU_Device* device);
+
+bool GPU_Swapchain_Create(GPU_Swapchain* out_swapchain);
+void GPU_Swapchain_Destroy(GPU_Swapchain* swapchain);
+void GPU_Swapchain_Resize(i32 new_width, i32 new_height);
+u32x2 GPU_Swapchain_GetDimensions();
+
+PUB GPU_Renderpass* GPU_Renderpass_Create(GPU_RenderpassDesc description);
+PUB void GPU_Renderpass_Destroy(GPU_Renderpass* pass);
+
+PUB GPU_Pipeline* GPU_GraphicsPipeline_Create(GraphicsPipelineDesc description,
+ GPU_Renderpass* renderpass);
+PUB void GraphicsPipeline_Destroy(GPU_Pipeline* pipeline);
+
+// --- Command buffer
+PUB GPU_CmdEncoder GPU_CmdEncoder_Create();
+PUB void GPU_CmdEncoder_Destroy(GPU_CmdEncoder* encoder);
+PUB void GPU_CmdEncoder_Begin(GPU_CmdEncoder* encoder);
+PUB void GPU_CmdEncoder_Finish(GPU_CmdEncoder* encoder);
+PUB void GPU_CmdEncoder_BeginRender(GPU_CmdEncoder* encoder, GPU_Renderpass* renderpass);
+PUB void GPU_CmdEncoder_EndRender(GPU_CmdEncoder* encoder);
+PUB GPU_CmdEncoder* GPU_GetDefaultEncoder();
+PUB void GPU_QueueSubmit(GPU_CmdBuffer* cmd_buffer);
+
+// --- Buffers
+PUB BufferHandle GPU_BufferCreate(u64 size, GPU_BufferType buf_type, GPU_BufferFlags flags,
+ const void* data);
+PUB void GPU_BufferDestroy(BufferHandle handle);
+PUB void GPU_BufferUpload(BufferHandle buffer, size_t n_bytes, const void* data);
+
+// --- Textures
+PUB TextureHandle GPU_TextureCreate(TextureDesc desc, bool create_view, const void* data);
+PUB GPU_Texture* GPU_TextureAlloc(TextureHandle* out_handle);
+PUB void GPU_TextureDestroy(TextureHandle handle);
+PUB void GPU_TextureUpload(TextureHandle handle, size_t n_bytes, const void* data);
+
+// --- Data copy commands
+PUB void GPU_EncodeCopyBufToBuf(GPU_CmdEncoder* encoder, BufferHandle src, u64 src_offset,
+ BufferHandle dst, u64 dst_offset, u64 copy_size);
+
+// PUB void GPU_EncodeCopyBufToTex(GPU_CmdEncoder* encoder, BufferHandle src, TextureHandle dst,
+// u32 x_offset, u32 y_offset, u32 width, u32 height, const void* data);
+/** @brief Convenience method for writing data directly into a texture. Staging memory is handled
+ * internally. */
+PUB void GPU_WriteTextureRegion(GPU_CmdEncoder* encoder, TextureHandle dst, u32 x_offset,
+ u32 y_offset, u32 width, u32 height, const void* data);
+PUB void GPU_WriteBuffer(GPU_CmdEncoder* encoder, BufferHandle buf, u64 offset, u64 size,
+ const void* data);
+
+// --- Render commands
+PUB void GPU_EncodeBindPipeline(GPU_CmdEncoder* encoder, GPU_Pipeline* pipeline);
+PUB void GPU_EncodeBindShaderData(GPU_CmdEncoder* encoder, u32 group, ShaderDataLayout layout);
+void GPU_EncodeSetDefaults(GPU_CmdEncoder* encoder);
+PUB void GPU_EncodeSetVertexBuffer(GPU_CmdEncoder* encoder, BufferHandle buf);
+PUB void GPU_EncodeSetIndexBuffer(GPU_CmdEncoder* encoder, BufferHandle buf);
+
+PUB void GPU_EncodeDraw(GPU_CmdEncoder* encoder, PrimitiveTopology topology, u64 count);
+PUB void GPU_EncodeDrawIndexed(GPU_CmdEncoder* encoder, PrimitiveTopology topology,
+ u64 index_count);
+// convenience versions of the above
+PUB void GPU_EncodeDrawTris(GPU_CmdEncoder* encoder, u64 count);
+PUB void GPU_EncodeDrawIndexedTris(GPU_CmdEncoder* encoder, u64 index_count);
+PUB void GPU_EncodeDrawInstanced(GPU_CmdEncoder* encoder, u64 index_count,
+ u64 instance_count); // TODO: implement instanced rendering
+
+// --- Frame cycle
+PUB bool GPU_Backend_BeginFrame();
+PUB void GPU_Backend_EndFrame();
+
+// Concrete implementation
+#if defined(CEL_REND_BACKEND_OPENGL)
+#include "backend_opengl.h"
+#elif defined(CEL_REND_BACKEND_VULKAN)
+#include "backend_vulkan.h"
+#endif
diff --git a/archive/src/ral/ral_types.h b/archive/src/ral/ral_types.h
new file mode 100644
index 0000000..fde3bed
--- /dev/null
+++ b/archive/src/ral/ral_types.h
@@ -0,0 +1,168 @@
+#pragma once
+#include "darray.h"
+#include "defines.h"
+#include "maths_types.h"
+#include "str.h"
+
+// --- Max size constants
+#define MAX_SHADER_DATA_LAYOUTS 8
+#define MAX_SHADER_BINDINGS 8
+#define MAX_BUFFERS 256
+#define MAX_TEXTURES 256
+#define MAX_PIPELINES 128
+#define MAX_RENDERPASSES 128
+#define MAX_VERTEX_ATTRIBUTES 16
+
+// --- Handle types
+CORE_DEFINE_HANDLE(BufferHandle);
+CORE_DEFINE_HANDLE(TextureHandle);
+CORE_DEFINE_HANDLE(SamplerHandle);
+CORE_DEFINE_HANDLE(ShaderHandle);
+CORE_DEFINE_HANDLE(PipelineLayoutHandle);
+CORE_DEFINE_HANDLE(PipelineHandle);
+CORE_DEFINE_HANDLE(RenderpassHandle);
+#define INVALID_TEX_HANDLE ((TextureHandle){ .raw = 9999981 })
+
+// --- Buffers
+typedef enum GPU_BufferType {
+ BUFFER_DEFAULT, // on Vulkan this would be a storage buffer?
+ BUFFER_VERTEX,
+ BUFFER_INDEX,
+ BUFFER_UNIFORM,
+ BUFFER_COUNT
+} GPU_BufferType;
+
+static const char* buffer_type_names[] = {
+ "RAL Buffer Default", "RAL Buffer Vertex", "RAL Buffer Index",
+ "RAL Buffer Uniform", "RAL Buffer Count",
+};
+
+typedef enum GPU_BufferFlag {
+ BUFFER_FLAG_CPU = 1 << 0,
+ BUFFER_FLAG_GPU = 1 << 1,
+ BUFFER_FLAG_STORAGE = 1 << 2,
+ BUFFER_FLAG_COUNT
+} GPU_BufferFlag;
+typedef u32 GPU_BufferFlags;
+
+static const char* texture_type_names[] = {
+ "RAL Texture 2D", "RAL Texture 3D", "RAL Texture 2D Array",
+ "RAL Texture Cubemap", "RAL Texture Count",
+};
+
+typedef enum GPU_TextureFormat {
+ TEXTURE_FORMAT_8_8_8_8_RGBA_UNORM,
+ TEXTURE_FORMAT_8_8_8_RGB_UNORM,
+ TEXTURE_FORMAT_DEPTH_DEFAULT,
+ TEXTURE_FORMAT_COUNT
+} GPU_TextureFormat;
+
+// --- Vertices
+
+typedef enum VertexFormat {
+ VERTEX_STATIC_3D,
+ VERTEX_SPRITE,
+ VERTEX_SKINNED,
+ VERTEX_COLOURED_STATIC_3D,
+ VERTEX_RAW_POS_COLOUR,
+ VERTEX_POS_ONLY,
+ VERTEX_COUNT
+} VertexFormat;
+
+#ifndef TYPED_VERTEX_ARRAY
+KITC_DECL_TYPED_ARRAY(Vertex);
+KITC_DECL_TYPED_ARRAY(u32)
+#define TYPED_VERTEX_ARRAY
+#endif
+
+/// @strip_prefix(ATTR_)
+typedef enum VertexAttribType {
+ ATTR_F32,
+ ATTR_F32x2,
+ ATTR_F32x3,
+ ATTR_F32x4,
+ ATTR_U32,
+ ATTR_U32x2,
+ ATTR_U32x3,
+ ATTR_U32x4,
+ ATTR_I32,
+ ATTR_I32x2,
+ ATTR_I32x3,
+ ATTR_I32x4,
+} VertexAttribType;
+
+typedef struct VertexDescription {
+ const char* debug_label;
+ const char* attr_names[MAX_VERTEX_ATTRIBUTES];
+ VertexAttribType attributes[MAX_VERTEX_ATTRIBUTES];
+ u32 attributes_count;
+ // size_t stride;
+ bool use_full_vertex_size;
+} VertexDescription;
+
+// --- Shaders
+typedef enum PipelineKind {
+ PIPELINE_GRAPHICS,
+ PIPELINE_COMPUTE,
+} PipelineKind;
+
+typedef struct ShaderDesc {
+ const char* debug_name;
+ Str8 filepath; // Where it came from
+ Str8 code; // Either GLSL or SPIRV bytecode
+ bool is_spirv;
+ bool is_combined_vert_frag; // Contains both vertex and fragment stages
+} ShaderDesc;
+
+typedef ShaderDataLayout (*FN_GetBindingLayout)(void* data);
+
+/** @brief takes a `ShaderDataLayout` without data, and puts the correct data into each binding */
+typedef void (*FN_BindShaderData)(ShaderDataLayout* layout, const void* data);
+
+// typedef struct ShaderData {
+// FN_GetBindingLayout get_layout;
+// void* data;
+// } ShaderData;
+
+typedef enum PrimitiveTopology {
+#ifdef TOPOLOGY_SHORT_NAMES
+ CEL_POINT,
+ CEL_LINE,
+ CEL_LINE_STRIP,
+ CEL_TRI,
+ CEL_TRI_STRIP,
+ PRIMITIVE_TOPOLOGY_COUNT
+#else
+ PRIMITIVE_TOPOLOGY_POINT,
+ PRIMITIVE_TOPOLOGY_LINE,
+ PRIMITIVE_TOPOLOGY_LINE_STRIP,
+ PRIMITIVE_TOPOLOGY_TRIANGLE,
+ PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP,
+ PRIMITIVE_TOPOLOGY_COUNT
+#endif
+} PrimitiveTopology;
+
+typedef enum Winding { WINDING_CCW, WINDING_CW } Winding;
+
+// based on https://registry.khronos.org/OpenGL-Refpages/gl4/html/glDepthFunc.xhtml
+typedef enum CompareFunc {
+ COMPARE_NEVER,
+ COMPARE_LESS,
+ COMPARE_EQUAL,
+ COMPARE_LESS_EQUAL,
+ COMPARE_GREATER,
+ COMPARE_NOT_EQUAL,
+ COMPARE_GREATER_EQUAL,
+ COMPARE_ALWAYS,
+ COMPARE_COUNT
+} CompareFunc;
+
+bool GraphicsPipelineDesc_AddShaderDataLayout(GraphicsPipelineDesc* desc, ShaderDataLayout layout);
+
+typedef struct GPU_RenderpassDesc {
+ bool default_framebuffer;
+ bool has_color_target;
+ TextureHandle color_target; // for now only support one
+ bool has_depth_stencil;
+ TextureHandle depth_stencil;
+} GPU_RenderpassDesc;
diff --git a/archive/src/render/archive/backends/backend_test.c b/archive/src/render/archive/backends/backend_test.c
new file mode 100644
index 0000000..6347e27
--- /dev/null
+++ b/archive/src/render/archive/backends/backend_test.c
@@ -0,0 +1 @@
+// #FUTURE \ No newline at end of file
diff --git a/archive/src/render/archive/backends/metal/README.md b/archive/src/render/archive/backends/metal/README.md
new file mode 100644
index 0000000..f87f5c1
--- /dev/null
+++ b/archive/src/render/archive/backends/metal/README.md
@@ -0,0 +1 @@
+# TODO \ No newline at end of file
diff --git a/archive/src/render/archive/backends/metal/backend_metal.h b/archive/src/render/archive/backends/metal/backend_metal.h
new file mode 100644
index 0000000..9561bb6
--- /dev/null
+++ b/archive/src/render/archive/backends/metal/backend_metal.h
@@ -0,0 +1,74 @@
+#pragma once
+// #define CEL_REND_BACKEND_METAL
+#if defined(CEL_REND_BACKEND_METAL)
+
+#include "defines.h"
+#include "maths_types.h"
+#ifdef __OBJC__
+#import <Foundation/Foundation.h>
+#import <Metal/Metal.h>
+#import <MetalKit/MetalKit.h>
+#import <QuartzCore/CAMetalLayer.h>
+#else
+typedef void* id;
+#endif
+
+typedef struct gpu_swapchain {
+ u32x2 dimensions;
+#ifdef __OBJC__
+ CAMetalLayer* swapchain;
+#else
+ void* swapchain;
+#endif
+} gpu_swapchain;
+typedef struct gpu_device {
+/** @brief `device` gives us access to our GPU */
+#ifdef __OBJC__
+ id<MTLDevice> id;
+#else
+ void* id;
+#endif
+} gpu_device;
+typedef struct gpu_pipeline_layout {
+ void* pad;
+} gpu_pipeline_layout;
+typedef struct gpu_pipeline {
+#ifdef __OBJC__
+ id<MTLRenderPipelineState> pipeline_state;
+#else
+ void* pipeline_state;
+#endif
+} gpu_pipeline;
+typedef struct gpu_renderpass {
+#ifdef __OBJC__
+ MTLRenderPassDescriptor* rpass_descriptor;
+#else
+ void* rpass_descriptor;
+#endif
+} gpu_renderpass;
+typedef struct gpu_cmd_encoder {
+#ifdef __OBJC__
+ id<MTLCommandBuffer> cmd_buffer;
+ id<MTLRenderCommandEncoder> render_encoder;
+#else
+ void* cmd_buffer;
+ void* render_encoder;
+#endif
+} gpu_cmd_encoder;
+typedef struct gpu_cmd_buffer {
+ void* pad;
+} gpu_cmd_buffer;
+
+typedef struct gpu_buffer {
+#ifdef __OBJC__
+ id<MTLBuffer> id;
+#else
+ void* id;
+#endif
+ u64 size;
+} gpu_buffer;
+typedef struct gpu_texture {
+ void* pad;
+} gpu_texture;
+
+#endif \ No newline at end of file
diff --git a/archive/src/render/archive/backends/metal/backend_metal.m b/archive/src/render/archive/backends/metal/backend_metal.m
new file mode 100644
index 0000000..4787755
--- /dev/null
+++ b/archive/src/render/archive/backends/metal/backend_metal.m
@@ -0,0 +1,285 @@
+#include <assert.h>
+// #define CEL_REND_BACKEND_METAL
+#if defined(CEL_REND_BACKEND_METAL)
+#include <stddef.h>
+#include "ral_types.h"
+#include "colours.h"
+#include <stdlib.h>
+#include "camera.h"
+#include "defines.h"
+#include "file.h"
+#include "log.h"
+#include "maths_types.h"
+#include "ral.h"
+
+#define GLFW_INCLUDE_NONE
+#define GLFW_EXPOSE_NATIVE_COCOA
+
+#include <GLFW/glfw3.h>
+#include <GLFW/glfw3native.h>
+
+#import <Foundation/Foundation.h>
+#import <Metal/Metal.h>
+#import <MetalKit/MetalKit.h>
+#import <QuartzCore/CAMetalLayer.h>
+#include "backend_metal.h"
+
+// --- Handy macros
+#define BUFFER_GET(h) (buffer_pool_get(&context.resource_pools->buffers, h))
+#define TEXTURE_GET(h) (texture_pool_get(&context.resource_pools->textures, h))
+
+typedef struct metal_context {
+ GLFWwindow* window;
+ NSWindow* metal_window;
+ arena pool_arena;
+
+ gpu_device* device;
+ gpu_swapchain* swapchain;
+ id<CAMetalDrawable> surface;
+
+ id<MTLCommandQueue> command_queue;
+ gpu_cmd_encoder main_command_buf;
+ gpu_backend_pools gpu_pools;
+ struct resource_pools* resource_pools;
+} metal_context;
+
+static metal_context context;
+
+struct GLFWwindow;
+
+bool gpu_backend_init(const char *window_name, struct GLFWwindow *window) {
+ INFO("loading Metal backend");
+
+ memset(&context, 0, sizeof(metal_context));
+ context.window = window;
+
+ size_t pool_buffer_size = 1024 * 1024;
+ context.pool_arena = arena_create(malloc(pool_buffer_size), pool_buffer_size);
+
+ backend_pools_init(&context.pool_arena, &context.gpu_pools);
+ context.resource_pools = malloc(sizeof(struct resource_pools));
+ resource_pools_init(&context.pool_arena, context.resource_pools);
+
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+
+ glfwMakeContextCurrent(window);
+ // FIXME: glfwSetFramebufferSizeCallback(ren->window, framebuffer_size_callback);
+
+ // get a NSWindow pointer from GLFWwindow
+ NSWindow *nswindow = glfwGetCocoaWindow(window);
+ context.metal_window = nswindow;
+
+ // const id<MTLCommandQueue> queue = [gpu newCommandQueue];
+ // CAMetalLayer *swapchain = [CAMetalLayer layer];
+ // swapchain.device = gpu;
+ // swapchain.opaque = YES;
+
+ // // set swapchain for the window
+ // nswindow.contentView.layer = swapchain;
+ // nswindow.contentView.wantsLayer = YES;
+
+ // MTLClearColor color = MTLClearColorMake(0.7, 0.1, 0.2, 1.0);
+
+ // // set all our state properties
+ // state->device = gpu;
+ // state->cmd_queue = queue;
+ // state->swapchain = swapchain;
+ // state->clear_color = color;
+
+ // NSError *err = 0x0; // TEMPORARY
+
+ // WARN("About to try loading metallib");
+ // id<MTLLibrary> defaultLibrary = [state->device newLibraryWithFile: @"build/gfx.metallib" error:&err];
+ // CASSERT(defaultLibrary);
+ // state->default_lib = defaultLibrary;
+ // if (!state->default_lib) {
+ // NSLog(@"Failed to load library");
+ // exit(0);
+ // }
+
+ // create_render_pipeline(state);
+
+ return true;
+}
+
+void gpu_backend_shutdown() {}
+
+bool gpu_device_create(gpu_device* out_device) {
+ TRACE("GPU Device creation");
+ const id<MTLDevice> gpu = MTLCreateSystemDefaultDevice();
+ out_device->id = gpu;
+ context.device = out_device;
+
+ const id<MTLCommandQueue> queue = [gpu newCommandQueue];
+ context.command_queue = queue;
+
+ return true;
+}
+void gpu_device_destroy() {}
+
+// --- Render Pipeline
+gpu_pipeline* gpu_graphics_pipeline_create(struct graphics_pipeline_desc description) {
+ TRACE("GPU Graphics Pipeline creation");
+ // Allocate
+ // gpu_pipeline_layout* layout =
+ // pipeline_layout_pool_alloc(&context.gpu_pools.pipeline_layouts, NULL);
+ gpu_pipeline* pipeline = pipeline_pool_alloc(&context.gpu_pools.pipelines, NULL);
+
+ WARN("About to try loading metallib");
+ assert(description.vs.is_combined_vert_frag);
+ // Ignore fragment shader data, as vert shader data contains both
+ NSError *err = 0x0; // TEMPORARY
+ NSString *myNSString = [NSString stringWithUTF8String:(char*)description.vs.filepath.buf];
+ id<MTLLibrary> default_library = [context.device->id newLibraryWithFile:myNSString error:&err];
+ assert(default_library);
+
+ // setup vertex and fragment shaders
+ id<MTLFunction> ren_vert = [default_library newFunctionWithName:@"basic_vertex"];
+ assert(ren_vert);
+ id<MTLFunction> ren_frag = [default_library newFunctionWithName:@"basic_fragment"];
+ assert(ren_frag);
+
+ // create pipeline descriptor
+ @autoreleasepool {
+ NSError *err = 0x0;
+ MTLRenderPipelineDescriptor *pld = [[MTLRenderPipelineDescriptor alloc] init];
+ NSString *pipeline_name = [NSString stringWithUTF8String: description.debug_name];
+ pld.label = pipeline_name;
+ pld.vertexFunction = ren_vert;
+ pld.fragmentFunction = ren_frag;
+ pld.colorAttachments[0].pixelFormat = MTLPixelFormatBGRA8Unorm;
+ pld.colorAttachments[0].blendingEnabled = YES;
+
+ MTLDepthStencilDescriptor *depthStencilDescriptor = [MTLDepthStencilDescriptor new];
+ depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionLess;
+ depthStencilDescriptor.depthWriteEnabled = YES;
+ pld.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;
+
+ id<MTLDepthStencilState> depth_descriptor = [context.device->id newDepthStencilStateWithDescriptor:depthStencilDescriptor];
+ // FIXME: state->depth_state = depth_descriptor;
+
+ id<MTLRenderPipelineState> pipeline_state = [context.device->id newRenderPipelineStateWithDescriptor:pld error:&err];
+ TRACE("created renderpipelinestate");
+ pipeline->pipeline_state = pipeline_state;
+
+ }
+
+ return pipeline;
+}
+void gpu_pipeline_destroy(gpu_pipeline* pipeline) {}
+
+// --- Renderpass
+gpu_renderpass* gpu_renderpass_create(const gpu_renderpass_desc* description) {
+ gpu_renderpass* renderpass = renderpass_pool_alloc(&context.gpu_pools.renderpasses, NULL);
+
+ // TODO: Configure based on description
+ // set up render pass
+ context.surface = [context.swapchain->swapchain nextDrawable];
+ MTLRenderPassDescriptor *renderPassDescriptor = [[MTLRenderPassDescriptor alloc] init];
+ MTLRenderPassColorAttachmentDescriptor *cd = renderPassDescriptor.colorAttachments[0];
+ [cd setTexture:context.surface.texture];
+ [cd setLoadAction:MTLLoadActionClear];
+ MTLClearColor clearColor = MTLClearColorMake(0.1, 0.1, 0.0, 1.0);
+ [cd setClearColor:clearColor];
+ [cd setStoreAction:MTLStoreActionStore];
+
+ renderpass->rpass_descriptor = renderPassDescriptor;
+
+ return renderpass;
+}
+
+void gpu_renderpass_destroy(gpu_renderpass* pass) {}
+
+// --- Swapchain
+bool gpu_swapchain_create(gpu_swapchain* out_swapchain) {
+ TRACE("GPU Swapchain creation");
+ CAMetalLayer *swapchain = [CAMetalLayer layer];
+ swapchain.device = context.device->id;
+ swapchain.opaque = YES;
+ out_swapchain->swapchain = swapchain;
+
+ // set swapchain for the window
+ context.metal_window.contentView.layer = swapchain;
+ context.metal_window.contentView.wantsLayer = YES;
+
+ context.swapchain = out_swapchain;
+ return true;
+}
+void gpu_swapchain_destroy(gpu_swapchain* swapchain) {}
+
+// --- Command buffer
+gpu_cmd_encoder gpu_cmd_encoder_create() {
+ id <MTLCommandBuffer> cmd_buffer = [context.command_queue commandBuffer];
+
+ return (gpu_cmd_encoder) {
+ .cmd_buffer = cmd_buffer
+ };
+}
+void gpu_cmd_encoder_destroy(gpu_cmd_encoder* encoder) {}
+void gpu_cmd_encoder_begin(gpu_cmd_encoder encoder) { /* no-op */ }
+void gpu_cmd_encoder_begin_render(gpu_cmd_encoder* encoder, gpu_renderpass* renderpass) {
+ DEBUG("Create Render Command Encoder");
+ id<MTLRenderCommandEncoder> render_encoder = [encoder->cmd_buffer renderCommandEncoderWithDescriptor:renderpass->rpass_descriptor];
+ encoder->render_encoder = render_encoder;
+ // [encoder setDepthStencilState:state->depth_state];
+}
+void gpu_cmd_encoder_end_render(gpu_cmd_encoder* encoder) {}
+void gpu_cmd_encoder_begin_compute() {}
+gpu_cmd_encoder* gpu_get_default_cmd_encoder() {
+ return &context.main_command_buf;
+}
+
+/** @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) {}
+
+void encode_buffer_copy(gpu_cmd_encoder* encoder, buffer_handle src, u64 src_offset,
+ buffer_handle dst, u64 dst_offset, u64 copy_size);
+void buffer_upload_bytes(buffer_handle gpu_buf, bytebuffer cpu_buf, u64 offset, u64 size);
+
+void copy_buffer_to_buffer_oneshot(buffer_handle src, u64 src_offset, buffer_handle dst,
+ u64 dst_offset, u64 copy_size);
+void copy_buffer_to_image_oneshot(buffer_handle src, texture_handle dst);
+
+void encode_bind_pipeline(gpu_cmd_encoder* encoder, pipeline_kind kind, gpu_pipeline* pipeline) {}
+void encode_bind_shader_data(gpu_cmd_encoder* encoder, u32 group, shader_data* data) {}
+void encode_set_default_settings(gpu_cmd_encoder* encoder) {
+ [encoder->render_encoder setCullMode:MTLCullModeBack];
+}
+void encode_set_vertex_buffer(gpu_cmd_encoder* encoder, buffer_handle buf) {
+ gpu_buffer* vertex_buf = BUFFER_GET(buf);
+ [encoder->render_encoder setVertexBuffer:vertex_buf->id offset:0 atIndex:0];
+}
+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) {}
+void encode_clear_buffer(gpu_cmd_encoder* encoder, buffer_handle buf) {}
+
+buffer_handle gpu_buffer_create(u64 size, gpu_buffer_type buf_type, gpu_buffer_flags flags,
+ const void* data) {
+ buffer_handle handle;
+ gpu_buffer* buffer = buffer_pool_alloc(&context.resource_pools->buffers, &handle);
+ buffer->size = size;
+
+ id<MTLBuffer> mtl_vert_buf = [context.device->id newBufferWithBytes:data
+ length: size
+ options:MTLResourceStorageModeShared];
+ return handle;
+}
+void gpu_buffer_destroy(buffer_handle buffer) {}
+void gpu_buffer_upload(const void* data) {}
+
+texture_handle gpu_texture_create(texture_desc desc, bool create_view, const void* data) {}
+void gpu_texture_destroy(texture_handle) {}
+void gpu_texture_upload(texture_handle texture, const void* data) {}
+
+bool gpu_backend_begin_frame() {
+ context.main_command_buf.cmd_buffer = [context.command_queue commandBuffer];
+ return true;
+ }
+void gpu_backend_end_frame() {}
+void gpu_temp_draw(size_t n_verts) {}
+
+#endif \ No newline at end of file
diff --git a/archive/src/render/archive/backends/opengl/backend_opengl.c b/archive/src/render/archive/backends/opengl/backend_opengl.c
new file mode 100644
index 0000000..43105e2
--- /dev/null
+++ b/archive/src/render/archive/backends/opengl/backend_opengl.c
@@ -0,0 +1,521 @@
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include "colours.h"
+#include "maths.h"
+#include "opengl_helpers.h"
+#include "ral_types.h"
+#define CEL_REND_BACKEND_OPENGL
+#if defined(CEL_REND_BACKEND_OPENGL)
+#include <assert.h>
+#include <stdlib.h>
+
+#include "backend_opengl.h"
+#include "defines.h"
+#include "file.h"
+#include "log.h"
+#include "maths_types.h"
+#include "ral.h"
+
+#include <glad/glad.h>
+#include <glfw3.h>
+
+typedef struct opengl_context {
+ GLFWwindow* window;
+ arena pool_arena;
+ gpu_cmd_encoder command_buffer;
+ gpu_backend_pools gpu_pools;
+ struct resource_pools* resource_pools;
+} opengl_context;
+
+static opengl_context context;
+
+struct GLFWwindow;
+
+bool gpu_backend_init(const char* window_name, struct GLFWwindow* window) {
+ INFO("loading OpenGL backend");
+
+ memset(&context, 0, sizeof(opengl_context));
+ context.window = window;
+
+ size_t pool_buffer_size = 1024 * 1024;
+ context.pool_arena = arena_create(malloc(pool_buffer_size), pool_buffer_size);
+
+ backend_pools_init(&context.pool_arena, &context.gpu_pools);
+ context.resource_pools = malloc(sizeof(struct resource_pools));
+ resource_pools_init(&context.pool_arena, context.resource_pools);
+
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
+
+ // glad: load all opengl function pointers
+ if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
+ ERROR("Failed to initialise GLAD \n");
+ return false;
+ }
+
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_CULL_FACE);
+
+ return true;
+}
+
+// --- Render Pipeline
+gpu_pipeline* gpu_graphics_pipeline_create(struct graphics_pipeline_desc description) {
+ gpu_pipeline* pipeline = pipeline_pool_alloc(&context.gpu_pools.pipelines, NULL);
+
+ // Create shader program
+ u32 shader_id = shader_create_separate(description.vs.filepath.buf, description.fs.filepath.buf);
+ pipeline->shader_id = shader_id;
+
+ // Vertex format
+ pipeline->vertex_desc = description.vertex_desc;
+
+ // Allocate uniform buffers if needed
+ u32 ubo_count = 0;
+ // printf("data layouts %d\n", description.data_layouts_count);
+ for (u32 layout_i = 0; layout_i < description.data_layouts_count; layout_i++) {
+ shader_data_layout sdl = description.data_layouts[layout_i].shader_data_get_layout(NULL);
+ TRACE("Got shader data layout %d's bindings! . found %d", layout_i, sdl.bindings_count);
+
+ for (u32 binding_j = 0; binding_j < sdl.bindings_count; binding_j++) {
+ u32 binding_id = binding_j;
+ assert(binding_id < MAX_PIPELINE_UNIFORM_BUFFERS);
+ shader_binding binding = sdl.bindings[binding_j];
+ if (binding.type == SHADER_BINDING_BYTES) {
+ static u32 s_binding_point = 0;
+ buffer_handle ubo_handle =
+ gpu_buffer_create(binding.data.bytes.size, CEL_BUFFER_UNIFORM, CEL_BUFFER_FLAG_GPU,
+ NULL); // no data right now
+ pipeline->uniform_bindings[ubo_count++] = ubo_handle;
+ gpu_buffer* ubo_buf = BUFFER_GET(ubo_handle);
+
+ i32 blockIndex = glGetUniformBlockIndex(pipeline->shader_id, binding.label);
+ printf("Block index for %s: %d", binding.label, blockIndex);
+ if (blockIndex < 0) {
+ WARN("Couldn't retrieve block index for uniform block '%s'", binding.label);
+ } else {
+ // DEBUG("Retrived block index %d for %s", blockIndex, binding.label);
+ }
+ u32 blocksize;
+ glGetActiveUniformBlockiv(pipeline->shader_id, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE,
+ &blocksize);
+ printf("\t with size %d bytes\n", blocksize);
+
+ glBindBufferBase(GL_UNIFORM_BUFFER, s_binding_point, ubo_buf->id.ubo);
+ if (blockIndex != GL_INVALID_INDEX) {
+ glUniformBlockBinding(pipeline->shader_id, blockIndex, s_binding_point);
+ }
+ ubo_buf->ubo_binding_point = s_binding_point++;
+ ubo_buf->name = binding.label;
+ assert(s_binding_point < GL_MAX_UNIFORM_BUFFER_BINDINGS);
+ }
+ }
+ }
+ pipeline->uniform_count = ubo_count;
+
+ pipeline->renderpass = description.renderpass;
+ pipeline->wireframe = description.wireframe;
+
+ return pipeline;
+}
+void gpu_pipeline_destroy(gpu_pipeline* pipeline) {}
+
+// --- Renderpass
+gpu_renderpass* gpu_renderpass_create(const gpu_renderpass_desc* description) {
+ gpu_renderpass* renderpass = renderpass_pool_alloc(&context.gpu_pools.renderpasses, NULL);
+ memcpy(&renderpass->description, description, sizeof(gpu_renderpass_desc));
+ bool default_framebuffer = description->default_framebuffer;
+
+ if (!default_framebuffer) {
+ GLuint gl_fbo_id;
+ glGenFramebuffers(1, &gl_fbo_id);
+ renderpass->fbo = gl_fbo_id;
+ } else {
+ renderpass->fbo = OPENGL_DEFAULT_FRAMEBUFFER;
+ assert(!description->has_color_target);
+ assert(!description->has_depth_stencil);
+ }
+ glBindFramebuffer(GL_FRAMEBUFFER, renderpass->fbo);
+
+ if (description->has_color_target && !default_framebuffer) {
+ gpu_texture* colour_attachment = TEXTURE_GET(description->color_target);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ colour_attachment->id, 0);
+ }
+ if (description->has_depth_stencil && !default_framebuffer) {
+ gpu_texture* depth_attachment = TEXTURE_GET(description->depth_stencil);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_attachment->id,
+ 0);
+ }
+
+ if (description->has_depth_stencil && !description->has_color_target) {
+ glDrawBuffer(GL_NONE);
+ glReadBuffer(GL_NONE);
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER, 0); // reset to default framebuffer
+
+ return renderpass;
+}
+void gpu_renderpass_destroy(gpu_renderpass* pass) { glDeleteFramebuffers(1, &pass->fbo); }
+
+// --- Command buffer
+gpu_cmd_encoder gpu_cmd_encoder_create() {
+ gpu_cmd_encoder encoder = { 0 };
+ return encoder;
+}
+void gpu_cmd_encoder_destroy(gpu_cmd_encoder* encoder) {}
+void gpu_cmd_encoder_begin(gpu_cmd_encoder encoder) {}
+void gpu_cmd_encoder_begin_render(gpu_cmd_encoder* encoder, gpu_renderpass* renderpass) {
+ glBindFramebuffer(GL_FRAMEBUFFER, renderpass->fbo);
+ rgba clear_colour = STONE_800;
+ glClearColor(clear_colour.r, clear_colour.g, clear_colour.b, 1.0f);
+ if (renderpass->description.has_depth_stencil) {
+ glClear(GL_DEPTH_BUFFER_BIT);
+ } else {
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ }
+}
+void gpu_cmd_encoder_end_render(gpu_cmd_encoder* encoder) { glBindFramebuffer(GL_FRAMEBUFFER, 0); }
+void gpu_cmd_encoder_begin_compute() {}
+gpu_cmd_encoder* gpu_get_default_cmd_encoder() { return &context.command_buffer; }
+
+/** @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) {}
+
+// --- Data copy commands
+/** @brief Copy data from one buffer to another */
+void encode_buffer_copy(gpu_cmd_encoder* encoder, buffer_handle src, u64 src_offset,
+ buffer_handle dst, u64 dst_offset, u64 copy_size) {}
+/** @brief Upload CPU-side data as array of bytes to a GPU buffer */
+void buffer_upload_bytes(buffer_handle gpu_buf, bytebuffer cpu_buf, u64 offset, u64 size) {
+ // TODO: finish implementing this
+ gpu_buffer* buf = BUFFER_GET(gpu_buf);
+}
+
+/** @brief Copy data from buffer to buffer using a one time submit command buffer and a wait */
+void copy_buffer_to_buffer_oneshot(buffer_handle src, u64 src_offset, buffer_handle dst,
+ u64 dst_offset, u64 copy_size) {}
+/** @brief Copy data from buffer to an image using a one time submit command buffer */
+void copy_buffer_to_image_oneshot(buffer_handle src, texture_handle dst) {}
+
+// --- Render commands
+void encode_bind_pipeline(gpu_cmd_encoder* encoder, pipeline_kind kind, gpu_pipeline* pipeline) {
+ encoder->pipeline = pipeline;
+
+ if (pipeline->wireframe) {
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ } else {
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ }
+
+ // In OpenGL binding a pipeline is more or less equivalent to just setting the shader
+ glUseProgram(pipeline->shader_id);
+}
+void encode_bind_shader_data(gpu_cmd_encoder* encoder, u32 group, shader_data* data) {
+ shader_data_layout sdl = data->shader_data_get_layout(data->data);
+ // printf("Binding %s shader data\n", sdl.name);
+
+ for (u32 i = 0; i < sdl.bindings_count; i++) {
+ shader_binding binding = sdl.bindings[i];
+ /* print_shader_binding(binding); */
+
+ if (binding.type == SHADER_BINDING_BYTES) {
+ buffer_handle b;
+ gpu_buffer* ubo_buf;
+ bool found = false;
+ for (u32 i = 0; i < encoder->pipeline->uniform_count; i++) {
+ b = encoder->pipeline->uniform_bindings[i];
+ ubo_buf = BUFFER_GET(b);
+ assert(ubo_buf->name != NULL);
+ if (strcmp(ubo_buf->name, binding.label) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ ERROR("Couldnt find uniform buffer object!!");
+ }
+
+ i32 blockIndex = glGetUniformBlockIndex(encoder->pipeline->shader_id, binding.label);
+ if (blockIndex < 0) {
+ WARN("Couldn't retrieve block index for uniform block '%s'", binding.label);
+ } else {
+ // DEBUG("Retrived block index %d for %s", blockIndex, binding.label);
+ }
+
+ glBindBuffer(GL_UNIFORM_BUFFER, ubo_buf->id.ubo);
+ glBufferSubData(GL_UNIFORM_BUFFER, 0, ubo_buf->size, binding.data.bytes.data);
+
+ } else if (binding.type == SHADER_BINDING_TEXTURE) {
+ gpu_texture* tex = TEXTURE_GET(binding.data.texture.handle);
+ GLint tex_slot = glGetUniformLocation(encoder->pipeline->shader_id, binding.label);
+ // printf("%d slot \n", tex_slot);
+ if (tex_slot == GL_INVALID_VALUE || tex_slot < 0) {
+ WARN("Invalid binding label for texture %s - couldn't fetch texture slot uniform",
+ binding.label);
+ }
+ glUniform1i(tex_slot, i);
+ glActiveTexture(GL_TEXTURE0 + i);
+ glBindTexture(GL_TEXTURE_2D, tex->id);
+ }
+ }
+}
+void encode_set_default_settings(gpu_cmd_encoder* encoder) {}
+void encode_set_vertex_buffer(gpu_cmd_encoder* encoder, buffer_handle buf) {
+ gpu_buffer* buffer = BUFFER_GET(buf);
+ if (buffer->vao == 0) { // if no VAO for this vertex buffer, create it
+ INFO("Setting up VAO");
+ buffer->vao = opengl_bindcreate_vao(buffer, encoder->pipeline->vertex_desc);
+ }
+ glBindVertexArray(buffer->vao);
+}
+void encode_set_index_buffer(gpu_cmd_encoder* encoder, buffer_handle buf) {
+ gpu_buffer* buffer = BUFFER_GET(buf);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer->id.ibo);
+}
+void encode_draw(gpu_cmd_encoder* encoder, u64 count) { glDrawArrays(GL_TRIANGLES, 0, count); }
+void encode_draw_indexed(gpu_cmd_encoder* encoder, u64 index_count) {
+ /* printf("Draw %ld indices\n", index_count); */
+ glDrawElements(GL_TRIANGLES, index_count, GL_UNSIGNED_INT, 0);
+}
+void encode_clear_buffer(gpu_cmd_encoder* encoder, buffer_handle buf) {}
+
+// --- Buffers
+buffer_handle gpu_buffer_create(u64 size, gpu_buffer_type buf_type, gpu_buffer_flags flags,
+ const void* data) {
+ // "allocating" the cpu-side buffer struct
+ buffer_handle handle;
+ gpu_buffer* buffer = buffer_pool_alloc(&context.resource_pools->buffers, &handle);
+ buffer->size = size;
+ buffer->vao = 0; // When we create a new buffer, there will be no VAO.
+
+ // Opengl buffer
+ GLuint gl_buffer_id;
+ glGenBuffers(1, &gl_buffer_id);
+
+ GLenum gl_buf_type;
+ GLenum gl_buf_usage = GL_STATIC_DRAW;
+
+ switch (buf_type) {
+ case CEL_BUFFER_UNIFORM:
+ DEBUG("Creating Uniform buffer");
+ gl_buf_type = GL_UNIFORM_BUFFER;
+ /* gl_buf_usage = GL_DYNAMIC_DRAW; */
+ buffer->id.ubo = gl_buffer_id;
+ break;
+ case CEL_BUFFER_DEFAULT:
+ case CEL_BUFFER_VERTEX:
+ DEBUG("Creating Vertex buffer");
+ gl_buf_type = GL_ARRAY_BUFFER;
+ buffer->id.vbo = gl_buffer_id;
+ break;
+ case CEL_BUFFER_INDEX:
+ DEBUG("Creating Index buffer");
+ gl_buf_type = GL_ELEMENT_ARRAY_BUFFER;
+ buffer->id.ibo = gl_buffer_id;
+ break;
+ default:
+ WARN("Unimplemented gpu_buffer_type provided %s", buffer_type_names[buf_type]);
+ break;
+ }
+ // bind buffer
+ glBindBuffer(gl_buf_type, gl_buffer_id);
+
+ if (data) {
+ TRACE("Upload data (%d bytes) as part of buffer creation", size);
+ glBufferData(gl_buf_type, buffer->size, data, gl_buf_usage);
+ } else {
+ TRACE("Allocating but not uploading (%d bytes)", size);
+ glBufferData(gl_buf_type, buffer->size, NULL, gl_buf_usage);
+ }
+
+ glBindBuffer(gl_buf_type, 0);
+
+ return handle;
+}
+
+texture_handle gpu_texture_create(texture_desc desc, bool create_view, const void* data) {
+ // "allocating" the cpu-side struct
+ texture_handle handle;
+ gpu_texture* texture = texture_pool_alloc(&context.resource_pools->textures, &handle);
+ DEBUG("Allocated texture with handle %d", handle.raw);
+
+ GLuint gl_texture_id;
+ glGenTextures(1, &gl_texture_id);
+ texture->id = gl_texture_id;
+
+ glBindTexture(GL_TEXTURE_2D, gl_texture_id);
+
+ GLint internal_format =
+ desc.format == CEL_TEXTURE_FORMAT_DEPTH_DEFAULT ? GL_DEPTH_COMPONENT : GL_RGB;
+ GLenum format = desc.format == CEL_TEXTURE_FORMAT_DEPTH_DEFAULT ? GL_DEPTH_COMPONENT : GL_RGBA;
+ GLenum data_type = desc.format == CEL_TEXTURE_FORMAT_DEPTH_DEFAULT ? GL_FLOAT : GL_UNSIGNED_BYTE;
+
+ if (desc.format == CEL_TEXTURE_FORMAT_DEPTH_DEFAULT) {
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+ } else {
+ // 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);
+ }
+
+ if (data) {
+ glTexImage2D(GL_TEXTURE_2D, 0, internal_format, desc.extents.x, desc.extents.y, 0, format,
+ data_type, data);
+ glGenerateMipmap(GL_TEXTURE_2D);
+ } else {
+ WARN("No image data provided");
+ glTexImage2D(GL_TEXTURE_2D, 0, internal_format, desc.extents.x, desc.extents.y, 0, format,
+ data_type, NULL);
+ }
+
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ return handle;
+}
+
+void gpu_texture_destroy(texture_handle) {}
+void gpu_texture_upload(texture_handle texture, const void* data) {}
+
+// --- Vertex formats
+bytebuffer vertices_as_bytebuffer(arena* a, vertex_format format, vertex_darray* vertices) {}
+
+// --- TEMP
+bool gpu_backend_begin_frame() {
+ glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ return true;
+}
+void gpu_backend_end_frame() {
+ // TODO: Reset all bindings
+ glfwSwapBuffers(context.window);
+}
+void gpu_temp_draw(size_t n_verts) {}
+
+u32 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);
+
+ return shader_prog;
+}
+
+inline void uniform_vec3f(u32 program_id, const char* uniform_name, vec3* value) {
+ glUniform3fv(glGetUniformLocation(program_id, uniform_name), 1, &value->x);
+}
+inline void uniform_f32(u32 program_id, const char* uniform_name, f32 value) {
+ glUniform1f(glGetUniformLocation(program_id, uniform_name), value);
+}
+inline void uniform_i32(u32 program_id, const char* uniform_name, i32 value) {
+ glUniform1i(glGetUniformLocation(program_id, uniform_name), value);
+}
+inline void uniform_mat4f(u32 program_id, const char* uniform_name, mat4* value) {
+ glUniformMatrix4fv(glGetUniformLocation(program_id, uniform_name), 1, GL_FALSE, value->data);
+}
+
+// void clear_screen(vec3 colour) {
+// glClearColor(colour.x, colour.y, colour.z, 1.0f);
+// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+// }
+
+// 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;
+// }
+// }
+#endif
diff --git a/archive/src/render/archive/backends/opengl/backend_opengl.h b/archive/src/render/archive/backends/opengl/backend_opengl.h
new file mode 100644
index 0000000..14b44af
--- /dev/null
+++ b/archive/src/render/archive/backends/opengl/backend_opengl.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#ifdef CEL_REND_BACKEND_OPENGL
+
+#include "defines.h"
+#include "maths_types.h"
+#include "ral.h"
+#include "ral_types.h"
+
+#define MAX_PIPELINE_UNIFORM_BUFFERS 32
+
+#define OPENGL_DEFAULT_FRAMEBUFFER 0
+
+typedef struct gpu_swapchain {
+ u32x2 dimensions;
+} gpu_swapchain;
+typedef struct gpu_device {
+} gpu_device;
+typedef struct gpu_pipeline_layout {
+ void* pad
+} gpu_pipeline_layout;
+typedef struct gpu_pipeline {
+ u32 shader_id;
+ gpu_renderpass* renderpass;
+ vertex_description vertex_desc;
+ buffer_handle uniform_bindings[MAX_PIPELINE_UNIFORM_BUFFERS];
+ u32 uniform_count;
+ bool wireframe;
+} gpu_pipeline;
+typedef struct gpu_renderpass {
+ u32 fbo;
+ gpu_renderpass_desc description;
+} gpu_renderpass;
+typedef struct gpu_cmd_encoder {
+ gpu_pipeline* pipeline;
+} gpu_cmd_encoder; // Recording
+typedef struct gpu_cmd_buffer {
+ void* pad;
+} gpu_cmd_buffer; // Ready for submission
+
+typedef struct gpu_buffer {
+ union {
+ u32 vbo;
+ u32 ibo;
+ u32 ubo;
+ } id;
+ union {
+ u32 vao;
+ u32 ubo_binding_point
+ }; // Optional
+ char* name;
+ u64 size;
+} gpu_buffer;
+typedef struct gpu_texture {
+ u32 id;
+ void* pad;
+} gpu_texture;
+
+typedef struct opengl_support {
+} opengl_support;
+
+u32 shader_create_separate(const char* vert_shader, const char* frag_shader);
+
+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);
+#endif
diff --git a/archive/src/render/archive/backends/vulkan/README.md b/archive/src/render/archive/backends/vulkan/README.md
new file mode 100644
index 0000000..220ed64
--- /dev/null
+++ b/archive/src/render/archive/backends/vulkan/README.md
@@ -0,0 +1 @@
+# Vulkan Backend Overview \ No newline at end of file
diff --git a/archive/src/render/archive/backends/vulkan/backend_vulkan.c b/archive/src/render/archive/backends/vulkan/backend_vulkan.c
new file mode 100644
index 0000000..8801230
--- /dev/null
+++ b/archive/src/render/archive/backends/vulkan/backend_vulkan.c
@@ -0,0 +1,1705 @@
+#include "defines.h"
+#if defined(CEL_REND_BACKEND_VULKAN)
+
+#define GLFW_INCLUDE_VULKAN
+#include <glfw3.h>
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <vulkan/vk_platform.h>
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_core.h>
+
+#include "backend_vulkan.h"
+#include "buf.h"
+#include "darray.h"
+#include "maths_types.h"
+#include "mem.h"
+#include "ral_types.h"
+#include "str.h"
+#include "vulkan_helpers.h"
+
+#include "file.h"
+#include "log.h"
+#include "ral.h"
+#include "utils.h"
+
+// TEMP
+#define SCREEN_WIDTH 1000
+#define SCREEN_HEIGHT 1000
+#define VULKAN_QUEUES_COUNT 2
+#define MAX_DESCRIPTOR_SETS 10
+
+const char* queue_names[VULKAN_QUEUES_COUNT] = { "GRAPHICS", "TRANSFER" };
+
+KITC_DECL_TYPED_ARRAY(VkDescriptorSet)
+
+typedef struct vulkan_context {
+ VkInstance instance;
+ VkAllocationCallbacks* allocator;
+ VkSurfaceKHR surface;
+ vulkan_swapchain_support_info swapchain_support;
+
+ arena temp_arena;
+ arena pool_arena;
+ gpu_device* device;
+ gpu_swapchain* swapchain;
+ u32 framebuffer_count;
+ VkFramebuffer*
+ swapchain_framebuffers; // TODO: Move this data into the swapchain as its own struct
+
+ u32 current_img_index;
+ u32 current_frame; // super important
+ gpu_cmd_encoder main_cmd_bufs[MAX_FRAMES_IN_FLIGHT];
+ VkSemaphore image_available_semaphores[MAX_FRAMES_IN_FLIGHT];
+ VkSemaphore render_finished_semaphores[MAX_FRAMES_IN_FLIGHT];
+ VkFence in_flight_fences[MAX_FRAMES_IN_FLIGHT];
+
+ // HACK
+ VkRenderPass main_renderpass;
+
+ u32 screen_width;
+ u32 screen_height;
+ bool is_resizing;
+ GLFWwindow* window;
+
+ // Storage
+ gpu_buffer buffers[1024];
+ size_t buffer_count;
+ VkDescriptorSet_darray* free_set_queue;
+ struct resource_pools* resource_pools;
+ gpu_backend_pools gpu_pools;
+
+ VkDebugUtilsMessengerEXT vk_debugger;
+} vulkan_context;
+
+static vulkan_context context;
+
+// --- Function forward declarations
+
+void backend_pools_init(arena* a, gpu_backend_pools* backend_pools);
+
+/** @brief Enumerates and selects the most appropriate graphics device */
+bool select_physical_device(gpu_device* out_device);
+
+bool is_physical_device_suitable(VkPhysicalDevice device);
+
+queue_family_indices find_queue_families(VkPhysicalDevice device);
+
+bool create_logical_device(gpu_device* out_device);
+void create_swapchain_framebuffers();
+void create_sync_objects();
+void create_descriptor_pools();
+size_t vertex_attrib_size(vertex_attrib_type attr);
+
+VkShaderModule create_shader_module(str8 spirv);
+
+/** @brief Helper function for creating array of all extensions we want */
+cstr_darray* get_all_extensions();
+
+VkImage vulkan_image_create(u32x2 dimensions, VkImageType image_type, VkFormat format,
+ VkImageUsageFlags usage);
+void vulkan_transition_image_layout(gpu_texture* texture, VkFormat format, VkImageLayout old_layout,
+ VkImageLayout new_layout);
+
+// --- Handy macros
+#define BUFFER_GET(h) (buffer_pool_get(&context.resource_pools->buffers, h))
+#define TEXTURE_GET(h) (texture_pool_get(&context.resource_pools->textures, h))
+
+bool gpu_backend_init(const char* window_name, GLFWwindow* window) {
+ memset(&context, 0, sizeof(vulkan_context));
+ context.allocator = 0; // TODO: use an allocator
+ context.screen_width = SCREEN_WIDTH;
+ context.screen_height = SCREEN_HEIGHT;
+ context.window = window;
+ context.current_img_index = 0;
+ context.current_frame = 0;
+ context.free_set_queue = VkDescriptorSet_darray_new(100);
+
+ // Create an allocator
+ size_t temp_arena_size = 1024 * 1024;
+ context.temp_arena = arena_create(malloc(temp_arena_size), temp_arena_size);
+
+ size_t pool_buffer_size = 1024 * 1024;
+ context.pool_arena = arena_create(malloc(pool_buffer_size), pool_buffer_size);
+
+ backend_pools_init(&context.pool_arena, &context.gpu_pools);
+
+ // Setup Vulkan instance
+ VkApplicationInfo app_info = { VK_STRUCTURE_TYPE_APPLICATION_INFO };
+ app_info.apiVersion = VK_API_VERSION_1_2;
+ app_info.pApplicationName = 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;
+
+ // Extensions
+ cstr_darray* required_extensions = cstr_darray_new(2);
+ // cstr_darray_push(required_extensions, VK_KHR_SURFACE_EXTENSION_NAME);
+
+ uint32_t count;
+ const char** extensions = glfwGetRequiredInstanceExtensions(&count);
+ for (u32 i = 0; i < count; i++) {
+ cstr_darray_push(required_extensions, extensions[i]);
+ }
+
+ 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]);
+ }
+
+ create_info.enabledExtensionCount = cstr_darray_len(required_extensions);
+ create_info.ppEnabledExtensionNames = required_extensions->data;
+
+ // TODO: Validation layers
+ create_info.enabledLayerCount = 0;
+ create_info.ppEnabledLayerNames = NULL;
+
+ 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* available_layers =
+ arena_alloc(&context.temp_arena, n_available_layers * sizeof(VkLayerProperties));
+ VK_CHECK(vkEnumerateInstanceLayerProperties(&n_available_layers, available_layers));
+
+ 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[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;
+
+ VkResult result = vkCreateInstance(&create_info, NULL, &context.instance);
+ if (result != VK_SUCCESS) {
+ ERROR("vkCreateInstance failed with result: %u", result);
+ return false;
+ }
+ TRACE("Vulkan Instance created");
+
+ 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");
+
+ // Surface creation
+ VkSurfaceKHR surface;
+ VK_CHECK(glfwCreateWindowSurface(context.instance, window, NULL, &surface));
+ context.surface = surface;
+ TRACE("Vulkan Surface created");
+
+ return true;
+}
+
+void gpu_backend_shutdown() {
+ gpu_swapchain_destroy(context.swapchain);
+
+ vkDestroySurfaceKHR(context.instance, context.surface, context.allocator);
+ vkDestroyInstance(context.instance, context.allocator);
+ arena_free_storage(&context.temp_arena);
+}
+
+bool gpu_device_create(gpu_device* out_device) {
+ // First things first store this poitner from the renderer
+ context.device = out_device;
+
+ arena_save savept = arena_savepoint(&context.temp_arena);
+ // Physical device
+ if (!select_physical_device(out_device)) {
+ return false;
+ }
+ TRACE("Physical device selected");
+
+ // Logical device & Queues
+ create_logical_device(out_device);
+
+ // Create the command pool
+ VkCommandPoolCreateInfo pool_create_info = { VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO };
+ pool_create_info.queueFamilyIndex = out_device->queue_family_indicies.graphics_family_index;
+ pool_create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+ vkCreateCommandPool(out_device->logical_device, &pool_create_info, context.allocator,
+ &out_device->pool);
+ TRACE("Command Pool created");
+
+ // Synchronisation objects
+ create_sync_objects();
+ TRACE("Synchronisation primitives created");
+
+ arena_rewind(savept); // Free any temp data
+ return true;
+}
+
+bool gpu_swapchain_create(gpu_swapchain* out_swapchain) {
+ context.swapchain = out_swapchain;
+
+ out_swapchain->swapchain_arena = arena_create(malloc(1024), 1024);
+
+ vulkan_device_query_swapchain_support(context.device->physical_device, context.surface,
+ &context.swapchain_support);
+ vulkan_swapchain_support_info swapchain_support = context.swapchain_support;
+
+ // TODO: custom swapchain extents VkExtent2D swapchain_extent = { width, height };
+
+ VkSurfaceFormatKHR image_format = choose_swapchain_format(&swapchain_support);
+ out_swapchain->image_format = image_format;
+ VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; // guaranteed to be implemented
+ out_swapchain->present_mode = present_mode;
+
+ u32 image_count = swapchain_support.capabilities.minImageCount + 1;
+ out_swapchain->image_count = image_count;
+
+ 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 = image_format.format;
+ swapchain_create_info.imageColorSpace = image_format.colorSpace;
+ swapchain_create_info.imageExtent = swapchain_support.capabilities.currentExtent;
+ 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 = NULL;
+
+ swapchain_create_info.preTransform = 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 = VK_NULL_HANDLE;
+
+ out_swapchain->extent = swapchain_support.capabilities.currentExtent;
+
+ VK_CHECK(vkCreateSwapchainKHR(context.device->logical_device, &swapchain_create_info,
+ context.allocator, &out_swapchain->handle));
+ TRACE("Vulkan Swapchain created");
+
+ // Retrieve Images
+ // out_swapchain->images =
+ // arena_alloc(&out_swapchain->swapchain_arena, image_count * sizeof(VkImage));
+ out_swapchain->images = malloc(image_count * sizeof(VkImage));
+ VK_CHECK(vkGetSwapchainImagesKHR(context.device->logical_device, out_swapchain->handle,
+ &image_count, out_swapchain->images));
+
+ // Create ImageViews
+ // TODO: Move this to a separate function
+ out_swapchain->image_views = malloc(image_count * sizeof(VkImageView));
+ // arena_alloc(&out_swapchain->swapchain_arena, image_count * sizeof(VkImageView));
+ for (u32 i = 0; i < image_count; i++) {
+ VkImageViewCreateInfo view_create_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
+ view_create_info.image = out_swapchain->images[i];
+ view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ view_create_info.format = image_format.format;
+ view_create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+ view_create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+ view_create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+ view_create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+ view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ 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,
+ &out_swapchain->image_views[i]);
+ }
+
+ return true;
+}
+
+void gpu_swapchain_destroy(gpu_swapchain* swapchain) {
+ // Destroy Framebuffers
+ DEBUG("Image count %d", swapchain->image_count);
+ for (u32 i = 0; i < swapchain->image_count; i++) {
+ DEBUG("Framebuffer handle %d", context.swapchain_framebuffers[i]);
+ vkDestroyFramebuffer(context.device->logical_device, context.swapchain_framebuffers[i],
+ context.allocator);
+ }
+ for (u32 i = 0; i < swapchain->image_count; i++) {
+ vkDestroyImageView(context.device->logical_device, swapchain->image_views[i],
+ context.allocator);
+ }
+ arena_free_all(&swapchain->swapchain_arena);
+ vkDestroySwapchainKHR(context.device->logical_device, swapchain->handle, context.allocator);
+ TRACE("Vulkan Swapchain destroyed");
+}
+
+static void recreate_swapchain(gpu_swapchain* swapchain) {
+ int width = 0, height = 0;
+ glfwGetFramebufferSize(context.window, &width, &height);
+ while (width == 0 || height == 0) {
+ glfwGetFramebufferSize(context.window, &width, &height);
+ glfwWaitEvents();
+ }
+ DEBUG("Recreating swapchain...");
+ vkDeviceWaitIdle(context.device->logical_device);
+
+ gpu_swapchain_destroy(swapchain);
+ gpu_swapchain_create(swapchain);
+ create_swapchain_framebuffers();
+}
+
+VkFormat format_from_vertex_attr(vertex_attrib_type attr) {
+ switch (attr) {
+ case ATTR_F32:
+ return VK_FORMAT_R32_SFLOAT;
+ case ATTR_U32:
+ return VK_FORMAT_R32_UINT;
+ case ATTR_I32:
+ return VK_FORMAT_R32_SINT;
+ case ATTR_F32x2:
+ return VK_FORMAT_R32G32_SFLOAT;
+ case ATTR_U32x2:
+ return VK_FORMAT_R32G32_UINT;
+ case ATTR_I32x2:
+ return VK_FORMAT_R32G32_UINT;
+ case ATTR_F32x3:
+ return VK_FORMAT_R32G32B32_SFLOAT;
+ case ATTR_U32x3:
+ return VK_FORMAT_R32G32B32_UINT;
+ case ATTR_I32x3:
+ return VK_FORMAT_R32G32B32_SINT;
+ case ATTR_F32x4:
+ return VK_FORMAT_R32G32B32A32_SFLOAT;
+ case ATTR_U32x4:
+ return VK_FORMAT_R32G32B32A32_UINT;
+ case ATTR_I32x4:
+ return VK_FORMAT_R32G32B32A32_SINT;
+ }
+}
+
+gpu_pipeline* gpu_graphics_pipeline_create(struct graphics_pipeline_desc description) {
+ TRACE("GPU Graphics Pipeline creation");
+ // Allocate
+ gpu_pipeline_layout* layout =
+ pipeline_layout_pool_alloc(&context.gpu_pools.pipeline_layouts, NULL);
+ gpu_pipeline* pipeline = pipeline_pool_alloc(&context.gpu_pools.pipelines, NULL);
+
+ // Shaders
+ printf("Vertex shader: %s\n", description.vs.filepath.buf);
+ printf("Fragment shader: %s\n", description.fs.filepath.buf);
+ VkShaderModule vertex_shader = create_shader_module(description.vs.code);
+ VkShaderModule fragment_shader = create_shader_module(description.fs.code);
+
+ // Vertex
+ VkPipelineShaderStageCreateInfo vert_shader_stage_info = {
+ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO
+ };
+ vert_shader_stage_info.stage = VK_SHADER_STAGE_VERTEX_BIT;
+ vert_shader_stage_info.module = vertex_shader;
+ vert_shader_stage_info.pName = "main";
+ // Fragment
+ VkPipelineShaderStageCreateInfo frag_shader_stage_info = {
+ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO
+ };
+ frag_shader_stage_info.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ frag_shader_stage_info.module = fragment_shader;
+ frag_shader_stage_info.pName = "main";
+
+ VkPipelineShaderStageCreateInfo shader_stages[2] = { vert_shader_stage_info,
+ frag_shader_stage_info };
+
+ // Attributes
+ u32 attr_count = description.vertex_desc.attributes_count;
+ printf("N attributes %d\n", attr_count);
+ VkVertexInputAttributeDescription attribute_descs[attr_count];
+ memset(attribute_descs, 0, attr_count * sizeof(VkVertexInputAttributeDescription));
+ u32 offset = 0;
+ for (u32 i = 0; i < description.vertex_desc.attributes_count; i++) {
+ attribute_descs[i].binding = 0;
+ attribute_descs[i].location = i;
+ attribute_descs[i].format = format_from_vertex_attr(description.vertex_desc.attributes[i]);
+ attribute_descs[i].offset = offset;
+ size_t this_offset = vertex_attrib_size(description.vertex_desc.attributes[i]);
+ printf("offset total %d this attr %ld\n", offset, this_offset);
+ printf("sizeof vertex %ld\n", sizeof(vertex));
+ offset += this_offset;
+ }
+
+ // Vertex input
+ // TODO: Generate this from descroiption now
+ VkVertexInputBindingDescription binding_desc;
+ binding_desc.binding = 0;
+ binding_desc.stride = description.vertex_desc.use_full_vertex_size
+ ? sizeof(vertex)
+ : description.vertex_desc.stride;
+ 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 =
+ attr_count; // description.vertex_desc.attributes_count;
+ vertex_input_info.pVertexAttributeDescriptions = attribute_descs;
+
+ // Input Assembly
+ 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;
+
+ // Viewport
+ VkViewport viewport = { .x = 0,
+ .y = 0,
+ .width = (f32)context.swapchain->extent.width,
+ .height = (f32)context.swapchain->extent.height,
+ .minDepth = 0.0,
+ .maxDepth = 1.0 };
+ VkRect2D scissor = { .offset = { .x = 0, .y = 0 }, .extent = context.swapchain->extent };
+ 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 =
+ description.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.frontFace = VK_FRONT_FACE_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;
+
+ // TODO: Depth and stencil testing
+ // VkPipelineDepthStencilStateCreateInfo depth_stencil = {
+ // VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO
+ // };
+ // depth_stencil.depthTestEnable = description.depth_test ? VK_TRUE : VK_FALSE;
+ // depth_stencil.depthWriteEnable = description.depth_test ? VK_TRUE : VK_FALSE;
+ // depth_stencil.depthCompareOp = VK_COMPARE_OP_LESS;
+ // depth_stencil.depthBoundsTestEnable = VK_FALSE;
+ // depth_stencil.stencilTestEnable = VK_FALSE;
+ // depth_stencil.pNext = 0;
+
+ // Blending
+ VkPipelineColorBlendAttachmentState color_blend_attachment_state;
+ color_blend_attachment_state.blendEnable = VK_FALSE;
+ 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;
+
+// Dynamic state
+#define DYNAMIC_STATE_COUNT 2
+ VkDynamicState dynamic_states[DYNAMIC_STATE_COUNT] = {
+ VK_DYNAMIC_STATE_VIEWPORT,
+ VK_DYNAMIC_STATE_SCISSOR,
+ };
+
+ VkPipelineDynamicStateCreateInfo dynamic_state = {
+ VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO
+ };
+ dynamic_state.dynamicStateCount = DYNAMIC_STATE_COUNT;
+ dynamic_state.pDynamicStates = dynamic_states;
+
+ // Descriptor Set layouts
+
+ VkDescriptorSetLayout* desc_set_layouts =
+ malloc(description.data_layouts_count * sizeof(VkDescriptorSetLayout));
+ pipeline->desc_set_layouts = desc_set_layouts;
+ pipeline->desc_set_layouts_count = description.data_layouts_count;
+ if (description.data_layouts_count > 0) {
+ pipeline->uniform_pointers =
+ malloc(description.data_layouts_count * sizeof(desc_set_uniform_buffer));
+ } else {
+ pipeline->uniform_pointers = NULL;
+ }
+
+ // assert(description.data_layouts_count == 1);
+ printf("data layouts %d\n", description.data_layouts_count);
+ for (u32 layout_i = 0; layout_i < description.data_layouts_count; layout_i++) {
+ shader_data_layout sdl = description.data_layouts[layout_i].shader_data_get_layout(NULL);
+ TRACE("Got shader data layout %d's bindings! . found %d", layout_i, sdl.bindings_count);
+
+ VkDescriptorSetLayoutBinding desc_set_bindings[sdl.bindings_count];
+
+ // Bindings
+ assert(sdl.bindings_count == 2);
+ for (u32 binding_j = 0; binding_j < sdl.bindings_count; binding_j++) {
+ desc_set_bindings[binding_j].binding = binding_j;
+ desc_set_bindings[binding_j].descriptorCount = 1;
+ switch (sdl.bindings[binding_j].type) {
+ case SHADER_BINDING_BUFFER:
+ case SHADER_BINDING_BYTES:
+ desc_set_bindings[binding_j].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ desc_set_bindings[binding_j].stageFlags =
+ VK_SHADER_STAGE_VERTEX_BIT; // FIXME: dont hardcode
+
+ u64 buffer_size = sdl.bindings[binding_j].data.bytes.size;
+ VkDeviceSize uniform_buf_size = buffer_size;
+ // TODO: Create backing buffer
+
+ VkBuffer buffers[MAX_FRAMES_IN_FLIGHT];
+ VkDeviceMemory uniform_buf_memorys[MAX_FRAMES_IN_FLIGHT];
+ void* uniform_buf_mem_mappings[MAX_FRAMES_IN_FLIGHT];
+ // void* s?
+ for (size_t frame_i = 0; frame_i < MAX_FRAMES_IN_FLIGHT; frame_i++) {
+ buffer_handle uniform_buf_handle =
+ gpu_buffer_create(buffer_size, CEL_BUFFER_UNIFORM, CEL_BUFFER_FLAG_CPU, NULL);
+
+ gpu_buffer* created_gpu_buffer =
+ BUFFER_GET(uniform_buf_handle); // context.buffers[uniform_buf_handle.raw];
+ buffers[frame_i] = created_gpu_buffer->handle;
+ uniform_buf_memorys[frame_i] = created_gpu_buffer->memory;
+ vkMapMemory(context.device->logical_device, uniform_buf_memorys[frame_i], 0,
+ uniform_buf_size, 0, &uniform_buf_mem_mappings[frame_i]);
+ // now we have a pointer in unifrom_buf_mem_mappings we can write to
+ }
+
+ desc_set_uniform_buffer uniform_data;
+ memcpy(&uniform_data.buffers, &buffers, sizeof(buffers));
+ memcpy(&uniform_data.uniform_buf_memorys, &uniform_buf_memorys,
+ sizeof(uniform_buf_memorys));
+ memcpy(&uniform_data.uniform_buf_mem_mappings, &uniform_buf_mem_mappings,
+ sizeof(uniform_buf_mem_mappings));
+ uniform_data.size = buffer_size;
+
+ pipeline->uniform_pointers[binding_j] = uniform_data;
+
+ break;
+ case SHADER_BINDING_TEXTURE:
+ desc_set_bindings[binding_j].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ desc_set_bindings[binding_j].stageFlags =
+ VK_SHADER_STAGE_FRAGMENT_BIT; // FIXME: dont hardcode
+ desc_set_bindings[binding_j].pImmutableSamplers = NULL;
+
+ break;
+ default:
+ ERROR_EXIT("Unimplemented binding type!! in backend_vulkan");
+ }
+ switch (sdl.bindings[binding_j].vis) {
+ case VISIBILITY_VERTEX:
+ desc_set_bindings[binding_j].stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
+ break;
+ case VISIBILITY_FRAGMENT:
+ desc_set_bindings[binding_j].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+ break;
+ case VISIBILITY_COMPUTE:
+ WARN("Compute is not implemented yet");
+ break;
+ }
+ }
+
+ VkDescriptorSetLayoutCreateInfo desc_set_layout_info = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO
+ };
+ desc_set_layout_info.bindingCount = sdl.bindings_count;
+ desc_set_layout_info.pBindings = desc_set_bindings;
+
+ VK_CHECK(vkCreateDescriptorSetLayout(context.device->logical_device, &desc_set_layout_info,
+ context.allocator, &desc_set_layouts[layout_i]));
+ }
+ printf("Descriptor set layouts\n");
+
+ // Layout
+ VkPipelineLayoutCreateInfo pipeline_layout_create_info = {
+ VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO
+ };
+ pipeline_layout_create_info.setLayoutCount = description.data_layouts_count;
+ pipeline_layout_create_info.pSetLayouts = desc_set_layouts;
+ pipeline_layout_create_info.pushConstantRangeCount = 0;
+ pipeline_layout_create_info.pPushConstantRanges = NULL;
+ VK_CHECK(vkCreatePipelineLayout(context.device->logical_device, &pipeline_layout_create_info,
+ context.allocator, &layout->handle));
+ pipeline->layout_handle = layout->handle; // keep a copy of the layout on the pipeline object
+
+ VkGraphicsPipelineCreateInfo pipeline_create_info = {
+ VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO
+ };
+
+ pipeline_create_info.stageCount = 2;
+ pipeline_create_info.pStages = shader_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 = NULL; // &depth_stencil;
+ pipeline_create_info.pColorBlendState = &color_blend;
+ pipeline_create_info.pDynamicState = &dynamic_state;
+ pipeline_create_info.pTessellationState = 0;
+
+ pipeline_create_info.layout = layout->handle;
+
+ pipeline_create_info.renderPass = description.renderpass->handle;
+ pipeline_create_info.subpass = 0;
+ pipeline_create_info.basePipelineHandle = VK_NULL_HANDLE;
+ pipeline_create_info.basePipelineIndex = -1;
+
+ printf("About to create graphics pipeline\n");
+
+ VkResult result =
+ vkCreateGraphicsPipelines(context.device->logical_device, VK_NULL_HANDLE, 1,
+ &pipeline_create_info, context.allocator, &pipeline->handle);
+ if (result != VK_SUCCESS) {
+ FATAL("graphics pipeline creation failed. its fked mate");
+ ERROR_EXIT("Doomed");
+ }
+ TRACE("Vulkan Graphics pipeline created");
+
+ // once the pipeline has been created we can destroy these
+ vkDestroyShaderModule(context.device->logical_device, vertex_shader, context.allocator);
+ vkDestroyShaderModule(context.device->logical_device, fragment_shader, context.allocator);
+
+ // Framebuffers
+ create_swapchain_framebuffers();
+ TRACE("Swapchain Framebuffers created");
+
+ for (u32 frame_i = 0; frame_i < MAX_FRAMES_IN_FLIGHT; frame_i++) {
+ context.main_cmd_bufs[frame_i] = gpu_cmd_encoder_create();
+ }
+ TRACE("main Command Buffer created");
+
+ TRACE("Graphics pipeline created");
+ return pipeline;
+}
+
+void gpu_pipeline_destroy(gpu_pipeline* pipeline) {
+ vkDestroyPipeline(context.device->logical_device, pipeline->handle, context.allocator);
+ vkDestroyPipelineLayout(context.device->logical_device, pipeline->layout_handle,
+ context.allocator);
+}
+
+gpu_cmd_encoder* gpu_get_default_cmd_encoder() {
+ return &context.main_cmd_bufs[context.current_frame];
+}
+
+gpu_renderpass* gpu_renderpass_create(const gpu_renderpass_desc* description) {
+ gpu_renderpass* renderpass = renderpass_pool_alloc(&context.gpu_pools.renderpasses, NULL);
+
+ // 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;
+
+ // Depth attachment
+ u32x2 ext = { .x = context.swapchain_support.capabilities.currentExtent.width,
+ .y = context.swapchain_support.capabilities.currentExtent.height };
+ texture_desc depth_desc = { .extents = ext,
+ .format = CEL_TEXTURE_FORMAT_DEPTH_DEFAULT,
+ .tex_type = CEL_TEXTURE_TYPE_2D };
+ texture_handle depth_texture_handle = gpu_texture_create(depth_desc, true, NULL);
+ gpu_texture* depth = TEXTURE_GET(depth_texture_handle);
+
+ VkAttachmentDescription depth_attachment;
+ depth_attachment.format = // TODO: 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;
+
+ // main subpass
+ VkSubpassDescription subpass = { 0 };
+ subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ subpass.colorAttachmentCount = 1;
+ subpass.pColorAttachments = &color_attachment_reference;
+
+ // sets everything up
+ // 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;
+
+ // Finally, create the RenderPass
+ VkRenderPassCreateInfo render_pass_create_info = { VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO };
+ render_pass_create_info.attachmentCount = 1;
+ render_pass_create_info.pAttachments = &color_attachment;
+ 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.flags = 0;
+ render_pass_create_info.pNext = 0;
+
+ VK_CHECK(vkCreateRenderPass(context.device->logical_device, &render_pass_create_info,
+ context.allocator, &renderpass->handle));
+
+ // HACK
+ context.main_renderpass = renderpass->handle;
+
+ return renderpass;
+}
+
+gpu_cmd_encoder gpu_cmd_encoder_create() {
+ // gpu_cmd_encoder* encoder = malloc(sizeof(gpu_cmd_encoder)); // TODO: fix leaking mem
+ gpu_cmd_encoder encoder = { 0 };
+
+ VkCommandBufferAllocateInfo allocate_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
+ allocate_info.commandPool = context.device->pool;
+ allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ allocate_info.commandBufferCount = 1;
+ allocate_info.pNext = NULL;
+
+ VK_CHECK(vkAllocateCommandBuffers(context.device->logical_device, &allocate_info,
+ &encoder.cmd_buffer););
+
+ VkDescriptorPoolSize pool_sizes[2];
+ // Uniforms pool
+ pool_sizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ pool_sizes[0].descriptorCount = MAX_FRAMES_IN_FLIGHT * MAX_DESCRIPTOR_SETS;
+ // Samplers pool
+ pool_sizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ pool_sizes[1].descriptorCount = MAX_FRAMES_IN_FLIGHT * MAX_DESCRIPTOR_SETS;
+
+ VkDescriptorPoolCreateInfo pool_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO };
+ pool_info.poolSizeCount = 2;
+ pool_info.pPoolSizes = pool_sizes;
+ pool_info.maxSets = 100;
+
+ VK_CHECK(vkCreateDescriptorPool(context.device->logical_device, &pool_info, context.allocator,
+ &encoder.descriptor_pool));
+
+ return encoder;
+}
+void gpu_cmd_encoder_destroy(gpu_cmd_encoder* encoder) {
+ vkFreeCommandBuffers(context.device->logical_device, context.device->pool, 1,
+ &encoder->cmd_buffer);
+}
+
+void gpu_cmd_encoder_begin(gpu_cmd_encoder encoder) {
+ VK_CHECK(vkResetDescriptorPool(context.device->logical_device, encoder.descriptor_pool, 0));
+
+ VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
+ VK_CHECK(vkBeginCommandBuffer(encoder.cmd_buffer, &begin_info));
+}
+
+void gpu_cmd_encoder_begin_render(gpu_cmd_encoder* encoder, gpu_renderpass* renderpass) {
+ VkRenderPassBeginInfo begin_info = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO };
+ begin_info.renderPass = renderpass->handle;
+ /* printf("Current img: %d Current frame %d\n", context.current_img_index, context.current_frame);
+ */
+ begin_info.framebuffer = context.swapchain_framebuffers[context.current_img_index];
+ begin_info.renderArea.offset = (VkOffset2D){ 0, 0 };
+ begin_info.renderArea.extent = context.swapchain->extent;
+
+ // VkClearValue clear_values[2];
+ VkClearValue clear_color = { { { 0.02f, 0.02f, 0.02f, 1.0f } } };
+ // clear_values[1].depthStencil.depth = renderpass->depth;
+ // clear_values[1].depthStencil.stencil = renderpass->stencil;
+
+ begin_info.clearValueCount = 1;
+ begin_info.pClearValues = &clear_color;
+
+ vkCmdBeginRenderPass(encoder->cmd_buffer, &begin_info, VK_SUBPASS_CONTENTS_INLINE);
+ // command_buffer->state = COMMAND_BUFFER_STATE_IN_RENDER_PASS;
+}
+
+void gpu_cmd_encoder_end_render(gpu_cmd_encoder* encoder) {
+ vkCmdEndRenderPass(encoder->cmd_buffer);
+}
+
+gpu_cmd_buffer gpu_cmd_encoder_finish(gpu_cmd_encoder* encoder) {
+ vkEndCommandBuffer(encoder->cmd_buffer);
+
+ // TEMP: submit
+ return (gpu_cmd_buffer){ .cmd_buffer = encoder->cmd_buffer };
+}
+
+// --- Binding
+void encode_bind_pipeline(gpu_cmd_encoder* encoder, pipeline_kind kind, gpu_pipeline* pipeline) {
+ vkCmdBindPipeline(encoder->cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->handle);
+ encoder->pipeline = pipeline;
+}
+
+void encode_bind_shader_data(gpu_cmd_encoder* encoder, u32 group, shader_data* data) {
+ arena tmp = arena_create(malloc(1024), 1024);
+
+ assert(data->data != NULL);
+
+ // Update the local buffer
+ desc_set_uniform_buffer ubo = encoder->pipeline->uniform_pointers[group];
+ memcpy(ubo.uniform_buf_mem_mappings[context.current_frame], data->data, ubo.size);
+
+ VkDescriptorSetAllocateInfo alloc_info = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO };
+ alloc_info.descriptorPool = encoder->descriptor_pool;
+ alloc_info.descriptorSetCount = 1;
+ alloc_info.pSetLayouts = &encoder->pipeline->desc_set_layouts[group];
+
+ shader_data_layout sdl = data->shader_data_get_layout(data->data);
+ size_t binding_count = sdl.bindings_count;
+ assert(binding_count == 2);
+
+ VkDescriptorSet sets[0];
+ VK_CHECK(vkAllocateDescriptorSets(context.device->logical_device, &alloc_info, sets));
+ // FIXME: hardcoded
+ VkDescriptorSet_darray_push(context.free_set_queue, sets[0]);
+ /* VkDescriptorSet_darray_push(context.free_set_queue, sets[1]); */
+
+ VkWriteDescriptorSet write_sets[binding_count];
+ memset(&write_sets, 0, binding_count * sizeof(VkWriteDescriptorSet));
+
+ for (u32 i = 0; i < sdl.bindings_count; i++) {
+ shader_binding binding = sdl.bindings[i];
+
+ if (binding.type == SHADER_BINDING_BUFFER || binding.type == SHADER_BINDING_BYTES) {
+ VkDescriptorBufferInfo* buffer_info = arena_alloc(&tmp, sizeof(VkDescriptorBufferInfo));
+ buffer_info->buffer = ubo.buffers[context.current_frame];
+ buffer_info->offset = 0;
+ buffer_info->range = binding.data.bytes.size;
+
+ write_sets[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write_sets[i].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ write_sets[i].descriptorCount = 1;
+ write_sets[i].dstSet = sets[0];
+ write_sets[i].dstBinding = i;
+ write_sets[i].dstArrayElement = 0;
+ write_sets[i].pBufferInfo = buffer_info;
+ } else if (binding.type == SHADER_BINDING_TEXTURE) {
+ gpu_texture* texture = TEXTURE_GET(binding.data.texture.handle);
+ VkDescriptorImageInfo* image_info = arena_alloc(&tmp, sizeof(VkDescriptorImageInfo));
+ image_info->imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ image_info->imageView = texture->view;
+ image_info->sampler = texture->sampler;
+
+ write_sets[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write_sets[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ write_sets[i].descriptorCount = 1;
+ write_sets[i].dstSet = sets[0];
+ write_sets[i].dstBinding = i;
+ write_sets[i].dstArrayElement = 0;
+ write_sets[i].pImageInfo = image_info;
+ } else {
+ WARN("Unknown binding");
+ }
+ }
+
+ // Update
+ vkUpdateDescriptorSets(context.device->logical_device, binding_count, write_sets, 0, NULL);
+
+ // Bind
+ vkCmdBindDescriptorSets(encoder->cmd_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+ encoder->pipeline->layout_handle, 0, 1, sets, 0, NULL);
+
+ arena_free_storage(&tmp);
+}
+
+void encode_set_vertex_buffer(gpu_cmd_encoder* encoder, buffer_handle buf) {
+ gpu_buffer* buffer = BUFFER_GET(buf); // context.buffers[buf.raw];
+ VkBuffer vbs[] = { buffer->handle };
+ VkDeviceSize offsets[] = { 0 };
+ vkCmdBindVertexBuffers(encoder->cmd_buffer, 0, 1, vbs, offsets);
+}
+
+void encode_set_index_buffer(gpu_cmd_encoder* encoder, buffer_handle buf) {
+ gpu_buffer* buffer = BUFFER_GET(buf); // context.buffers[buf.raw];
+ vkCmdBindIndexBuffer(encoder->cmd_buffer, buffer->handle, 0, VK_INDEX_TYPE_UINT32);
+}
+
+// TEMP
+void encode_set_default_settings(gpu_cmd_encoder* encoder) {
+ VkViewport viewport = { 0 };
+ viewport.x = 0.0f;
+ viewport.y = 0.0f;
+ viewport.width = context.swapchain->extent.width;
+ viewport.height = context.swapchain->extent.height;
+ viewport.minDepth = 0.0f;
+ viewport.maxDepth = 1.0f;
+ vkCmdSetViewport(encoder->cmd_buffer, 0, 1, &viewport);
+
+ VkRect2D scissor = { 0 };
+ scissor.offset = (VkOffset2D){ 0, 0 };
+ scissor.extent = context.swapchain->extent;
+ vkCmdSetScissor(encoder->cmd_buffer, 0, 1, &scissor);
+}
+
+// --- Drawing
+
+bool gpu_backend_begin_frame() {
+ u32 current_frame = context.current_frame;
+ vkWaitForFences(context.device->logical_device, 1, &context.in_flight_fences[current_frame],
+ VK_TRUE, UINT64_MAX);
+
+ u32 image_index;
+ VkResult result = vkAcquireNextImageKHR(
+ context.device->logical_device, context.swapchain->handle, UINT64_MAX,
+ context.image_available_semaphores[current_frame], VK_NULL_HANDLE, &image_index);
+ if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || context.is_resizing) {
+ ERROR("Acquire next image failure. recreate swapchain");
+ context.is_resizing = false;
+ recreate_swapchain(context.swapchain);
+ return false;
+ } else if (result != VK_SUCCESS) {
+ ERROR_EXIT("failed to acquire swapchain image");
+ }
+
+ vkResetFences(context.device->logical_device, 1, &context.in_flight_fences[current_frame]);
+
+ context.current_img_index = image_index;
+ VK_CHECK(vkResetCommandBuffer(context.main_cmd_bufs[current_frame].cmd_buffer, 0));
+ return true;
+}
+
+void gpu_temp_draw(size_t n_indices) {
+ gpu_cmd_encoder* encoder = gpu_get_default_cmd_encoder(); // &context.main_cmd_buf;
+ /* vkCmdDraw(encoder->cmd_buffer, n_verts, 1, 0, 0); */
+ vkCmdDrawIndexed(encoder->cmd_buffer, n_indices, 1, 0, 0, 0);
+}
+
+void gpu_backend_end_frame() {
+ VkPresentInfoKHR present_info = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR };
+ present_info.waitSemaphoreCount = 1;
+ present_info.pWaitSemaphores = &context.render_finished_semaphores[context.current_frame];
+
+ VkSwapchainKHR swapchains[] = { context.swapchain->handle };
+ present_info.swapchainCount = 1;
+ present_info.pSwapchains = swapchains;
+ present_info.pImageIndices = &context.current_img_index;
+
+ VkResult result = vkQueuePresentKHR(context.device->present_queue, &present_info);
+ if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
+ ERROR("Queue present error. recreate swapchain");
+ recreate_swapchain(context.swapchain);
+ return;
+ } else if (result != VK_SUCCESS) {
+ ERROR_EXIT("failed to present swapchain image");
+ }
+ context.current_frame = (context.current_frame + 1) % MAX_FRAMES_IN_FLIGHT;
+
+ /* vkDeviceWaitIdle(context.device->logical_device); */
+}
+
+// TODO: Move into better order in file
+void gpu_queue_submit(gpu_cmd_buffer* buffer) {
+ VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
+
+ // Specify semaphore to wait on
+ VkSemaphore wait_semaphores[] = { context.image_available_semaphores[context.current_frame] };
+ VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
+
+ submit_info.waitSemaphoreCount = 1;
+ submit_info.pWaitSemaphores = wait_semaphores;
+ submit_info.pWaitDstStageMask = wait_stages;
+
+ // Specify semaphore to signal when finished executing buffer
+ VkSemaphore signal_semaphores[] = { context.render_finished_semaphores[context.current_frame] };
+ submit_info.signalSemaphoreCount = 1;
+ submit_info.pSignalSemaphores = signal_semaphores;
+
+ submit_info.commandBufferCount = 1;
+ submit_info.pCommandBuffers = &buffer->cmd_buffer;
+
+ VK_CHECK(vkQueueSubmit(context.device->graphics_queue, 1, &submit_info,
+ context.in_flight_fences[context.current_frame]));
+}
+
+inline void encode_draw_indexed(gpu_cmd_encoder* encoder, u64 index_count) {
+ vkCmdDrawIndexed(encoder->cmd_buffer, index_count, 1, 0, 0, 0);
+}
+
+bool select_physical_device(gpu_device* out_device) {
+ u32 physical_device_count = 0;
+ VK_CHECK(vkEnumeratePhysicalDevices(context.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 =
+ arena_alloc(&context.temp_arena, physical_device_count * sizeof(VkPhysicalDevice));
+ VK_CHECK(vkEnumeratePhysicalDevices(context.instance, &physical_device_count, physical_devices));
+
+ bool found = false;
+ for (u32 device_i = 0; device_i < physical_device_count; device_i++) {
+ if (is_physical_device_suitable(physical_devices[device_i])) {
+ out_device->physical_device = physical_devices[device_i];
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ FATAL("Couldn't find a suitable physical device");
+ return false;
+ }
+
+ vkGetPhysicalDeviceProperties(out_device->physical_device, &out_device->properties);
+ vkGetPhysicalDeviceFeatures(out_device->physical_device, &out_device->features);
+ vkGetPhysicalDeviceMemoryProperties(out_device->physical_device, &out_device->memory);
+
+ return true;
+}
+
+bool is_physical_device_suitable(VkPhysicalDevice device) {
+ VkPhysicalDeviceProperties properties;
+ vkGetPhysicalDeviceProperties(device, &properties);
+
+ VkPhysicalDeviceFeatures features;
+ vkGetPhysicalDeviceFeatures(device, &features);
+
+ VkPhysicalDeviceMemoryProperties memory;
+ vkGetPhysicalDeviceMemoryProperties(device, &memory);
+
+ // TODO: Check against these device properties
+
+ queue_family_indices indices = find_queue_families(device);
+
+ vulkan_device_query_swapchain_support(device, context.surface, &context.swapchain_support);
+
+ return indices.has_graphics && indices.has_present && context.swapchain_support.mode_count > 0 &&
+ context.swapchain_support.format_count > 0;
+}
+
+queue_family_indices find_queue_families(VkPhysicalDevice device) {
+ queue_family_indices indices = { 0 };
+
+ u32 queue_family_count = 0;
+ vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, 0);
+
+ VkQueueFamilyProperties* queue_families =
+ arena_alloc(&context.temp_arena, queue_family_count * sizeof(VkQueueFamilyProperties));
+ vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families);
+
+ for (u32 q_fam_i = 0; q_fam_i < queue_family_count; q_fam_i++) {
+ // Graphics queue
+ if (queue_families[q_fam_i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ indices.graphics_family_index = q_fam_i;
+ indices.has_graphics = true;
+ }
+
+ VkBool32 present_support = false;
+ vkGetPhysicalDeviceSurfaceSupportKHR(device, q_fam_i, context.surface, &present_support);
+ if (present_support && !indices.has_present) {
+ indices.present_family_index = q_fam_i;
+ indices.has_present = true;
+ }
+ }
+
+ return indices;
+}
+
+bool create_logical_device(gpu_device* out_device) {
+ queue_family_indices indices = find_queue_families(out_device->physical_device);
+ INFO(" %s | %s | %s | %s | %s", bool_str(indices.has_graphics), bool_str(indices.has_present),
+ bool_str(indices.has_compute), bool_str(indices.has_transfer),
+ out_device->properties.deviceName);
+ TRACE("Graphics Family queue index: %d", indices.graphics_family_index);
+ TRACE("Present Family queue index: %d", indices.present_family_index);
+ TRACE("Compute Family queue index: %d", indices.compute_family_index);
+ TRACE("Transfer Family queue index: %d", indices.transfer_family_index);
+
+ // Queues
+ f32 prio_one = 1.0;
+ VkDeviceQueueCreateInfo queue_create_infos[1] = { 0 };
+ queue_create_infos[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queue_create_infos[0].queueFamilyIndex = indices.graphics_family_index;
+ queue_create_infos[0].queueCount = 1;
+ queue_create_infos[0].pQueuePriorities = &prio_one;
+ queue_create_infos[0].flags = 0;
+ queue_create_infos[0].pNext = 0;
+
+ // queue_create_infos[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ // queue_create_infos[1].queueFamilyIndex = indices.present_family_index;
+ // queue_create_infos[1].queueCount = 1;
+ // queue_create_infos[1].pQueuePriorities = &prio_one;
+ // queue_create_infos[1].flags = 0;
+ // queue_create_infos[1].pNext = 0;
+
+ // Features
+ VkPhysicalDeviceFeatures device_features = { 0 };
+ device_features.samplerAnisotropy = VK_TRUE; // request anistrophy
+
+ // Device itself
+ VkDeviceCreateInfo device_create_info = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO };
+ device_create_info.queueCreateInfoCount = 1;
+ device_create_info.pQueueCreateInfos = queue_create_infos;
+ 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("Unable to create vulkan logical device. Exiting..");
+ }
+ TRACE("Logical device created");
+
+ context.device->queue_family_indicies = indices;
+
+ // Retrieve queue handles
+ vkGetDeviceQueue(context.device->logical_device, indices.graphics_family_index, 0,
+ &context.device->graphics_queue);
+ vkGetDeviceQueue(context.device->logical_device, indices.present_family_index, 0,
+ &context.device->present_queue);
+
+ return true;
+}
+
+VkShaderModule create_shader_module(str8 spirv) {
+ VkShaderModuleCreateInfo create_info = { VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO };
+ create_info.codeSize = spirv.len;
+ create_info.pCode = (uint32_t*)spirv.buf;
+
+ VkShaderModule shader_module;
+ VK_CHECK(vkCreateShaderModule(context.device->logical_device, &create_info, context.allocator,
+ &shader_module));
+
+ return shader_module;
+}
+
+void create_descriptor_pools() {}
+
+void create_swapchain_framebuffers() {
+ WARN("Recreating framebuffers...");
+ u32 image_count = context.swapchain->image_count;
+ context.swapchain_framebuffers =
+ arena_alloc(&context.swapchain->swapchain_arena, image_count * sizeof(VkFramebuffer));
+ for (u32 i = 0; i < image_count; i++) {
+ VkImageView attachments[1] = { context.swapchain->image_views[i] };
+
+ VkFramebufferCreateInfo framebuffer_create_info = { VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO };
+ framebuffer_create_info.attachmentCount = 1;
+ framebuffer_create_info.pAttachments = attachments;
+
+ framebuffer_create_info.renderPass =
+ context.main_renderpass; // TODO: description.renderpass->handle;
+ framebuffer_create_info.width = context.swapchain->extent.width;
+ framebuffer_create_info.height = context.swapchain->extent.height;
+ framebuffer_create_info.layers = 1;
+
+ VK_CHECK(vkCreateFramebuffer(context.device->logical_device, &framebuffer_create_info,
+ context.allocator, &context.swapchain_framebuffers[i]));
+ }
+}
+
+void create_sync_objects() {
+ VkSemaphoreCreateInfo semaphore_info = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO };
+ VkFenceCreateInfo fence_info = { VK_STRUCTURE_TYPE_FENCE_CREATE_INFO };
+ fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+ for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
+ VK_CHECK(vkCreateSemaphore(context.device->logical_device, &semaphore_info, context.allocator,
+ &context.image_available_semaphores[i]););
+ VK_CHECK(vkCreateSemaphore(context.device->logical_device, &semaphore_info, context.allocator,
+ &context.render_finished_semaphores[i]););
+
+ VK_CHECK(vkCreateFence(context.device->logical_device, &fence_info, context.allocator,
+ &context.in_flight_fences[i]));
+ }
+}
+
+static i32 find_memory_index(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;
+}
+
+buffer_handle gpu_buffer_create(u64 size, gpu_buffer_type buf_type, gpu_buffer_flags flags,
+ const void* data) {
+ VkBufferCreateInfo buffer_info = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
+ buffer_info.size = size;
+ buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
+
+ switch (buf_type) {
+ case CEL_BUFFER_DEFAULT:
+ buffer_info.usage |= VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
+ break;
+ case CEL_BUFFER_VERTEX:
+ buffer_info.usage |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+ break;
+ case CEL_BUFFER_INDEX:
+ buffer_info.usage |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
+ break;
+ case CEL_BUFFER_UNIFORM:
+ buffer_info.usage |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
+ break;
+ case CEL_BUFFER_COUNT:
+ WARN("Incorrect gpu_buffer_type provided. using default");
+ break;
+ }
+
+ buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+ // "allocating" the cpu-side buffer struct
+ /* gpu_buffer buffer; */
+ /* buffer.size = size; */
+ buffer_handle handle;
+ gpu_buffer* buffer = buffer_pool_alloc(&context.resource_pools->buffers, &handle);
+ buffer->size = size;
+
+ VK_CHECK(vkCreateBuffer(context.device->logical_device, &buffer_info, context.allocator,
+ &buffer->handle));
+
+ VkMemoryRequirements requirements;
+ vkGetBufferMemoryRequirements(context.device->logical_device, buffer->handle, &requirements);
+
+ // Just make them always need all of them for now
+ i32 memory_index =
+ find_memory_index(requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+
+ // Allocate the actual VRAM
+ VkMemoryAllocateInfo allocate_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
+ allocate_info.allocationSize = requirements.size;
+ allocate_info.memoryTypeIndex = (u32)memory_index;
+
+ vkAllocateMemory(context.device->logical_device, &allocate_info, context.allocator,
+ &buffer->memory);
+ vkBindBufferMemory(context.device->logical_device, buffer->handle, buffer->memory, 0);
+
+ /* Now there are two options:
+ * 1. create CPU-accessible memory -> map memory -> memcpy -> unmap
+ * 2. use a staging buffer thats CPU-accessible and copy its contents to a
+ * GPU-only buffer
+ */
+
+ /* context.buffers[context.buffer_count] = buffer; */
+ /* context.buffer_count++; */
+
+ if (data) {
+ TRACE("Upload data as part of buffer creation");
+ if (flags & CEL_BUFFER_FLAG_CPU) {
+ // map memory -> copy data in -> unmap memory
+ buffer_upload_bytes(handle, (bytebuffer){ .buf = (u8*)data, .size = size }, 0, size);
+ } else if (flags & CEL_BUFFER_FLAG_GPU) {
+ TRACE("Uploading data to buffer using staging buffer");
+ // Create a staging buffer
+ buffer_handle staging = gpu_buffer_create(size, buf_type, CEL_BUFFER_FLAG_CPU, NULL);
+
+ // Copy data into it
+ buffer_upload_bytes(staging, (bytebuffer){ .buf = (u8*)data, .size = size }, 0, size);
+
+ // Enqueue a copy from the staging buffer into the DEVICE_LOCAL buffer
+ gpu_cmd_encoder temp_encoder = gpu_cmd_encoder_create();
+ gpu_cmd_encoder_begin(temp_encoder);
+ encode_buffer_copy(&temp_encoder, staging, 0, handle, 0, size);
+ gpu_cmd_buffer copy_cmd_buffer = gpu_cmd_encoder_finish(&temp_encoder);
+
+ VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
+ submit_info.commandBufferCount = 1;
+ submit_info.pCommandBuffers = &temp_encoder.cmd_buffer;
+ vkQueueSubmit(context.device->graphics_queue, 1, &submit_info, VK_NULL_HANDLE);
+
+ // Cleanup
+ vkQueueWaitIdle(context.device->graphics_queue);
+ gpu_cmd_encoder_destroy(&temp_encoder);
+ gpu_buffer_destroy(staging);
+ }
+ }
+
+ return handle;
+}
+
+void gpu_buffer_destroy(buffer_handle buffer) {
+ gpu_buffer* b = buffer_pool_get(&context.resource_pools->buffers, buffer);
+ vkDestroyBuffer(context.device->logical_device, b->handle, context.allocator);
+ vkFreeMemory(context.device->logical_device, b->memory, context.allocator);
+ buffer_pool_dealloc(&context.resource_pools->buffers, buffer);
+}
+
+// Upload data to a
+void buffer_upload_bytes(buffer_handle gpu_buf, bytebuffer cpu_buf, u64 offset, u64 size) {
+ gpu_buffer* buffer = buffer_pool_get(&context.resource_pools->buffers, gpu_buf);
+ void* data_ptr;
+ vkMapMemory(context.device->logical_device, buffer->memory, 0, size, 0, &data_ptr);
+ DEBUG("Uploading %d bytes to buffer", size);
+ memcpy(data_ptr, cpu_buf.buf, size);
+ vkUnmapMemory(context.device->logical_device, buffer->memory);
+}
+
+void encode_buffer_copy(gpu_cmd_encoder* encoder, buffer_handle src, u64 src_offset,
+ buffer_handle dst, u64 dst_offset, u64 copy_size) {
+ VkBufferCopy copy_region;
+ copy_region.srcOffset = src_offset;
+ copy_region.dstOffset = dst_offset;
+ copy_region.size = copy_size;
+
+ gpu_buffer* src_buf = buffer_pool_get(&context.resource_pools->buffers, src);
+ gpu_buffer* dst_buf = buffer_pool_get(&context.resource_pools->buffers, dst);
+ vkCmdCopyBuffer(encoder->cmd_buffer, src_buf->handle, dst_buf->handle, 1, &copy_region);
+}
+
+// one-shot command buffers
+VkCommandBuffer vulkan_command_buffer_create_oneshot() {
+ VkCommandBufferAllocateInfo alloc_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO };
+ alloc_info.commandPool = context.device->pool;
+ alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ alloc_info.commandBufferCount = 1;
+ alloc_info.pNext = 0;
+
+ VkCommandBuffer cmd_buffer;
+ vkAllocateCommandBuffers(context.device->logical_device, &alloc_info, &cmd_buffer);
+
+ VkCommandBufferBeginInfo begin_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO };
+ begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+
+ vkBeginCommandBuffer(cmd_buffer, &begin_info);
+
+ return cmd_buffer;
+}
+
+void vulkan_command_buffer_finish_oneshot(VkCommandBuffer cmd_buffer) {
+ VK_CHECK(vkEndCommandBuffer(cmd_buffer));
+
+ // submit to queue
+ VkSubmitInfo submit_info = { VK_STRUCTURE_TYPE_SUBMIT_INFO };
+ submit_info.commandBufferCount = 1;
+ submit_info.pCommandBuffers = &cmd_buffer;
+ VK_CHECK(vkQueueSubmit(context.device->graphics_queue, 1, &submit_info, 0));
+ VK_CHECK(vkQueueWaitIdle(context.device->graphics_queue));
+
+ vkFreeCommandBuffers(context.device->logical_device, context.device->pool, 1, &cmd_buffer);
+}
+
+void copy_buffer_to_buffer_oneshot(buffer_handle src, u64 src_offset, buffer_handle dst,
+ u64 dst_offset, u64 copy_size) {
+ VkBufferCopy copy_region;
+ copy_region.srcOffset = src_offset;
+ copy_region.dstOffset = dst_offset;
+ copy_region.size = copy_size;
+
+ gpu_buffer* src_buf = buffer_pool_get(&context.resource_pools->buffers, src);
+ gpu_buffer* dst_buf = buffer_pool_get(&context.resource_pools->buffers, dst);
+ VkCommandBuffer temp_cmd_buffer = vulkan_command_buffer_create_oneshot();
+ vkCmdCopyBuffer(temp_cmd_buffer, src_buf->handle, dst_buf->handle, 1, &copy_region);
+ vulkan_command_buffer_finish_oneshot(temp_cmd_buffer);
+}
+
+void copy_buffer_to_image_oneshot(buffer_handle src, texture_handle dst) {
+ gpu_buffer* src_buf = buffer_pool_get(&context.resource_pools->buffers, src);
+ gpu_texture* dst_tex = texture_pool_get(&context.resource_pools->textures, dst);
+
+ VkCommandBuffer temp_cmd_buffer = vulkan_command_buffer_create_oneshot();
+
+ 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", dst_tex->desc.extents.x, dst_tex->desc.extents.y);
+ region.imageOffset.x = 0;
+ region.imageOffset.y = 0;
+ region.imageOffset.z = 0;
+ region.imageExtent.width = dst_tex->desc.extents.x;
+ region.imageExtent.height = dst_tex->desc.extents.y;
+ region.imageExtent.depth = 1;
+
+ vkCmdCopyBufferToImage(temp_cmd_buffer, src_buf->handle, dst_tex->handle,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
+
+ vulkan_command_buffer_finish_oneshot(temp_cmd_buffer);
+}
+
+VkImage vulkan_image_create(u32x2 dimensions, VkImageType image_type, VkFormat format,
+ VkImageUsageFlags usage) {
+ VkImage image;
+
+ VkImageCreateInfo image_create_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
+ image_create_info.imageType = VK_IMAGE_TYPE_2D;
+ image_create_info.extent.width = dimensions.x;
+ image_create_info.extent.height = dimensions.y;
+ 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 = VK_IMAGE_TILING_OPTIMAL;
+ image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ image_create_info.usage = usage; // VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+ image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
+
+ VK_CHECK(
+ vkCreateImage(context.device->logical_device, &image_create_info, context.allocator, &image));
+
+ return image;
+}
+
+texture_handle gpu_texture_create(texture_desc desc, bool create_view, const void* data) {
+ VkDeviceSize image_size = desc.extents.x * desc.extents.y * 4;
+ // FIXME: handle this properly
+ VkFormat format = desc.format == CEL_TEXTURE_FORMAT_8_8_8_8_RGBA_UNORM ? VK_FORMAT_R8G8B8A8_SRGB
+ : VK_FORMAT_D32_SFLOAT;
+
+ VkImage image; // vulkan_image_create(desc.extents, VK_IMAGE_TYPE_2D, format,
+ // VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT);
+ VkDeviceMemory image_memory;
+
+ VkImageCreateInfo image_create_info = { VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO };
+ image_create_info.imageType = VK_IMAGE_TYPE_2D;
+ image_create_info.extent.width = desc.extents.x;
+ image_create_info.extent.height = desc.extents.y;
+ 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 = VK_IMAGE_TILING_OPTIMAL;
+ image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+ if (format == VK_FORMAT_D32_SFLOAT) {
+ image_create_info.usage |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ }
+ image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
+
+ VK_CHECK(
+ vkCreateImage(context.device->logical_device, &image_create_info, context.allocator, &image));
+
+ VkMemoryRequirements memory_reqs;
+ vkGetImageMemoryRequirements(context.device->logical_device, image, &memory_reqs);
+
+ VkMemoryAllocateInfo alloc_info = { VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO };
+ alloc_info.allocationSize = memory_reqs.size;
+ alloc_info.memoryTypeIndex =
+ find_memory_index(memory_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
+ vkAllocateMemory(context.device->logical_device, &alloc_info, context.allocator, &image_memory);
+
+ vkBindImageMemory(context.device->logical_device, image, image_memory, 0);
+
+ texture_handle handle;
+ gpu_texture* texture = texture_pool_alloc(&context.resource_pools->textures, &handle);
+ DEBUG("Allocated texture with handle %d", handle.raw);
+ texture->handle = image;
+ texture->debug_label = "Test Texture";
+ texture->desc = desc;
+ texture->memory = image_memory;
+ texture->size = image_size;
+
+ if (data) {
+ TRACE("Uploading pixel data to texture using staging buffer");
+ // Create a staging buffer
+ buffer_handle staging =
+ gpu_buffer_create(image_size, CEL_BUFFER_DEFAULT, CEL_BUFFER_FLAG_CPU, NULL);
+ // Copy data into it
+ vulkan_transition_image_layout(texture, format, VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+ buffer_upload_bytes(staging, (bytebuffer){ .buf = (u8*)data, .size = image_size }, 0,
+ image_size);
+ copy_buffer_to_image_oneshot(staging, handle);
+ vulkan_transition_image_layout(texture, format, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+
+ gpu_buffer_destroy(staging);
+ }
+
+ // Texture View
+ if (create_view) {
+ VkImageViewCreateInfo view_create_info = { VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO };
+ view_create_info.image = image;
+ view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ view_create_info.format = format;
+ view_create_info.subresourceRange.aspectMask =
+ format == VK_FORMAT_D32_SFLOAT ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT;
+
+ view_create_info.subresourceRange.baseMipLevel = 0;
+ view_create_info.subresourceRange.levelCount = 1;
+ view_create_info.subresourceRange.baseArrayLayer = 0;
+ view_create_info.subresourceRange.layerCount = 1;
+
+ VK_CHECK(vkCreateImageView(context.device->logical_device, &view_create_info, context.allocator,
+ &texture->view));
+ }
+
+ // Sampler
+ 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,
+ &texture->sampler);
+ if (res != VK_SUCCESS) {
+ ERROR("Error creating texture sampler for image %s", texture->debug_label);
+ exit(1);
+ }
+
+ return handle;
+}
+
+void vulkan_transition_image_layout(gpu_texture* texture, VkFormat format, VkImageLayout old_layout,
+ VkImageLayout new_layout) {
+ VkCommandBuffer temp_cmd_buffer = vulkan_command_buffer_create_oneshot();
+
+ VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER };
+ barrier.oldLayout = old_layout;
+ barrier.newLayout = new_layout;
+ barrier.srcQueueFamilyIndex = context.device->queue_family_indicies.graphics_family_index;
+ barrier.dstQueueFamilyIndex = context.device->queue_family_indicies.graphics_family_index;
+ barrier.image = texture->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; // TODO
+ barrier.dstAccessMask = 0; // TODO
+
+ 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(temp_cmd_buffer, source_stage, dest_stage, 0, 0, 0, 0, 0, 1, &barrier);
+
+ vulkan_command_buffer_finish_oneshot(temp_cmd_buffer);
+}
+
+/* TYPED_POOL(gpu_buffer, buffer); */
+/* TYPED_POOL(gpu_texture, texture); */
+
+/* void resource_pools_init(arena* a, struct resource_pools* res_pools) { */
+/* buffer_pool buf_pool = buffer_pool_create(a, MAX_BUFFERS, sizeof(gpu_buffer)); */
+/* res_pools->buffers = buf_pool; */
+/* texture_pool tex_pool = texture_pool_create(a, MAX_TEXTURES, sizeof(gpu_texture)); */
+/* res_pools->textures = tex_pool; */
+
+/* context.resource_pools = res_pools; */
+/* } */
+
+#endif
diff --git a/archive/src/render/archive/backends/vulkan/backend_vulkan.h b/archive/src/render/archive/backends/vulkan/backend_vulkan.h
new file mode 100644
index 0000000..6ca0bb5
--- /dev/null
+++ b/archive/src/render/archive/backends/vulkan/backend_vulkan.h
@@ -0,0 +1,118 @@
+#pragma once
+#include "defines.h"
+#if defined(CEL_REND_BACKEND_VULKAN)
+#include <vulkan/vk_platform.h>
+#include <vulkan/vulkan.h>
+#include <vulkan/vulkan_core.h>
+
+#include "mem.h"
+#include "ral.h"
+#include "ral_types.h"
+
+#define MAX_FRAMES_IN_FLIGHT 2
+#define GPU_SWAPCHAIN_IMG_COUNT 2
+
+/*
+Conventions:
+ - Place the 'handle' as the first field of a struct
+ - Vulkan specific data goes at the top, followed by our internal data
+*/
+
+typedef struct queue_family_indices {
+ u32 graphics_family_index;
+ u32 present_family_index;
+ u32 compute_family_index;
+ u32 transfer_family_index;
+ bool has_graphics;
+ bool has_present;
+ bool has_compute;
+ bool has_transfer;
+} queue_family_indices;
+
+// typedef struct vulkan_framebuffer {
+// } vulkan_framebuffer;
+
+typedef struct gpu_swapchain {
+ VkSwapchainKHR handle;
+ arena swapchain_arena;
+ VkExtent2D extent;
+ u32x2 dimensions;
+ VkSurfaceFormatKHR image_format;
+ VkPresentModeKHR present_mode;
+ u32 image_count;
+ VkImage* images;
+ VkImageView* image_views;
+} 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;
+ queue_family_indices queue_family_indicies;
+ VkQueue graphics_queue;
+ VkQueue present_queue;
+ VkQueue compute_queue;
+ VkQueue transfer_queue;
+ VkCommandPool pool;
+} gpu_device;
+
+typedef struct gpu_pipeline_layout {
+ VkPipelineLayout handle;
+} gpu_pipeline_layout;
+
+typedef struct desc_set_uniform_buffer {
+ VkBuffer buffers[MAX_FRAMES_IN_FLIGHT];
+ VkDeviceMemory uniform_buf_memorys[MAX_FRAMES_IN_FLIGHT];
+ void* uniform_buf_mem_mappings[MAX_FRAMES_IN_FLIGHT];
+ size_t size;
+} desc_set_uniform_buffer;
+
+typedef struct gpu_pipeline {
+ VkPipeline handle;
+ VkPipelineLayout layout_handle;
+
+ // Descriptor gubbins
+ shader_data data_layouts[MAX_SHADER_DATA_LAYOUTS];
+ u32 data_layouts_count;
+
+ VkDescriptorSetLayout* desc_set_layouts;
+ // Based on group, we know which data to load
+ desc_set_uniform_buffer* uniform_pointers;
+ u32 desc_set_layouts_count;
+
+} gpu_pipeline;
+
+typedef struct gpu_renderpass {
+ VkRenderPass handle;
+ // TODO: Where to store framebuffers? VkFramebuffer framebuffers[GPU_SWAPCHAIN_IMG_COUNT];
+} gpu_renderpass;
+
+typedef struct gpu_cmd_encoder {
+ VkCommandBuffer cmd_buffer;
+ VkDescriptorPool descriptor_pool;
+ gpu_pipeline* pipeline;
+} gpu_cmd_encoder;
+
+typedef struct gpu_cmd_buffer {
+ VkCommandBuffer cmd_buffer;
+} gpu_cmd_buffer;
+
+typedef struct gpu_buffer {
+ VkBuffer handle;
+ VkDeviceMemory memory;
+ u64 size;
+} gpu_buffer;
+
+typedef struct gpu_texture {
+ VkImage handle;
+ VkDeviceMemory memory;
+ u64 size;
+ texture_desc desc;
+ VkImageView view;
+ VkSampler sampler;
+ char* debug_label;
+} gpu_texture;
+#endif \ No newline at end of file
diff --git a/archive/src/render/immdraw.c b/archive/src/render/immdraw.c
new file mode 100644
index 0000000..8a10c65
--- /dev/null
+++ b/archive/src/render/immdraw.c
@@ -0,0 +1,176 @@
+#include "immdraw.h"
+#include "core.h"
+#include "file.h"
+#include "log.h"
+#include "maths.h"
+#include "maths_types.h"
+#include "primitives.h"
+#include "ral_common.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+#include "render.h"
+#include "render_types.h"
+#include "shader_layouts.h"
+
+void Immdraw_Init(Immdraw_Storage* storage) {
+ INFO("Immediate drawing initialisation");
+
+ // Meshes
+ Geometry sphere_geo = Geo_CreateUVsphere(1.0, 16, 16);
+ storage->sphere = Mesh_Create(&sphere_geo, true);
+
+ Geometry cube_geo = Geo_CreateCuboid(f32x3(1.0, 1.0, 1.0));
+ storage->cube = Mesh_Create(&cube_geo, true);
+
+ Geometry plane_geo = Geo_CreatePlane(f32x2(1.0, 1.0), 1, 1);
+ storage->plane = Mesh_Create(&plane_geo, true);
+
+ Geometry cone_geo = Geo_CreateCone(1.0, 1.0, 8);
+ storage->cone = Mesh_Create(&cone_geo, true);
+
+ Geometry cyl_geo = Geo_CreateCylinder(1.0, 2.0, 8);
+ storage->cylinder = Mesh_Create(&cyl_geo, true);
+
+ storage->bbox = GenBboxMesh();
+
+ // Pipeline / material
+ VertexDescription vertex_desc = {
+ .debug_label = "Immdraw Vertex",
+ .use_full_vertex_size = true,
+ };
+ VertexDesc_AddAttr(&vertex_desc, "position", ATTR_F32x3);
+ VertexDesc_AddAttr(&vertex_desc, "normal", ATTR_F32x3);
+
+ const char* vert_path = "assets/shaders/immdraw.vert";
+ const char* frag_path = "assets/shaders/immdraw.frag";
+ const char* vert_shader = string_from_file(vert_path);
+ const char* frag_shader = string_from_file(frag_path);
+
+ ShaderDataLayout camera_data = Binding_Camera_GetLayout(NULL);
+ ShaderDataLayout imm_uniform_data = ImmediateUniforms_GetLayout(NULL);
+
+ GraphicsPipelineDesc pipeline_desc = {
+ .debug_name = "Immediate Draw Pipeline",
+ .vertex_desc = static_3d_vertex_description(),
+ .data_layouts = { camera_data, imm_uniform_data },
+ .data_layouts_count = 2,
+ .vs = { .debug_name = "Immdraw Vertex Shader", .filepath = vert_path, .code = vert_shader },
+ .fs = { .debug_name = "Immdraw Fragment Shader", .filepath = frag_path, .code = frag_shader },
+ .depth_test = true,
+ .wireframe = true,
+ };
+ GPU_Renderpass* rpass =
+ GPU_Renderpass_Create((GPU_RenderpassDesc){ .default_framebuffer = true });
+ storage->colour_pipeline = GPU_GraphicsPipeline_Create(pipeline_desc, rpass);
+}
+
+void Immdraw_Shutdown(Immdraw_Storage* storage) {
+ GraphicsPipeline_Destroy(storage->colour_pipeline);
+}
+
+void Immdraw_Sphere(Transform tf, Vec4 colour, bool wireframe) {
+ TRACE("Draw sphere");
+ Immdraw_Storage* imm = Render_GetImmdrawStorage();
+ Immdraw_Primitive(tf, CEL_TRI, 1.0, colour, wireframe, imm->sphere);
+}
+void Immdraw_Cuboid(Transform tf, Vec4 colour, bool wireframe) {
+ TRACE("Draw cube");
+ Immdraw_Storage* imm = Render_GetImmdrawStorage();
+ Immdraw_Primitive(tf, CEL_TRI, 1.0, colour, wireframe, imm->cube);
+}
+void Immdraw_Plane(Transform tf, Vec4 colour, bool wireframe) {
+ TRACE("Draw plane");
+ Immdraw_Storage* imm = Render_GetImmdrawStorage();
+ Immdraw_Primitive(tf, CEL_TRI, 1.0, colour, wireframe, imm->plane);
+}
+
+void Immdraw_Bbox(Transform tf, Vec4 colour, bool wireframe) {
+ TRACE("Draw bbox");
+ Immdraw_Storage* imm = Render_GetImmdrawStorage();
+ Immdraw_Primitive(tf, CEL_LINE, 1.0, colour, wireframe, imm->bbox);
+}
+
+void Immdraw_Cylinder(Transform tf, Vec4 colour, bool wireframe) {
+ TRACE("Draw cylinder");
+ Immdraw_Storage* imm = Render_GetImmdrawStorage();
+ Immdraw_Primitive(tf, CEL_TRI, 1.0, colour, wireframe, imm->cylinder);
+}
+
+void Immdraw_Cone(Transform tf, Vec4 colour, bool wireframe) {
+ TRACE("Draw cone");
+ Immdraw_Storage* imm = Render_GetImmdrawStorage();
+ Immdraw_Primitive(tf, CEL_TRI, 1.0, colour, wireframe, imm->cone);
+}
+
+void Immdraw_Primitive(Transform tf, PrimitiveTopology topology, f32 size, Vec4 colour,
+ bool wireframe, Mesh mesh) {
+ Immdraw_Storage* imm = Render_GetImmdrawStorage();
+ GPU_CmdEncoder* enc = GPU_GetDefaultEncoder();
+
+ // begin renderpass
+ GPU_CmdEncoder_BeginRender(enc, imm->colour_pipeline->renderpass);
+ // bind pipeline
+ GPU_EncodeBindPipeline(enc, imm->colour_pipeline);
+
+ // TODO: implement wireframe in other apis
+#if defined(CEL_REND_BACKEND_OPENGL)
+#include <glad/glad.h>
+ if (wireframe) {
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ } else {
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ }
+#endif
+
+ // update uniforms
+ ImmediateUniforms uniforms = {
+ .model = transform_to_mat(&tf),
+ .colour = colour,
+ };
+ Mat4 view, proj;
+ u32x2 dimensions = GPU_Swapchain_GetDimensions();
+ RenderScene* scene = Render_GetScene();
+ Camera_ViewProj(&scene->camera, (f32)dimensions.x, (f32)dimensions.y, &view, &proj);
+ Binding_Camera camera_data = { .view = view,
+ .projection = proj,
+ .viewPos = vec4(scene->camera.position.x, scene->camera.position.y,
+ scene->camera.position.z, 1.0) };
+ GPU_EncodeBindShaderData(enc, 0, Binding_Camera_GetLayout(&camera_data));
+ GPU_EncodeBindShaderData(enc, 1, ImmediateUniforms_GetLayout(&uniforms));
+
+ // draw call
+ GPU_EncodeSetVertexBuffer(enc, mesh.vertex_buffer);
+ GPU_EncodeSetIndexBuffer(enc, mesh.index_buffer);
+ GPU_EncodeDrawIndexed(enc, topology, mesh.geometry.index_count);
+
+ // end renderpass
+ GPU_CmdEncoder_EndRender(enc);
+}
+
+Mesh GenBboxMesh() {
+ Vertex_darray* vertices = Vertex_darray_new(8);
+ u32_darray* indices = u32_darray_new(24);
+
+ // normals & uvs dont matter
+ VERT_3D(vertices, FRONT_BOT_LEFT, VEC3_NEG_Z, vec2(0, 0));
+ VERT_3D(vertices, FRONT_BOT_RIGHT, VEC3_NEG_Z, vec2(0, 0));
+ VERT_3D(vertices, BACK_BOT_LEFT, VEC3_NEG_Z, vec2(0, 0));
+ VERT_3D(vertices, BACK_BOT_RIGHT, VEC3_NEG_Z, vec2(0, 0));
+ VERT_3D(vertices, FRONT_TOP_LEFT, VEC3_NEG_Z, vec2(0, 0));
+ VERT_3D(vertices, FRONT_TOP_RIGHT, VEC3_NEG_Z, vec2(0, 0));
+ VERT_3D(vertices, BACK_TOP_LEFT, VEC3_NEG_Z, vec2(0, 0));
+ VERT_3D(vertices, BACK_TOP_RIGHT, VEC3_NEG_Z, vec2(0, 0));
+
+ u32 line_indices[24] = { 0, 1, 2, 3, 0, 2, 1, 3, 4, 5, 6, 7, 4, 6, 5, 7, 0, 4, 1, 5, 2, 6, 3, 7 };
+ for (u32 i = 0; i < 24; i++) {
+ u32_darray_push(indices, line_indices[i]);
+ }
+
+ Geometry geo = { .format = VERTEX_STATIC_3D,
+ .has_indices = true,
+ .index_count = indices->len,
+ .vertices = vertices,
+ .indices = indices };
+
+ return Mesh_Create(&geo, true);
+}
diff --git a/archive/src/render/immdraw.h b/archive/src/render/immdraw.h
new file mode 100644
index 0000000..2911350
--- /dev/null
+++ b/archive/src/render/immdraw.h
@@ -0,0 +1,63 @@
+/**
+ * @brief Immediate-mode drawing APIs
+ */
+
+#pragma once
+#include "defines.h"
+#include "maths_types.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+#include "render_types.h"
+
+typedef struct Immdraw_Storage {
+ Mesh plane;
+ Mesh cube;
+ Mesh sphere;
+ Mesh cylinder;
+ Mesh cone;
+ Mesh bbox;
+ GPU_Pipeline* colour_pipeline; /** @brief Pipeline for drawing geometry that has vertex colours */
+} Immdraw_Storage;
+
+typedef struct ImmediateUniforms {
+ Mat4 model;
+ Vec4 colour;
+} ImmediateUniforms;
+
+// --- Public API
+
+PUB void Immdraw_Init(Immdraw_Storage* storage);
+PUB void Immdraw_Shutdown(Immdraw_Storage* storage);
+
+// These functions cause a pipeline switch and so aren't optimised for performance
+PUB void Immdraw_Plane(Transform tf, Vec4 colour, bool wireframe);
+PUB void Immdraw_Cuboid(Transform tf, Vec4 colour, bool wireframe);
+PUB void Immdraw_Cylinder(Transform tf, Vec4 colour, bool wireframe);
+PUB void Immdraw_Cone(Transform tf, Vec4 colour, bool wireframe);
+PUB void Immdraw_Sphere(Transform tf, Vec4 colour, bool wireframe);
+PUB void Immdraw_Bbox(Transform tf, Vec4 colour, bool wireframe);
+
+PUB void Immdraw_TransformGizmo(Transform tf, f32 size);
+
+// --- Internal
+
+void Immdraw_Primitive(Transform tf, PrimitiveTopology topology, f32 size, Vec4 colour,
+ bool wireframe, Mesh mesh);
+
+Mesh GenBboxMesh();
+
+static ShaderDataLayout ImmediateUniforms_GetLayout(void* data) {
+ ImmediateUniforms* d = (ImmediateUniforms*)data;
+ bool has_data = data != NULL;
+
+ ShaderBinding b1 = { .label = "ImmUniforms",
+ .kind = BINDING_BYTES,
+ // .vis = VISIBILITY_VERTEX,
+ .data.bytes.size = sizeof(ImmediateUniforms) };
+
+ if (has_data) {
+ b1.data.bytes.data = d;
+ }
+
+ return (ShaderDataLayout){ .bindings = { b1 }, .binding_count = 1 };
+}
diff --git a/archive/src/render/pbr.c b/archive/src/render/pbr.c
new file mode 100644
index 0000000..4bad528
--- /dev/null
+++ b/archive/src/render/pbr.c
@@ -0,0 +1,266 @@
+#include "pbr.h"
+#include "animation.h"
+#include "camera.h"
+#include "core.h"
+#include "file.h"
+#include "log.h"
+#include "maths.h"
+#include "mem.h"
+#include "ral_common.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+#include "render.h"
+#include "render_types.h"
+#include "shader_layouts.h"
+
+void PBR_Init(PBR_Storage* storage) {
+ INFO("PBR shaders init");
+ storage->pbr_pass = PBR_RPassCreate();
+ PBR_PipelinesCreate(storage, storage->pbr_pass);
+}
+
+GPU_Renderpass* PBR_RPassCreate() {
+ GPU_RenderpassDesc desc = { .default_framebuffer = true };
+ return GPU_Renderpass_Create(desc);
+}
+
+void PBR_PipelinesCreate(PBR_Storage* storage, GPU_Renderpass* rpass) {
+ // Common shader bindings
+ ShaderDataLayout camera_data = Binding_Camera_GetLayout(NULL);
+ ShaderDataLayout model_data = Binding_Model_GetLayout(NULL);
+ ShaderDataLayout material_data = PBRMaterial_GetLayout(NULL);
+ ShaderDataLayout lights_data = Binding_Lights_GetLayout(NULL);
+
+ // Static
+ {
+ const char* vert_path = "assets/shaders/static_geometry.vert";
+ const char* frag_path = "assets/shaders/pbr_textured.frag";
+ char* vert_shader = string_from_file(vert_path);
+ char* frag_shader = string_from_file(frag_path);
+
+ GraphicsPipelineDesc desc = {
+ .debug_name = "PBR (Static) Pipeline",
+ .vertex_desc = static_3d_vertex_description(),
+ .data_layouts = { camera_data, model_data, material_data, lights_data },
+ .data_layouts_count = 4,
+ .vs = { .debug_name = "PBR (textured) Vertex Shader",
+ .filepath = str8(vert_path),
+ .code = vert_shader },
+ .fs = { .debug_name = "PBR (textured) Fragment Shader",
+ .filepath = str8(frag_path),
+ .code = frag_shader },
+ .depth_test = true,
+ .wireframe = true,
+ };
+ storage->pbr_static_pipeline = GPU_GraphicsPipeline_Create(desc, rpass);
+ }
+
+ // Skinned
+ {
+ const char* vert_path = "assets/shaders/skinned_geometry.vert";
+ const char* frag_path = "assets/shaders/pbr_textured.frag";
+ char* vert_shader = string_from_file(vert_path);
+ char* frag_shader = string_from_file(frag_path);
+
+ ShaderDataLayout anim_uniform = AnimData_GetLayout(NULL);
+
+ VertexDescription vertex_desc = { .debug_label = "Skinned vertices",
+ .use_full_vertex_size = true };
+ VertexDesc_AddAttr(&vertex_desc, "inPosition", ATTR_F32x3);
+ VertexDesc_AddAttr(&vertex_desc, "inNormal", ATTR_F32x3);
+ VertexDesc_AddAttr(&vertex_desc, "inTexCoords", ATTR_F32x2);
+ VertexDesc_AddAttr(&vertex_desc, "inBoneIndices", ATTR_I32x4);
+ VertexDesc_AddAttr(&vertex_desc, "inWeights", ATTR_F32x4);
+
+ GraphicsPipelineDesc desc = {
+ .debug_name = "PBR (Skinned) Pipeline",
+ .vertex_desc = vertex_desc,
+ .data_layouts = { camera_data, model_data, material_data, lights_data, anim_uniform },
+ .data_layouts_count = 5,
+ .vs = { .debug_name = "PBR (textured) Vertex Shader",
+ .filepath = str8(vert_path),
+ .code = vert_shader },
+ .fs = { .debug_name = "PBR (textured) Fragment Shader",
+ .filepath = str8(frag_path),
+ .code = frag_shader },
+ .depth_test = true,
+ .wireframe = true,
+ };
+ storage->pbr_skinned_pipeline = GPU_GraphicsPipeline_Create(desc, rpass);
+ }
+}
+
+void PBR_Execute(PBR_Storage* storage, Camera camera, TextureHandle shadowmap_tex,
+ RenderEnt* entities, size_t entity_count) {
+ // 1. set up our pipeline
+ // 2. upload constant data (camera, lights)
+ // 3. draw each entity
+ // - upload material data -> in the future we will sort & batch by material
+ // - upload model transform
+ // - emit draw call
+
+ GPU_CmdEncoder* enc = GPU_GetDefaultEncoder();
+ GPU_CmdEncoder_BeginRender(enc, storage->pbr_pass);
+
+ // TEMP: only do skinned
+ GPU_EncodeBindPipeline(enc, storage->pbr_skinned_pipeline);
+
+ // Feed shader data
+ Mat4 view, proj;
+ u32x2 dimensions = GPU_Swapchain_GetDimensions();
+ Camera_ViewProj(&camera, (f32)dimensions.x, (f32)dimensions.y, &view, &proj);
+ Binding_Camera camera_data = { .view = view,
+ .projection = proj,
+ .viewPos = vec4(camera.position.x, camera.position.y,
+ camera.position.z, 1.0) };
+ GPU_EncodeBindShaderData(enc, 0, Binding_Camera_GetLayout(&camera_data));
+
+ Vec3 light_color = vec3(300.0, 300.0, 300.0);
+ Binding_Lights
+ lights_data = { .pointLights = {
+ // FIXME: add lights to our RenderScene structure. for now these are
+ // hardcoded
+ (pbr_point_light){ .pos = vec3(0.0, 6.0, 6.0), .color = light_color },
+ (pbr_point_light){ .pos = vec3(-10, 10, 10), .color = light_color },
+ (pbr_point_light){ .pos = vec3(10, -10, 10), .color = light_color },
+ (pbr_point_light){ .pos = vec3(-10, -10, 10), .color = light_color },
+ } };
+ GPU_EncodeBindShaderData(enc, 3, Binding_Lights_GetLayout(&lights_data));
+
+ // TODO: Add shadowmap texture to uniforms
+ Mesh_pool* mesh_pool = Render_GetMeshPool();
+ Material_pool* material_pool = Render_GetMaterialPool();
+
+ for (size_t ent_i = 0; ent_i < entity_count; ent_i++) {
+ RenderEnt renderable = entities[ent_i];
+ Mesh* mesh = Mesh_pool_get(mesh_pool, renderable.mesh);
+ Material* mat = Material_pool_get(material_pool, renderable.material);
+
+ // upload material data
+ PBRMaterialUniforms material_data = { .mat = *mat };
+ GPU_EncodeBindShaderData(enc, 2, PBRMaterial_GetLayout(&material_data));
+
+ // upload model transform
+ Binding_Model model_data = { .model = renderable.affine };
+ GPU_EncodeBindShaderData(enc, 1, Binding_Model_GetLayout(&model_data));
+
+ // Skinning matrices
+
+ // 1. calculate matrices
+ AnimDataUniform anim_data = { 0 };
+ CASSERT(renderable.armature);
+ Armature* skeleton = renderable.armature;
+ // Skip the first one as we assume its root for this test
+ for (int j_i = 1; j_i < skeleton->joints->len; j_i++) {
+ Joint* j = &skeleton->joints->data[j_i];
+ j->local_transform = transform_to_mat(&j->transform_components);
+ Mat4 m = mat4_mult(j->local_transform, j->inverse_bind_matrix);
+ Joint* p = &skeleton->joints->data[j->parent];
+ j->local_transform = mat4_mult(j->local_transform, p->local_transform);
+ printf("Quat %f \n", j->transform_components.rotation.z);
+ }
+
+ // 2. bind and upload
+ for (int j_i = 1; j_i < skeleton->joints->len; j_i++) {
+ anim_data.bone_matrices[j_i] = skeleton->joints->data[j_i].local_transform;
+ }
+ GPU_EncodeBindShaderData(enc, 3, AnimData_GetLayout(&anim_data));
+
+ // set buffers
+ GPU_EncodeSetVertexBuffer(enc, mesh->vertex_buffer);
+ GPU_EncodeSetIndexBuffer(enc, mesh->index_buffer);
+ // draw
+ GPU_EncodeDrawIndexedTris(enc, mesh->geometry.index_count);
+ }
+
+ GPU_CmdEncoder_EndRender(enc);
+}
+
+void PBRMaterial_BindData(ShaderDataLayout* layout, const void* data) {
+ PBRMaterialUniforms* d = (PBRMaterialUniforms*)data;
+ CASSERT(data);
+ CASSERT(layout->binding_count == 5);
+
+ TextureHandle white1x1 = Render_GetWhiteTexture();
+ if (d->mat.albedo_map.raw != INVALID_TEX_HANDLE.raw) {
+ layout->bindings[0].data.texture.handle = d->mat.albedo_map;
+ } else {
+ layout->bindings[0].data.texture.handle = white1x1;
+ }
+ // TODO .. the rest
+}
+
+ShaderDataLayout PBRMaterial_GetLayout(void* data) {
+ PBRMaterialUniforms* d = (PBRMaterialUniforms*)data;
+ bool has_data = data != NULL;
+
+ ShaderBinding b1 = {
+ .label = "albedoMap",
+ .kind = BINDING_TEXTURE,
+ };
+ ShaderBinding b2 = {
+ .label = "metallicRoughnessMap",
+ .kind = BINDING_TEXTURE,
+ };
+ ShaderBinding b3 = {
+ .label = "aoMap",
+ .kind = BINDING_TEXTURE,
+ };
+ ShaderBinding b4 = {
+ .label = "normalMap",
+ .kind = BINDING_TEXTURE,
+ };
+ ShaderBinding b5 = { .label = "PBR_Params",
+ .kind = BINDING_BYTES,
+ .data.bytes.size = sizeof(PBR_Params) };
+
+ if (has_data) {
+ TextureHandle white1x1 = Render_GetWhiteTexture();
+ if (d->mat.albedo_map.raw != INVALID_TEX_HANDLE.raw) {
+ b1.data.texture.handle = d->mat.albedo_map;
+ } else {
+ b1.data.texture.handle = white1x1;
+ }
+
+ if (d->mat.metallic_roughness_map.raw != INVALID_TEX_HANDLE.raw) {
+ b2.data.texture.handle = d->mat.metallic_roughness_map;
+ } else {
+ b2.data.texture.handle = white1x1;
+ }
+
+ if (d->mat.ambient_occlusion_map.raw != INVALID_TEX_HANDLE.raw) {
+ b3.data.texture.handle = d->mat.ambient_occlusion_map;
+ } else {
+ b3.data.texture.handle = white1x1;
+ }
+
+ if (d->mat.normal_map.raw != INVALID_TEX_HANDLE.raw) {
+ b4.data.texture.handle = d->mat.normal_map;
+ } else {
+ b4.data.texture.handle = white1x1;
+ }
+
+ arena* frame = Render_GetFrameArena();
+ PBR_Params* params = arena_alloc(frame, sizeof(PBR_Params));
+ params->albedo = d->mat.base_colour;
+ params->metallic = d->mat.metallic;
+ params->roughness = d->mat.roughness;
+ params->ambient_occlusion = d->mat.ambient_occlusion;
+ b5.data.bytes.data = params;
+ }
+
+ return (ShaderDataLayout){ .bindings = { b1, b2, b3, b4, b5 }, .binding_count = 5 };
+}
+
+Material PBRMaterialDefault() {
+ return (Material){ .name = "Standard Material",
+ .kind = MAT_PBR,
+ .base_colour = vec3(1.0, 1.0, 1.0),
+ .metallic = 0.0,
+ .roughness = 0.5,
+ .ambient_occlusion = 0.0,
+ .albedo_map = INVALID_TEX_HANDLE,
+ .metallic_roughness_map = INVALID_TEX_HANDLE,
+ .normal_map = INVALID_TEX_HANDLE,
+ .ambient_occlusion_map = INVALID_TEX_HANDLE };
+}
diff --git a/archive/src/render/pbr.h b/archive/src/render/pbr.h
new file mode 100644
index 0000000..5a21533
--- /dev/null
+++ b/archive/src/render/pbr.h
@@ -0,0 +1,70 @@
+/**
+ * @file pbr.h
+ * @brief PBR render pass and uniforms
+ */
+
+#pragma once
+#include "backend_opengl.h"
+#include "camera.h"
+#include "defines.h"
+#include "maths_types.h"
+#include "ral_types.h"
+#include "render_types.h"
+
+// --- Public API
+
+/** @brief Holds data for the PBR pipeline */
+typedef struct PBR_Storage {
+ GPU_Renderpass* pbr_pass;
+ GPU_Pipeline* pbr_static_pipeline;
+ GPU_Pipeline* pbr_skinned_pipeline;
+} PBR_Storage;
+
+typedef struct PBRMaterialUniforms {
+ Material mat;
+} PBRMaterialUniforms;
+
+/** @brief */
+PUB void PBR_Init(PBR_Storage* storage);
+
+// NOTE: For simplicity's sake we will render this pass directly to the default framebuffer
+// internally this defers to `PBR_Execute()`
+PUB void PBR_Run(PBR_Storage* storage
+ // light data
+ // camera
+ // geometry
+ // materials
+);
+
+/** @brief Parameters that get passed as a uniform block to the PBR shader */
+typedef struct PBR_Params {
+ Vec3 albedo;
+ f32 metallic;
+ f32 roughness;
+ f32 ambient_occlusion;
+} PBR_Params;
+
+/** @brief Textures that will get passed into the PBR shader if they're not `INVALID_TEX_HANDLE` */
+typedef struct PBR_Textures {
+ TextureHandle albedo_map;
+ TextureHandle normal_map;
+ bool metal_roughness_combined;
+ TextureHandle metallic_map;
+ TextureHandle roughness_map;
+ TextureHandle ao_map;
+} PBR_Textures;
+
+/** @brief Returns a default white matte material */
+PUB Material PBRMaterialDefault();
+
+PUB ShaderDataLayout PBRMaterial_GetLayout(void* data);
+
+// --- Internal
+
+GPU_Renderpass* PBR_RPassCreate(); /** @brief Create the PBR Renderpass */
+
+void PBR_PipelinesCreate(PBR_Storage* storage,
+ GPU_Renderpass* rpass); /** @brief Create PBR Pipelines */
+
+void PBR_Execute(PBR_Storage* storage, Camera camera, TextureHandle shadowmap_tex,
+ RenderEnt* entities, size_t entity_count);
diff --git a/archive/src/render/render.c b/archive/src/render/render.c
new file mode 100644
index 0000000..af636a8
--- /dev/null
+++ b/archive/src/render/render.c
@@ -0,0 +1,359 @@
+/**
+ * @brief
+ */
+
+#include "render.h"
+#include <assert.h>
+#include <glfw3.h>
+#include <stdio.h>
+#include "camera.h"
+#include "core.h"
+#include "grid.h"
+#include "immdraw.h"
+#include "log.h"
+#include "maths.h"
+#include "maths_types.h"
+#include "mem.h"
+#include "pbr.h"
+#include "ral_common.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+#include "render_types.h"
+#include "shadows.h"
+#include "terrain.h"
+
+#define STB_IMAGE_IMPLEMENTATION
+#include <stb_image.h>
+
+#define FRAME_ARENA_SIZE MB(1)
+#define POOL_SIZE_BYTES \
+ MB(10) // we will reserve 10 megabytes up front to store resource, mesh, and material pools
+#define MAX_MESHES 1024
+#define MAX_MATERIALS 256
+
+extern Core g_core;
+
+struct Renderer {
+ struct GLFWwindow* window;
+ RendererConfig config;
+ GPU_Device device;
+ GPU_Swapchain swapchain;
+ GPU_Renderpass* default_renderpass;
+ bool frame_aborted;
+ RenderMode render_mode;
+ RenderScene scene;
+ PBR_Storage* pbr;
+ Shadow_Storage* shadows;
+ Terrain_Storage* terrain;
+ Grid_Storage* grid;
+ Immdraw_Storage* immediate;
+ // Text_Storage* text;
+ ResourcePools* resource_pools;
+ Mesh_pool mesh_pool;
+ Material_pool material_pool;
+ arena frame_arena;
+ TextureHandle white_1x1;
+ TextureHandle black_1x1;
+};
+
+Renderer* get_renderer() { return g_core.renderer; }
+
+bool Renderer_Init(RendererConfig config, Renderer* ren, GLFWwindow** out_window,
+ GLFWwindow* optional_window) {
+ INFO("Renderer init");
+ ren->render_mode = RENDER_MODE_DEFAULT;
+
+ ren->frame_arena = arena_create(malloc(FRAME_ARENA_SIZE), FRAME_ARENA_SIZE);
+
+ // init resource pools
+ DEBUG("Initialise GPU resource pools");
+ arena pool_arena = arena_create(malloc(POOL_SIZE_BYTES), POOL_SIZE_BYTES);
+ ren->resource_pools = arena_alloc(&pool_arena, sizeof(struct ResourcePools));
+ ResourcePools_Init(&pool_arena, ren->resource_pools);
+ ren->mesh_pool = Mesh_pool_create(&pool_arena, MAX_MESHES, sizeof(Mesh));
+ ren->material_pool = Material_pool_create(&pool_arena, MAX_MATERIALS, sizeof(Material));
+
+ // GLFW window creation
+ GLFWwindow* window;
+ if (optional_window != NULL) {
+ INFO("GLFWwindow pointer was provided!!!! Skipping generic glfw init..");
+ window = optional_window;
+ } else {
+ INFO("No GLFWwindow provided - creating one");
+ // 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
+
+ window = glfwCreateWindow(config.scr_width, config.scr_height, config.window_name, NULL, NULL);
+ INFO("Window created");
+ if (window == NULL) {
+ ERROR("Failed to create GLFW window\n");
+ glfwTerminate();
+ return false;
+ }
+ }
+
+ ren->window = window;
+ *out_window = window;
+
+ glfwMakeContextCurrent(ren->window);
+
+ DEBUG("Set up GLFW window callbacks");
+ glfwSetWindowSizeCallback(window, Render_WindowSizeChanged);
+
+ // set the RAL backend up
+ if (!GPU_Backend_Init(config.window_name, window, ren->resource_pools)) {
+ return false;
+ }
+
+ GPU_Device_Create(&ren->device);
+ GPU_Swapchain_Create(&ren->swapchain);
+
+ // set up default scene
+ Camera default_cam =
+ Camera_Create(vec3(0.0, 2.0, 4.0), vec3_normalise(vec3(0.0, -2.0, -4.0)), VEC3_Y, 45.0);
+ SetCamera(default_cam);
+ DirectionalLight default_light = { /* TODO */ };
+ SetMainLight(default_light);
+
+ // create our renderpasses
+ ren->shadows = malloc(sizeof(Shadow_Storage));
+ Shadow_Init(ren->shadows, 1024, 1024);
+
+ ren->pbr = calloc(1, sizeof(PBR_Storage));
+ PBR_Init(ren->pbr);
+
+ ren->terrain = calloc(1, sizeof(Terrain_Storage));
+ Terrain_Init(ren->terrain);
+
+ // FIXME
+ // ren->grid = calloc(1, sizeof(Grid_Storage));
+ // Grid_Init(ren->grid);
+
+ ren->immediate = calloc(1, sizeof(Immdraw_Storage));
+ Immdraw_Init(ren->immediate);
+
+ // load default textures
+ ren->white_1x1 = TextureLoadFromFile("assets/textures/white1x1.png");
+ ren->black_1x1 = TextureLoadFromFile("assets/textures/black1x1.png");
+
+ return true;
+}
+
+void Renderer_Shutdown(Renderer* ren) {
+ free(ren->shadows);
+ DEBUG("Freed Shadows storage");
+ free(ren->pbr);
+ DEBUG("Freed PBR storage");
+ free(ren->terrain);
+ DEBUG("Freed Terrain storage");
+ free(ren->immediate);
+ DEBUG("Freed Immdraw storage");
+ arena_free_storage(&ren->frame_arena);
+ DEBUG("Freed frame allocator buffer");
+}
+size_t Renderer_GetMemReqs() { return sizeof(Renderer); }
+
+void Render_WindowSizeChanged(GLFWwindow* window, i32 new_width, i32 new_height) {
+ (void)window;
+ INFO("Window size changed callback");
+ // Renderer* ren = Core_GetRenderer(&g_core);
+ GPU_Swapchain_Resize(new_width, new_height);
+}
+
+void Render_FrameBegin(Renderer* ren) {
+ arena_free_all(&ren->frame_arena);
+ ren->frame_aborted = false;
+ if (!GPU_Backend_BeginFrame()) {
+ ren->frame_aborted = true;
+ WARN("Frame aborted");
+ return;
+ }
+}
+void Render_FrameEnd(Renderer* ren) {
+ if (ren->frame_aborted) {
+ return;
+ }
+
+ GPU_CmdEncoder* enc = GPU_GetDefaultEncoder();
+
+ GPU_Backend_EndFrame();
+}
+void Render_RenderEntities(RenderEnt* entities, size_t entity_count) {
+ Renderer* ren = get_renderer();
+ RenderScene scene = ren->scene;
+
+ // FUTURE: Depth pre-pass
+
+ Shadow_Storage* shadow_storage = Render_GetShadowStorage();
+ shadow_storage->enabled = false;
+ TextureHandle sun_shadowmap =
+ shadow_storage->enabled ? Shadow_GetShadowMapTexture(shadow_storage) : INVALID_TEX_HANDLE;
+
+ PBR_Execute(ren->pbr, scene.camera, sun_shadowmap, entities, entity_count);
+}
+
+TextureData TextureDataLoad(const char* path, bool invert_y) {
+ TRACE("Load texture %s", path);
+
+ // load the file data
+ 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);
+ if (data) {
+ DEBUG("loaded texture: %s", path);
+ } else {
+ WARN("failed to load texture");
+ }
+
+ // printf("width: %d height: %d num channels: %d\n", width, height, num_channels);
+
+ unsigned int channel_type;
+ GPU_TextureFormat format;
+ if (num_channels == 4) {
+ channel_type = GL_RGBA;
+ format = TEXTURE_FORMAT_8_8_8_8_RGBA_UNORM;
+ } else {
+ channel_type = GL_RGB;
+ format = TEXTURE_FORMAT_8_8_8_RGB_UNORM;
+ }
+ TextureDesc desc = {
+ .extents = { width, height },
+ .format = format,
+ .num_channels = num_channels,
+ .tex_type = TEXTURE_TYPE_2D,
+ };
+
+ return (TextureData){ .description = desc, .image_data = data };
+}
+
+TextureHandle TextureLoadFromFile(const char* path) {
+ TextureData tex_data = TextureDataLoad(path, false);
+ TextureHandle h = GPU_TextureCreate(tex_data.description, true, tex_data.image_data);
+ return h;
+}
+
+Mesh Mesh_Create(Geometry* geometry, bool free_on_upload) {
+ Mesh m = { 0 };
+
+ // Create and upload vertex buffer
+ size_t vert_bytes = geometry->vertices->len * sizeof(Vertex);
+ INFO("Creating vertex buffer with size %d (%d x %d)", vert_bytes, geometry->vertices->len,
+ sizeof(Vertex));
+ m.vertex_buffer =
+ GPU_BufferCreate(vert_bytes, BUFFER_VERTEX, BUFFER_FLAG_GPU, geometry->vertices->data);
+
+ // Create and upload index buffer
+ if (geometry->has_indices) {
+ size_t index_bytes = geometry->indices->len * sizeof(u32);
+ INFO("Creating index buffer with size %d (len: %d)", index_bytes, geometry->indices->len);
+ m.index_buffer =
+ GPU_BufferCreate(index_bytes, BUFFER_INDEX, BUFFER_FLAG_GPU, geometry->indices->data);
+ }
+
+ m.is_uploaded = true;
+ m.geometry = *geometry; // clone geometry data and store on Mesh struct
+ if (free_on_upload) {
+ Geometry_Destroy(geometry);
+ }
+ return m;
+}
+
+void Geometry_Destroy(Geometry* geometry) {
+ if (geometry->indices) {
+ u32_darray_free(geometry->indices);
+ }
+ if (geometry->vertices) {
+ Vertex_darray_free(geometry->vertices);
+ }
+}
+PUB MeshHandle Mesh_Insert(Mesh* mesh) { return Mesh_pool_insert(Render_GetMeshPool(), mesh); }
+PUB MaterialHandle Material_Insert(Material* material) {
+ return Material_pool_insert(Render_GetMaterialPool(), material);
+}
+Mesh* Mesh_Get(MeshHandle handle) { return Mesh_pool_get(Render_GetMeshPool(), handle); }
+
+void Mesh_DebugPrint(Mesh* mesh) {
+ printf("Mesh %d vertices %d indices %d joints \n", mesh->geometry.vertices->len,
+ mesh->geometry.indices->len);
+}
+
+size_t ModelExtractRenderEnts(RenderEnt_darray* entities, ModelHandle model_handle, Mat4 affine,
+ RenderEntityFlags flags) {
+ Model* model = MODEL_GET(model_handle);
+ for (u32 i = 0; i < model->mesh_count; i++) {
+ Mesh* m = Mesh_pool_get(Render_GetMeshPool(), model->meshes[i]);
+ RenderEnt data = { .mesh = model->meshes[i],
+ .material = m->material,
+ .affine = affine,
+ // .bounding_box
+ .flags = flags };
+ RenderEnt_darray_push(entities, data);
+ }
+ return model->mesh_count; // how many RenderEnts we pushed
+}
+
+void SetCamera(Camera camera) { g_core.renderer->scene.camera = camera; }
+void SetMainLight(DirectionalLight light) { g_core.renderer->scene.sun = light; }
+
+arena* GetRenderFrameArena(Renderer* r) { return &r->frame_arena; }
+
+RenderScene* Render_GetScene() {
+ Renderer* ren = Core_GetRenderer(&g_core);
+ return &ren->scene;
+}
+
+Shadow_Storage* Render_GetShadowStorage() {
+ Renderer* ren = Core_GetRenderer(&g_core);
+ return ren->shadows;
+}
+
+Terrain_Storage* Render_GetTerrainStorage() {
+ Renderer* ren = Core_GetRenderer(&g_core);
+ return ren->terrain;
+}
+
+Grid_Storage* Render_GetGridStorage() {
+ Renderer* ren = Core_GetRenderer(&g_core);
+ return ren->grid;
+}
+
+Immdraw_Storage* Render_GetImmdrawStorage() {
+ Renderer* ren = Core_GetRenderer(&g_core);
+ return ren->immediate;
+}
+
+TextureHandle Render_GetWhiteTexture() {
+ Renderer* ren = Core_GetRenderer(&g_core);
+ return ren->white_1x1;
+}
+
+/** @return an arena allocator that gets cleared at the beginning of every render frame */
+arena* Render_GetFrameArena() {
+ Renderer* ren = Core_GetRenderer(&g_core);
+ return &ren->frame_arena;
+}
+
+Mesh_pool* Render_GetMeshPool() {
+ Renderer* ren = Core_GetRenderer(&g_core);
+ return &ren->mesh_pool;
+}
+Material_pool* Render_GetMaterialPool() {
+ Renderer* ren = Core_GetRenderer(&g_core);
+ return &ren->material_pool;
+}
+
+void Render_SetRenderMode(RenderMode mode) {
+ Renderer* ren = Core_GetRenderer(&g_core);
+ ren->render_mode = mode;
+}
diff --git a/archive/src/render/render.h b/archive/src/render/render.h
new file mode 100644
index 0000000..d752f8b
--- /dev/null
+++ b/archive/src/render/render.h
@@ -0,0 +1,151 @@
+/**
+ * @brief
+ */
+
+#pragma once
+#include "camera.h"
+#include "defines.h"
+#include "grid.h"
+#include "immdraw.h"
+#include "maths_types.h"
+#include "ral_types.h"
+#include "render_types.h"
+#include "shadows.h"
+
+typedef struct Renderer Renderer;
+typedef struct GLFWwindow GLFWwindow;
+typedef struct RendererConfig {
+ const char* window_name;
+ u32 scr_width, scr_height;
+ Vec3 clear_colour;
+} RendererConfig;
+
+typedef struct RenderFlags {
+ bool wireframe;
+} RenderFlags;
+
+typedef struct RenderCtx {
+ Mat4 view;
+ Mat4 projection;
+} RenderCtx;
+
+/** @brief Holds globally bound data for rendering a scene. Typically held by the renderer.
+ * Whenever you call draw functions you can think of this as an implicit parameter. */
+typedef struct RenderScene {
+ Camera camera;
+ DirectionalLight sun;
+} RenderScene;
+
+PUB void SetCamera(Camera camera);
+PUB void SetMainLight(DirectionalLight light);
+
+// #define MESH_GET(h) (Mesh_pool_get(g_core.renderer->meshes, h))
+// #define MATERIAL_GET(h) (Material_pool_get(g_core.renderer->material, h))
+
+// --- Lifecycle
+
+PUB bool Renderer_Init(RendererConfig config, Renderer* renderer, GLFWwindow** out_window,
+ GLFWwindow* optional_window);
+PUB void Renderer_Shutdown(Renderer* renderer);
+PUB size_t Renderer_GetMemReqs();
+void Render_WindowSizeChanged(GLFWwindow* window, i32 new_width, i32 new_height);
+
+// internal init functions
+void DefaultPipelinesInit(Renderer* renderer);
+
+// NOTE: All of these functions grab the Renderer instance off the global Core
+PUB void Render_FrameBegin(Renderer* renderer);
+PUB void Render_FrameEnd(Renderer* renderer);
+
+/** @brief */
+PUB void Render_RenderEntities(RenderEnt* entities, size_t entity_count);
+
+// TODO: Render_FrameDraw(); - this will
+
+// --- Resources
+
+PUB TextureData TextureDataLoad(const char* path, bool invert_y);
+PUB void TextureUpload(TextureHandle handle, size_t n_bytes, const void* data);
+PUB TextureHandle TextureLoadFromFile(const char* path);
+PUB ModelHandle ModelLoad(const char* debug_name, const char* filepath);
+
+// --- Rendering Data
+
+PUB Mesh Mesh_Create(Geometry* geometry, bool free_on_upload);
+PUB void Mesh_Delete(Mesh* mesh);
+Mesh* Mesh_Get(MeshHandle handle);
+void Geometry_Destroy(Geometry* geometry);
+MeshHandle Mesh_Insert(Mesh* mesh);
+void Mesh_DebugPrint(Mesh* mesh);
+MaterialHandle Material_Insert(Material* material);
+
+/** @brief gets render entities from a model and pushes them into a dynamic array for rendering */
+size_t ModelExtractRenderEnts(RenderEnt_darray* entities, ModelHandle model_handle, Mat4 affine,
+ RenderEntityFlags flags);
+
+// --- Drawing
+
+// NOTE: These functions use the globally bound camera in RenderScene
+PUB void DrawMesh(Mesh* mesh, Material* material, Mat4 model);
+
+/** @brief the renderer does some internal bookkeeping for terrain so we use the terrain
+ stored on the Renderer rather than accept it as a parameter */
+PUB void Render_DrawTerrain();
+
+// --- Getters (not in love with this but I'm finding keeping Renderer internals private to be okay)
+arena* GetRenderFrameArena(Renderer* r);
+
+typedef struct RenderScene RenderScene;
+typedef struct Shadow_Storage Shadow_Storage;
+typedef struct Terrain_Storage Terrain_Storage;
+
+RenderScene* Render_GetScene();
+Shadow_Storage* Render_GetShadowStorage();
+Terrain_Storage* Render_GetTerrainStorage();
+Grid_Storage* Render_GetGridStorage();
+Immdraw_Storage* Render_GetImmdrawStorage();
+TextureHandle Render_GetWhiteTexture();
+arena* Render_GetFrameArena();
+Mesh_pool* Render_GetMeshPool();
+Material_pool* Render_GetMaterialPool();
+
+// --- Setters
+void Render_SetRenderMode(RenderMode mode);
+
+// -------------------------------------------------
+
+// Frame lifecycle on CPU
+
+// 1. extract
+// 2. culling
+// 3. render
+// 4. dispatch (combined with render for now)
+
+// typedef struct Cull_Result {
+// u64 n_visible_objects;
+// u64 n_culled_objects;
+// u32* visible_ent_indices; // allocated on frame arena
+// size_t index_count;
+// } Cull_Result;
+
+// // everything that can be in the world, knows how to extract rendering data
+// typedef void (*ExtractRenderData)(void* world_data);
+
+// typedef struct Renderer Renderer;
+
+// /** @brief Produces a smaller set of only those meshes visible in the camera frustum on the CPU
+// */ Cull_Result Frame_Cull(Renderer* ren, RenderEnt* entities, size_t entity_count, Camera*
+// camera);
+
+// Cull_Result Frame_Cull(Renderer* ren, RenderEnt* entities, size_t entity_count, Camera* camera) {
+// // TODO: u32 chunk_count = Tpool_GetNumWorkers();
+
+// arena* frame_arena = GetRenderFrameArena(ren);
+
+// Cull_Result result = { 0 };
+// result.visible_ent_indices = arena_alloc(
+// frame_arena, sizeof(u32) * entity_count); // make space for if all ents are visible
+
+// assert((result.n_visible_objects + result.n_culled_objects == entity_count));
+// return result;
+// }
diff --git a/archive/src/render/render_types.h b/archive/src/render/render_types.h
new file mode 100644
index 0000000..bdf9849
--- /dev/null
+++ b/archive/src/render/render_types.h
@@ -0,0 +1,138 @@
+/**
+ * @brief
+ */
+
+#pragma once
+#include "animation.h"
+#include "defines.h"
+#include "maths_types.h"
+#include "mem.h"
+#include "ral_types.h"
+
+// --- Handles
+
+#define INVALID_MODEL_HANDLE ((ModelHandle){ .raw = 9999991 })
+#define INVALID_MATERIAL_HANDLE ((MaterialHandle){ .raw = 9999992 })
+#define INVALID_MESH_HANDLE ((MeshHandle){ .raw = 9999993 })
+
+typedef enum RenderMode {
+ RENDER_MODE_DEFAULT,
+ RENDER_MODE_WIREFRAME,
+ RENDER_MODE_WIREFRAME_ON_LIT,
+ RENDER_MODE_COUNT
+} RenderMode;
+
+typedef struct u32_opt {
+ u32 value;
+ bool has_value;
+} u32_opt;
+
+typedef struct Mesh {
+ BufferHandle vertex_buffer;
+ BufferHandle index_buffer;
+ Geometry geometry; // NULL means it has been freed CPU-side
+ MaterialHandle material;
+ bool is_skinned; // false = its static
+ Armature armature;
+ bool is_uploaded; // has the data been uploaded to the GPU
+} Mesh;
+#ifndef TYPED_MESH_CONTAINERS
+KITC_DECL_TYPED_ARRAY(Mesh)
+TYPED_POOL(Mesh, Mesh)
+#define TYPED_MESH_CONTAINERS
+#endif
+
+typedef struct TextureData {
+ TextureDesc description;
+ void* image_data;
+} TextureData;
+
+// --- Supported materials
+typedef enum MaterialKind {
+ MAT_BLINN_PHONG, // NOTE: we're dropping support for this
+ MAT_PBR, // uses textures for PBR properties
+ MAT_PBR_PARAMS, // uses float values to represent a surface uniformly
+ MAT_COUNT
+} MaterialKind;
+static const char* material_kind_names[] = { "Blinn Phong", "PBR (Textures)", "PBR (Params)",
+ "Count (This should be an error)" };
+
+/**
+ * @brief
+ * @note based on https://google.github.io/filament/Filament.html#materialsystem/standardmodel
+ */
+typedef struct Material {
+ char name[64];
+ MaterialKind kind; // at the moment all materials are PBR materials
+ Vec3 base_colour; // linear RGB {0,0,0} to {1,1,1}
+ f32 metallic;
+ f32 roughness;
+ f32 ambient_occlusion;
+ TextureHandle albedo_map;
+ TextureHandle normal_map;
+ TextureHandle metallic_roughness_map;
+ TextureHandle ambient_occlusion_map;
+} Material;
+
+#ifndef TYPED_MATERIAL_CONTAINERS
+KITC_DECL_TYPED_ARRAY(Material)
+TYPED_POOL(Material, Material)
+#define TYPED_MATERIAL_CONTAINERS
+#endif
+
+/** @brief Convenient wrapper around a number of meshes each with a material */
+typedef struct Model {
+ Str8 name;
+ MeshHandle* meshes;
+ size_t mesh_count;
+ MaterialHandle* materials;
+ size_t material_count;
+ arena anim_arena;
+ AnimationClip_darray* animations;
+} Model;
+#ifndef TYPED_MODEL_ARRAY
+KITC_DECL_TYPED_ARRAY(Model)
+#define TYPED_MODEL_ARRAY
+#endif
+
+// TODO: function to create a model from a single mesh (like when using primitives)
+
+// --- Lights
+typedef struct PointLight {
+ Vec3 position;
+ f32 constant, linear, quadratic;
+ Vec3 ambient;
+ Vec3 diffuse;
+ Vec3 specular;
+} PointLight;
+
+typedef struct DirectionalLight {
+ Vec3 direction;
+ Vec3 ambient;
+ Vec3 diffuse;
+ Vec3 specular;
+} DirectionalLight;
+
+// ---
+
+typedef enum RenderEntityFlag {
+ REND_ENT_CASTS_SHADOWS = 1 << 0,
+ REND_ENT_VISIBLE = 1 << 1,
+} RenderEntityFlag;
+typedef u32 RenderEntityFlags;
+
+/** @brief A renderable 'thing' */
+typedef struct RenderEnt {
+ MeshHandle mesh;
+ MaterialHandle material;
+ /** If NULL, no armature and the mesh is static geometry, else it is to be skinned */
+ Armature* armature;
+ Mat4 affine; // In the future this should be updated by the transform graph
+ Bbox_3D bounding_box;
+ RenderEntityFlags flags;
+} RenderEnt;
+
+#ifndef TYPED_RENDERENT_ARRAY
+KITC_DECL_TYPED_ARRAY(RenderEnt)
+#define TYPED_RENDERENT_ARRAY
+#endif
diff --git a/archive/src/render/shader_layouts.h b/archive/src/render/shader_layouts.h
new file mode 100644
index 0000000..ef94c89
--- /dev/null
+++ b/archive/src/render/shader_layouts.h
@@ -0,0 +1,70 @@
+#pragma once
+#include "maths_types.h"
+#include "ral_types.h"
+
+/** @brief shader layout for camera matrices */
+typedef struct Binding_Camera {
+ Mat4 view;
+ Mat4 projection;
+ Vec4 viewPos;
+} Binding_Camera;
+
+typedef struct Binding_Model {
+ Mat4 model;
+} Binding_Model;
+
+/** @brief data that is handy to have in any shader */
+typedef struct Binding_Globals {
+} Binding_Globals;
+
+typedef struct pbr_point_light {
+ Vec3 pos;
+ f32 pad;
+ Vec3 color;
+ f32 pad2;
+} pbr_point_light;
+
+typedef struct Binding_Lights {
+ pbr_point_light pointLights[4];
+} Binding_Lights;
+
+static ShaderDataLayout Binding_Camera_GetLayout(void* data) {
+ Binding_Camera* d = data;
+ bool has_data = data != NULL;
+
+ ShaderBinding b1 = { .label = "Camera",
+ .kind = BINDING_BYTES,
+ .data.bytes = { .size = sizeof(Binding_Camera) } };
+ if (has_data) {
+ b1.data.bytes.data = d;
+ }
+ return (ShaderDataLayout){ .bindings = { b1 }, .binding_count = 1 };
+}
+
+static ShaderDataLayout Binding_Model_GetLayout(void* data) {
+ Binding_Model* d = data;
+ bool has_data = data != NULL;
+
+ ShaderBinding b1 = { .label = "Model",
+ .kind = BINDING_BYTES,
+ .vis = VISIBILITY_VERTEX,
+ .data.bytes = { .size = sizeof(Binding_Model) } };
+ if (has_data) {
+ b1.data.bytes.data = d;
+ }
+ return (ShaderDataLayout){ .bindings = { b1 }, .binding_count = 1 };
+}
+
+static ShaderDataLayout Binding_Lights_GetLayout(void* data) {
+ Binding_Lights* d = data;
+ bool has_data = data != NULL;
+
+ ShaderBinding b1 = { .label = "Lights",
+ .kind = BINDING_BYTES,
+ .vis = VISIBILITY_FRAGMENT,
+ .data.bytes = { .size = sizeof(Binding_Lights) } };
+ if (has_data) {
+ b1.data.bytes.data = d;
+ }
+ return (ShaderDataLayout){ .bindings = { b1 }, .binding_count = 1 };
+}
diff --git a/archive/src/render/shadows.c b/archive/src/render/shadows.c
new file mode 100644
index 0000000..029eefb
--- /dev/null
+++ b/archive/src/render/shadows.c
@@ -0,0 +1,211 @@
+#include "shadows.h"
+#include <string.h>
+#include "file.h"
+#include "glad/glad.h"
+#include "log.h"
+#include "maths.h"
+#include "maths_types.h"
+#include "primitives.h"
+#include "ral_common.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+#include "render.h"
+#include "render_types.h"
+#include "str.h"
+
+ShaderDataLayout ShadowUniforms_GetLayout(void* data) {
+ ShadowUniforms* d = (ShadowUniforms*)data;
+ bool has_data = data != NULL;
+
+ ShaderBinding b1 = {
+ .label = "ShadowUniforms",
+ .kind = BINDING_BYTES,
+ .vis = VISIBILITY_VERTEX,
+ .data = { .bytes = { .size = sizeof(ShadowUniforms) } }
+ // TODO: split this into two bindings so we can update model matrix independently
+ };
+
+ if (has_data) {
+ b1.data.bytes.data = data;
+ }
+
+ return (ShaderDataLayout){ .binding_count = 1, .bindings = { b1 } };
+}
+
+ShaderDataLayout ShadowDebugQuad_GetLayout(void* data) {
+ TextureHandle* handle = data;
+ bool has_data = data != NULL;
+
+ ShaderBinding b1 = {
+ .label = "depthMap",
+ .kind = BINDING_TEXTURE,
+ .vis = VISIBILITY_FRAGMENT,
+ };
+
+ if (has_data) {
+ b1.data.texture.handle = *handle;
+ }
+
+ return (ShaderDataLayout){ .binding_count = 1, .bindings = { b1 } };
+}
+
+void Shadow_Init(Shadow_Storage* storage, u32 shadowmap_width, u32 shadowmap_height) {
+ memset(storage, 0, sizeof(Shadow_Storage));
+ arena scratch = arena_create(malloc(1024 * 1024), 1024 * 1024);
+
+ TextureDesc depthmap_desc = { .extents = u32x2(shadowmap_width, shadowmap_height),
+ .format = TEXTURE_FORMAT_DEPTH_DEFAULT,
+ .tex_type = TEXTURE_TYPE_2D };
+ DEBUG("Creating depth map texture for shadows");
+ TextureHandle depthmap = GPU_TextureCreate(depthmap_desc, false, NULL);
+ storage->depth_texture = depthmap;
+
+ // -- shadowmap drawing pass
+ GPU_RenderpassDesc rpass_desc = { .default_framebuffer = false,
+ .has_color_target = false,
+ .has_depth_stencil = true,
+ .depth_stencil = depthmap };
+
+ storage->shadowmap_pass = GPU_Renderpass_Create(rpass_desc);
+
+ WARN("About to laod shaders");
+ WARN("Shader paths: %s %s", "assets/shaders/shadows.vert", "assets/shaders/shadows.frag");
+ Str8 vert_path = str8("assets/shaders/shadows.vert");
+ Str8 frag_path = str8("assets/shaders/shadows.frag");
+ str8_opt vertex_shader = str8_from_file(&scratch, vert_path);
+ str8_opt fragment_shader = str8_from_file(&scratch, frag_path);
+ if (!vertex_shader.has_value || !fragment_shader.has_value) {
+ ERROR_EXIT("Failed to load shaders from disk");
+ }
+
+ ShaderDataLayout uniforms = ShadowUniforms_GetLayout(NULL);
+
+ GraphicsPipelineDesc pipeline_desc = {
+ .debug_name = "Shadows Pipeline",
+ .vertex_desc = static_3d_vertex_description(),
+ .data_layouts = { uniforms },
+ .data_layouts_count = 1,
+ .vs = { .debug_name = "Shadows Vert shader",
+ .filepath = vert_path,
+ .code = vertex_shader.contents,
+ .is_spirv = false },
+ .fs = { .debug_name = "Shadows Frag shader",
+ .filepath = frag_path,
+ .code = fragment_shader.contents,
+ .is_spirv = false },
+ };
+ storage->shadowmap_pipeline = GPU_GraphicsPipeline_Create(pipeline_desc, storage->shadowmap_pass);
+
+ // -- debug quad pipeline
+ GPU_RenderpassDesc debug_pass_desc = { .default_framebuffer = true };
+ storage->debugquad_pass = GPU_Renderpass_Create(debug_pass_desc);
+
+ vert_path = str8("assets/shaders/debug_quad.vert");
+ frag_path = str8("assets/shaders/debug_quad.frag");
+ vertex_shader = str8_from_file(&scratch, vert_path);
+ fragment_shader = str8_from_file(&scratch, frag_path);
+ if (!vertex_shader.has_value || !fragment_shader.has_value) {
+ ERROR_EXIT("Failed to load shaders from disk");
+ }
+
+ ShaderDataLayout debugquad_uniforms = ShadowDebugQuad_GetLayout(NULL);
+
+ GraphicsPipelineDesc debugquad_pipeline_desc = {
+ .debug_name = "Shadows debug quad Pipeline",
+ .vertex_desc = static_3d_vertex_description(),
+ .data_layouts = { debugquad_uniforms },
+ .data_layouts_count = 1,
+ .vs = { .debug_name = "depth debug quad vert shader",
+ .filepath = vert_path,
+ .code = vertex_shader.contents,
+ .is_spirv = false },
+ .fs = { .debug_name = "depth debug quad frag shader",
+ .filepath = frag_path,
+ .code = fragment_shader.contents,
+ .is_spirv = false },
+ };
+ storage->debugquad_pipeline =
+ GPU_GraphicsPipeline_Create(debugquad_pipeline_desc, storage->debugquad_pass);
+
+ Geometry quad_geo = Geo_CreatePlane(f32x2(1, 1), 1, 1);
+ // HACK: Swap vertices to make it face us
+ Vertex top0 = quad_geo.vertices->data[0];
+ quad_geo.vertices->data[0] = quad_geo.vertices->data[2];
+ quad_geo.vertices->data[2] = top0;
+ Vertex top1 = quad_geo.vertices->data[1];
+ quad_geo.vertices->data[1] = quad_geo.vertices->data[3];
+ quad_geo.vertices->data[3] = top1;
+ storage->quad = Mesh_Create(&quad_geo, false);
+
+ arena_free_storage(&scratch);
+}
+
+void Shadow_Run(RenderEnt* entities, size_t entity_count) {
+ Shadow_Storage* shadow_storage = Render_GetShadowStorage();
+
+ // calculations
+ RenderScene* render_scene = Render_GetScene();
+ f32 near_plane = 1.0, far_plane = 10.0;
+ // -- Not sure about how we want to handle lights
+ Vec3 light_position = { 1, 4, -1 };
+ // --
+ Mat4 light_projection = mat4_orthographic(-10.0, 10.0, -10.0, 10.0, near_plane, far_plane);
+ Mat4 light_view = mat4_look_at(light_position, VEC3_ZERO, VEC3_Y);
+ Mat4 light_space_matrix = mat4_mult(light_view, light_projection);
+
+ Shadow_ShadowmapExecute(shadow_storage, light_space_matrix, entities, entity_count);
+}
+
+void Shadow_DrawDebugQuad() {
+ Shadow_Storage* shadow_storage = Render_GetShadowStorage();
+
+ GPU_CmdEncoder* enc = GPU_GetDefaultEncoder();
+ GPU_CmdEncoder_BeginRender(enc, shadow_storage->debugquad_pass);
+
+ GPU_EncodeBindPipeline(enc, shadow_storage->debugquad_pipeline);
+ ShaderDataLayout quad_data = ShadowDebugQuad_GetLayout(&shadow_storage->depth_texture);
+ GPU_EncodeBindShaderData(enc, 0, quad_data);
+ GPU_EncodeSetVertexBuffer(enc, shadow_storage->quad.vertex_buffer);
+ GPU_EncodeSetIndexBuffer(enc, shadow_storage->quad.index_buffer);
+ GPU_EncodeDrawIndexedTris(enc, shadow_storage->quad.geometry.indices->len);
+
+ GPU_CmdEncoder_EndRender(enc);
+}
+
+void Shadow_ShadowmapExecute(Shadow_Storage* storage, Mat4 light_space_transform,
+ RenderEnt* entities, size_t entity_count) {
+ GPU_CmdEncoder shadow_encoder = GPU_CmdEncoder_Create();
+
+ GPU_CmdEncoder_BeginRender(&shadow_encoder, storage->shadowmap_pass);
+ // DEBUG("Begin shadowmap renderpass");
+
+ // FIXME: shouldnt be gl specific
+ glClear(GL_DEPTH_BUFFER_BIT);
+
+ GPU_EncodeBindPipeline(&shadow_encoder, storage->shadowmap_pipeline);
+
+ ShadowUniforms uniforms = {
+ .light_space = light_space_transform,
+ .model = mat4_ident() // this will be overwritten for each Model
+ };
+ ShaderDataLayout shader_data = ShadowUniforms_GetLayout(&uniforms);
+
+ for (size_t ent_i = 0; ent_i < entity_count; ent_i++) {
+ RenderEnt renderable = entities[ent_i];
+ if (renderable.flags && REND_ENT_CASTS_SHADOWS) {
+ // Model* model = MODEL_GET(renderable.model);
+
+ uniforms.model = renderable.affine; // update the model transform
+
+ Mesh* mesh = Mesh_pool_get(Render_GetMeshPool(), renderable.mesh);
+ GPU_EncodeBindShaderData(&shadow_encoder, 0, shader_data);
+ GPU_EncodeSetVertexBuffer(&shadow_encoder, mesh->vertex_buffer);
+ GPU_EncodeSetIndexBuffer(&shadow_encoder, mesh->index_buffer);
+ GPU_EncodeDrawIndexedTris(&shadow_encoder, mesh->geometry.indices->len);
+ }
+ }
+
+ GPU_CmdEncoder_EndRender(&shadow_encoder); // end renderpass
+}
+
+TextureHandle Shadow_GetShadowMapTexture(Shadow_Storage* storage) { return storage->depth_texture; }
diff --git a/archive/src/render/shadows.h b/archive/src/render/shadows.h
new file mode 100644
index 0000000..0482d10
--- /dev/null
+++ b/archive/src/render/shadows.h
@@ -0,0 +1,48 @@
+/**
+ * @brief Functions for adding shadows to scene rendering.
+ */
+
+#pragma once
+#include "defines.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+#include "render_types.h"
+
+typedef struct Shadow_Storage {
+ bool enabled;
+ GPU_Renderpass* shadowmap_pass;
+ GPU_Pipeline* shadowmap_pipeline;
+ TextureHandle depth_texture;
+ bool debug_quad_enabled;
+ Mesh quad;
+ GPU_Renderpass* debugquad_pass;
+ GPU_Pipeline* debugquad_pipeline;
+ // TODO: Some statistics tracking
+} Shadow_Storage;
+
+typedef struct ShadowUniforms {
+ Mat4 light_space;
+ Mat4 model;
+} ShadowUniforms;
+
+typedef struct Camera Camera;
+typedef struct Mat4 Mat4;
+
+// --- Public API
+PUB void Shadow_Init(Shadow_Storage* storage, u32 shadowmap_width, u32 shadowmap_height);
+
+/** @brief Run shadow map generation for given entities, and store in a texture.
+ * @note Uses active directional light for now */
+PUB void Shadow_Run(RenderEnt* entities, size_t entity_count);
+
+PUB void Shadow_DrawDebugQuad();
+
+/** @brief Get the shadow texture generated from shadowmap pass */
+PUB TextureHandle Shadow_GetShadowMapTexture(Shadow_Storage* storage);
+
+// --- Internal
+GPU_Renderpass* Shadow_RPassCreate(); // Creates the render pass
+GPU_Pipeline* Shadow_PipelineCreate(GPU_Renderpass* rpass); // Creates the pipeline
+void Shadow_ShadowmapExecute(Shadow_Storage* storage, Mat4 light_space_transform,
+ RenderEnt* entities, size_t entity_count);
+void Shadow_RenderDebugQuad();
diff --git a/archive/src/render/skybox.c b/archive/src/render/skybox.c
new file mode 100644
index 0000000..b4e1e42
--- /dev/null
+++ b/archive/src/render/skybox.c
@@ -0,0 +1,161 @@
+#include "skybox.h"
+#include <assert.h>
+#include "file.h"
+#include "glad/glad.h"
+#include "log.h"
+#include "maths.h"
+#include "primitives.h"
+#include "ral_common.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+#include "render.h"
+#include "render_types.h"
+#include "shader_layouts.h"
+
+float skyboxVertices[] = {
+ // positions
+ -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
+
+ -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
+ -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f,
+
+ 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f,
+
+ -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f,
+
+ -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f,
+ 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f,
+
+ -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f,
+ 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f
+};
+
+static const char* faces[6] = { "assets/demo/skybox/right.jpg", "assets/demo/skybox/left.jpg",
+ "assets/demo/skybox/top.jpg", "assets/demo/skybox/bottom.jpg",
+ "assets/demo/skybox/front.jpg", "assets/demo/skybox/back.jpg" };
+
+Skybox Skybox_Create(const char** face_paths, int n) {
+ INFO("Creating a skybox");
+ CASSERT_MSG(
+ n == 6,
+ "We only support full cubemaps for now"); // ! we're only supporting a full cubemap for now
+
+ // -- cube verts
+ Geometry geom = { .format = VERTEX_POS_ONLY, // doesnt matter
+ .has_indices = false,
+ .indices = NULL,
+ .vertices = Vertex_darray_new(36) };
+ for (u32 i = 0; i < (36 * 3); i += 3) {
+ Vertex_darray_push(
+ geom.vertices,
+ (Vertex){ .pos_only = { .position = vec3(skyboxVertices[i], skyboxVertices[i + 1],
+ skyboxVertices[i + 2]) } });
+ }
+ Mesh cube = Mesh_Create(&geom, false);
+
+ // -- cubemap texture
+ TextureHandle handle;
+ GPU_Texture* tex = GPU_TextureAlloc(&handle);
+ glBindTexture(GL_TEXTURE_CUBE_MAP, tex->id);
+
+ for (unsigned int i = 0; i < n; i++) {
+ TextureData data = TextureDataLoad(face_paths[i], false);
+ assert(data.description.format == TEXTURE_FORMAT_8_8_8_RGB_UNORM);
+ glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, data.description.extents.x,
+ data.description.extents.y, 0, GL_RGB, GL_UNSIGNED_BYTE, data.image_data);
+ }
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+
+ // shader pipeline
+ GPU_RenderpassDesc rpass_desc = {
+ .default_framebuffer = true,
+ };
+ GPU_Renderpass* pass = GPU_Renderpass_Create(rpass_desc);
+
+ arena scratch = arena_create(malloc(1024 * 1024), 1024 * 1024);
+
+ Str8 vert_path = str8("assets/shaders/skybox.vert");
+ Str8 frag_path = str8("assets/shaders/skybox.frag");
+ str8_opt vertex_shader = str8_from_file(&scratch, vert_path);
+ str8_opt fragment_shader = str8_from_file(&scratch, frag_path);
+ if (!vertex_shader.has_value || !fragment_shader.has_value) {
+ ERROR_EXIT("Failed to load shaders from disk")
+ }
+
+ ShaderDataLayout camera_data = Binding_Camera_GetLayout(NULL);
+ ShaderDataLayout shader_data = Skybox_GetLayout(NULL);
+
+ VertexDescription builder = { .debug_label = "pos only" };
+ VertexDesc_AddAttr(&builder, "inPosition", ATTR_F32x3);
+ builder.use_full_vertex_size = true;
+
+ GraphicsPipelineDesc pipeline_desc = {
+ .debug_name = "Skybox pipeline",
+ .vertex_desc = builder,
+ .data_layouts = { shader_data, camera_data },
+ .data_layouts_count = 2,
+ .vs = { .debug_name = "Skybox Vertex Shader",
+ .filepath = vert_path,
+ .code = vertex_shader.contents },
+ .fs = { .debug_name = "Skybox Fragment Shader",
+ .filepath = frag_path,
+ .code = fragment_shader.contents },
+ .wireframe = false,
+ .depth_test = true,
+ };
+
+ GPU_Pipeline* pipeline = GPU_GraphicsPipeline_Create(pipeline_desc, pass);
+
+ return (Skybox){ .cube = cube, .texture = handle, .pipeline = pipeline };
+}
+
+Skybox Skybox_Default() { return Skybox_Create(faces, 6); }
+
+void Skybox_Draw(Skybox* skybox, Camera camera) {
+ GPU_CmdEncoder* enc = GPU_GetDefaultEncoder();
+ glDepthFunc(GL_LEQUAL);
+ GPU_CmdEncoder_BeginRender(enc, skybox->pipeline->renderpass);
+ GPU_EncodeBindPipeline(enc, skybox->pipeline);
+ GPU_EncodeSetDefaults(enc);
+
+ // Shader data
+
+ Mat4 view, proj;
+ u32x2 dimensions = GPU_Swapchain_GetDimensions();
+ Camera_ViewProj(&camera, dimensions.x, dimensions.y, &view, &proj);
+ Mat4 new = mat4_ident();
+ new.data[0] = view.data[0];
+ new.data[1] = view.data[1];
+ new.data[2] = view.data[2];
+ new.data[4] = view.data[4];
+ new.data[5] = view.data[5];
+ new.data[6] = view.data[6];
+ new.data[8] = view.data[8];
+ new.data[9] = view.data[9];
+ new.data[10] = view.data[10];
+
+ Binding_Camera camera_data = { .view = new,
+ .projection = proj,
+ .viewPos = vec4(camera.position.x, camera.position.y,
+ camera.position.z, 1.0) };
+ GPU_EncodeBindShaderData(enc, 0, Binding_Camera_GetLayout(&camera_data));
+
+ SkyboxUniforms uniforms = { .cubemap = skybox->texture };
+ ShaderDataLayout skybox_data = Skybox_GetLayout(&uniforms);
+ GPU_EncodeBindShaderData(enc, 0, skybox_data);
+
+ GPU_EncodeSetVertexBuffer(enc, skybox->cube.vertex_buffer);
+ GPU_EncodeSetIndexBuffer(enc, skybox->cube.index_buffer);
+
+ GPU_EncodeDrawTris(enc, 36);
+
+ GPU_CmdEncoder_EndRender(enc);
+ glDepthFunc(GL_LESS);
+}
diff --git a/archive/src/render/skybox.h b/archive/src/render/skybox.h
new file mode 100644
index 0000000..c2ef3a2
--- /dev/null
+++ b/archive/src/render/skybox.h
@@ -0,0 +1,41 @@
+/**
+ * @brief
+ */
+
+#pragma once
+#include "camera.h"
+#include "defines.h"
+#include "ral_impl.h"
+#include "render_types.h"
+
+typedef struct Skybox {
+ Mesh cube;
+ TextureHandle texture;
+ GPU_Pipeline* pipeline; // "shader"
+} Skybox;
+
+PUB Skybox Skybox_Create(const char** face_paths, int n); // should always pass n = 6 for now
+
+PUB void Skybox_Draw(Skybox* skybox, Camera camera);
+
+typedef struct SkyboxUniforms {
+ TextureHandle cubemap;
+} SkyboxUniforms;
+
+static ShaderDataLayout Skybox_GetLayout(void* data) {
+ SkyboxUniforms* d = (SkyboxUniforms*)data; // cold cast
+ bool has_data = data != NULL;
+
+ ShaderBinding b1 = {
+ .label = "cubeMap",
+ .vis = VISIBILITY_FRAGMENT,
+ .kind = BINDING_TEXTURE,
+ };
+
+ if (has_data) {
+ b1.data.texture.handle = d->cubemap;
+ }
+ return (ShaderDataLayout){ .bindings = { b1 }, .binding_count = 1 };
+}
+
+Skybox Skybox_Default(); \ No newline at end of file
diff --git a/archive/src/resources/gltf.c b/archive/src/resources/gltf.c
new file mode 100644
index 0000000..66ae1b6
--- /dev/null
+++ b/archive/src/resources/gltf.c
@@ -0,0 +1,596 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include "animation.h"
+#include "colours.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 "pbr.h"
+#include "platform.h"
+#include "ral_types.h"
+#include "render.h"
+#include "render_types.h"
+#include "str.h"
+
+#define CGLTF_IMPLEMENTATION
+#include <cgltf.h>
+
+extern Core g_core;
+
+/* GLTF Loading Pipeline
+ ===================== */
+
+struct face {
+ cgltf_uint indices[3];
+};
+typedef struct face face;
+
+KITC_DECL_TYPED_ARRAY(Vec3)
+KITC_DECL_TYPED_ARRAY(Vec2)
+KITC_DECL_TYPED_ARRAY(Vec4u)
+KITC_DECL_TYPED_ARRAY(Vec4i)
+KITC_DECL_TYPED_ARRAY(Vec4)
+KITC_DECL_TYPED_ARRAY(face)
+KITC_DECL_TYPED_ARRAY(i32)
+
+size_t GLTF_LoadMaterials(cgltf_data* data, Str8 relative_path, Material_darray* out_materials);
+
+ModelHandle ModelLoad_gltf(const char* path, bool invert_texture_y) {
+ size_t arena_size = MB(1);
+ 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);
+
+ ModelHandle handle;
+ Model* model = Model_pool_alloc(&g_core.models, &handle);
+ model->name = Str8_cstr_view(path);
+
+ bool success =
+ model_load_gltf_str(file_string, path, relative_path.path, model, invert_texture_y);
+
+ if (!success) {
+ FATAL("Couldnt load GLTF file at path %s", path);
+ ERROR_EXIT("Load fails are considered crash-worthy right now. This will change later.\n");
+ }
+
+ arena_free_all(&scratch);
+ arena_free_storage(&scratch);
+ return handle;
+}
+
+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
+
+void load_position_components(Vec3_darray* positions, cgltf_accessor* accessor) {
+ TRACE("Loading %d vec3 position components", accessor->count);
+ CASSERT_MSG(accessor->component_type == cgltf_component_type_r_32f,
+ "Positions components are floats");
+ 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);
+ Vec3_darray_push(positions, pos);
+ }
+}
+
+void load_normal_components(Vec3_darray* normals, cgltf_accessor* accessor) {
+ TRACE("Loading %d vec3 normal components", accessor->count);
+ CASSERT_MSG(accessor->component_type == cgltf_component_type_r_32f,
+ "Normal vector components are floats");
+ CASSERT_MSG(accessor->type == cgltf_type_vec3, "Vertex normals 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(normals, pos);
+ }
+}
+
+void load_texcoord_components(Vec2_darray* texcoords, cgltf_accessor* accessor) {
+ TRACE("Load texture coordinates from accessor");
+ 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");
+ }
+ Vec2_darray_push(texcoords, tex);
+ }
+}
+
+void load_joint_index_components(Vec4i_darray* joint_indices, cgltf_accessor* accessor) {
+ TRACE("Load joint indices from accessor");
+ CASSERT(accessor->component_type == cgltf_component_type_r_16u);
+ CASSERT_MSG(accessor->type == cgltf_type_vec4, "Joint indices should be a vec4");
+ Vec4i tmp_joint_index;
+ Vec4 joints_as_floats;
+ for (cgltf_size v = 0; v < accessor->count; ++v) {
+ cgltf_accessor_read_float(accessor, v, &joints_as_floats.x, 4);
+ tmp_joint_index.x = (u32)joints_as_floats.x;
+ tmp_joint_index.y = (u32)joints_as_floats.y;
+ tmp_joint_index.z = (u32)joints_as_floats.z;
+ tmp_joint_index.w = (u32)joints_as_floats.w;
+ printf("Joints affecting vertex %d : %d %d %d %d\n", v, tmp_joint_index.x, tmp_joint_index.y,
+ tmp_joint_index.z, tmp_joint_index.w);
+ Vec4i_darray_push(joint_indices, tmp_joint_index);
+ }
+}
+
+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);
+ Vec4i_darray* tmp_joint_indices = Vec4i_darray_new(1000);
+ Vec4_darray* tmp_weights = Vec4_darray_new(1000);
+ Material_darray* tmp_materials = Material_darray_new(1);
+ Mesh_darray* tmp_meshes = Mesh_darray_new(1);
+ i32_darray* tmp_material_indexes = i32_darray_new(1);
+
+ Joint_darray* joints = Joint_darray_new(256);
+
+ 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;
+ Armature main_skeleton = { 0 };
+ 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);
+
+ // Create our data that will be placed onto the model
+ Armature armature = { .label = "test_skin" };
+ printf("Skin %s\n", gltf_skin->name);
+ // armature.label = Clone_cstr(&armature.arena, gltf_skin->name);
+ armature.joints = joints; // ! Make sure not to free this
+
+ cgltf_accessor* gltf_inverse_bind_matrices = gltf_skin->inverse_bind_matrices;
+
+ // --- Joints
+ // for each one we'll spit out a joint
+ for (size_t i = 0; i < num_joints; i++) {
+ // Get the joint and assign its node index for later referencing
+ cgltf_node* joint_node = gltf_skin->joints[i];
+ TRACE("Joint %d (node index %d)", i, cgltf_node_index(data, joint_node));
+ Joint joint_i = { .debug_label = "test_joint",
+ .node_idx = cgltf_node_index(data, joint_node),
+ .inverse_bind_matrix = mat4_ident() };
+
+ 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;
+ joint_i.parent = -1;
+ for (u32 c_i = 0; c_i < joint_node->children_count; c_i++) {
+ joint_i.children[c_i] = cgltf_node_index(data, joint_node->children[c_i]);
+ joint_i.children_count++;
+ }
+ } 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));
+ }
+ if (joint_node->has_scale) {
+ memcpy(&joint_i.transform_components.scale, &joint_node->scale, 3 * sizeof(f32));
+ }
+ joint_i.parent = cgltf_node_index(data, joint_node->parent);
+ }
+ // Calculate and store the starting transform of the joint
+ joint_i.local_transform = transform_to_mat(&joint_i.transform_components);
+ // Read in the inverse bind matrix
+ cgltf_accessor_read_float(gltf_inverse_bind_matrices, i, &joint_i.inverse_bind_matrix.data[0],
+ 16);
+ Joint_darray_push(armature.joints, joint_i);
+ }
+ main_skeleton = armature;
+ // out_model->armature = armature;
+ // out_model->has_joints = true;
+ }
+
+ // --- Materials
+ size_t num_materials = GLTF_LoadMaterials(data, relative_path, tmp_materials);
+
+ // --- Meshes
+ size_t num_meshes = data->meshes_count;
+ TRACE("Num meshes %d", num_meshes);
+ for (size_t m = 0; m < num_meshes; m++) {
+ printf("Primitive count %d\n", data->meshes[m].primitives_count);
+ for (size_t prim_i = 0; prim_i < data->meshes[m].primitives_count; prim_i++) {
+ DEBUG("Primitive %d\n", prim_i);
+
+ cgltf_primitive primitive = data->meshes[m].primitives[prim_i];
+ DEBUG("Found %d attributes", primitive.attributes_count);
+
+ for (cgltf_size a = 0; a < primitive.attributes_count; a++) {
+ cgltf_attribute attribute = primitive.attributes[a];
+ if (attribute.type == cgltf_attribute_type_position) {
+ cgltf_accessor* accessor = attribute.data;
+ load_position_components(tmp_positions, accessor);
+ } else if (attribute.type == cgltf_attribute_type_normal) {
+ cgltf_accessor* accessor = attribute.data;
+ load_normal_components(tmp_normals, accessor);
+ } else if (attribute.type == cgltf_attribute_type_texcoord) {
+ cgltf_accessor* accessor = attribute.data;
+ load_texcoord_components(tmp_uvs, accessor);
+ } else if (attribute.type == cgltf_attribute_type_joints) {
+ TRACE("Load joint indices from accessor");
+ cgltf_accessor* accessor = attribute.data;
+ load_joint_index_components(tmp_joint_indices, accessor);
+ } else if (attribute.type == cgltf_attribute_type_weights) {
+ TRACE("Load joint weights from accessor");
+ cgltf_accessor* accessor = attribute.data;
+ CASSERT(accessor->component_type == cgltf_component_type_r_32f);
+ CASSERT(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.vertex_bone_data = vertex_bone_data_darray_new(1);
+ i32 mat_idx = -1;
+ if (primitive.material != NULL) {
+ DEBUG("Primitive Material %s", primitive.material->name);
+ // FIXME!
+ for (u32 i = 0; i < Material_darray_len(tmp_materials); i++) {
+ printf("%s vs %s \n", primitive.material->name, tmp_materials->data[i].name);
+ if (strcmp(primitive.material->name, tmp_materials->data[i].name) == 0) {
+ INFO("Found material");
+ mat_idx = i;
+ i32_darray_push(tmp_material_indexes, mat_idx);
+ break;
+ }
+ }
+ } else {
+ i32_darray_push(tmp_material_indexes, -1);
+ }
+
+ TRACE("Vertex data has been loaded");
+
+ Vertex_darray* geo_vertices = Vertex_darray_new(3);
+ u32_darray* geo_indices = u32_darray_new(0);
+
+ // Store vertices
+ printf("Positions %d Normals %d UVs %d\n", tmp_positions->len, tmp_normals->len,
+ tmp_uvs->len);
+ // assert(tmp_positions->len == tmp_normals->len);
+ // assert(tmp_normals->len == tmp_uvs->len);
+ bool has_normals = tmp_normals->len > 0;
+ bool has_uvs = tmp_uvs->len > 0;
+ for (u32 v_i = 0; v_i < tmp_positions->len; v_i++) {
+ Vertex v = { 0 };
+ if (is_skinned) {
+ v.skinned_3d.position = tmp_positions->data[v_i];
+ v.skinned_3d.normal = has_normals ? tmp_normals->data[v_i] : VEC3_ZERO,
+ v.skinned_3d.tex_coords = has_uvs ? tmp_uvs->data[v_i] : vec2_create(0., 0.);
+ v.skinned_3d.bone_ids = tmp_joint_indices->data[v_i];
+ v.skinned_3d.bone_weights = tmp_weights->data[v_i];
+ } else {
+ v.static_3d.position = tmp_positions->data[v_i];
+ v.static_3d.normal = has_normals ? tmp_normals->data[v_i] : VEC3_ZERO,
+ v.static_3d.tex_coords = has_uvs ? tmp_uvs->data[v_i] : vec2_create(0., 0.);
+ }
+ Vertex_darray_push(geo_vertices, v);
+ };
+
+ // Store indices
+ cgltf_accessor* indices = primitive.indices;
+ if (primitive.indices > 0) {
+ WARN("indices! %d", indices->count);
+
+ // store indices
+ for (cgltf_size i = 0; i < indices->count; ++i) {
+ cgltf_uint ei;
+ cgltf_accessor_read_uint(indices, i, &ei, 1);
+ u32_darray_push(geo_indices, ei);
+ }
+
+ Geometry* geometry = malloc(sizeof(Geometry));
+ geometry->format = is_skinned ? VERTEX_SKINNED : VERTEX_STATIC_3D;
+ geometry->has_indices = true;
+ geometry->vertices = geo_vertices;
+ geometry->indices = geo_indices;
+ geometry->index_count = geo_indices->len;
+
+ Mesh m = Mesh_Create(geometry, false);
+ if (is_skinned) {
+ m.is_skinned = true;
+ m.armature = main_skeleton;
+ }
+ Mesh_darray_push(tmp_meshes, m);
+
+ Vec3_darray_clear(tmp_positions);
+ Vec3_darray_clear(tmp_normals);
+ Vec2_darray_clear(tmp_uvs);
+ Vec4i_darray_clear(tmp_joint_indices);
+ Vec4_darray_clear(tmp_weights);
+ } else {
+ WARN("No indices found. Ignoring mesh...");
+ }
+ }
+
+ // --- Animations
+ size_t num_animations = data->animations_count;
+ TRACE("Num animations %d", num_animations);
+
+ if (num_animations > 0) {
+ if (!out_model->animations) {
+ out_model->animations = AnimationClip_darray_new(num_animations);
+ }
+ out_model->anim_arena = arena_create(malloc(MB(1)), MB(1));
+ arena* arena = &out_model->anim_arena;
+
+ // Iterate over each animation in the GLTF
+ for (int anim_idx = 0; anim_idx < data->animations_count; anim_idx++) {
+ cgltf_animation animation = data->animations[anim_idx];
+ AnimationClip clip = { 0 };
+ clip.clip_name = "test anim clip";
+ clip.channels = AnimationSampler_darray_new(1);
+
+ // for each animation, loop through all the channels
+ for (size_t c = 0; c < animation.channels_count; c++) {
+ cgltf_animation_channel channel = animation.channels[c];
+
+ AnimationSampler sampler = { 0 };
+
+ KeyframeKind data_type;
+
+ switch (channel.target_path) {
+ case cgltf_animation_path_type_rotation:
+ data_type = KEYFRAME_ROTATION;
+ break;
+ case cgltf_animation_path_type_translation:
+ data_type = KEYFRAME_TRANSLATION;
+ break;
+ case cgltf_animation_path_type_scale:
+ data_type = KEYFRAME_SCALE;
+ break;
+ case cgltf_animation_path_type_weights:
+ data_type = KEYFRAME_WEIGHTS;
+ WARN("Morph target weights arent supported yet");
+ return false;
+ default:
+ WARN("unsupported animation type");
+ return false;
+ }
+
+ sampler.current_index = 0;
+ sampler.animation.interpolation = INTERPOLATION_LINEAR; // NOTE: hardcoded for now
+
+ // Keyframe times
+ size_t n_frames = channel.sampler->input->count;
+ CASSERT_MSG(channel.sampler->input->component_type == cgltf_component_type_r_32f,
+ "Expected animation sampler input component to be type f32");
+ 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);
+
+ // Keyframe values
+ size_t n_values = channel.sampler->output->count;
+ CASSERT_MSG(n_frames == n_values, "keyframe times = keyframe values");
+
+ Keyframes keyframes = { 0 };
+ keyframes.kind = data_type;
+ 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: morph weights
+ break;
+ }
+ }
+ }
+ sampler.animation.values = keyframes;
+ sampler.min = channel.sampler->input->min[0];
+ sampler.max = channel.sampler->input->max[0];
+
+ // *target_property = sampler;
+ printf("%d timestamps between %f and %f\n", sampler.animation.n_timestamps, sampler.min,
+ sampler.max);
+
+ // TODO: get target
+ size_t target_index = cgltf_node_index(data, channel.target_node);
+ size_t joint_index = 0;
+ bool found = false;
+ for (u32 ji = 0; ji < main_skeleton.joints->len; ji++) {
+ if (main_skeleton.joints->data[ji].node_idx == target_index) {
+ joint_index = ji;
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ WARN("Coulndnt find joint index");
+ }
+ sampler.target_joint_idx =
+ joint_index; // NOTE: this assuming the target is a joint at the moment
+ AnimationSampler_darray_push(clip.channels, sampler);
+ }
+
+ AnimationClip_darray_push(out_model->animations, clip);
+ }
+ }
+
+ // exit(0);
+ }
+
+ num_meshes = tmp_meshes->len;
+
+ // we now have an array of meshes, materials, and the material which each mesh should get
+ out_model->meshes = malloc(num_meshes * sizeof(MeshHandle));
+ out_model->mesh_count = num_meshes;
+ out_model->materials = malloc(num_materials * sizeof(MaterialHandle));
+ out_model->material_count = num_materials;
+
+ MaterialHandle* mat_handles = calloc(num_materials, sizeof(MaterialHandle));
+ for (u32 mat_i = 0; mat_i < num_materials; mat_i++) {
+ mat_handles[mat_i] =
+ Material_pool_insert(Render_GetMaterialPool(), &tmp_materials->data[mat_i]);
+ }
+ memcpy(out_model->materials, mat_handles, num_materials * sizeof(MaterialHandle));
+
+ for (u32 mesh_i = 0; mesh_i < num_meshes; mesh_i++) {
+ i32 mat_idx = tmp_material_indexes->data[mesh_i];
+ if (mat_idx > 0) {
+ tmp_meshes->data[mesh_i].material = mat_handles[mat_idx];
+
+ } else {
+ Material default_mat = PBRMaterialDefault();
+ tmp_meshes->data[mesh_i].material =
+ Material_pool_insert(Render_GetMaterialPool(), &default_mat);
+ }
+ MeshHandle mesh = Mesh_pool_insert(Render_GetMeshPool(), &tmp_meshes->data[mesh_i]);
+ out_model->meshes[mesh_i] = mesh;
+ }
+
+ free(mat_handles);
+
+ return true;
+}
+
+const char* bool_yes_no(bool pred) { return pred ? "Yes" : "No"; }
+
+// Loads all materials
+size_t GLTF_LoadMaterials(cgltf_data* data, Str8 relative_path, Material_darray* out_materials) {
+ size_t num_materials = data->materials_count;
+ TRACE("Num materials %d", num_materials);
+ for (size_t m = 0; m < num_materials; m++) {
+ cgltf_material gltf_material = data->materials[m];
+ TRACE("Loading material '%s'", gltf_material.name);
+ cgltf_pbr_metallic_roughness pbr = gltf_material.pbr_metallic_roughness;
+
+ Material our_material = PBRMaterialDefault(); // focusing on PBR materials for now
+
+ our_material.base_colour =
+ vec3(pbr.base_color_factor[0], pbr.base_color_factor[1], pbr.base_color_factor[2]);
+ our_material.metallic = pbr.metallic_factor;
+ our_material.roughness = pbr.roughness_factor;
+
+ // -- albedo / base colour
+ cgltf_texture_view albedo_tex_view = pbr.base_color_texture;
+ bool has_albedo_texture = albedo_tex_view.texture != NULL;
+ TRACE("Has PBR base colour texture? %s", bool_yes_no(has_albedo_texture));
+ printf("Base colour factor: %f %f %f\n", pbr.base_color_factor[0], pbr.base_color_factor[1],
+ pbr.base_color_factor[2]);
+ if (has_albedo_texture) {
+ char albedo_map_path[1024];
+ snprintf(albedo_map_path, sizeof(albedo_map_path), "%s/%s", relative_path.buf,
+ albedo_tex_view.texture->image->uri);
+ our_material.albedo_map = TextureLoadFromFile(albedo_map_path);
+ } else {
+ our_material.albedo_map = Render_GetWhiteTexture();
+ WARN("GLTF model has no albedo map");
+ our_material.base_colour =
+ vec3_create(pbr.base_color_factor[0], pbr.base_color_factor[1], pbr.base_color_factor[2]);
+ }
+
+ // -- metallic
+ cgltf_texture_view metal_rough_tex_view = pbr.metallic_roughness_texture;
+ printf("Metal factor: %f\n", pbr.metallic_factor);
+ printf("Roughness factor: %f\n", pbr.roughness_factor);
+ if (metal_rough_tex_view.texture != NULL) {
+ char metal_rough_map_path[1024];
+ snprintf(metal_rough_map_path, sizeof(metal_rough_map_path), "%s/%s", relative_path.buf,
+ metal_rough_tex_view.texture->image->uri);
+ our_material.metallic_roughness_map = TextureLoadFromFile(metal_rough_map_path);
+ } else {
+ WARN("GLTF model has no metal/roughness map");
+ our_material.metallic = pbr.metallic_factor;
+ our_material.roughness = pbr.roughness_factor;
+ }
+
+ cgltf_texture_view normal_tex_view = gltf_material.normal_texture;
+ if (normal_tex_view.texture != NULL) {
+ char normal_map_path[1024];
+ snprintf(normal_map_path, sizeof(normal_map_path), "%s/%s", relative_path.buf,
+ normal_tex_view.texture->image->uri);
+ our_material.normal_map = TextureLoadFromFile(normal_map_path);
+ } else {
+ WARN("GLTF model has no normal map");
+ }
+
+ u32 string_length = strlen(gltf_material.name) + 1;
+ assert(string_length < 64);
+ strcpy(our_material.name, gltf_material.name);
+
+ Material_darray_push(out_materials, our_material);
+ }
+
+ return out_materials->len;
+}
diff --git a/archive/src/resources/loaders.h b/archive/src/resources/loaders.h
new file mode 100644
index 0000000..ea1f9a2
--- /dev/null
+++ b/archive/src/resources/loaders.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include "defines.h"
+#include "render_types.h"
+#include "str.h"
+
+// --- Public API
+PUB ModelHandle ModelLoad_obj(const char* path, bool invert_texture_y);
+PUB ModelHandle ModelLoad_gltf(const char* path, bool invert_texture_y);
+
+typedef struct GLTF_LoadStats {
+ u32 mesh_count, material_count, vertex_count, index_count, animation_count, joint_count;
+} GLTF_LoadStats;
+
+// --- Internal
+bool model_load_gltf_str(const char* file_string, const char* filepath, Str8 relative_path,
+ Model* out_model, bool invert_textures_y);
diff --git a/archive/src/resources/obj.c b/archive/src/resources/obj.c
new file mode 100644
index 0000000..a5e9b18
--- /dev/null
+++ b/archive/src/resources/obj.c
@@ -0,0 +1,398 @@
+/**
+ * @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 "platform.h"
+#include "render.h"
+#include "render_types.h"
+#include "str.h"
+
+extern Core g_core;
+
+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);
+
+ModelHandle 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);
+
+ ModelHandle handle;
+ // model *model = model_pool_alloc(&g_core.models, &handle);
+ // model->name = str8_cstr_view(path);
+ // model->meshes = mesh_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");
+ // }
+
+ // arena_free_all(&scratch);
+ // arena_free_storage(&scratch);
+ return handle;
+}
+
+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,
+ // NULL, // 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,
+ // NULL, // FIXME: 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,
+ // NULL, // TODO: 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
+
+ // TODO: copy from mesh_darray to malloc'd mesh* array
+
+ return true;
+}
+
+// /**
+// * @brief Takes the current positions, normals, uvs arrays and constructs the vertex array
+// * from those indices.
+// */
+// void create_submesh(mesh_darray *meshes, vec3_darray *tmp_positions, vec3_darray *tmp_normals,
+// vec2_darray *tmp_uvs, face_darray *tmp_faces, material_darray *materials,
+// bool material_loaded, char current_material_name[256]) {
+// // size_t num_verts = face_darray_len(tmp_faces) * 3;
+// // vertex_darray *out_vertices = vertex_darray_new(num_verts);
+
+// // face_darray_iter face_iter = face_darray_iter_new(tmp_faces);
+// // struct face *f;
+
+// // while ((f = face_darray_iter_next(&face_iter))) {
+// // for (int j = 0; j < 3; j++) {
+// // vertex vert = { 0 };
+// // vert.position = tmp_positions->data[f->vertex_indices[j] - 1];
+// // if (vec3_darray_len(tmp_normals) == 0) {
+// // vert.normal = vec3_create(0.0, 0.0, 0.0);
+// // } else {
+// // vert.normal = tmp_normals->data[f->normal_indices[j] - 1];
+// // }
+// // vert.uv = tmp_uvs->data[f->uv_indices[j] - 1];
+// // vertex_darray_push(out_vertices, vert);
+// // }
+// // }
+
+// // DEBUG("Loaded submesh\n vertices: %zu\n uvs: %zu\n normals: %zu\n faces: %zu",
+// // vec3_darray_len(tmp_positions), vec2_darray_len(tmp_uvs),
+// vec3_darray_len(tmp_normals),
+// // face_darray_len(tmp_faces));
+
+// // // Clear current object faces
+// // face_darray_clear(tmp_faces);
+
+// // mesh m = { .vertices = out_vertices };
+// // if (material_loaded) {
+// // // linear scan to find material
+// // bool found = false;
+// // DEBUG("Num of materials : %ld", material_darray_len(materials));
+// // material_darray_iter mat_iter = material_darray_iter_new(materials);
+// // blinn_phong_material *cur_material;
+// // while ((cur_material = material_darray_iter_next(&mat_iter))) {
+// // if (strcmp(cur_material->name, current_material_name) == 0) {
+// // DEBUG("Found match");
+// // m.material_index = mat_iter.current_idx - 1;
+// // found = true;
+// // break;
+// // }
+// // }
+
+// // if (!found) {
+// // // TODO: default material
+// // m.material_index = 0;
+// // DEBUG("Set default material");
+// // }
+// // }
+// // mesh_darray_push(meshes, m);
+// }
+
+// bool load_material_lib(const char *path, str8 relative_path, material_darray *materials) {
+// TRACE("BEGIN load material lib at %s", path);
+
+// // const char *file_string = string_from_file(path);
+// // if (file_string == NULL) {
+// // ERROR("couldnt load %s", path);
+// // return false;
+// // }
+
+// // char *pch;
+// // char *saveptr;
+// // pch = strtok_r((char *)file_string, "\n", &saveptr);
+
+// // material current_material = DEFAULT_MATERIAL;
+
+// // bool material_set = false;
+
+// // while (pch != NULL) {
+// // char line_header[128];
+// // int offset = 0;
+// // // read the first word of the line
+// // int res = sscanf(pch, "%s %n", line_header, &offset);
+// // if (res != 1) {
+// // break;
+// // }
+
+// // // When we see "newmtl", start a new material, or flush the previous one
+// // if (strcmp(line_header, "newmtl") == 0) {
+// // if (material_set) {
+// // // a material was being parsed, so flush that one and start a new one
+// // material_darray_push(materials, current_material);
+// // DEBUG("pushed material with name %s", current_material.name);
+// // WARN("Reset current material");
+// // current_material = DEFAULT_MATERIAL;
+// // } else {
+// // material_set = true;
+// // }
+// // // scan the new material name
+// // char material_name[64];
+// // sscanf(pch + offset, "%s", current_material.name);
+// // DEBUG("material name %s\n", current_material.name);
+// // // current_material.name = material_name;
+// // } else if (strcmp(line_header, "Ka") == 0) {
+// // // ambient
+// // sscanf(pch + offset, "%f %f %f", &current_material.ambient_colour.x,
+// // &current_material.ambient_colour.y, &current_material.ambient_colour.z);
+// // } else if (strcmp(line_header, "Kd") == 0) {
+// // // diffuse
+// // sscanf(pch + offset, "%f %f %f", &current_material.diffuse.x,
+// &current_material.diffuse.y,
+// // &current_material.diffuse.z);
+// // } else if (strcmp(line_header, "Ks") == 0) {
+// // // specular
+// // sscanf(pch + offset, "%f %f %f", &current_material.specular.x,
+// // &current_material.specular.y,
+// // &current_material.specular.z);
+// // } else if (strcmp(line_header, "Ns") == 0) {
+// // // specular exponent
+// // sscanf(pch + offset, "%f", &current_material.spec_exponent);
+// // } else if (strcmp(line_header, "map_Kd") == 0) {
+// // char diffuse_map_filename[1024];
+// // sscanf(pch + offset, "%s", diffuse_map_filename);
+// // char diffuse_map_path[1024];
+// // snprintf(diffuse_map_path, sizeof(diffuse_map_path), "%s/%s", relative_path.buf,
+// // diffuse_map_filename);
+// // printf("load from %s\n", diffuse_map_path);
+
+// // // --------------
+// // texture diffuse_texture = texture_data_load(diffuse_map_path, true);
+// // current_material.diffuse_texture = diffuse_texture;
+// // strcpy(current_material.diffuse_tex_path, diffuse_map_path);
+// // texture_data_upload(&current_material.diffuse_texture);
+// // // --------------
+// // } else if (strcmp(line_header, "map_Ks") == 0) {
+// // // char specular_map_path[1024] = "assets/";
+// // // sscanf(pch + offset, "%s", specular_map_path + 7);
+// // char specular_map_filename[1024];
+// // sscanf(pch + offset, "%s", specular_map_filename);
+// // char specular_map_path[1024];
+// // snprintf(specular_map_path, sizeof(specular_map_path), "%s/%s", relative_path.buf,
+// // specular_map_filename);
+// // printf("load from %s\n", specular_map_path);
+// // // --------------
+// // texture specular_texture = texture_data_load(specular_map_path, true);
+// // current_material.specular_texture = specular_texture;
+// // strcpy(current_material.specular_tex_path, specular_map_path);
+// // texture_data_upload(&current_material.specular_texture);
+// // // --------------
+// // } else if (strcmp(line_header, "map_Bump") == 0) {
+// // // TODO
+// // }
+
+// // pch = strtok_r(NULL, "\n", &saveptr);
+// // }
+
+// // TRACE("end load material lib");
+
+// // // last mesh or if one wasnt created with 'o' directive
+// // // TRACE("Last leftover material");
+// // material_darray_push(materials, current_material);
+
+// // INFO("Loaded %ld materials", material_darray_len(materials));
+// TRACE("END load material lib");
+// return true;
+// }
diff --git a/archive/src/std/buf.h b/archive/src/std/buf.h
new file mode 100644
index 0000000..77fc7b9
--- /dev/null
+++ b/archive/src/std/buf.h
@@ -0,0 +1,11 @@
+/**
+ * @file buf.h
+ * @brief
+ */
+#pragma once
+#include "defines.h"
+
+typedef struct bytebuffer {
+ u8* buf;
+ size_t size;
+} bytebuffer;
diff --git a/archive/src/std/containers/container_utils.h b/archive/src/std/containers/container_utils.h
new file mode 100644
index 0000000..e1d164c
--- /dev/null
+++ b/archive/src/std/containers/container_utils.h
@@ -0,0 +1,17 @@
+/**
+ * @file container_utils.h
+ * @author your name (you@domain.com)
+ * @brief
+ * @version 0.1
+ * @date 2024-06-19
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
+
+#pragma once
+
+typedef struct generic_iterator {
+} generic_iterator;
+
+typedef void* (*iterator_next_item)(void* iterator); \ No newline at end of file
diff --git a/archive/src/std/containers/darray.h b/archive/src/std/containers/darray.h
new file mode 100644
index 0000000..080afb4
--- /dev/null
+++ b/archive/src/std/containers/darray.h
@@ -0,0 +1,151 @@
+/**
+ * @file darray.h
+ * @brief Typed dynamic array
+ * @copyright Copyright (c) 2023
+ */
+// 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
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define DARRAY_DEFAULT_CAPACITY 64
+#define DARRAY_RESIZE_FACTOR 3
+
+/** @brief create a new darray type and functions with type `N` */
+#define typed_array(T) \
+ struct { \
+ /* @brief current number of items in the array */ \
+ size_t len; \
+ size_t capacity; \
+ T* data; \
+ }
+
+#define typed_array_iterator(T) \
+ struct { \
+ T##_darray* array; \
+ size_t current_idx; \
+ }
+
+#define PREFIX static
+
+#define KITC_DECL_TYPED_ARRAY(T) 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 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; \
+ d->data = data; \
+ \
+ return d; \
+ } \
+ \
+ PREFIX void Type##_darray_free(Type##_darray* d) { \
+ if (d != NULL) { \
+ free(d->data); \
+ free(d); \
+ } \
+ } \
+ \
+ 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 */ \
+ \
+ d->capacity = capacity; \
+ d->data = new_data; \
+ return new_data; \
+ } \
+ \
+ 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 = Type##_darray_resize(d, new_capacity); \
+ (void)resized; \
+ } \
+ \
+ d->data[d->len] = value; \
+ d->len += 1; \
+ } \
+ \
+ 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 = Type##_darray_resize(d, new_capacity); \
+ (void)resized; \
+ } \
+ \
+ T* place = d->data + d->len; \
+ d->len += 1; \
+ memcpy(place, value, sizeof(T)); \
+ } \
+ \
+ 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 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 = Type##_darray_resize(d, new_capacity); \
+ (void)resized; \
+ } \
+ \
+ /* shift existing data after index */ \
+ T* insert_dest = d->data + index; \
+ T* shift_dest = insert_dest + 1; \
+ \
+ int num_items = d->len - index; \
+ \
+ d->len += 1; \
+ memcpy(shift_dest, insert_dest, num_items * sizeof(T)); \
+ memcpy(insert_dest, value, sizeof(T)); \
+ } \
+ \
+ PREFIX void Type##_darray_clear(Type##_darray* d) { \
+ d->len = 0; \
+ memset(d->data, 0, d->capacity * sizeof(T)); \
+ } \
+ \
+ PREFIX size_t Type##_darray_len(Type##_darray* d) { return d->len; } \
+ \
+ 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* Type##_darray_iter_next(Type##_darray_iter* iterator) { \
+ if (iterator->current_idx < iterator->array->len) { \
+ return &iterator->array->data[iterator->current_idx++]; \
+ } else { \
+ return NULL; \
+ } \
+ }
+
+#endif // KITC_TYPED_ARRAY_H
diff --git a/archive/src/std/containers/graphs.h b/archive/src/std/containers/graphs.h
new file mode 100644
index 0000000..5dbec97
--- /dev/null
+++ b/archive/src/std/containers/graphs.h
@@ -0,0 +1,14 @@
+/**
+ * @file graphs.h
+ * @author your name (you@domain.com)
+ * @brief
+ * @version 0.1
+ * @date 2024-04-27
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
+
+// Adjacency list backed graphs
+
+// Matrix backed graphs (not as useful) \ No newline at end of file
diff --git a/archive/src/std/containers/hashmap.h b/archive/src/std/containers/hashmap.h
new file mode 100644
index 0000000..95c1c6b
--- /dev/null
+++ b/archive/src/std/containers/hashmap.h
@@ -0,0 +1,27 @@
+/**
+ * @file hashmap.h
+ * @author your name (you@domain.com)
+ * @brief
+ * @version 0.1
+ * @date 2024-04-27
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
+
+typedef struct hashmap hashmap;
+
+/*
+Example usage
+-------------
+init hashmap
+insert (string, material)
+get (string) -> material_opt or material* ?
+
+*/
+
+void hashmap_init(hashmap* map);
+
+// ...
+
+void hashmap_free(hashmap* map); \ No newline at end of file
diff --git a/archive/src/std/containers/hashset.h b/archive/src/std/containers/hashset.h
new file mode 100644
index 0000000..7f87213
--- /dev/null
+++ b/archive/src/std/containers/hashset.h
@@ -0,0 +1,29 @@
+/**
+ * @file hashset.h
+ * @author your name (you@domain.com)
+ * @brief
+ * @version 0.1
+ * @date 2024-04-27
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
+
+#include "defines.h"
+
+typedef struct hashset hashset;
+
+/** @brief Describes a function that will take a pointer to a datatype (e.g. a u64 or a struct)
+ and return a hashed key. */
+typedef uint64_t (*hash_item)(void* item);
+
+void hashset_init(hashset* set, hash_item hash_func, size_t initial_capacity);
+// TODO: void hashset_from_iterator();
+bool hashset_insert(hashset* set, void* item, uint64_t* out_key);
+void hashset_batch_insert(hashset* set, void* items, u64 item_count);
+bool hashset_contains(hashset* set, void* item);
+bool hashset_remove_item(hashset* set, void* item);
+bool hashset_remove_key(hashset* set, uint64_t key);
+void hashset_merge(hashset* set_a, hashset* set_b);
+hashset* hashset_merge_cloned(hashset* set_a, hashset* set_b);
+void hashset_free(hashset* set); \ No newline at end of file
diff --git a/archive/src/std/containers/ring_queue.c b/archive/src/std/containers/ring_queue.c
new file mode 100644
index 0000000..8bfc10b
--- /dev/null
+++ b/archive/src/std/containers/ring_queue.c
@@ -0,0 +1,68 @@
+#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) {
+ ring_queue* q = malloc(sizeof(ring_queue));
+ q->len = 0;
+ q->capacity = capacity;
+ q->type_size = type_size;
+ q->head = 0;
+ q->tail = -1;
+
+ if (memory) {
+ // caller owns the memory
+ q->owns_memory = false;
+ q->data = memory;
+ } else {
+ // ring queue should own the memory
+ q->owns_memory = true;
+ q->data = malloc(capacity * type_size);
+ }
+
+ return q;
+}
+
+void ring_queue_free(ring_queue* queue) {
+ if (queue) {
+ if (queue->owns_memory) {
+ free(queue->data);
+ }
+ free(queue);
+ }
+}
+
+bool ring_queue_enqueue(ring_queue* queue, const void* value) {
+ if (queue->len == queue->capacity) {
+ return false;
+ }
+
+ queue->tail = (queue->tail + 1) % queue->capacity;
+ memcpy(queue->data + (queue->tail * queue->type_size), value, queue->type_size);
+ queue->len++;
+ return true;
+}
+
+bool ring_queue_dequeue(ring_queue* queue, void* out_value) {
+ if (queue->len == 0) {
+ // queue is empty
+ return false;
+ }
+
+ memcpy(out_value, queue->data + (queue->head * queue->type_size), queue->type_size);
+ queue->head = (queue->head + 1) % queue->capacity;
+ queue->len--;
+ return true;
+}
+
+bool ring_queue_peek(const ring_queue* queue, void* out_value) {
+ if (queue->len == 0) {
+ // queue is empty
+ return false;
+ }
+
+ memcpy(out_value, queue->data + (queue->head * queue->type_size), queue->type_size);
+ return true;
+} \ No newline at end of file
diff --git a/archive/src/std/containers/ring_queue.h b/archive/src/std/containers/ring_queue.h
new file mode 100644
index 0000000..15d5da4
--- /dev/null
+++ b/archive/src/std/containers/ring_queue.h
@@ -0,0 +1,35 @@
+/**
+ * @file ring_queue.h
+ * @author your name (you@domain.com)
+ * @brief
+ * @version 0.1
+ * @date 2024-02-24
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
+#pragma once
+#include "defines.h"
+
+/**
+ * @brief a fixed-size ring queue
+ */
+typedef struct ring_queue {
+ size_t len;
+ size_t capacity;
+ size_t type_size;
+ void* data;
+ bool owns_memory;
+ int32_t head;
+ int32_t tail;
+} ring_queue;
+
+ring_queue* ring_queue_new(size_t type_size, size_t capacity, void* memory_block);
+
+void ring_queue_free(ring_queue* queue);
+
+bool ring_queue_enqueue(ring_queue* queue, const void* value);
+
+bool ring_queue_dequeue(ring_queue* queue, void* out_value);
+
+bool ring_queue_peek(const ring_queue* queue, void* out_value); \ No newline at end of file
diff --git a/archive/src/std/containers/stack_array.h b/archive/src/std/containers/stack_array.h
new file mode 100644
index 0000000..d2b6bdd
--- /dev/null
+++ b/archive/src/std/containers/stack_array.h
@@ -0,0 +1,19 @@
+#pragma once
+#include <stdbool.h>
+
+// Defines "_sarray" types
+
+#define TYPED_STACK_ARRAY(T, Name, Len) \
+ typedef struct Name##_sarray { \
+ T items[ Len ]; \
+ size_t len; \
+ } Name##_sarray; \
+ Name##_sarray Name##_sarray_create() { \
+ Name##_sarray arr = { .len = 0 }; \
+ return arr; \
+ } \
+ bool Name##_sarray_push(Name##_sarray* arr, T item) { \
+ if (arr->len == Len) { return false; }\
+ arr->items[arr->len++] = item;\
+ return true;\
+ }
diff --git a/archive/src/std/mem.c b/archive/src/std/mem.c
new file mode 100644
index 0000000..1f9078b
--- /dev/null
+++ b/archive/src/std/mem.c
@@ -0,0 +1,135 @@
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "log.h"
+#include "mem.h"
+
+#ifndef DEFAULT_ALIGNMENT
+#define DEFAULT_ALIGNMENT (2 * sizeof(void*))
+#endif
+
+// --- Arena
+
+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);
+ if (available < 0 || (ptrdiff_t)size > available) {
+ ERROR_EXIT("Arena ran out of memory\n");
+ }
+ 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); }
+
+arena arena_create(void* backing_buffer, size_t capacity) {
+ return (arena){ .begin = backing_buffer,
+ .curr = backing_buffer,
+ .end = backing_buffer + (ptrdiff_t)capacity };
+}
+
+void arena_free_all(arena* a) {
+ a->curr = a->begin; // pop everything at once and reset to the start.
+}
+
+void arena_free_storage(arena* a) { free(a->begin); }
+
+arena_save arena_savepoint(arena* a) {
+ arena_save savept = { .arena = a, .savepoint = a->curr };
+ return savept;
+}
+
+void arena_rewind(arena_save savepoint) { savepoint.arena->curr = savepoint.savepoint; }
+
+// --- Pool
+
+void_pool void_pool_create(arena* a, const char* debug_label, u64 capacity, u64 entry_size) {
+ size_t memory_requirements = capacity * entry_size;
+ void* backing_buf = arena_alloc(a, memory_requirements);
+
+ assert(entry_size >= sizeof(void_pool_header)); // TODO: create my own assert with error message
+
+ void_pool pool = { .capacity = capacity,
+ .entry_size = entry_size,
+ .count = 0,
+ .backing_buffer = backing_buf,
+ .free_list_head = NULL,
+ .debug_label = debug_label };
+
+ void_pool_free_all(&pool);
+
+ return pool;
+}
+
+void void_pool_free_all(void_pool* pool) {
+ // set all entries to be free
+ for (u64 i = 0; i < pool->capacity; i++) {
+ void* ptr = &pool->backing_buffer[i * pool->entry_size];
+ void_pool_header* free_node =
+ (void_pool_header*)ptr; // we reuse the actual entry itself to hold the header
+ if (i == (pool->capacity - 1)) {
+ // if the last one we make its next pointer NULL indicating its full
+ free_node->next = NULL;
+ }
+ free_node->next = pool->free_list_head;
+ // now the head points to this entry
+ pool->free_list_head = free_node;
+ }
+}
+
+void* void_pool_get(void_pool* pool, u32 raw_handle) {
+ // An handle is an index into the array essentially
+ void* ptr = pool->backing_buffer + (raw_handle * pool->entry_size);
+ return ptr;
+}
+
+void* void_pool_alloc(void_pool* pool, u32* out_raw_handle) {
+ // get the next free node
+ if (pool->count == pool->capacity) {
+ WARN("Pool is full!");
+ return NULL;
+ }
+ if (pool->free_list_head == NULL) {
+ ERROR("%s Pool is full (head = null)", pool->debug_label);
+ return NULL;
+ }
+ void_pool_header* free_node = pool->free_list_head;
+
+ // What index does this become?
+ uintptr_t start = (uintptr_t)pool->backing_buffer;
+ uintptr_t cur = (uintptr_t)free_node;
+ // TRACE("%ld %ld ", start, cur);
+ assert(cur > start);
+ u32 index = (u32)((cur - start) / pool->entry_size);
+ /* printf("Index %d\n", index); */
+ if (out_raw_handle != NULL) {
+ *out_raw_handle = index;
+ }
+
+ pool->free_list_head = free_node->next;
+
+ memset(free_node, 0, pool->entry_size);
+ pool->count++;
+ return (void*)free_node;
+}
+
+void void_pool_dealloc(void_pool* pool, u32 raw_handle) {
+ // push free node back onto the free list
+ void* ptr = void_pool_get(pool, raw_handle);
+ void_pool_header* freed_node = (void_pool_header*)ptr;
+
+ freed_node->next = pool->free_list_head;
+ pool->free_list_head = freed_node;
+
+ pool->count--;
+}
+
+u32 void_pool_insert(void_pool* pool, void* item) {
+ u32 raw_handle;
+ void* item_dest = void_pool_alloc(pool, &raw_handle);
+ memcpy(item_dest, item, pool->entry_size);
+ return raw_handle;
+}
diff --git a/archive/src/std/mem.h b/archive/src/std/mem.h
new file mode 100644
index 0000000..56c1230
--- /dev/null
+++ b/archive/src/std/mem.h
@@ -0,0 +1,96 @@
+/**
+ * @file mem.h
+ * @brief Allocators, memory tracking
+ * @version 0.1
+ * @date 2024-02-24
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
+#pragma once
+
+#include <stddef.h>
+#include "defines.h"
+
+typedef void* (*alloc_fn)(size_t size);
+typedef void (*free_fn)(void* ptr);
+
+typedef struct allocator_t {
+ alloc_fn alloc;
+ free_fn free;
+} allocator_t;
+
+// --- Arena
+
+// Inspired by https://nullprogram.com/blog/2023/09/27/
+typedef struct arena {
+ char* begin;
+ char* curr;
+ char* end;
+} arena;
+
+typedef struct arena_save {
+ arena* arena;
+ char* savepoint;
+} arena_save;
+
+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);
+arena_save arena_savepoint(arena* a);
+void arena_rewind(arena_save savepoint);
+// TODO: arena_resize
+
+// --- Pool
+
+typedef struct void_pool_header void_pool_header;
+struct void_pool_header {
+ void_pool_header* next;
+};
+
+typedef struct void_pool {
+ u64 capacity;
+ u64 entry_size;
+ u64 count;
+ void* backing_buffer;
+ void_pool_header* free_list_head;
+ const char* debug_label;
+} void_pool;
+
+void_pool void_pool_create(arena* a, const char* debug_label, u64 capacity, u64 entry_size);
+void void_pool_free_all(void_pool* pool);
+bool void_pool_is_empty(void_pool* pool);
+bool void_pool_is_full(void_pool* pool);
+void* void_pool_get(void_pool* pool, u32 raw_handle);
+void* void_pool_alloc(void_pool* pool, u32* out_raw_handle);
+void void_pool_dealloc(void_pool* pool, u32 raw_handle);
+u32 void_pool_insert(void_pool* pool, void* item);
+// TODO: fn to dealloc from the pointer that was handed out
+
+// TODO: macro that lets us specialise
+
+/* typedef struct Name##_handle Name##_handle; \ */
+#define TYPED_POOL(T, Name) \
+ typedef struct Name##_pool { \
+ void_pool inner; \
+ } Name##_pool; \
+ \
+ static Name##_pool Name##_pool_create(arena* a, u64 cap, u64 entry_size) { \
+ void_pool p = void_pool_create(a, "\"" #Name "\"", cap, entry_size); \
+ return (Name##_pool){ .inner = p }; \
+ } \
+ static inline T* Name##_pool_get(Name##_pool* pool, Name##Handle handle) { \
+ return (T*)void_pool_get(&pool->inner, handle.raw); \
+ } \
+ static inline T* Name##_pool_alloc(Name##_pool* pool, Name##Handle* out_handle) { \
+ return (T*)void_pool_alloc(&pool->inner, &out_handle->raw); \
+ } \
+ static inline void Name##_pool_dealloc(Name##_pool* pool, Name##Handle handle) { \
+ void_pool_dealloc(&pool->inner, handle.raw); \
+ } \
+ static Name##Handle Name##_pool_insert(Name##_pool* pool, T* item) { \
+ u32 raw_handle = void_pool_insert(pool, item); \
+ return (Name##Handle){ .raw = raw_handle }; \
+ }
diff --git a/archive/src/std/str.c b/archive/src/std/str.c
new file mode 100644
index 0000000..89c76a0
--- /dev/null
+++ b/archive/src/std/str.c
@@ -0,0 +1,74 @@
+#include "str.h"
+#include <assert.h>
+#include <string.h>
+#include "log.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;
+ }
+
+ for (size_t i = 0; i < a.len; i++) {
+ if (a.buf[i] != b.buf[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+char* Str8_to_cstr(arena* a, Str8 s) {
+ bool is_null_terminated = s.buf[s.len - 1] == 0;
+ size_t n_bytes = is_null_terminated ? s.len : s.len + 1;
+
+ u8* dest = arena_alloc(a, n_bytes);
+
+ memcpy(dest, s.buf, s.len);
+ if (is_null_terminated) {
+ dest[s.len] = '\0';
+ }
+ return (char*)dest;
+}
+
+char* Clone_cstr(arena* a, const char* s) {
+ if (s == NULL) {
+ WARN("Tried to clone a NULL char*");
+ return NULL;
+ }
+ Str8 st = Str8_cstr_view(s);
+ return Str8_to_cstr(a, st);
+}
+
+Str8 Str8_concat(arena* a, Str8 left, Str8 right) {
+ size_t n_bytes = left.len + right.len + 1;
+
+ u8* dest = arena_alloc(a, n_bytes);
+ memcpy(dest, left.buf, left.len);
+ memcpy(dest + right.len, right.buf, right.len);
+
+ dest[n_bytes - 1] = '\0';
+
+ return Str8_create(dest, n_bytes);
+}
+
+Str8 Str8_substr(Str8 s, u64 min, u64 max) {
+ assert(min >= 0);
+ assert(min < s.len);
+ assert(max >= 0);
+ assert(max <= s.len);
+ uint8_t* start = s.buf + (ptrdiff_t)min;
+ size_t new_len = max - min;
+ return (Str8){ .buf = start, .len = new_len };
+}
+
+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); }
diff --git a/archive/src/std/str.h b/archive/src/std/str.h
new file mode 100644
index 0000000..a29bf9a
--- /dev/null
+++ b/archive/src/std/str.h
@@ -0,0 +1,89 @@
+/**
+ * @file str.h
+ * @author your name (you@domain.com)
+ * @brief
+ * @version 0.1
+ * @date 2024-02-25
+ *
+ * @copyright Copyright (c) 2024
+ *
+ */
+#pragma once
+
+#include <ctype.h>
+
+#include "defines.h"
+#include "mem.h"
+
+/**
+ * @brief Fat pointer representing a UTF8 (TODO some APIs supporting utf8) encoded string
+ * @note when using `printf` you must use %s.*s length, string until our own modified
+ print routines are written. alternatively wrap in `cstr()` and pass to `%s`.
+ */
+typedef struct {
+ u8* buf;
+ size_t len;
+} Str8;
+
+// --- Constructors
+
+/** @brief Take a string literal and turn it into a `str8` */
+#define str8(s) \
+ (Str8) { (u8*)s, ((sizeof(s) / sizeof(*(s)) - 1)) }
+
+Str8 Str8_create(u8* buf, size_t len);
+
+// TODO: Str8_OntoArena(arena* a, Str8 s);
+
+/** @brief Return a null-terminated C string cloned onto an arena */
+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);
+
+char* Clone_cstr(arena* a, const char* s);
+
+// --- Comparisons
+
+/** @brief Compare two strings for exact equality */
+bool Str8_equals(Str8 a, Str8 b);
+
+/**
+ * @brief Compare the first `first_nchars` of each string for equality
+ * @details If either of the strings are shorter than the number only the characters up until the
+ end of the shorter string will be compared.
+ * @returns 0 if they are fully equal up until `first_nchars`, i.e they never differed, else it
+ returns the index at which the first string differed from the second string.
+*/
+size_t Str8_nequals(Str8 a, Str8 b, size_t first_nchars);
+
+bool Str8_ends_with(Str8 input_str, Str8 suffix);
+
+/// --- Subviews
+
+Str8 Str8_substr(Str8 s, u64 min, u64 max);
+/** @brief Keeps only the `first_n` chars of `s` */
+Str8 Str8_take(Str8 s, u64 first_n);
+/** @brief Keeps only the `last_n` chars of `s` */
+Str8 Str8_drop(Str8 s, u64 last_n);
+/** @brief Keeps everything after the first `n` chars of `s` */
+Str8 Str8_skip(Str8 s, u64 n);
+/** @brief Keeps everything before the last `n` chars of `s` */
+Str8 Str8_chop(Str8 s, u64 n);
+
+Str8 Str8_concat(arena* a, Str8 left, Str8 right);
+
+/// --- Misc
+
+static inline bool Str8_is_null_term(Str8 a) {
+ return a.buf[a.len] == 0; // This doesn't seem safe. YOLO
+}
+
+// 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/archive/src/std/utils.h b/archive/src/std/utils.h
new file mode 100644
index 0000000..c9827a3
--- /dev/null
+++ b/archive/src/std/utils.h
@@ -0,0 +1,4 @@
+#pragma once
+#include <stdbool.h>
+
+const char* bool_str(bool input) { return input ? "True" : "False"; } \ No newline at end of file
diff --git a/archive/src/systems/grid.c b/archive/src/systems/grid.c
new file mode 100644
index 0000000..e907865
--- /dev/null
+++ b/archive/src/systems/grid.c
@@ -0,0 +1,84 @@
+#include "grid.h"
+#include "file.h"
+#include "log.h"
+#include "maths.h"
+#include "maths_types.h"
+#include "primitives.h"
+#include "ral_common.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+#include "render.h"
+#include "render_types.h"
+#include "shader_layouts.h"
+
+void Grid_Init(Grid_Storage* storage) {
+ INFO("Infinite Grid initialisation");
+ Geometry plane_geo = Geo_CreatePlane(f32x2(1.0, 1.0), 1, 1);
+ Mesh plane_mesh = Mesh_Create(&plane_geo, true);
+ storage->plane_vertices = plane_mesh.vertex_buffer;
+
+ u32 indices[6] = { 5, 4, 3, 2, 1, 0 };
+ storage->plane_indices =
+ GPU_BufferCreate(6 * sizeof(u32), BUFFER_INDEX, BUFFER_FLAG_GPU, &indices);
+
+ GPU_RenderpassDesc rpass_desc = {
+ .default_framebuffer = true,
+ };
+ storage->renderpass = GPU_Renderpass_Create(rpass_desc);
+
+ arena scratch = arena_create(malloc(1024 * 1024), 1024 * 1024);
+
+ Str8 vert_path = str8("assets/shaders/grid.vert");
+ Str8 frag_path = str8("assets/shaders/grid.frag");
+ str8_opt vertex_shader = str8_from_file(&scratch, vert_path);
+ str8_opt fragment_shader = str8_from_file(&scratch, frag_path);
+ if (!vertex_shader.has_value || !fragment_shader.has_value) {
+ ERROR_EXIT("Failed to load shaders from disk")
+ }
+
+ ShaderDataLayout camera_data = Binding_Camera_GetLayout(NULL);
+
+ GraphicsPipelineDesc pipeline_desc = {
+ .debug_name = "Infinite grid pipeline",
+ .vertex_desc = static_3d_vertex_description(),
+ .data_layouts = { camera_data },
+ .data_layouts_count = 1,
+ .vs = {
+ .debug_name = "Grid vertex shader",
+ .filepath = vert_path,
+ .code = vertex_shader.contents,
+ },
+ .fs = {
+ .debug_name = "Grid fragment shader",
+ .filepath = frag_path,
+ .code = fragment_shader.contents,
+ },
+ .wireframe = false,
+ };
+ storage->pipeline = GPU_GraphicsPipeline_Create(pipeline_desc, storage->renderpass);
+}
+
+void Grid_Draw() {
+ Grid_Storage* grid = Render_GetGridStorage();
+ Grid_Execute(grid);
+}
+
+void Grid_Execute(Grid_Storage* storage) {
+ RenderScene* scene = Render_GetScene();
+ GPU_CmdEncoder* enc = GPU_GetDefaultEncoder();
+ GPU_CmdEncoder_BeginRender(enc, storage->renderpass);
+ GPU_EncodeBindPipeline(enc, storage->pipeline);
+ Mat4 view, proj;
+ u32x2 dimensions = GPU_Swapchain_GetDimensions();
+ Camera camera = scene->camera;
+ Camera_ViewProj(&camera, (f32)dimensions.x, (f32)dimensions.y, &view, &proj);
+ Binding_Camera camera_data = { .view = view,
+ .projection = proj,
+ .viewPos = vec4(camera.position.x, camera.position.y,
+ camera.position.z, 1.0) };
+ GPU_EncodeBindShaderData(enc, 0, Binding_Camera_GetLayout(&camera_data));
+ GPU_EncodeSetVertexBuffer(enc, storage->plane_vertices);
+ GPU_EncodeSetIndexBuffer(enc, storage->plane_indices);
+ GPU_EncodeDrawIndexedTris(enc, 6);
+ GPU_CmdEncoder_EndRender(enc);
+}
diff --git a/archive/src/systems/grid.h b/archive/src/systems/grid.h
new file mode 100644
index 0000000..d8bc567
--- /dev/null
+++ b/archive/src/systems/grid.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "ral_impl.h"
+#include "ral_types.h"
+
+typedef struct Grid_Storage {
+ GPU_Renderpass* renderpass;
+ GPU_Pipeline* pipeline;
+ BufferHandle plane_vertices;
+ BufferHandle plane_indices;
+} Grid_Storage;
+
+// --- Public API
+PUB void Grid_Init(Grid_Storage* storage);
+// void Grid_Shutdown(Grid_Storage* storage);
+PUB void Grid_Draw();
+
+// --- Internal
+void Grid_Execute(Grid_Storage* storage);
+// typedef struct GridUniforms {} GridUniforms;
+// ShaderDataLayout GridUniforms_GetLayout(void* data); \ No newline at end of file
diff --git a/archive/src/systems/input.c b/archive/src/systems/input.c
new file mode 100644
index 0000000..c3af96a
--- /dev/null
+++ b/archive/src/systems/input.c
@@ -0,0 +1,105 @@
+#include "input.h"
+
+#include <assert.h>
+#include <glfw3.h>
+#include <string.h>
+
+#include "keys.h"
+#include "log.h"
+
+static Input_State* g_input; // Use a global to simplify caller code
+
+bool Input_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 = KEYCODE_SPACE; i < KEYCODE_MAX; i++) {
+ input->depressed_keys[i] = false;
+ input->just_pressed_keys[i] = false;
+ input->just_released_keys[i] = false;
+ }
+
+ g_input = input;
+
+ assert(input->mouse.x_delta == 0);
+ assert(input->mouse.y_delta == 0);
+
+ INFO("Finish input init");
+ return true;
+}
+
+void Input_Shutdown(Input_State* input) {}
+
+void Input_Update(Input_State* input) {
+ glfwPollEvents();
+ // --- update keyboard input
+
+ // if we go from un-pressed -> pressed, set as "just pressed"
+ // if we go from pressed -> un-pressed, set as "just released"
+ for (int i = KEYCODE_SPACE; i < KEYCODE_MAX; i++) {
+ bool new_state = false;
+ if (glfwGetKey(input->window, i) == GLFW_PRESS) {
+ new_state = true;
+ } else {
+ new_state = false;
+ }
+ if (!input->depressed_keys[i] == false && new_state) {
+ input->just_pressed_keys[i] = true;
+ } else {
+ input->just_pressed_keys[i] = false;
+ }
+
+ if (input->depressed_keys[i] && !new_state) {
+ input->just_released_keys[i] = true;
+ } else {
+ input->just_released_keys[i] = false;
+ }
+
+ input->depressed_keys[i] = new_state;
+ }
+
+ // --- update mouse input
+
+ // cursor position
+ f64 current_x, current_y;
+ glfwGetCursorPos(input->window, &current_x, &current_y);
+ i32 quantised_cur_x = (i32)current_x;
+ i32 quantised_cur_y = (i32)current_y;
+
+ mouse_state new_mouse_state = { 0 };
+ new_mouse_state.x = quantised_cur_x;
+ new_mouse_state.y = quantised_cur_y;
+ new_mouse_state.x_delta = quantised_cur_x - input->mouse.x;
+ new_mouse_state.y_delta = quantised_cur_y - input->mouse.y;
+
+ // buttons
+ int left_state = glfwGetMouseButton(input->window, GLFW_MOUSE_BUTTON_LEFT);
+ int right_state = glfwGetMouseButton(input->window, GLFW_MOUSE_BUTTON_RIGHT);
+
+ for (int i = 0; i < 3; i++) {
+ new_mouse_state.prev_pressed_states[i] = input->mouse.cur_pressed_states[i];
+ }
+ new_mouse_state.cur_pressed_states[MOUSEBTN_LEFT] = left_state == GLFW_PRESS;
+ new_mouse_state.cur_pressed_states[MOUSEBTN_RIGHT] = right_state == GLFW_PRESS;
+
+ // this was dumb! need to also check button state changes lol
+ // if (new_mouse_state.x != input->mouse.x || new_mouse_state.y != input->mouse.y)
+ // TRACE("Mouse (x,y) = (%d,%d)", input->mouse.x, input->mouse.y);
+
+ input->mouse = new_mouse_state;
+}
+
+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]; }
+
+bool MouseBtn_Held(MouseBtn btn) {
+ assert(btn < 3);
+ return g_input->mouse.prev_pressed_states[btn] && g_input->mouse.cur_pressed_states[btn];
+}
+
+mouse_state Input_GetMouseState() { return g_input->mouse; } \ No newline at end of file
diff --git a/archive/src/systems/input.h b/archive/src/systems/input.h
new file mode 100644
index 0000000..c3b2500
--- /dev/null
+++ b/archive/src/systems/input.h
@@ -0,0 +1,53 @@
+/**
+ * @brief
+ */
+#pragma once
+
+#include "defines.h"
+#include "keys.h"
+
+struct GLFWWindow;
+
+typedef enum MouseBtn {
+ MOUSEBTN_LEFT = 0,
+ MOUSEBTN_RIGHT = 1,
+ MOUSEBTN_MIDDLE = 2,
+} MouseBtn;
+
+typedef struct mouse_state {
+ i32 x;
+ i32 y;
+ i32 x_delta;
+ i32 y_delta;
+ bool prev_pressed_states[3];
+ bool cur_pressed_states[3];
+} mouse_state;
+
+typedef struct Input_State {
+ struct GLFWwindow* window;
+ mouse_state mouse;
+ bool depressed_keys[KEYCODE_MAX];
+ bool just_pressed_keys[KEYCODE_MAX];
+ bool just_released_keys[KEYCODE_MAX];
+} Input_State;
+
+/** @brief `key` is currently being held down */
+PUB bool key_is_pressed(keycode key);
+
+/** @brief `key` was just pressed */
+PUB bool key_just_pressed(keycode key);
+
+/** @brief `key` was just released */
+PUB bool key_just_released(keycode key);
+
+// TODO: right btn as well
+PUB bool MouseBtn_Held(MouseBtn btn);
+
+// --- Lifecycle
+
+PUB bool Input_Init(Input_State* input, struct GLFWwindow* window);
+PUB void Input_Shutdown(Input_State* input);
+
+PUB void Input_Update(Input_State* state); // must be run once per main loop
+
+PUB mouse_state Input_GetMouseState(); \ No newline at end of file
diff --git a/archive/src/systems/keys.h b/archive/src/systems/keys.h
new file mode 100644
index 0000000..6082a59
--- /dev/null
+++ b/archive/src/systems/keys.h
@@ -0,0 +1,59 @@
+#pragma once
+
+typedef enum keycode {
+ // TODO: add all keycodes
+ KEYCODE_SPACE = 32,
+ KEYCODE_APOSTROPHE = 39,
+ KEYCODE_COMMA = 44,
+ KEYCODE_MINUS = 45,
+ KEYCODE_PERIOD = 46,
+ KEYCODE_SLASH = 47,
+ KEYCODE_0 = 48,
+ KEYCODE_1 = 49,
+ KEYCODE_2 = 50,
+ KEYCODE_3 = 51,
+ KEYCODE_4 = 52,
+ KEYCODE_5 = 53,
+ KEYCODE_6 = 54,
+ KEYCODE_7 = 55,
+ KEYCODE_8 = 56,
+ KEYCODE_9 = 57,
+ KEYCODE_SEMICOLON = 59,
+ KEYCODE_EQUAL = 61,
+ KEYCODE_A = 65,
+ KEYCODE_B = 66,
+ KEYCODE_C = 67,
+ KEYCODE_D = 68,
+ KEYCODE_E = 69,
+ KEYCODE_F = 70,
+ KEYCODE_G = 71,
+ KEYCODE_H = 72,
+ KEYCODE_I = 73,
+ KEYCODE_J = 74,
+ KEYCODE_K = 75,
+ KEYCODE_L = 76,
+ KEYCODE_M = 77,
+ KEYCODE_N = 78,
+ KEYCODE_O = 79,
+ KEYCODE_P = 80,
+ KEYCODE_Q = 81,
+ KEYCODE_R = 82,
+ KEYCODE_S = 83,
+ KEYCODE_T = 84,
+ KEYCODE_U = 85,
+ KEYCODE_V = 86,
+ KEYCODE_W = 87,
+ KEYCODE_X = 88,
+ KEYCODE_Y = 89,
+ KEYCODE_Z = 90,
+
+ 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;
diff --git a/archive/src/systems/screenspace.h b/archive/src/systems/screenspace.h
new file mode 100644
index 0000000..5f0c579
--- /dev/null
+++ b/archive/src/systems/screenspace.h
@@ -0,0 +1,53 @@
+/**
+ * @brief Drawing shapes for UI or other reasons in screenspace
+ */
+#pragma once
+
+#include "colours.h"
+#include "darray.h"
+#include "defines.h"
+#include "render_types.h"
+
+/** A draw_cmd packet for rendering a rectangle */
+struct draw_rect {
+ i32 x, y; // signed ints so we can draw things offscreen (e.g. a window half inside the viewport)
+ u32 width, height;
+ rgba colour;
+ // TODO: border colour, gradients
+};
+
+/** A draw_cmd packet for rendering a circle */
+struct draw_circle {
+ i32 x, y;
+ f32 radius;
+ rgba colour;
+};
+
+/** @brief Tagged union that represents a UI shape to be drawn. */
+typedef struct draw_cmd {
+ enum { DRAW_RECT, CIRCLE } draw_cmd_type;
+ union {
+ struct draw_rect rect;
+ struct draw_circle circle;
+ };
+} draw_cmd;
+
+KITC_DECL_TYPED_ARRAY(draw_cmd)
+
+typedef struct screenspace_state {
+ u32 rect_vbo;
+ u32 rect_vao;
+ // shader rect_shader;
+ draw_cmd_darray* draw_cmd_buf;
+} screenspace_state;
+
+// --- Lifecycle
+bool screenspace_2d_init(screenspace_state* state);
+void screenspace_2d_shutdown(screenspace_state* state);
+/** Drains the draw_cmd buffer and emits draw calls to render each one */
+void screenspace_2d_render(screenspace_state* state);
+
+struct core;
+
+/** @brief Draw a rectangle to the screen. (0,0) is the bottom-left */
+void draw_rectangle(struct core* core, rgba colour, i32 x, i32 y, u32 width, u32 height); \ No newline at end of file
diff --git a/archive/src/systems/terrain.c b/archive/src/systems/terrain.c
new file mode 100644
index 0000000..069000e
--- /dev/null
+++ b/archive/src/systems/terrain.c
@@ -0,0 +1,204 @@
+/**
+ * @brief
+ */
+#include "terrain.h"
+#include <assert.h>
+#include "file.h"
+#include "glad/glad.h"
+#include "log.h"
+#include "maths.h"
+#include "mem.h"
+#include "ral_common.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+#include "render.h"
+#include "shader_layouts.h"
+#include "str.h"
+
+#define TERRAIN_GRID_U 505
+#define TERRAIN_GRID_V 505
+
+bool Terrain_Init(Terrain_Storage* storage) {
+ storage->grid_dimensions = u32x2(TERRAIN_GRID_U, TERRAIN_GRID_V);
+ storage->hmap_loaded = false;
+
+ GPU_RenderpassDesc rpass_desc = {
+ .default_framebuffer = true,
+ };
+ storage->hmap_renderpass = GPU_Renderpass_Create(rpass_desc);
+
+ arena scratch = arena_create(malloc(1024 * 1024), 1024 * 1024);
+
+ Str8 vert_path = str8("assets/shaders/terrain.vert");
+ Str8 frag_path = str8("assets/shaders/terrain.frag");
+ str8_opt vertex_shader = str8_from_file(&scratch, vert_path);
+ str8_opt fragment_shader = str8_from_file(&scratch, frag_path);
+ if (!vertex_shader.has_value || !fragment_shader.has_value) {
+ ERROR_EXIT("Failed to load shaders from disk")
+ }
+
+ ShaderDataLayout camera_data = Binding_Camera_GetLayout(NULL);
+ ShaderDataLayout terrain_data = TerrainUniforms_GetLayout(NULL);
+
+ GraphicsPipelineDesc pipeline_desc = {
+ .debug_name = "terrain rendering pipeline",
+ .vertex_desc = static_3d_vertex_description(),
+ .data_layouts = { camera_data, terrain_data },
+ .data_layouts_count = 2,
+ .vs = {
+ .debug_name = "terrain vertex shader",
+ .filepath = vert_path,
+ .code = vertex_shader.contents,
+ },
+ .fs = {
+ .debug_name = "terrain fragment shader",
+ .filepath = frag_path,
+ .code = fragment_shader.contents,
+ },
+ .wireframe = false,
+ };
+ storage->hmap_pipeline = GPU_GraphicsPipeline_Create(pipeline_desc, storage->hmap_renderpass);
+
+ storage->texture = TextureLoadFromFile("assets/demo/textures/grass2.png");
+
+ return true;
+}
+
+void Terrain_Shutdown(Terrain_Storage* storage) {}
+
+void Terrain_LoadHeightmap(Terrain_Storage* storage, Heightmap hmap, f32 grid_scale,
+ bool free_on_upload) {
+ // If there's a current one we will delete it and reallocate buffers
+ if (storage->hmap_loaded) {
+ GPU_BufferDestroy(storage->vertex_buffer);
+ GPU_BufferDestroy(storage->index_buffer);
+ }
+
+ u32 width = hmap.pixel_dimensions.x;
+ u32 height = hmap.pixel_dimensions.y;
+ storage->grid_scale = grid_scale;
+
+ size_t num_vertices = storage->grid_dimensions.x * storage->grid_dimensions.y;
+ storage->num_vertices = num_vertices;
+
+ Vertex_darray* vertices = Vertex_darray_new(num_vertices);
+ u32 index = 0;
+ for (u32 i = 0; i < storage->grid_dimensions.x; i++) {
+ for (u32 j = 0; j < storage->grid_dimensions.y; j++) {
+ size_t position = j * storage->grid_dimensions.x + i;
+ u8* bytes = hmap.image_data;
+ u8 channel = bytes[position];
+ float value = (float)channel / 255.0;
+ // printf("(%d, %d) %d : %f \n", i, j, channel, value);
+
+ assert(index < num_vertices);
+ f32 height = Heightmap_HeightXZ(&hmap, i, j);
+ Vec3 v_pos = vec3_create(i * grid_scale, height, j * grid_scale);
+ Vec3 v_normal = VEC3_Y;
+ float tiling_factor = 505.0f;
+ Vec2 v_uv = vec2((f32)i / width * tiling_factor, (f32)j / height * tiling_factor);
+ Vertex v = { .static_3d = { .position = v_pos, .normal = v_normal, .tex_coords = v_uv } };
+ Vertex_darray_push(vertices, v);
+ index++;
+ }
+ }
+ BufferHandle vertices_handle = GPU_BufferCreate(num_vertices * sizeof(Vertex), BUFFER_VERTEX,
+ BUFFER_FLAG_GPU, vertices->data);
+ storage->vertex_buffer = vertices_handle;
+
+ u32 quad_count = (width - 1) * (height - 1);
+ u32 indices_count = quad_count * 6;
+ storage->indices_count = indices_count;
+ u32_darray* indices = u32_darray_new(indices_count);
+ for (u32 i = 0; i < (width - 1); i++) { // row
+ for (u32 j = 0; j < (height - 1); j++) { // col
+ u32 bot_left = i * width + j;
+ u32 top_left = (i + 1) * width + j;
+ u32 top_right = (i + 1) * width + (j + 1);
+ u32 bot_right = i * width + j + 1;
+
+ // top left tri
+ u32_darray_push(indices, top_right);
+ u32_darray_push(indices, top_left);
+ u32_darray_push(indices, bot_left);
+
+ // bottom right tri
+ u32_darray_push(indices, bot_right);
+ u32_darray_push(indices, top_right);
+ u32_darray_push(indices, bot_left);
+ }
+ }
+
+ BufferHandle indices_handle =
+ GPU_BufferCreate(indices_count * sizeof(u32), BUFFER_INDEX, BUFFER_FLAG_GPU, indices->data);
+ storage->index_buffer = indices_handle;
+}
+
+Heightmap Heightmap_FromImage(Str8 filepath) {
+ size_t max_size = MB(16);
+ arena arena = arena_create(malloc(max_size), max_size);
+ // str8_opt maybe_file = str8_from_file(&arena, filepath);
+ // assert(maybe_file.has_value);
+
+ TextureData hmap_tex = TextureDataLoad(Str8_to_cstr(&arena, filepath), false);
+
+ // arena_free_storage(&arena);
+
+ return (Heightmap){
+ .pixel_dimensions = hmap_tex.description.extents,
+ .filepath = filepath,
+ .image_data = hmap_tex.image_data,
+ .num_channels = hmap_tex.description.num_channels,
+ .is_uploaded = false,
+ };
+}
+
+void Terrain_Draw(Terrain_Storage* storage) {
+ GPU_CmdEncoder* enc = GPU_GetDefaultEncoder();
+ GPU_EncodeBindPipeline(enc, storage->hmap_pipeline);
+ RenderScene* scene = Render_GetScene();
+
+ Mat4 view, proj;
+ u32x2 dimensions = GPU_Swapchain_GetDimensions();
+ Camera_ViewProj(&scene->camera, (f32)dimensions.x, (f32)dimensions.y, &view, &proj);
+ Binding_Camera camera_data = { .view = view,
+ .projection = proj,
+ .viewPos = vec4(scene->camera.position.x, scene->camera.position.y,
+ scene->camera.position.z, 1.0) };
+ GPU_EncodeBindShaderData(enc, 0, Binding_Camera_GetLayout(&camera_data));
+
+ TerrainUniforms uniforms = { .tex_slot_1 = storage->texture };
+ ShaderDataLayout terrain_data = TerrainUniforms_GetLayout(&uniforms);
+ GPU_EncodeBindShaderData(enc, 1, terrain_data);
+
+ GPU_EncodeSetVertexBuffer(enc, storage->vertex_buffer);
+ GPU_EncodeSetIndexBuffer(enc, storage->index_buffer);
+
+ GPU_EncodeDrawIndexedTris(enc, storage->indices_count);
+ // glDrawArrays(GL_POINTS, 0, storage->num_vertices);
+}
+
+f32 Heightmap_HeightXZ(const Heightmap* hmap, u32 x, u32 z) {
+ // its single channel so only one byte per pixel
+ size_t position = x * hmap->pixel_dimensions.x + z;
+ u8* bytes = hmap->image_data;
+ u8 channel = bytes[position];
+ float value = (float)channel / 2.0; /// 255.0;
+ return value;
+}
+
+ShaderDataLayout TerrainUniforms_GetLayout(void* data) {
+ TerrainUniforms* d = data;
+ bool has_data = data != NULL;
+
+ ShaderBinding b1 = {
+ .label = "TextureSlot1",
+ .kind = BINDING_TEXTURE,
+ .vis = VISIBILITY_FRAGMENT,
+ };
+
+ if (has_data) {
+ b1.data.texture.handle = d->tex_slot_1;
+ }
+ return (ShaderDataLayout){ .bindings = { b1 }, .binding_count = 1 };
+}
diff --git a/archive/src/systems/terrain.h b/archive/src/systems/terrain.h
new file mode 100644
index 0000000..5a96132
--- /dev/null
+++ b/archive/src/systems/terrain.h
@@ -0,0 +1,72 @@
+/**
+ * @file terrain.h
+ * @brief
+ */
+
+#pragma once
+
+/*
+Future:
+ - Chunked terrain
+ - Dynamic LOD
+*/
+
+#include "defines.h"
+#include "maths_types.h"
+#include "ral_types.h"
+#include "render.h"
+#include "str.h"
+
+typedef struct Heightmap {
+ Str8 filepath;
+ u32x2 pixel_dimensions;
+ void* image_data;
+ u32 num_channels;
+ bool is_uploaded;
+} Heightmap;
+
+typedef struct Terrain_Storage {
+ // arena terrain_allocator;
+ u32x2 grid_dimensions;
+ f32 grid_scale;
+ u32 num_vertices;
+ Heightmap heightmap; // NULL = no heightmap
+ GPU_Renderpass* hmap_renderpass;
+ GPU_Pipeline* hmap_pipeline;
+ TextureHandle texture;
+
+ bool hmap_loaded;
+ BufferHandle vertex_buffer;
+ BufferHandle index_buffer;
+ u32 indices_count;
+} Terrain_Storage;
+
+// --- Public API
+PUB bool Terrain_Init(Terrain_Storage* storage);
+PUB void Terrain_Shutdown(Terrain_Storage* storage);
+PUB void Terrain_Draw(
+ Terrain_Storage* storage); // NOTE: For now it renders directly to main framebuffer
+
+/** @brief Sets the active heightmap to be rendered and collided against. */
+PUB void Terrain_LoadHeightmap(Terrain_Storage* storage, Heightmap hmap, f32 grid_scale,
+ bool free_on_upload);
+PUB Heightmap Heightmap_FromImage(Str8 filepath);
+PUB Heightmap Heightmap_FromPerlin(/* TODO: perlin noise generation parameters */);
+
+PUB bool Terrain_IsActive(); // checks whether we have a loaded heightmap and it's being rendered
+
+/** @brief Get the height (the Y component) for a vertex at a particular coordinate in the heightmap
+ */
+PUB f32 Heightmap_HeightXZ(const Heightmap* hmap, u32 x, u32 z);
+
+/** @brief Calculate the normal vector of a vertex at a particular coordinate in the heightmap */
+PUB Vec3 Heightmap_NormalXZ(const Heightmap* hmap, f32 x, f32 z);
+
+// /** @brief Generate the `geometry_data` for a heightmap ready to be uploaded to the GPU */
+// Geometry geo_heightmap(arena* a, Heightmap heightmap);
+
+typedef struct TerrainUniforms {
+ TextureHandle tex_slot_1;
+} TerrainUniforms;
+
+ShaderDataLayout TerrainUniforms_GetLayout(void* data); \ No newline at end of file
diff --git a/archive/src/systems/text.c b/archive/src/systems/text.c
new file mode 100644
index 0000000..2bb5399
--- /dev/null
+++ b/archive/src/systems/text.c
@@ -0,0 +1 @@
+// TODO: Port from previous repo \ No newline at end of file
diff --git a/archive/src/systems/text.h b/archive/src/systems/text.h
new file mode 100644
index 0000000..983ffd6
--- /dev/null
+++ b/archive/src/systems/text.h
@@ -0,0 +1,53 @@
+/**
+ * @brief
+ */
+#pragma once
+
+#include <stb_truetype.h>
+
+#include "darray.h"
+#include "defines.h"
+#include "ral.h"
+#include "render_types.h"
+
+// struct core;
+
+// /** @brief internal font struct */
+// typedef struct font {
+// const char *name;
+// stbtt_fontinfo stbtt_font;
+// stbtt_bakedchar c_data[96];
+// texture_handle bitmap_tex;
+// } font;
+
+// typedef struct draw_text_packet {
+// char *contents;
+// f32 x;
+// f32 y;
+// } draw_text_packet;
+
+// KITC_DECL_TYPED_ARRAY(draw_text_packet)
+
+// typedef struct text_system_state {
+// font default_font;
+// shader_handle glyph_shader;
+// u32 glyph_vbo;
+// u32 glyph_vao;
+// draw_text_packet_darray *draw_cmd_buf;
+// // TODO: fonts array or hashtable
+// } text_system_state;
+
+// void text_system_render(text_system_state *text);
+
+// // --- Lifecycle functions
+// bool text_system_init(text_system_state *text);
+// void text_system_shutdown(text_system_state *text);
+
+// // --- Drawing
+
+// /**
+// * @brief immediate mode draw text.
+// * @note immediately emits draw calls causing a shader program switch if you weren't previously
+// drawing text in the current frame.
+// */
+// void draw_text(struct core *core, f32 x, f32 y, char *contents);
diff --git a/archive/src/transform_hierarchy.c b/archive/src/transform_hierarchy.c
new file mode 100644
index 0000000..a5f4d97
--- /dev/null
+++ b/archive/src/transform_hierarchy.c
@@ -0,0 +1,185 @@
+
+/**
+ * @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, ModelHandle 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
+// // FIXME
+// // 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);
+// }
diff --git a/archive/src/transform_hierarchy.h b/archive/src/transform_hierarchy.h
new file mode 100644
index 0000000..142ea99
--- /dev/null
+++ b/archive/src/transform_hierarchy.h
@@ -0,0 +1,80 @@
+/**
+ * @file transform_hierarchy.h
+ */
+#pragma once
+
+#include "maths_types.h"
+#include "ral.h"
+#include "render_types.h"
+
+#define MAX_TF_NODE_CHILDREN \
+ 32 /** TEMP: Make it simpler to manage children in `transform_node`s */
+
+typedef struct TransformHierarchy TransformHierarchy;
+
+struct Transform_Node {
+ ModelHandle 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;
+typedef struct Transform_Node TF_Node;
+
+// --- Lifecycle
+
+/** @brief Allocates and returns an empty transform hierarchy with a root node */
+TransformHierarchy* TransformHierarchy_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* TransformHierarchy_RootNode(TransformHierarchy* tfh);
+
+// // --- Mutations
+// Transform_Node* TransformHierarchy_AddNode(transform_node* parent, ModelHandle 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);