summaryrefslogtreecommitdiff
path: root/src/render
diff options
context:
space:
mode:
Diffstat (limited to 'src/render')
-rw-r--r--src/render/archive/backends/backend_test.c (renamed from src/render/backends/backend_test.c)0
-rw-r--r--src/render/archive/backends/metal/README.md (renamed from src/render/backends/metal/README.md)0
-rw-r--r--src/render/archive/backends/metal/backend_metal.h (renamed from src/render/backends/metal/backend_metal.h)0
-rw-r--r--src/render/archive/backends/metal/backend_metal.m (renamed from src/render/backends/metal/backend_metal.m)0
-rw-r--r--src/render/archive/backends/opengl/backend_opengl.c (renamed from src/render/backends/opengl/backend_opengl.c)0
-rw-r--r--src/render/archive/backends/opengl/backend_opengl.h (renamed from src/render/backends/opengl/backend_opengl.h)0
-rw-r--r--src/render/archive/backends/vulkan/README.md (renamed from src/render/backends/vulkan/README.md)0
-rw-r--r--src/render/archive/backends/vulkan/backend_vulkan.c (renamed from src/render/backends/vulkan/backend_vulkan.c)0
-rw-r--r--src/render/archive/backends/vulkan/backend_vulkan.h (renamed from src/render/backends/vulkan/backend_vulkan.h)0
-rw-r--r--src/render/builtin_materials.h153
-rw-r--r--src/render/immdraw.c28
-rw-r--r--src/render/immdraw.h27
-rw-r--r--src/render/immediate.c46
-rw-r--r--src/render/pbr.c216
-rw-r--r--src/render/pbr.h61
-rw-r--r--src/render/ral.c97
-rw-r--r--src/render/ral_types.h102
-rw-r--r--src/render/render.c357
-rw-r--r--src/render/render.h146
-rw-r--r--src/render/render_types.h143
-rw-r--r--src/render/renderpasses.c140
-rw-r--r--src/render/renderpasses.h56
-rw-r--r--src/render/shader_layouts.h70
-rw-r--r--src/render/shadows.c211
-rw-r--r--src/render/shadows.h48
-rw-r--r--src/render/skybox.c167
-rw-r--r--src/render/skybox.h41
27 files changed, 1515 insertions, 594 deletions
diff --git a/src/render/backends/backend_test.c b/src/render/archive/backends/backend_test.c
index 6347e27..6347e27 100644
--- a/src/render/backends/backend_test.c
+++ b/src/render/archive/backends/backend_test.c
diff --git a/src/render/backends/metal/README.md b/src/render/archive/backends/metal/README.md
index f87f5c1..f87f5c1 100644
--- a/src/render/backends/metal/README.md
+++ b/src/render/archive/backends/metal/README.md
diff --git a/src/render/backends/metal/backend_metal.h b/src/render/archive/backends/metal/backend_metal.h
index 9561bb6..9561bb6 100644
--- a/src/render/backends/metal/backend_metal.h
+++ b/src/render/archive/backends/metal/backend_metal.h
diff --git a/src/render/backends/metal/backend_metal.m b/src/render/archive/backends/metal/backend_metal.m
index 4787755..4787755 100644
--- a/src/render/backends/metal/backend_metal.m
+++ b/src/render/archive/backends/metal/backend_metal.m
diff --git a/src/render/backends/opengl/backend_opengl.c b/src/render/archive/backends/opengl/backend_opengl.c
index 43105e2..43105e2 100644
--- a/src/render/backends/opengl/backend_opengl.c
+++ b/src/render/archive/backends/opengl/backend_opengl.c
diff --git a/src/render/backends/opengl/backend_opengl.h b/src/render/archive/backends/opengl/backend_opengl.h
index 73a19ed..73a19ed 100644
--- a/src/render/backends/opengl/backend_opengl.h
+++ b/src/render/archive/backends/opengl/backend_opengl.h
diff --git a/src/render/backends/vulkan/README.md b/src/render/archive/backends/vulkan/README.md
index 220ed64..220ed64 100644
--- a/src/render/backends/vulkan/README.md
+++ b/src/render/archive/backends/vulkan/README.md
diff --git a/src/render/backends/vulkan/backend_vulkan.c b/src/render/archive/backends/vulkan/backend_vulkan.c
index 8801230..8801230 100644
--- a/src/render/backends/vulkan/backend_vulkan.c
+++ b/src/render/archive/backends/vulkan/backend_vulkan.c
diff --git a/src/render/backends/vulkan/backend_vulkan.h b/src/render/archive/backends/vulkan/backend_vulkan.h
index 6ca0bb5..6ca0bb5 100644
--- a/src/render/backends/vulkan/backend_vulkan.h
+++ b/src/render/archive/backends/vulkan/backend_vulkan.h
diff --git a/src/render/builtin_materials.h b/src/render/builtin_materials.h
deleted file mode 100644
index b15e62e..0000000
--- a/src/render/builtin_materials.h
+++ /dev/null
@@ -1,153 +0,0 @@
-/**
- * @file builtin_materials.h
- * @author your name (you@domain.com)
- * @brief
- * @version 0.1
- * @date 2024-06-15
- *
- * @copyright Copyright (c) 2024
- *
- */
-#pragma once
-
-#include <assert.h>
-#include "defines.h"
-#include "ral_types.h"
-
-// Currently supported materials
-// - Blinn Phong (textured)
-// - PBR (params)
-// - PBR (textured)
-
-// Thoughts
-// --------
-//
-// A material and a shader are inextricably linked. The input data for a shader needs the material.
-// However, a shader may require more than just a material?
-
-// --- Common uniform blocks
-
-/* In glsl code we call it 'MVP_Matrices' */
-typedef struct mvp_matrix_uniforms {
- mat4 model;
- mat4 view;
- mat4 projection;
-} mvp_matrix_uniforms;
-
-// --- PBR (params)
-
-typedef struct pbr_params_material_uniforms {
- vec3 albedo;
- f32 metallic;
- f32 roughness;
- f32 ao;
- f32 pad[2];
-} pbr_params_material_uniforms;
-
-typedef struct pbr_point_light {
- vec3 pos;
- f32 pad;
- vec3 color;
- f32 pad2;
-} pbr_point_light;
-
-typedef struct pbr_params_light_uniforms {
- pbr_point_light pointLights[4];
- vec4 viewPos;
-} pbr_params_light_uniforms;
-
-typedef struct pbr_params_bindgroup {
- mvp_matrix_uniforms mvp_matrices;
- pbr_params_material_uniforms material;
- pbr_params_light_uniforms lights;
-} pbr_params_bindgroup;
-
-static shader_data_layout pbr_params_shader_layout(void* data) {
- pbr_params_bindgroup* d = (pbr_params_bindgroup*)data;
- bool has_data = data != NULL;
-
- shader_binding b1 = { .label = "MVP_Matrices",
- .type = SHADER_BINDING_BYTES,
- .stores_data = has_data,
- .data = { .bytes = { .size = sizeof(mvp_matrix_uniforms) } } };
-
- shader_binding b2 = { .label = "PBR_Params",
- .type = SHADER_BINDING_BYTES,
- .stores_data = has_data,
- .data = { .bytes = { .size = sizeof(pbr_params_material_uniforms) } } };
-
- shader_binding b3 = { .label = "Scene_Lights",
- .type = SHADER_BINDING_BYTES,
- .stores_data = has_data,
- .data = { .bytes = { .size = sizeof(pbr_params_light_uniforms) } } };
-
- if (has_data) {
- // printf("Size %d \n", b3.data.bytes.size);
- b1.data.bytes.data = &d->mvp_matrices;
- b2.data.bytes.data = &d->material;
- /* d->lights.viewPos = vec3(0, 1, 0); */
- b3.data.bytes.data = &d->lights;
- // print_vec3(d->lights.viewPos);
- }
-
- return (shader_data_layout){ .name = "pbr_params", .bindings = { b1, b2, b3 }, .bindings_count = 3
-
- };
-}
-
-static void* shader_layout_get_binding(shader_data_layout* layout, u32 nth_binding) {
- assert(nth_binding < layout->bindings_count);
- return &layout->bindings[nth_binding].data;
-}
-
-typedef struct pbr_textures {
- texture_handle albedo_tex;
- texture_handle metal_roughness_tex;
- texture_handle ao_tex;
- texture_handle normal_tex;
-} pbr_textures;
-
-typedef struct pbr_textured_bindgroup {
- mvp_matrix_uniforms mvp_matrices;
- pbr_params_light_uniforms lights;
- pbr_textures textures;
-} pbr_textured_bindgroup;
-
-static shader_data_layout pbr_textured_shader_layout(void* data) {
- pbr_textured_bindgroup* d = (pbr_textured_bindgroup*)data;
- bool has_data = data != NULL;
-
- shader_binding b1 = { .label = "MVP_Matrices",
- .type = SHADER_BINDING_BYTES,
- .stores_data = has_data,
- .data = { .bytes = { .size = sizeof(mvp_matrix_uniforms) } } };
-
- shader_binding b2 = { .label = "Scene_Lights",
- .type = SHADER_BINDING_BYTES,
- .stores_data = has_data,
- .data = { .bytes = { .size = sizeof(pbr_params_light_uniforms) } } };
-
- shader_binding b3 = { .label = "albedoMap",
- .type = SHADER_BINDING_TEXTURE,
- .stores_data = has_data };
- shader_binding b4 = { .label = "metallicRoughnessMap",
- .type = SHADER_BINDING_TEXTURE,
- .stores_data = has_data };
- shader_binding b5 = { .label = "aoMap", .type = SHADER_BINDING_TEXTURE, .stores_data = has_data };
- shader_binding b6 = { .label = "normalMap",
- .type = SHADER_BINDING_TEXTURE,
- .stores_data = has_data };
-
- if (has_data) {
- b1.data.bytes.data = &d->mvp_matrices;
- b2.data.bytes.data = &d->lights;
- b3.data.texture.handle = d->textures.albedo_tex;
- b4.data.texture.handle = d->textures.metal_roughness_tex;
- b5.data.texture.handle = d->textures.ao_tex;
- b6.data.texture.handle = d->textures.normal_tex;
- }
-
- return (shader_data_layout){ .name = "pbr_params",
- .bindings = { b1, b2, b3, b4, b5, b6 },
- .bindings_count = 6 };
-}
diff --git a/src/render/immdraw.c b/src/render/immdraw.c
new file mode 100644
index 0000000..c711b0c
--- /dev/null
+++ b/src/render/immdraw.c
@@ -0,0 +1,28 @@
+#include "immdraw.h"
+#include "log.h"
+#include "primitives.h"
+#include "ral_common.h"
+#include "ral_impl.h"
+#include "ral_types.h"
+#include "render.h"
+#include "shader_layouts.h"
+
+void Immdraw_Init(Immdraw_Storage* storage) {
+ INFO("Immediate drawing initialisation");
+ // meshes
+ // Geometry sphere_geo = Geo_CreateUVsphere(1.0, 8, 8);
+ // storage->sphere = Mesh_Create(&sphere_geo, false);
+
+ // pipeline / material
+ ShaderDataLayout camera_data = Binding_Camera_GetLayout(NULL);
+ GraphicsPipelineDesc pipeline_desc = {
+ .debug_name = "Immediate Draw Pipeline",
+ .data_layouts = { camera_data },
+ .data_layouts_count = 1,
+
+ };
+ // storage->colour_pipeline = GPU_GraphicsPipeline_Create(pipeline_desc,
+ // GPU_GetDefaultRenderpass());
+}
+
+void Immdraw_Sphere(Transform tf, f32 size, Vec4 colour, bool wireframe) {} \ No newline at end of file
diff --git a/src/render/immdraw.h b/src/render/immdraw.h
new file mode 100644
index 0000000..0d58375
--- /dev/null
+++ b/src/render/immdraw.h
@@ -0,0 +1,27 @@
+/**
+ * @brief Immediate-mode drawing APIs
+ */
+
+#pragma once
+#include "defines.h"
+#include "maths_types.h"
+#include "ral_impl.h"
+#include "render_types.h"
+
+typedef struct Immdraw_Storage {
+ Mesh plane;
+ Mesh cube;
+ Mesh sphere;
+ GPU_Pipeline* colour_pipeline;
+} Immdraw_Storage;
+
+// --- 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_Sphere(Transform tf, f32 size, Vec4 colour, bool wireframe);
+PUB void Immdraw_TransformGizmo(Transform tf, f32 size);
diff --git a/src/render/immediate.c b/src/render/immediate.c
deleted file mode 100644
index 63a62b8..0000000
--- a/src/render/immediate.c
+++ /dev/null
@@ -1,46 +0,0 @@
-#include "immediate.h"
-#include "glad/glad.h"
-#include "maths.h"
-#include "primitives.h"
-#include "ral_types.h"
-#include "render.h"
-#include "render_types.h"
-
-typedef struct immdraw_system {
- // primitive meshes (get reused for each draw call)
- mesh plane;
- mesh cube;
- mesh sphere;
- // command lists
-
-} immdraw_system;
-
-bool immdraw_system_init(immdraw_system* state) {
- geometry_data plane_geometry = geo_create_plane(f32x2(1, 1));
- state->plane = mesh_create(&plane_geometry, true);
-
- geometry_data cube_geometry = geo_create_cuboid(f32x3(1, 1, 1));
- state->cube = mesh_create(&cube_geometry, true);
-
- geometry_data sphere_geometry = geo_create_uvsphere(1.0, 48, 48);
- state->sphere = mesh_create(&sphere_geometry, true);
-
- return true;
-}
-
-void immdraw_plane(vec3 pos, quat rotation, f32 u_scale, f32 v_scale, vec4 colour) {}
-
-void immdraw_system_render(immdraw_system* state) {}
-
-// void imm_draw_sphere(vec3 pos, f32 radius, vec4 colour) {
-// // Create the vertices
-// geometry_data geometry = geo_create_uvsphere(radius, 16, 16);
-// geo_set_vertex_colours(&geometry, colour);
-
-// // Upload to GPU
-// mat4 model = mat4_translation(pos);
-
-// // Set pipeline
-
-// // Draw
-// } \ No newline at end of file
diff --git a/src/render/pbr.c b/src/render/pbr.c
new file mode 100644
index 0000000..1a7b4de
--- /dev/null
+++ b/src/render/pbr.c
@@ -0,0 +1,216 @@
+#include "pbr.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();
+ storage->pbr_pipeline = PBR_PipelineCreate(storage->pbr_pass);
+}
+
+GPU_Renderpass* PBR_RPassCreate() {
+ GPU_RenderpassDesc desc = { .default_framebuffer = true };
+ return GPU_Renderpass_Create(desc);
+}
+
+GPU_Pipeline* PBR_PipelineCreate(GPU_Renderpass* rpass) {
+ arena scratch = arena_create(malloc(1024 * 1024), 1024 * 1024);
+
+ const char* vert_path = "assets/shaders/pbr_textured.vert";
+ const char* frag_path = "assets/shaders/pbr_textured.frag";
+ // Str8 vert_path = str8("assets/shaders/pbr_textured.vert");
+ // Str8 frag_path = str8("assets/shaders/pbr_textured.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")
+ // }
+ char* vert_shader = string_from_file(vert_path);
+ char* frag_shader = string_from_file(frag_path);
+
+ 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);
+
+ GraphicsPipelineDesc desc = {
+ .debug_name = "PBR 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 = vertex_shader.contents
+ .code = vert_shader
+ },
+ .fs = { .debug_name = "PBR (textured) Fragment Shader",
+ .filepath = str8(frag_path),
+ .code = frag_shader
+ // .code = fragment_shader.contents,
+ },
+ .depth_test = true,
+ .wireframe = false,
+ };
+ return 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);
+ GPU_EncodeBindPipeline(enc, storage->pbr_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));
+
+ // set buffers
+ GPU_EncodeSetVertexBuffer(enc, mesh->vertex_buffer);
+ GPU_EncodeSetIndexBuffer(enc, mesh->index_buffer);
+ // draw
+ GPU_EncodeDrawIndexed(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/src/render/pbr.h b/src/render/pbr.h
new file mode 100644
index 0000000..cbdc577
--- /dev/null
+++ b/src/render/pbr.h
@@ -0,0 +1,61 @@
+/**
+ * @file pbr.h
+ * @brief PBR render pass
+ */
+
+#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
+typedef struct PBR_Storage {
+ GPU_Renderpass* pbr_pass;
+ GPU_Pipeline* pbr_pipeline;
+
+} PBR_Storage; // Stores all necessary data and handles
+
+typedef struct PBRMaterialUniforms {
+ Material mat;
+} PBRMaterialUniforms;
+
+PUB void PBR_Init(PBR_Storage* storage);
+
+// NOTE: For simplicity's sake we will render this pass directly to the default framebuffer
+PUB void PBR_Run(PBR_Storage* storage
+ // light data
+ // camera
+ // geometry
+ // materials
+);
+
+typedef struct PBR_Params {
+ Vec3 albedo;
+ f32 metallic;
+ f32 roughness;
+ f32 ambient_occlusion;
+} PBR_Params;
+
+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;
+
+PUB Material PBRMaterialDefault();
+PUB ShaderDataLayout PBRMaterial_GetLayout(void* data);
+
+// --- Internal
+
+GPU_Renderpass* PBR_RPassCreate();
+
+GPU_Pipeline* PBR_PipelineCreate(GPU_Renderpass* rpass);
+
+void PBR_Execute(PBR_Storage* storage, Camera camera, TextureHandle shadowmap_tex,
+ RenderEnt* entities, size_t entity_count);
diff --git a/src/render/ral.c b/src/render/ral.c
deleted file mode 100644
index 9ca99ce..0000000
--- a/src/render/ral.c
+++ /dev/null
@@ -1,97 +0,0 @@
-#include "ral.h"
-#include "file.h"
-#include "log.h"
-#include "mem.h"
-#include "str.h"
-
-#if defined(CEL_REND_BACKEND_VULKAN)
-#include "backend_vulkan.h"
-#elif defined(CEL_REND_BACKEND_METAL)
-#include "backend_metal.h"
-#elif defined(CEL_REND_BACKEND_OPENGL)
-#include "backend_opengl.h"
-#endif
-
-size_t vertex_attrib_size(vertex_attrib_type 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;
- }
-}
-
-void vertex_desc_add(vertex_description* builder, const char* name, vertex_attrib_type type) {
- u32 i = builder->attributes_count;
-
- size_t size = vertex_attrib_size(type);
- builder->attributes[i] = type;
- builder->stride += size;
- builder->attr_names[i] = name;
-
- builder->attributes_count++;
-}
-
-vertex_description static_3d_vertex_description() {
- vertex_description builder = { .debug_label = "Standard static 3d vertex format" };
- vertex_desc_add(&builder, "inPosition", ATTR_F32x3);
- vertex_desc_add(&builder, "inNormal", ATTR_F32x3);
- vertex_desc_add(&builder, "inTexCoords", ATTR_F32x2);
- builder.use_full_vertex_size = true;
- return builder;
-}
-
-void backend_pools_init(arena* a, gpu_backend_pools* backend_pools) {
- pipeline_layout_pool pipeline_layout_pool =
- pipeline_layout_pool_create(a, MAX_PIPELINES, sizeof(gpu_pipeline_layout));
- 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;
-
- // context.gpu_pools;
-}
-
-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;
-}
-
-void print_shader_binding(shader_binding b) {
- printf("Binding name: %s type %s vis %d stores data %d\n", b.label,
- shader_binding_type_name[b.type], b.vis, b.stores_data);
-}
-
-shader_desc shader_quick_load(const char* filepath) {
- arena a = arena_create(malloc(1024 * 1024), 1024 * 1024);
- str8 path = str8_cstr_view(filepath);
- str8_opt shader = str8_from_file(&a, path);
- if (!shader.has_value) {
- ERROR_EXIT("Failed to load shaders from disk");
- }
-
- return (shader_desc){
- .debug_name = filepath,
- .code = shader.contents,
- .filepath = path,
- .is_spirv = true,
- };
-}
diff --git a/src/render/ral_types.h b/src/render/ral_types.h
deleted file mode 100644
index 19f6ea3..0000000
--- a/src/render/ral_types.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/**
- * @file ral_types.h
- * @author your name (you@domain.com)
- * @brief Struct and enum definitions for RAL
- * @version 0.1
- * @date 2024-04-27
- *
- * @copyright Copyright (c) 2024
- *
- */
-#pragma once
-
-#include "darray.h"
-#include "defines.h"
-#include "maths_types.h"
-
-#define MAX_VERTEX_ATTRIBUTES 16
-
-/* #ifndef RENDERER_TYPED_HANDLES */
-CORE_DEFINE_HANDLE(buffer_handle);
-CORE_DEFINE_HANDLE(texture_handle);
-CORE_DEFINE_HANDLE(sampler_handle);
-CORE_DEFINE_HANDLE(shader_handle);
-CORE_DEFINE_HANDLE(pipeline_layout_handle);
-CORE_DEFINE_HANDLE(pipeline_handle);
-CORE_DEFINE_HANDLE(renderpass_handle);
-#define ABSENT_MODEL_HANDLE 999999999
-
-// --- Shaders & Bindings
-
-typedef enum shader_visibility {
- VISIBILITY_VERTEX = 1 << 0,
- VISIBILITY_FRAGMENT = 1 << 1,
- VISIBILITY_COMPUTE = 1 << 2,
-} shader_visibility;
-
-/** @brief Describes the kind of binding a `shader_binding` is for. This changes how we create
- * backing data for it. */
-typedef enum shader_binding_type {
- /**
- * @brief Binds a buffer to a shader
- * @note Vulkan: Becomes a Storage Buffer
- */
- SHADER_BINDING_BUFFER,
- SHADER_BINDING_BUFFER_ARRAY,
- SHADER_BINDING_TEXTURE,
- SHADER_BINDING_TEXTURE_ARRAY,
- SHADER_BINDING_SAMPLER,
- /**
- * @brief Binds raw data to a shader
- * @note Vulkan: Becomes a Uniform Buffer
- */
- SHADER_BINDING_BYTES,
- // TODO: Acceleration Structure
- SHADER_BINDING_COUNT
-} shader_binding_type;
-
-static const char* shader_binding_type_name[] = { "BUFFER", "BUFFER ARRAY", "TEXTURE",
- "TEXTURE ARRAY", "SAMPLER", "BYTES",
- "COUNT" };
-
-// pub trait ShaderBindable: Clone + Copy {
-// fn bind_to(&self, context: &mut PipelineContext, index: u32);
-// }
-
-typedef struct shader_binding {
- const char* label;
- shader_binding_type type;
- shader_visibility vis;
- bool stores_data; /** @brief if this is true then the shader binding has references to live data,
- if false then its just being used to describe a layout and .data
- should be zeroed */
- union {
- struct {
- buffer_handle handle;
- } buffer;
- struct {
- void* data;
- size_t size;
- } bytes;
- struct {
- texture_handle handle;
- } texture;
- } data; /** @brief can store any kind of data that we can bind to a shader / descriptor set */
-} shader_binding;
-
-void print_shader_binding(shader_binding b);
-
-/** @brief A list of bindings that describe what data a shader / pipeline expects
- @note This roughly correlates to a descriptor set layout in Vulkan
-*/
-
-// ? How to tie together materials and shaders
-
-// Three registers
-// 1. low level graphics api calls "ral"
-// 2. higher level render calls
-// 3. simplified immediate mode API
-
-// 3 - you don't need to know how the renderer works at all
-// 2 - you need to know how the overall renderer is designed
-// 1 - you need to understand graphics API specifics
diff --git a/src/render/render.c b/src/render/render.c
new file mode 100644
index 0000000..697a79e
--- /dev/null
+++ b/src/render/render.c
@@ -0,0 +1,357 @@
+/**
+ * @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;
+};
+
+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;
+ }
+ }
+
+ // #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
+
+ ren->window = window;
+ *out_window = window;
+
+ glfwMakeContextCurrent(ren->window);
+
+ // FIXME
+ // 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);
+
+ 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");
+ // TODO: black_1x1
+
+ 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");
+ 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);
+}
+
+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;
+}
+
+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/src/render/render.h b/src/render/render.h
new file mode 100644
index 0000000..0aee51c
--- /dev/null
+++ b/src/render/render.h
@@ -0,0 +1,146 @@
+/**
+ * @brief
+ */
+
+#pragma once
+#include "defines.h"
+#include "grid.h"
+#include "maths_types.h"
+#include "ral_types.h"
+#include "render_types.h"
+#include "shadows.h"
+#include "camera.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);
+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();
+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/src/render/render_types.h b/src/render/render_types.h
new file mode 100644
index 0000000..5fdca8a
--- /dev/null
+++ b/src/render/render_types.h
@@ -0,0 +1,143 @@
+/**
+ * @file render_types.h
+ * @brief
+ */
+
+#pragma once
+#include "defines.h"
+#include "maths_types.h"
+#include "mem.h"
+#include "ral_types.h"
+
+// --- Handles
+CORE_DEFINE_HANDLE(ModelHandle);
+CORE_DEFINE_HANDLE(MaterialHandle);
+CORE_DEFINE_HANDLE(MeshHandle);
+#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 Geometry {
+ VertexFormat format;
+ Vertex_darray* vertices;
+ u32_darray* indices;
+ bool has_indices;
+ size_t index_count;
+} Geometry;
+
+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
+ // i32 material_index; // -1 => no material
+ MaterialHandle material;
+ 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;
+} 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;
+ 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 \ No newline at end of file
diff --git a/src/render/renderpasses.c b/src/render/renderpasses.c
deleted file mode 100644
index b93d487..0000000
--- a/src/render/renderpasses.c
+++ /dev/null
@@ -1,140 +0,0 @@
-/**
- * @file renderpasses.c
- * @author your name (you@domain.com)
- * @brief
- * @version 0.1
- * @date 2024-06-22
- *
- * @copyright Copyright (c) 2024
- *
- */
-
-#include "renderpasses.h"
-#include "file.h"
-#include "log.h"
-#include "maths_types.h"
-#include "ral.h"
-#include "ral_types.h"
-
-#define SHADOW_WIDTH 1000
-#define SHADOW_HEIGHT 1000
-
-shader_data_layout debug_quad_layout(void* data) {
- debug_quad_uniform* d = data;
- bool has_data = data != NULL;
-
- shader_binding b1 = { .label = "depthMap",
- .type = SHADER_BINDING_TEXTURE,
- .stores_data = has_data };
- if (has_data) {
- b1.data.texture.handle = d->depthMap;
- }
- return (
- shader_data_layout){ .name = "debug quad uniforms", .bindings = { b1 }, .bindings_count = 1 };
-}
-
-gpu_pipeline* debug_quad_pipeline_create() {
- gpu_renderpass_desc rpass_desc = { .default_framebuffer = true };
- gpu_renderpass* rpass = gpu_renderpass_create(&rpass_desc);
- shader_data shader_layout = { .data = NULL, .shader_data_get_layout = debug_quad_layout };
- struct graphics_pipeline_desc desc = { .debug_name = "Shadow maps debug quad",
- .vertex_desc = static_3d_vertex_description(),
- .data_layouts = { shader_layout },
- .data_layouts_count = 1,
- .vs = shader_quick_load("assets/shaders/debug_quad.vert"),
- .fs = shader_quick_load("assets/shaders/debug_quad.frag"),
- .renderpass = rpass,
- .wireframe = false };
-
- return gpu_graphics_pipeline_create(desc);
-}
-
-void ren_shadowmaps_init(ren_shadowmaps* storage) {
- storage->rpass = shadowmaps_renderpass_create();
- storage->static_pipeline = shadowmaps_pipeline_create(storage->rpass);
- storage->debug_quad = debug_quad_pipeline_create();
- storage->depth_tex = storage->rpass->description.depth_stencil;
-}
-
-gpu_renderpass* shadowmaps_renderpass_create() {
- // Create depthmap texture
- u32x2 extents = u32x2(SHADOW_WIDTH, SHADOW_HEIGHT);
- texture_desc depthmap_desc = { .extents = extents,
- .format = CEL_TEXTURE_FORMAT_DEPTH_DEFAULT,
- .tex_type = CEL_TEXTURE_TYPE_2D };
- texture_handle depthmap = gpu_texture_create(depthmap_desc, false, NULL);
-
- gpu_renderpass_desc shadows_desc = { .default_framebuffer = false,
- .has_color_target = false,
- .has_depth_stencil = true,
- .depth_stencil = depthmap };
- return gpu_renderpass_create(&shadows_desc);
-}
-
-// == shader bindings
-
-shader_data_layout model_uniform_layout(void* data) {
- bool has_data = data != NULL;
-
- shader_binding b1 = { .label = "Model",
- .type = SHADER_BINDING_BYTES,
- .stores_data = has_data,
- .data = { .bytes.size = sizeof(model_uniform) } };
- if (has_data) {
- b1.data.bytes.data = data;
- }
- return (shader_data_layout){ .name = "model_uniform", .bindings = { b1 }, .bindings_count = 1 };
-}
-shader_data_layout lightspace_uniform_layout(void* data) {
- bool has_data = data != NULL;
-
- shader_binding b1 = { .label = "LightSpace",
- .type = SHADER_BINDING_BYTES,
- .stores_data = has_data,
- .data = { .bytes.size = sizeof(lightspace_tf_uniform) } };
- if (has_data) {
- b1.data.bytes.data = data;
- }
- return (shader_data_layout){ .name = "lightspace_tf_uniform",
- .bindings = { b1 },
- .bindings_count = 1 };
-}
-
-// ==================
-
-gpu_pipeline* shadowmaps_pipeline_create(gpu_renderpass* rpass) {
- arena scratch = arena_create(malloc(1024 * 1024), 1024 * 1024);
-
- str8 vert_path = str8lit("assets/shaders/shadows.vert");
- str8 frag_path = str8lit("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");
- }
-
- // We'll have two data layouts. 1. for the light-space transform, and 2. for the model matrix
- shader_data model_uniform = { .data = NULL, .shader_data_get_layout = &model_uniform_layout };
- shader_data lightspace_uniform = { .data = NULL,
- .shader_data_get_layout = &lightspace_uniform_layout };
-
- struct graphics_pipeline_desc desc = { .debug_name = "Shadowmap drawing pipeline",
- .vertex_desc = static_3d_vertex_description(),
- .data_layouts = { model_uniform, lightspace_uniform },
- .data_layouts_count = 2,
- .vs = { .debug_name = "Shadows Vert shader",
- .filepath = vert_path,
- .code = vertex_shader.contents,
- .is_spirv = true },
- .fs = { .debug_name = "Shadows Frag shader",
- .filepath = frag_path,
- .code = fragment_shader.contents,
- .is_spirv = true },
- .renderpass = rpass };
-
- arena_free_storage(&scratch);
- return gpu_graphics_pipeline_create(desc);
-}
-
-void renderpass_shadowmap_execute(gpu_renderpass* pass, render_entity* entities,
- size_t entity_count) {}
diff --git a/src/render/renderpasses.h b/src/render/renderpasses.h
deleted file mode 100644
index 1ceea6c..0000000
--- a/src/render/renderpasses.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * @file renderpasses.h
- * @author your name (you@domain.com)
- * @brief Built-in renderpasses to the engine
- * @version 0.1
- * @date 2024-04-28
- *
- * @copyright Copyright (c) 2024
- *
- */
-#pragma once
-#include "ral.h"
-#include "ral_types.h"
-#include "render_types.h"
-
-// Shadowmap pass
-// Blinn-phong pass
-// Unlit pass
-// Debug visualisations pass
-
-// Don't need to pass in *anything*.
-gpu_renderpass* renderpass_blinn_phong_create();
-void renderpass_blinn_phong_execute(gpu_renderpass* pass, render_entity* entities,
- size_t entity_count);
-
-typedef struct ren_shadowmaps {
- u32 width;
- u32 height;
- gpu_renderpass* rpass;
- gpu_pipeline* static_pipeline;
- gpu_pipeline* debug_quad;
- texture_handle depth_tex;
-} ren_shadowmaps;
-
-typedef struct model_uniform {
- mat4 model;
-} model_uniform;
-typedef struct lightspace_tf_uniform {
- mat4 lightSpaceMatrix;
-} lightspace_tf_uniform;
-
-typedef struct debug_quad_uniform {
- texture_handle depthMap;
-} debug_quad_uniform;
-
-shader_data_layout model_uniform_layout(void* data);
-shader_data_layout lightspace_uniform_layout(void* data);
-shader_data_layout debug_quad_layout(void* data);
-
-void ren_shadowmaps_init(ren_shadowmaps* storage);
-
-gpu_renderpass* shadowmaps_renderpass_create();
-gpu_pipeline* shadowmaps_pipeline_create(gpu_renderpass* rpass);
-
-void renderpass_shadowmap_execute(gpu_renderpass* pass, render_entity* entities,
- size_t entity_count);
diff --git a/src/render/shader_layouts.h b/src/render/shader_layouts.h
new file mode 100644
index 0000000..09cf129
--- /dev/null
+++ b/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 };
+} \ No newline at end of file
diff --git a/src/render/shadows.c b/src/render/shadows.c
new file mode 100644
index 0000000..18a5f7a
--- /dev/null
+++ b/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));
+ // 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_EncodeDrawIndexed(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_EncodeDrawIndexed(&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/src/render/shadows.h b/src/render/shadows.h
new file mode 100644
index 0000000..0482d10
--- /dev/null
+++ b/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/src/render/skybox.c b/src/render/skybox.c
new file mode 100644
index 0000000..cc5797f
--- /dev/null
+++ b/src/render/skybox.c
@@ -0,0 +1,167 @@
+#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");
+ assert(n == 6); // ! 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);
+
+ int width, height, nrChannels;
+ // unsigned char *data;
+ for (unsigned int i = 0; i < n; i++) {
+ TextureData data = TextureDataLoad(
+ face_paths[i],
+ false); // stbi_load(textures_faces[i].c_str(), &width, &height, &nrChannels, 0);
+ 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")
+ }
+
+ // VertexDescription pos_only = { .debug_label = "Position only verts" };
+ // VertexDesc_AddAttr(&pos_only, "inPos", ATTR_F32x3);
+ // pos_only.use_full_vertex_size = true;
+
+ 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_EncodeDraw(enc, 36);
+
+ GPU_CmdEncoder_EndRender(enc);
+ glDepthFunc(GL_LESS);
+}
diff --git a/src/render/skybox.h b/src/render/skybox.h
new file mode 100644
index 0000000..c2ef3a2
--- /dev/null
+++ b/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