summaryrefslogtreecommitdiff
path: root/include/celeritas.h
blob: 4a75abdb525f753f66900c7d635ca89ef98b48db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
#pragma once

/*
  Common abbreviations:
  buf = buffer
  tex = texture
  desc = description
  idx = index
*/

// Standard library includes
#include <assert.h>
#include <math.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Third party dependency includes
#include <glfw3.h>

#define inlined inline  // remove inlined functions so we can generate bindings

typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;

typedef float f32;
typedef double f64;

_Static_assert(sizeof(bool) == 1, "type bool should be 1 byte");

_Static_assert(sizeof(u8) == 1, "type u8 should be 1 byte");
_Static_assert(sizeof(u16) == 2, "type u16 should be 2 byte");
_Static_assert(sizeof(u32) == 4, "type u32 should be 4 byte");
_Static_assert(sizeof(u64) == 8, "type u64 should be 8 byte");

_Static_assert(sizeof(i8) == 1, "type i8 should be 1 byte");
_Static_assert(sizeof(i16) == 2, "type i16 should be 2 byte");
_Static_assert(sizeof(i32) == 4, "type i32 should be 4 byte");
_Static_assert(sizeof(i64) == 8, "type i64 should be 8 byte");

_Static_assert(sizeof(f32) == 4, "type f32 should be 4 bytes");
_Static_assert(sizeof(f64) == 8, "type f64 should be 8 bytes");

_Static_assert(sizeof(ptrdiff_t) == 8, "type ptrdiff_t should be 8 bytes");

#define alignof(x) _Alignof(x)

#define threadlocal _Thread_local

// Wrap a u32 to make a type-safe "handle" or ID
#define DEFINE_HANDLE(name) \
  typedef struct name name; \
  struct name {             \
    u32 raw;                \
  }

#define KB(x) ((size_t)x * 1000)
#define MB(x) ((size_t)x * 1000 * 1000)
#define GB(x) ((size_t)x * 1000 * 1000 * 1000)

// TEMP
// #define CEL_PLATFORM_LINUX

// Platform informs renderer backend (unless user overrides)
#if defined(CEL_PLATFORM_LINUX) || defined(CEL_PLATFORM_WINDOWS)
#define GPU_VULKAN 1
#else
#define GPU_METAL 1
#endif

#define TODO(msg)            \
  printf("TODO: %s\n", msg); \
  exit(1);

// --- Forward declare vital structures
typedef struct core core;
typedef struct renderer renderer;
typedef struct input_state input_state;
struct GLFWwindow;

// Global getters
core* get_g_core();
renderer* get_g_renderer();
struct GLFWwindow* get_window();

struct core {
  const char* app_name;
  struct GLFWwindow* window;
  bool should_exit;
  renderer* renderer;
  input_state* input;
};
extern core g_core; /** @brief global `Core` that other files can use */

void core_bringup(const char* window_name, struct GLFWwindow* optional_window);
void core_shutdown();
void core_resize_viewport(int width, int height);
bool app_should_exit();

// --- Error handling

// global error handling
#define CEL_OK 0
extern int g_last_error;

/** @brief get last global status value */
int cel_check_status();
void _cel_push_error(int error_code);

// --- Memory facilities: Allocators, helpers

/** @brief Fixed-size arena allocator
    @note Inspired by https://nullprogram.com/blog/2023/09/27/
*/
typedef struct arena {
  u8* begin;
  u8* curr;
  u8* end;
} arena;

typedef struct arena_save {
  arena* arena;
  u8* savepoint;
} arena_save;

/** @brief Create a new fixed arena with the provided storage. */
arena arena_create(void* backing_buffer, size_t capacity);

/**
 * @brief Allocates memory on the arena.
 *
 * @param arena
 * @param size Number of bytes to reserve
 * @return Pointer to the allocated memory or NULL if there's not enough space
 */
void* arena_alloc(arena* a, size_t size);

void* arena_alloc_align(arena* a, size_t size, size_t alignment);

/** @brief Clear the arena thereby resetting the index to the start. */
void arena_clear(arena* a);

// TODO
arena_save arena_savepoint(arena* a);
// TODO
void arena_rewind(arena_save savepoint);

/** @brief Growable arena allocator based on OS-level memory address space overcommit */
typedef struct grow_arena {
  // TODO: grow arena
} grow_arena;

// Pool
typedef struct void_pool_header void_pool_header;  // TODO: change name of this
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(void* storage, 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);

// Create a pool of objects with type `T`
#define TYPED_POOL(T, Name)                                                          \
  typedef struct Name##_pool {                                                       \
    void_pool inner;                                                                 \
  } Name##_pool;                                                                     \
                                                                                     \
  static Name##_pool Name##_pool_create(void* storage, u64 cap, u64 entry_size) {    \
    void_pool p = void_pool_create(storage, "\"" #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 };                                     \
  }

// --- Strings

/**
 * @brief Fat pointer representing a 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 str8 {
  u8* buf;
  size_t len;
} str8;


/** @brief Take a string literal and turn it into a `str8` */
#define str8(s) \
  (Str8) { (u8*)s, ((sizeof(s) / sizeof(*(s)) - 1)) }

// Comparisons

/** @brief Compare two strings for exact equality */
bool str8_equals(str8 a, str8 b);

// 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);

// Misc

static inline bool Str8_is_null_term(str8 a) {
  return a.buf[a.len] == 0;  // This doesn't seem safe. YOLO
}

// --- Logging

// Log levels
typedef enum loglevel {
  LOG_LEVEL_FATAL = 0,
  LOG_LEVEL_ERROR = 1,
  LOG_LEVEL_WARN = 2,
  LOG_LEVEL_INFO = 3,
  LOG_LEVEL_DEBUG = 4,
  LOG_LEVEL_TRACE = 5,
} loglevel;

void log_output(char* module, loglevel level, const char* msg, ...);

#define NAMESPACED_LOGGER(module)                    \
  static inline void FATAL(const char* msg, ...) {   \
    va_list args;                                    \
    va_start(args, msg);                             \
    log_output(#module, LOG_LEVEL_FATAL, msg, args); \
    va_end(args);                                    \
  }                                                  \
  static inline void ERROR(const char* msg, ...) {   \
    va_list args;                                    \
    va_start(args, msg);                             \
    log_output(#module, LOG_LEVEL_ERROR, msg, args); \
    va_end(args);                                    \
  }                                                  \
  static inline void WARN(const char* msg, ...) {    \
    va_list args;                                    \
    va_start(args, msg);                             \
    log_output(#module, LOG_LEVEL_WARN, msg, args);  \
    va_end(args);                                    \
  }                                                  \
  static inline void INFO(const char* msg, ...) {    \
    va_list args;                                    \
    va_start(args, msg);                             \
    log_output(#module, LOG_LEVEL_INFO, msg, args);  \
    va_end(args);                                    \
  }                                                  \
  static inline void DEBUG(const char* msg, ...) {   \
    va_list args;                                    \
    va_start(args, msg);                             \
    log_output(#module, LOG_LEVEL_DEBUG, msg, args); \
    va_end(args);                                    \
  }                                                  \
  static inline void TRACE(const char* msg, ...) {   \
    va_list args;                                    \
    va_start(args, msg);                             \
    log_output(#module, LOG_LEVEL_TRACE, msg, args); \
    va_end(args);                                    \
  }

// --- Maths

// Constants
#define PI 3.14159265358979323846
#define HALF_PI 1.57079632679489661923
#define TAU (2.0 * PI)

/** @brief 2D Vector */
typedef struct vec2 {
  f32 x, y;
} vec2;

/** @brief 3D Vector */
typedef struct vec3 {
  f32 x, y, z;
} vec3;

/** @brief 4D Vector */
typedef struct vec4 {
  f32 x, y, z, w;
} vec4;

/** @brief Quaternion */
typedef vec4 quat;

/** @brief 4x4 Matrix
 * @note Column-major is used throughout the Celeritas maths code.
 */
typedef struct mat4 {
  vec4 x_axis, y_axis, z_axis, w_axis;
} mat4;

/** @brief 3D affine transformation */
typedef struct transform {
  vec3 position;
  quat rotation;
  vec3 scale;
  bool is_dirty;
} transform;

_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");

// vector 2 functions
inlined vec2 vec2_create(f32 x, f32 y);

// vector 3 functions
inlined vec3 vec3_create(f32 x, f32 y, f32 z);
inlined vec3 vec3_add(vec3 u, vec3 v);
inlined vec3 vec3_sub(vec3 u, vec3 v);
inlined vec3 vec3_mult(vec3 u, f32 s);
inlined vec3 vec3_div(vec3 u, f32 s);
inlined f32 vec3_len_squared(vec3 a);
inlined f32 vec3_len(vec3 a);
inlined vec3 vec3_negate(vec3 a);
inlined vec3 vec3_normalise(vec3 a);
inlined f32 vec3_dot(vec3 a, vec3 b);
inlined vec3 vec3_cross(vec3 a, vec3 b);

// vector 4 functions
vec4 vec4_create(f32 x, f32 y, f32 z, f32 w);

// quaternion functions
inlined quat quat_ident();
quat quat_normalise(quat a);
f32 quat_dot(quat a, quat b);
quat quat_from_axis_angle(vec3 axis, f32 angle, bool normalise);
quat quat_slerp(quat a, quat b, f32 percentage);

// matrix functions
mat4 mat4_ident();
mat4 mat4_translation(vec3 position);
mat4 mat4_scale(vec3 scale);
mat4 mat4_rotation(quat rotation);
mat4 mat4_mult(mat4 lhs, mat4 rhs);
mat4 mat4_transposed(mat4 m);
mat4 mat4_perspective(f32 fov_radians, f32 aspect_ratio, f32 near_clip, f32 far_clip);
mat4 mat4_orthographic(f32 left, f32 right, f32 bottom, f32 top, f32 near_clip, f32 far_clip);
mat4 mat4_look_at(vec3 position, vec3 target, vec3 up);

// transform functions
inlined transform transform_create(vec3 pos, quat rot, vec3 scale);
mat4 transform_to_mat(transform* tf);

// helpers

#define vec3(x, y, z) ((vec3){ x, y, z })
#define vec4(x, y, z, w) ((vec4){ x, y, z, w })
inlined vec4 v3tov4(vec3 v3) { return vec4_create(v3.x, v3.y, v3.z, 1.0); }

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);

// --- RAL

DEFINE_HANDLE(buf_handle);
DEFINE_HANDLE(tex_handle);
DEFINE_HANDLE(pipeline_handle);
DEFINE_HANDLE(compute_pipeline_handle);

#define MAX_VERTEX_ATTRIBUTES 16
#define MAX_SHADER_BINDINGS 16

// Backend-specific structs
typedef struct gpu_swapchain gpu_swapchain;
typedef struct gpu_encoder gpu_encoder;  // Render command encoder
typedef struct gpu_compute_encoder gpu_compute_encoder;
// typedef struct gpu_buffer gpu_buffer;
// typedef struct gpu_texture gpu_texture;

// NOTE: Can we just use Storage buffer for everything?
// typedef enum gpu_buf_type {} gpu_buf_type;

typedef enum gpu_tex_type {
  TEXTURE_TYPE_2D,
  TEXTURE_TYPE_3D,
  TEXTURE_TYPE_2D_ARRAY,
  TEXTURE_TYPE_CUBE_MAP,
  TEXTURE_TYPE_COUNT
} gpu_tex_type;

/** @brief Buffer Description - used by buffer creation functions and for internal bookkeeping */
typedef struct buffer_desc {
  const char* label;
  u64 size;
} buffer_desc;

/** @brief Texture Description - used by texture creation functions and for internal bookkeeping */
typedef struct texture_desc {
  gpu_tex_type tex_type;
  // GPU_TextureFormat format;
  int width, height, num_channels;
} texture_desc;

typedef struct buffer_region {
  buf_handle buffer;
  u64 offset;
  u64 size;
} buffer_region;

typedef struct texture_region {
  tex_handle texture;
  u32 layer;
  u32 x, y, z;
  u32 width, height, depth;
  // todo: mip level
} texture_region;

/// @strip_prefix(ATTR_)
typedef enum vertex_attrib_type {
  ATTR_F32,
  ATTR_F32x2,
  ATTR_F32x3,
  ATTR_F32x4,
  ATTR_U32,
  ATTR_U32x2,
  ATTR_U32x3,
  ATTR_U32x4,
  ATTR_I32,
  ATTR_I32x2,
  ATTR_I32x3,
  ATTR_I32x4,
} vertex_attrib_type;

typedef struct vertex_desc {
  const char* label;
  vertex_attrib_type attributes[MAX_VERTEX_ATTRIBUTES];
  u32 attribute_count;
  u32 padding;
} vertex_desc;

typedef struct static_3d_vert {
  vec4 pos;
  vec4 norm;
  vec2 uv;
  vec2 pad;
} static_3d_vert;

// Some default formats
vertex_desc static_3d_vertex_format();

void print_static_3d_vert(static_3d_vert* vertex_data);

typedef enum shader_binding_type {
  BINDING_BYTES,
  BINDING_BUFFER,
  BINDING_BUFFER_ARRAY,
  BINDING_TEXTURE,
  BINDING_TEXTURE_ARRAY,
  BINDING_SAMPLER,
  BINDING_COUNT
} shader_binding_type;

typedef enum shader_stage {
  STAGE_VERTEX = 1 << 0,
  STAGE_FRAGMENT = 1 << 1,
  STAGE_COMPUTE = 1 << 2,
} shader_stage;

typedef struct shader_binding {
  const char* label;
  shader_binding_type binding_type;
  shader_stage visibility;
  union {
    struct {
      u32 size;
      void* data;
    } bytes;
    struct {
      buf_handle handle;
    } buffer;
    struct {
      tex_handle handle;
    } texture;
  } data;
} shader_binding;

typedef struct shader_data_layout {
  shader_binding bindings[MAX_SHADER_BINDINGS];
  size_t binding_count;
} shader_data_layout;

typedef struct shader_function {
  const char* source;
  bool is_spirv;
  const char* entry_point;
  shader_stage stage;
} shader_function;

typedef enum cull_mode { Cull_BackFace, Cull_FrontFace } cull_mode;

typedef struct gfx_pipeline_desc {
  const char* label;
  vertex_desc vertex_desc;
  shader_function vertex;
  shader_function fragment;
  // ShaderDataLayout data_layouts[MAX_SHADER_DATA_LAYOUTS];
  // u32 data_layouts_count;
} gfx_pipeline_desc;

typedef struct compute_pipeline_desc { /* TODO */
} compute_pipeline_desc;

typedef struct render_pass_desc {
} render_pass_desc;

typedef struct compute_pass_desc {
} compute_pass_desc;

// --- RAL Functions

// Resources
buf_handle ral_buffer_create(u64 size, const void* data);
void ral_buffer_destroy(buf_handle handle);
void ral_buffer_upload_data(buf_handle, u64 size, const void* data);

tex_handle ral_texture_create(texture_desc desc, bool create_view, const void* data);
tex_handle ral_texture_load_from_file(const char* filepath);
void ral_texture_destroy(tex_handle handle);

// Encoders / cmd buffers
/** @brief Grabs a new command encoder from the pool of available ones and begins recording */
gpu_encoder* ral_render_encoder(render_pass_desc rpass_desc);

/** @brief Grabs a new compute pass encoder and begins recording */
gpu_compute_encoder ral_compute_encoder(compute_pass_desc cpass_desc);

void ral_encoder_finish(gpu_encoder* enc);
void ral_encoder_submit(gpu_encoder* enc);
void ral_encoder_finish_and_submit(gpu_encoder* enc);

pipeline_handle ral_gfx_pipeline_create(gfx_pipeline_desc desc);
void ral_gfx_pipeline_destroy(pipeline_handle handle);

compute_pipeline_handle ral_compute_pipeline_create(compute_pipeline_desc);
void ral_compute_pipeline_destroy(compute_pipeline_handle handle);

// Encoding
void ral_encode_bind_pipeline(gpu_encoder* enc, pipeline_handle pipeline);
void ral_set_default_settings(gpu_encoder* enc);
void ral_encode_set_vertex_buf(gpu_encoder* enc, buf_handle vbuf);
void ral_encode_set_index_buf(gpu_encoder* enc, buf_handle ibuf);
void ral_encode_set_texture(gpu_encoder* enc, tex_handle texture, u32 slot);
void ral_bind_buffer(gpu_encoder* enc, buf_handle, u32 index);
void ral_encode_draw_tris(gpu_encoder* enc, size_t start, size_t count);

// Backend lifecycle
void ral_backend_init(const char* window_name, struct GLFWwindow* window);
void ral_backend_shutdown();
void ral_backend_resize_framebuffer(int width, int height);

// Frame lifecycle

/** @brief A function pointer to a function that is called with any prepared graphics resources for a frame
           + access to the renderer. */
typedef void (*scoped_draw_commands)();  // callback that we run our draw commands within.
// allows us to wrap some api-specific behaviour

void ral_frame_start();

void ral_frame_draw(scoped_draw_commands draw_fn);
void ral_frame_end();

// --- Containers (Forward declared as internals are unnecessary for external header)
typedef struct u32_darray u32_darray;

// --- Base Renderer types

DEFINE_HANDLE(mesh_handle);
DEFINE_HANDLE(material_handle);
DEFINE_HANDLE(model_handle);

typedef struct geometry {
  vertex_desc vertex_format;
  void* vertex_data;
  size_t data_length;
  size_t n_vertices;
  bool has_indices;  // When this is false indexed drawing is not used
  u32_darray* indices;
} geometry;

typedef u32 joint_idx;

/** @brief Bone hierarchy for a mesh */
typedef struct armature {
} armature;

/** @brief Mesh data that has been uploaded to GPU and is ready to be rendered each frame
           Gets stored in a pool */
typedef struct mesh {
  buf_handle vertex_buffer;
  buf_handle index_buffer;
  // todo: material?
  geometry geo;  // the originating mesh data
  const armature* skinning_data;
} mesh;

typedef struct draw_mesh_cmd {
  mesh_handle mesh;
  mat4 transform;
  vec3 bounding_sphere_center;
  f32 bounding_sphere_radius;
  const armature* skinning_data;  // NULL = static mesh
  bool cast_shadows;
} draw_mesh_cmd;

// --- Geometry/Mesh primitives

geometry geo_plane(f32 x_scale, f32 z_scale, u32 tiling_u, u32 tiling_v);
geometry geo_cuboid(f32 x_scale, f32 y_scale, f32 z_scale);
geometry geo_cylinder(f32 radius, f32 height, u32 resolution);
geometry geo_cone(f32 radius, f32 height, u32 resolution);
geometry geo_uv_sphere(f32 radius, u32 north_south_lines, u32 east_west_lines);
geometry geo_ico_sphere(f32 radius, f32 n_subdivisions);
void geo_scale_uniform(geometry* geo, f32 scale);
void geo_scale_xyz(geometry* geo, vec3 scale_xyz);

// --- Renderer

/**
 * @brief Reference renderer you can use to draw beautiful 3D worlds.
 * @details Implements a Forward+ renderer on top of the RAL (Render Abstraction Layer) provided in celeritas-core.

 */
typedef struct renderer {
  /* PBR pipeline */
  pipeline_handle pbr_pipeline;
  /* CSM pipeline */
} renderer;

// void renderer_init(renderer* rend);
// void renderer_shutdown(renderer* rend);

typedef struct camera {
  vec3 position;
  // TODO: move to using a quaternion for the camera's orientation - need to update
  //       how the view transformation matrix is calculated
  vec3 forwards;
  vec3 up;
  f32 fov;
} camera;

/**
 * @brief Calculates a matrix that transforms a model into camera-space with perspective projection applied.
 */
mat4 camera_view_proj(camera camera, f32 lens_height, f32 lens_width, mat4* out_view, mat4* out_proj);

// TODO: Filament PBR model

// --- Scene / Transform Hierarchy

// --- Gameplay

// --- Game and model data

/**
 * @brief 
 * @details A 'model' is a fairly complex construct within the Celeritas renderer comprised of multiple cpu & gpu resources.
 */
typedef struct model {
} model;

/**
 * @brief 
 * @note The memory required to load the GLTF is handled by the system, hence no arena parameter. On fail that temporary allocation
 *       will be freed for you.
 * @param filepath path to the .gltf file 
 * @return model_handle 
 */
model_handle model_load_from_gltf(const char* filepath);

// --- Animation

typedef enum keyframe_kind { Keyframe_Rotation, Keyframe_Translation, Keyframe_Scale, Keyframe_Weights } keyframe_kind;

extern const char* keyframe_kind_strings[4];

typedef union keyframe {
  quat rotation;
  vec3 translation;
  vec3 scale;
  f32 weights[4];
} keyframe;

typedef struct keyframes {
  keyframe_kind kind;
  keyframe* values;
  size_t n_frames;
} keyframes;

typedef enum interpolation { Interpolation_Step, Interpolation_Linear, Interpolation_Cubic } interpolation;

extern const char* interpolation_strings[3];

typedef struct animation_spline {
  f32* timestamps;
  size_t n_timestamps;
  keyframes frames;
} animation_spline;

// Compute shader approach so we only need one kind of vertex format

// --- Input

// --- Collisions

// --- Physics

// --- Platform

/** @brief Loads the contents at a filepath into a string */
str8 plat_load_string_from_file(arena* a, const char* filepath);

// synchronisation primitives
/** wrapper around a platform specific mutex */
typedef struct plat_mutex plat_mutex;

/** @brief Locks the mutex, blocking until its available */
void plat_mutex_lock(plat_mutex* mutex);

/** @brief Tries to lock the mutex but if it is already acquired by something else, will return immediately with false */
bool plat_mutex_try_lock(plat_mutex* mutex);

/** @brief Releases the lock allowing other threads to acquire it */
void plat_mutex_unlock(plat_mutex* mutex);

typedef struct plat_thread plat_thread; 

// --- Audio

// --- Threadpool

typedef struct ring_queue {

} ring_queue;

typedef struct threadpool_worker {} threadpool_worker;

#define MAX_THREADPOOL_WORKERS 32

typedef struct threadpool {
  ring_queue task_queue;
  plat_mutex* task_queue_lock;
  // todo: atomic u16 remaining tasks
  threadpool_worker workers[MAX_THREADPOOL_WORKERS];
  size_t n_workers;
} threadpool; 

void threadpool_add_task();