summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2023-01-04 15:46:22 -0800
committer3gg <3gg@shellblade.net>2023-01-04 15:46:22 -0800
commit27ff505b6daaf5b0ec5f6af422f727a032f83c6b (patch)
treeca005986bfd02b760e2e8363627258f37f4de320
parent1e3fcf5b38d67fb54102786be74af42be5c6792f (diff)
Move ShaderProgram from Material to Mesh in preparation for shader permutations.
-rw-r--r--gfx/include/gfx/scene/README.md8
-rw-r--r--gfx/include/gfx/scene/material.h7
-rw-r--r--gfx/include/gfx/scene/mesh.h6
-rw-r--r--gfx/include/gfx/util/scene.h5
-rw-r--r--gfx/shaders/cook_torrance.frag1
-rw-r--r--gfx/shaders/cook_torrance.vert1
-rw-r--r--gfx/src/renderer/renderer.c103
-rw-r--r--gfx/src/scene/material.c6
-rw-r--r--gfx/src/scene/material_impl.h5
-rw-r--r--gfx/src/scene/mesh.c2
-rw-r--r--gfx/src/scene/mesh_impl.h1
-rw-r--r--gfx/src/util/scene.c310
-rw-r--r--gfx/src/util/skyquad.c23
-rw-r--r--gltfview/src/game.c104
14 files changed, 303 insertions, 279 deletions
diff --git a/gfx/include/gfx/scene/README.md b/gfx/include/gfx/scene/README.md
index 916596b..1910abe 100644
--- a/gfx/include/gfx/scene/README.md
+++ b/gfx/include/gfx/scene/README.md
@@ -21,7 +21,7 @@ former, the API could create the illusion that the hierarchy can be a DAG.
21The strict tree hierarchy should not be that restrictive in practice. Even the 21The strict tree hierarchy should not be that restrictive in practice. Even the
22glTF 2.0 spec [enforces this](https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#nodes-and-hierarchy): 22glTF 2.0 spec [enforces this](https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#nodes-and-hierarchy):
23 23
24*For Version 2.0 conformance, the glTF node hierarchy is not a directed acyclic 24> *For Version 2.0 conformance, the glTF node hierarchy is not a directed
25graph (DAG) or scene graph, but a disjoint union of strict trees. That is, no 25> acyclic graph (DAG) or scene graph, but a disjoint union of strict trees. That
26node may be a direct descendant of more than one node. This restriction is meant 26> is, no node may be a direct descendant of more than one node. This restriction
27to simplify implementation and facilitate conformance.* 27> is meant to simplify implementation and facilitate conformance.*
diff --git a/gfx/include/gfx/scene/material.h b/gfx/include/gfx/scene/material.h
index 6c22515..07e31d4 100644
--- a/gfx/include/gfx/scene/material.h
+++ b/gfx/include/gfx/scene/material.h
@@ -11,13 +11,8 @@ typedef struct Material Material;
11/// variables. Two materials can share the same shader, but shader parameters 11/// variables. Two materials can share the same shader, but shader parameters
12/// generally give two materials a different appearance. 12/// generally give two materials a different appearance.
13typedef struct MaterialDesc { 13typedef struct MaterialDesc {
14 ShaderProgram* shader; // TODO: Move to Mesh? Cannot fully determine shader
15 // permutation without geometry. Or move the creation
16 // of permutations to the renderer? A multi-pass
17 // renderer will juggle multiple shader programs
18 // anyway.
19 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL]; 14 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL];
20 int num_uniforms; 15 int num_uniforms;
21} MaterialDesc; 16} MaterialDesc;
22 17
23/// Create a material. 18/// Create a material.
diff --git a/gfx/include/gfx/scene/mesh.h b/gfx/include/gfx/scene/mesh.h
index f16572a..0d3b4d4 100644
--- a/gfx/include/gfx/scene/mesh.h
+++ b/gfx/include/gfx/scene/mesh.h
@@ -1,7 +1,8 @@
1#pragma once 1#pragma once
2 2
3typedef struct Geometry Geometry; 3typedef struct Geometry Geometry;
4typedef struct Material Material; 4typedef struct Material Material;
5typedef struct ShaderProgram ShaderProgram;
5 6
6typedef struct Mesh Mesh; 7typedef struct Mesh Mesh;
7 8
@@ -9,6 +10,7 @@ typedef struct Mesh Mesh;
9typedef struct MeshDesc { 10typedef struct MeshDesc {
10 const Geometry* geometry; 11 const Geometry* geometry;
11 const Material* material; 12 const Material* material;
13 ShaderProgram* shader;
12} MeshDesc; 14} MeshDesc;
13 15
14/// Create a mesh. 16/// Create a mesh.
diff --git a/gfx/include/gfx/util/scene.h b/gfx/include/gfx/util/scene.h
index 98267aa..7340d1d 100644
--- a/gfx/include/gfx/util/scene.h
+++ b/gfx/include/gfx/util/scene.h
@@ -24,7 +24,10 @@ typedef struct LoadSceneCmd {
24/// Load a scene. 24/// Load a scene.
25/// 25///
26/// |root_node| is the node under which scene elements are loaded. 26/// |root_node| is the node under which scene elements are loaded.
27/// |shader| is the shader program assigned to the loaded scene objects. 27///
28/// |shader| is an optional shader program assigned to the loaded scene objects.
29/// If no shader is given, a Cook-Torrance shader based on the object's
30/// characteristics (presence of normals, tangents, etc) is assigned.
28/// 31///
29/// Currently only supports the GLTF format. 32/// Currently only supports the GLTF format.
30bool gfx_load_scene( 33bool gfx_load_scene(
diff --git a/gfx/shaders/cook_torrance.frag b/gfx/shaders/cook_torrance.frag
index b4cf590..b2bfd7d 100644
--- a/gfx/shaders/cook_torrance.frag
+++ b/gfx/shaders/cook_torrance.frag
@@ -11,6 +11,7 @@ uniform sampler2D EmissiveTexture;
11uniform sampler2D AmbientOcclusionTexture; 11uniform sampler2D AmbientOcclusionTexture;
12uniform sampler2D NormalMap; 12uniform sampler2D NormalMap;
13 13
14// TODO: Handle case in which there is no sky. Pass a boolean.
14uniform samplerCube Sky; 15uniform samplerCube Sky;
15uniform samplerCube IrradianceMap; 16uniform samplerCube IrradianceMap;
16uniform samplerCube PrefilteredEnvironmentMap; 17uniform samplerCube PrefilteredEnvironmentMap;
diff --git a/gfx/shaders/cook_torrance.vert b/gfx/shaders/cook_torrance.vert
index c66efa6..56dfeda 100644
--- a/gfx/shaders/cook_torrance.vert
+++ b/gfx/shaders/cook_torrance.vert
@@ -4,6 +4,7 @@ uniform mat4 ModelMatrix;
4uniform mat4 MVP; 4uniform mat4 MVP;
5 5
6layout (location = 0) in vec3 vPosition; 6layout (location = 0) in vec3 vPosition;
7// TODO: Add HAS_NORMALS
7layout (location = 1) in vec3 vNormal; 8layout (location = 1) in vec3 vNormal;
8#ifdef HAS_TANGENTS 9#ifdef HAS_TANGENTS
9layout (location = 2) in vec4 vTangent; 10layout (location = 2) in vec4 vTangent;
diff --git a/gfx/src/renderer/renderer.c b/gfx/src/renderer/renderer.c
index ed781c9..6b77ffe 100644
--- a/gfx/src/renderer/renderer.c
+++ b/gfx/src/renderer/renderer.c
@@ -18,12 +18,12 @@
18 18
19#include <assert.h> 19#include <assert.h>
20 20
21static const int IRRADIANCE_MAP_WIDTH = 1024; 21static const int IRRADIANCE_MAP_WIDTH = 1024;
22static const int IRRADIANCE_MAP_HEIGHT = 1024; 22static const int IRRADIANCE_MAP_HEIGHT = 1024;
23static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128; 23static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128;
24static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128; 24static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128;
25static const int BRDF_INTEGRATION_MAP_WIDTH = 512; 25static const int BRDF_INTEGRATION_MAP_WIDTH = 512;
26static const int BRDF_INTEGRATION_MAP_HEIGHT = 512; 26static const int BRDF_INTEGRATION_MAP_HEIGHT = 512;
27 27
28bool renderer_make(Renderer* renderer, RenderBackend* render_backend) { 28bool renderer_make(Renderer* renderer, RenderBackend* render_backend) {
29 assert(renderer); 29 assert(renderer);
@@ -63,9 +63,9 @@ void renderer_destroy(Renderer* renderer, RenderBackend* render_backend) {
63 63
64/// Computes irradiance and prefiltered environment maps for the light if they 64/// Computes irradiance and prefiltered environment maps for the light if they
65/// have not been already computed. 65/// have not been already computed.
66static bool setup_environment_light(Renderer* renderer, 66static bool setup_environment_light(
67 RenderBackend* render_backend, 67 Renderer* renderer, RenderBackend* render_backend,
68 EnvironmentLight* light) { 68 EnvironmentLight* light) {
69 assert(renderer); 69 assert(renderer);
70 assert(light); 70 assert(light);
71 assert(renderer->ibl); 71 assert(renderer->ibl);
@@ -75,7 +75,7 @@ static bool setup_environment_light(Renderer* renderer,
75 return true; 75 return true;
76 } 76 }
77 77
78 Texture* irradiance_map = 0; 78 Texture* irradiance_map = 0;
79 Texture* prefiltered_environment_map = 0; 79 Texture* prefiltered_environment_map = 0;
80 80
81 if (!(irradiance_map = gfx_make_irradiance_map( 81 if (!(irradiance_map = gfx_make_irradiance_map(
@@ -92,9 +92,9 @@ static bool setup_environment_light(Renderer* renderer,
92 goto cleanup; 92 goto cleanup;
93 } 93 }
94 94
95 light->irradiance_map = irradiance_map; 95 light->irradiance_map = irradiance_map;
96 light->prefiltered_environment_map = prefiltered_environment_map; 96 light->prefiltered_environment_map = prefiltered_environment_map;
97 light->max_reflection_lod = max_mip_level; 97 light->max_reflection_lod = max_mip_level;
98 98
99 return true; 99 return true;
100 100
@@ -110,21 +110,21 @@ cleanup:
110 110
111typedef struct RenderState { 111typedef struct RenderState {
112 RenderBackend* render_backend; 112 RenderBackend* render_backend;
113 Renderer* renderer; 113 Renderer* renderer;
114 const Scene* scene; 114 const Scene* scene;
115 const Camera* camera; 115 const Camera* camera;
116 const mat4* view_matrix; 116 const mat4* view_matrix;
117 const mat4* projection; 117 const mat4* projection;
118 Light* environment_light; 118 Light* environment_light;
119 const float fovy; 119 const float fovy;
120 const float aspect; 120 const float aspect;
121} RenderState; 121} RenderState;
122 122
123static void draw_recursively(RenderState* state, mat4 parent_transform, 123static void draw_recursively(
124 node_idx node_index) { 124 RenderState* state, mat4 parent_transform, node_idx node_index) {
125 assert(state); 125 assert(state);
126 const SceneNode* node = mem_get_node(node_index); 126 const SceneNode* node = mem_get_node(node_index);
127 const mat4 node_transform = mat4_mul(parent_transform, node->transform); 127 const mat4 node_transform = mat4_mul(parent_transform, node->transform);
128 128
129 // Activate light. 129 // Activate light.
130 if (node->type == LightNode) { 130 if (node->type == LightNode) {
@@ -145,13 +145,13 @@ static void draw_recursively(RenderState* state, mat4 parent_transform,
145 assert(object); 145 assert(object);
146 146
147 const mat4 model_matrix = mat4_mul(node_transform, object->transform); 147 const mat4 model_matrix = mat4_mul(node_transform, object->transform);
148 const mat4 modelview = mat4_mul(*state->view_matrix, model_matrix); 148 const mat4 modelview = mat4_mul(*state->view_matrix, model_matrix);
149 const mat4 mvp = mat4_mul(*state->projection, modelview); 149 const mat4 mvp = mat4_mul(*state->projection, modelview);
150 150
151 for (mesh_link_idx mesh_link_index = object->mesh_link; 151 for (mesh_link_idx mesh_link_index = object->mesh_link;
152 mesh_link_index.val;) { 152 mesh_link_index.val;) {
153 const MeshLink* mesh_link = mem_get_mesh_link(mesh_link_index); 153 const MeshLink* mesh_link = mem_get_mesh_link(mesh_link_index);
154 mesh_link_index = mesh_link->next; 154 mesh_link_index = mesh_link->next;
155 155
156 const Mesh* mesh = mem_get_mesh(mesh_link->mesh); 156 const Mesh* mesh = mem_get_mesh(mesh_link->mesh);
157 if (!mesh) { 157 if (!mesh) {
@@ -159,11 +159,10 @@ static void draw_recursively(RenderState* state, mat4 parent_transform,
159 } 159 }
160 assert(mesh->geometry); 160 assert(mesh->geometry);
161 assert(mesh->material); 161 assert(mesh->material);
162 material_activate(mesh->material);
163 // Apply common shader uniforms not captured by materials. 162 // Apply common shader uniforms not captured by materials.
164 // TODO: Avoid computing matrices like Modelview or MVP if the shader does 163 // TODO: Avoid computing matrices like Modelview or MVP if the shader does
165 // not use them. 164 // not use them.
166 ShaderProgram* shader = mesh->material->shader; 165 ShaderProgram* shader = mesh->shader;
167 gfx_set_mat4_uniform(shader, "ModelMatrix", &model_matrix); 166 gfx_set_mat4_uniform(shader, "ModelMatrix", &model_matrix);
168 gfx_set_mat4_uniform(shader, "Modelview", &modelview); 167 gfx_set_mat4_uniform(shader, "Modelview", &modelview);
169 gfx_set_mat4_uniform(shader, "Projection", state->projection); 168 gfx_set_mat4_uniform(shader, "Projection", state->projection);
@@ -177,19 +176,19 @@ static void draw_recursively(RenderState* state, mat4 parent_transform,
177 assert(light->irradiance_map); 176 assert(light->irradiance_map);
178 assert(light->prefiltered_environment_map); 177 assert(light->prefiltered_environment_map);
179 assert(state->renderer->brdf_integration_map); 178 assert(state->renderer->brdf_integration_map);
180 gfx_set_texture_uniform(shader, "BRDFIntegrationMap", 179 gfx_set_texture_uniform(
181 state->renderer->brdf_integration_map); 180 shader, "BRDFIntegrationMap",
181 state->renderer->brdf_integration_map);
182 gfx_set_texture_uniform(shader, "Sky", light->environment_map); 182 gfx_set_texture_uniform(shader, "Sky", light->environment_map);
183 gfx_set_texture_uniform(shader, "IrradianceMap", light->irradiance_map); 183 gfx_set_texture_uniform(shader, "IrradianceMap", light->irradiance_map);
184 gfx_set_texture_uniform(shader, "PrefilteredEnvironmentMap", 184 gfx_set_texture_uniform(
185 light->prefiltered_environment_map); 185 shader, "PrefilteredEnvironmentMap",
186 gfx_set_float_uniform(shader, "MaxReflectionLOD", 186 light->prefiltered_environment_map);
187 light->max_reflection_lod); 187 gfx_set_float_uniform(
188 shader, "MaxReflectionLOD", light->max_reflection_lod);
188 } 189 }
189 // TODO: Remove this altogether. 190 material_activate(shader, mesh->material);
190 // This is not needed because material_activate() already activates the 191 gfx_activate_shader_program(shader);
191 // shader.
192 // gfx_activate_shader_program(shader);
193 gfx_apply_uniforms(shader); 192 gfx_apply_uniforms(shader);
194 gfx_render_geometry(mesh->geometry); 193 gfx_render_geometry(mesh->geometry);
195 } 194 }
@@ -200,12 +199,13 @@ static void draw_recursively(RenderState* state, mat4 parent_transform,
200 draw_recursively(state, node_transform, child_index); 199 draw_recursively(state, node_transform, child_index);
201 200
202 const SceneNode* child = mem_get_node(child_index); 201 const SceneNode* child = mem_get_node(child_index);
203 child_index = child->next; 202 child_index = child->next;
204 } 203 }
205} 204}
206 205
207void gfx_render_scene(Renderer* renderer, RenderBackend* render_backend, 206void gfx_render_scene(
208 const Scene* scene, const SceneCamera* camera) { 207 Renderer* renderer, RenderBackend* render_backend, const Scene* scene,
208 const SceneCamera* camera) {
209 assert(renderer); 209 assert(renderer);
210 assert(render_backend); 210 assert(render_backend);
211 211
@@ -221,16 +221,17 @@ void gfx_render_scene(Renderer* renderer, RenderBackend* render_backend,
221 gfx_get_viewport(render_backend, &width, &height); 221 gfx_get_viewport(render_backend, &width, &height);
222 const float aspect = (float)width / (float)height; 222 const float aspect = (float)width / (float)height;
223 223
224 RenderState state = {.render_backend = render_backend, 224 RenderState state = {
225 .renderer = renderer, 225 .render_backend = render_backend,
226 .scene = scene, 226 .renderer = renderer,
227 .camera = &camera->camera, 227 .scene = scene,
228 .view_matrix = &view_matrix, 228 .camera = &camera->camera,
229 .projection = &projection, 229 .view_matrix = &view_matrix,
230 .environment_light = 0, 230 .projection = &projection,
231 // Assuming a perspective matrix. 231 .environment_light = 0,
232 .fovy = atan(1.0 / (mat4_at(projection, 1, 1))) * 2, 232 // Assuming a perspective matrix.
233 .aspect = aspect}; 233 .fovy = atan(1.0 / (mat4_at(projection, 1, 1))) * 2,
234 .aspect = aspect};
234 235
235 gfx_start_frame(render_backend); 236 gfx_start_frame(render_backend);
236 draw_recursively(&state, mat4_id(), scene->root); 237 draw_recursively(&state, mat4_id(), scene->root);
diff --git a/gfx/src/scene/material.c b/gfx/src/scene/material.c
index d44746a..e5856d0 100644
--- a/gfx/src/scene/material.c
+++ b/gfx/src/scene/material.c
@@ -8,7 +8,6 @@ static void material_make(Material* material, const MaterialDesc* desc) {
8 assert(material); 8 assert(material);
9 assert(desc); 9 assert(desc);
10 assert(desc->num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); 10 assert(desc->num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
11 material->shader = desc->shader;
12 material->num_uniforms = desc->num_uniforms; 11 material->num_uniforms = desc->num_uniforms;
13 for (int i = 0; i < desc->num_uniforms; ++i) { 12 for (int i = 0; i < desc->num_uniforms; ++i) {
14 material->uniforms[i] = desc->uniforms[i]; 13 material->uniforms[i] = desc->uniforms[i];
@@ -47,11 +46,10 @@ static void set_uniform(ShaderProgram* prog, const ShaderUniform* uniform) {
47 } 46 }
48} 47}
49 48
50void material_activate(const Material* material) { 49void material_activate(ShaderProgram* shader, const Material* material) {
51 assert(material); 50 assert(material);
52 gfx_activate_shader_program(material->shader);
53 for (int i = 0; i < material->num_uniforms; ++i) { 51 for (int i = 0; i < material->num_uniforms; ++i) {
54 const ShaderUniform* uniform = &material->uniforms[i]; 52 const ShaderUniform* uniform = &material->uniforms[i];
55 set_uniform(material->shader, uniform); 53 set_uniform(shader, uniform);
56 } 54 }
57} 55}
diff --git a/gfx/src/scene/material_impl.h b/gfx/src/scene/material_impl.h
index c680ccf..a6aa95b 100644
--- a/gfx/src/scene/material_impl.h
+++ b/gfx/src/scene/material_impl.h
@@ -5,13 +5,12 @@
5typedef struct ShaderProgram ShaderProgram; 5typedef struct ShaderProgram ShaderProgram;
6 6
7typedef struct Material { 7typedef struct Material {
8 ShaderProgram* shader;
9 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL]; 8 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL];
10 int num_uniforms; 9 int num_uniforms;
11} Material; 10} Material;
12 11
13/// Activate the material. 12/// Activate the material.
14/// 13///
15/// This activates the material's shader and configures the shader uniforms that 14/// This activates the material's shader and configures the shader uniforms that
16/// are specific to the material. 15/// are specific to the material.
17void material_activate(const Material* material); 16void material_activate(ShaderProgram* shader, const Material* material);
diff --git a/gfx/src/scene/mesh.c b/gfx/src/scene/mesh.c
index 722eae7..689105c 100644
--- a/gfx/src/scene/mesh.c
+++ b/gfx/src/scene/mesh.c
@@ -9,8 +9,10 @@ static void mesh_make(Mesh* mesh, const MeshDesc* desc) {
9 assert(desc); 9 assert(desc);
10 assert(desc->geometry); 10 assert(desc->geometry);
11 assert(desc->material); 11 assert(desc->material);
12 assert(desc->shader);
12 mesh->geometry = desc->geometry; 13 mesh->geometry = desc->geometry;
13 mesh->material = desc->material; 14 mesh->material = desc->material;
15 mesh->shader = desc->shader;
14} 16}
15 17
16Mesh* gfx_make_mesh(const MeshDesc* desc) { 18Mesh* gfx_make_mesh(const MeshDesc* desc) {
diff --git a/gfx/src/scene/mesh_impl.h b/gfx/src/scene/mesh_impl.h
index 858b147..560b77e 100644
--- a/gfx/src/scene/mesh_impl.h
+++ b/gfx/src/scene/mesh_impl.h
@@ -5,6 +5,7 @@
5typedef struct Mesh { 5typedef struct Mesh {
6 const Geometry* geometry; 6 const Geometry* geometry;
7 const Material* material; 7 const Material* material;
8 ShaderProgram* shader;
8} Mesh; 9} Mesh;
9 10
10// TODO: a mesh_render() that takes a transform, applies the material and the 11// TODO: a mesh_render() that takes a transform, applies the material and the
diff --git a/gfx/src/util/scene.c b/gfx/src/util/scene.c
index 9511a71..dc97259 100644
--- a/gfx/src/util/scene.c
+++ b/gfx/src/util/scene.c
@@ -141,8 +141,9 @@ typedef struct MeshPermutation {
141 union { 141 union {
142 struct { 142 struct {
143 // Vertex attributes. 143 // Vertex attributes.
144 bool has_normals : 1; 144 bool has_texcoords : 1;
145 bool has_tangents : 1; 145 bool has_normals : 1;
146 bool has_tangents : 1;
146 // Textures. 147 // Textures.
147 bool has_normal_map : 1; 148 bool has_normal_map : 1;
148 bool has_occlusion_texture : 1; 149 bool has_occlusion_texture : 1;
@@ -282,36 +283,31 @@ cleanup:
282 return 0; 283 return 0;
283} 284}
284 285
285/// Load all textures from the glTF scene. 286/// Lazily load all textures from the glTF scene.
286/// 287///
287/// Return an array of Textures such that the index of each glTF texture in the 288/// Colour textures like albedo are in sRGB colour space. Non-colour textures
288/// original array matches the same Texture in the resulting array. 289/// like normal maps are in linear space (e.g. DamagedHelmet sample). Since we
290/// don't know how the texture is going to be used at this point, we can't tell
291/// what colour space it should be loaded in (ideally this would be part of the
292/// image file format, but not all formats specify colour space.) Therefore, we
293/// load the textures lazily and don't actually commit them to GPU memory until
294/// we know their colour space when loading glTF materials.
289/// 295///
290/// Most textures are in sRGB colour space. One exception is normal maps, which 296/// Return an array of LoadTextureCmds such that the index of each cmd matches
291/// are typically authored in linear space (e.g. DamagedHelmet sample). Since 297/// the index of each glTF texture in the scene. Also return the number of
292/// we don't know what colour space we should use at this point, we load the 298/// textures.
293/// textures lazily and don't actually commit them to GPU memory until we know
294/// their colour space.
295/// 299///
296/// This function returns an array of Texture objects, all of which are null due 300/// Return true on success (all textures processed or no textures in the
297/// to lazy loading. It also returns an array of LoadTextureCmd which describes 301/// scene), false otherwise.
298/// how each Texture in the first array should be loaded. 302static bool load_textures_lazy(
299static Texture** load_textures(
300 const cgltf_data* data, RenderBackend* render_backend, 303 const cgltf_data* data, RenderBackend* render_backend,
301 const char* directory, LoadTextureCmd** load_texture_cmds, 304 const char* directory, LoadTextureCmd** load_texture_cmds,
302 cgltf_size* num_textures) { 305 cgltf_size* num_textures) {
303 assert(data); 306 assert(data);
304 assert(render_backend); 307 assert(render_backend);
308 assert(load_texture_cmds);
305 assert(num_textures); 309 assert(num_textures);
306 310
307 Texture** textures = 0;
308 *load_texture_cmds = 0;
309
310 textures = calloc(data->textures_count, sizeof(Texture*));
311 if (!textures) {
312 goto cleanup;
313 }
314
315 *load_texture_cmds = calloc(data->textures_count, sizeof(LoadTextureCmd)); 311 *load_texture_cmds = calloc(data->textures_count, sizeof(LoadTextureCmd));
316 if (!*load_texture_cmds) { 312 if (!*load_texture_cmds) {
317 goto cleanup; 313 goto cleanup;
@@ -374,29 +370,21 @@ static Texture** load_textures(
374 } 370 }
375 371
376 *num_textures = data->textures_count; 372 *num_textures = data->textures_count;
377 return textures; 373 return true;
378 374
379cleanup: 375cleanup:
380 if (textures) {
381 for (cgltf_size i = 0; i < data->textures_count; ++i) {
382 if (textures[i]) {
383 gfx_destroy_texture(render_backend, &textures[i]);
384 }
385 }
386 free(textures);
387 }
388 if (*load_texture_cmds) { 376 if (*load_texture_cmds) {
389 free(*load_texture_cmds); 377 free(*load_texture_cmds);
390 } 378 }
391 *num_textures = 0; 379 *num_textures = 0;
392 return 0; 380 return false;
393} 381}
394 382
395/// Load a texture uniform. 383/// Load a texture uniform.
396/// 384///
397/// This determines a texture's colour space based on its intended use, loads 385/// This determines a texture's colour space based on its intended use, loads
398/// the texture, and then defines the sampler shader uniform. 386/// the texture, and then defines the sampler shader uniform.
399static bool load_texture_uniform( 387static bool load_texture_and_uniform(
400 const cgltf_data* data, RenderBackend* render_backend, 388 const cgltf_data* data, RenderBackend* render_backend,
401 const cgltf_texture_view* texture_view, TextureType texture_type, 389 const cgltf_texture_view* texture_view, TextureType texture_type,
402 Texture** textures, LoadTextureCmd* load_texture_cmds, int* next_uniform, 390 Texture** textures, LoadTextureCmd* load_texture_cmds, int* next_uniform,
@@ -412,24 +400,31 @@ static bool load_texture_uniform(
412 const size_t texture_index = texture_view->texture - data->textures; 400 const size_t texture_index = texture_view->texture - data->textures;
413 assert(texture_index < data->textures_count); 401 assert(texture_index < data->textures_count);
414 402
415 LoadTextureCmd* cmd = &load_texture_cmds[texture_index]; 403 // Here we are assuming that if a texture is re-used, it is re-used with the
416 if (texture_type == NormalMap) { 404 // same texture view. This should be fine because, e.g., a normal map would
417 cmd->colour_space = LinearColourSpace; 405 // not be used as albedo and vice versa.
418 }
419
420 LOGD(
421 "Load texture: %s (mipmaps: %d, filtering: %d)",
422 mstring_cstring(&cmd->data.texture.filepath), cmd->mipmaps,
423 cmd->filtering);
424
425 textures[texture_index] = gfx_load_texture(render_backend, cmd);
426 if (!textures[texture_index]) { 406 if (!textures[texture_index]) {
427 gfx_prepend_error( 407 LoadTextureCmd* cmd = &load_texture_cmds[texture_index];
428 "Failed to load texture: %s", 408 // TODO: Check for colour textures and default to LinearColourSpace instead.
429 mstring_cstring(&cmd->data.texture.filepath)); 409 if (texture_type == NormalMap) {
430 return false; 410 cmd->colour_space = LinearColourSpace;
411 }
412
413 LOGD(
414 "Load texture: %s (mipmaps: %d, filtering: %d)",
415 mstring_cstring(&cmd->data.texture.filepath), cmd->mipmaps,
416 cmd->filtering);
417
418 textures[texture_index] = gfx_load_texture(render_backend, cmd);
419 if (!textures[texture_index]) {
420 gfx_prepend_error(
421 "Failed to load texture: %s",
422 mstring_cstring(&cmd->data.texture.filepath));
423 return false;
424 }
431 } 425 }
432 426
427 assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
433 desc->uniforms[(*next_uniform)++] = (ShaderUniform){ 428 desc->uniforms[(*next_uniform)++] = (ShaderUniform){
434 .name = sstring_make(TextureUniformName(texture_type)), 429 .name = sstring_make(TextureUniformName(texture_type)),
435 .type = UniformTexture, 430 .type = UniformTexture,
@@ -440,97 +435,102 @@ static bool load_texture_uniform(
440 435
441/// Load all materials from the glTF scene. 436/// Load all materials from the glTF scene.
442/// 437///
443/// Return an array of Materials such that the index of each glTF material in 438/// Return an array of Materials such that the index of each descriptor matches
444/// the original array matches the same Material in the resulting array. 439/// the index of each glTF material in the scene. Also return the number of
440/// materials and the textures used by them.
445static Material** load_materials( 441static Material** load_materials(
446 const cgltf_data* data, RenderBackend* render_backend, 442 const cgltf_data* data, RenderBackend* render_backend,
447 ShaderProgram* shader, Texture** textures, 443 LoadTextureCmd* load_texture_cmds, Texture*** textures,
448 LoadTextureCmd* load_texture_cmds, cgltf_size* num_materials) { 444 cgltf_size* num_materials) {
449 assert(data); 445 assert(data);
450 assert(render_backend); 446 assert(render_backend);
451 assert(shader);
452 assert(textures);
453 assert(load_texture_cmds); 447 assert(load_texture_cmds);
448 assert(textures);
454 assert(num_materials); 449 assert(num_materials);
455 450
456 Material** materials = 0; 451 Material** materials = calloc(data->materials_count, sizeof(Material*));
457
458 materials = calloc(data->materials_count, sizeof(Material*));
459 if (!materials) { 452 if (!materials) {
460 goto cleanup; 453 goto cleanup;
461 } 454 }
462 455
456 *textures = calloc(data->textures_count, sizeof(Texture*));
457 if (!*textures) {
458 goto cleanup;
459 }
460
463 for (cgltf_size i = 0; i < data->materials_count; ++i) { 461 for (cgltf_size i = 0; i < data->materials_count; ++i) {
464 const cgltf_material* mat = &data->materials[i]; 462 const cgltf_material* mat = &data->materials[i];
465 463
466 int next_uniform = 0; 464 int next_uniform = 0;
467 MaterialDesc desc = (MaterialDesc){.shader = shader}; 465 MaterialDesc desc = {0};
468 466
469 // TODO: emissive texture/factor and other material parameters. 467 // TODO: emissive texture/factor and other material parameters.
470 if (mat->has_pbr_metallic_roughness) { 468 if (mat->has_pbr_metallic_roughness) {
471 const cgltf_pbr_metallic_roughness* pbr = &mat->pbr_metallic_roughness; 469 const cgltf_pbr_metallic_roughness* pbr = &mat->pbr_metallic_roughness;
472 470
473 assert(next_uniform + 3 < GFX_MAX_UNIFORMS_PER_MATERIAL); 471 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
474
475 desc.uniforms[next_uniform++] = (ShaderUniform){ 472 desc.uniforms[next_uniform++] = (ShaderUniform){
476 .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), 473 .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR),
477 .type = UniformVec4, 474 .type = UniformVec4,
478 .value.vec4 = vec4_from_array(pbr->base_color_factor)}; 475 .value.vec4 = vec4_from_array(pbr->base_color_factor)};
479 476
477 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
480 desc.uniforms[next_uniform++] = (ShaderUniform){ 478 desc.uniforms[next_uniform++] = (ShaderUniform){
481 .name = sstring_make(UNIFORM_METALLIC_FACTOR), 479 .name = sstring_make(UNIFORM_METALLIC_FACTOR),
482 .type = UniformFloat, 480 .type = UniformFloat,
483 .value.scalar = pbr->metallic_factor}; 481 .value.scalar = pbr->metallic_factor};
484 482
483 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
485 desc.uniforms[next_uniform++] = (ShaderUniform){ 484 desc.uniforms[next_uniform++] = (ShaderUniform){
486 .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), 485 .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR),
487 .type = UniformFloat, 486 .type = UniformFloat,
488 .value.scalar = pbr->roughness_factor}; 487 .value.scalar = pbr->roughness_factor};
489 488
489 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
490 desc.uniforms[next_uniform++] = (ShaderUniform){
491 .name = sstring_make(UNIFORM_EMISSIVE_FACTOR),
492 .type = UniformVec3,
493 .value.vec3 = vec3_from_array(mat->emissive_factor)};
494
490 if (pbr->base_color_texture.texture) { 495 if (pbr->base_color_texture.texture) {
491 if (!load_texture_uniform( 496 if (!load_texture_and_uniform(
492 data, render_backend, &pbr->base_color_texture, 497 data, render_backend, &pbr->base_color_texture,
493 BaseColorTexture, textures, load_texture_cmds, &next_uniform, 498 BaseColorTexture, *textures, load_texture_cmds, &next_uniform,
494 &desc)) { 499 &desc)) {
495 goto cleanup; 500 goto cleanup;
496 } 501 }
497 } 502 }
498 503
499 if (pbr->metallic_roughness_texture.texture) { 504 if (pbr->metallic_roughness_texture.texture) {
500 if (!load_texture_uniform( 505 if (!load_texture_and_uniform(
501 data, render_backend, &pbr->metallic_roughness_texture, 506 data, render_backend, &pbr->metallic_roughness_texture,
502 MetallicRoughnessTexture, textures, load_texture_cmds, 507 MetallicRoughnessTexture, *textures, load_texture_cmds,
503 &next_uniform, &desc)) { 508 &next_uniform, &desc)) {
504 goto cleanup; 509 goto cleanup;
505 } 510 }
506 } 511 }
507 } 512 }
508 513
509 desc.uniforms[next_uniform++] = (ShaderUniform){
510 .name = sstring_make(UNIFORM_EMISSIVE_FACTOR),
511 .type = UniformVec3,
512 .value.vec3 = vec3_from_array(mat->emissive_factor)};
513
514 if (mat->emissive_texture.texture) { 514 if (mat->emissive_texture.texture) {
515 if (!load_texture_uniform( 515 if (!load_texture_and_uniform(
516 data, render_backend, &mat->emissive_texture, EmissiveTexture, 516 data, render_backend, &mat->emissive_texture, EmissiveTexture,
517 textures, load_texture_cmds, &next_uniform, &desc)) { 517 *textures, load_texture_cmds, &next_uniform, &desc)) {
518 goto cleanup; 518 goto cleanup;
519 } 519 }
520 } 520 }
521 521
522 if (mat->occlusion_texture.texture) { 522 if (mat->occlusion_texture.texture) {
523 if (!load_texture_uniform( 523 if (!load_texture_and_uniform(
524 data, render_backend, &mat->occlusion_texture, 524 data, render_backend, &mat->occlusion_texture,
525 AmbientOcclusionTexture, textures, load_texture_cmds, 525 AmbientOcclusionTexture, *textures, load_texture_cmds,
526 &next_uniform, &desc)) { 526 &next_uniform, &desc)) {
527 goto cleanup; 527 goto cleanup;
528 } 528 }
529 } 529 }
530 530
531 if (mat->normal_texture.texture) { 531 if (mat->normal_texture.texture) {
532 if (!load_texture_uniform( 532 if (!load_texture_and_uniform(
533 data, render_backend, &mat->normal_texture, NormalMap, textures, 533 data, render_backend, &mat->normal_texture, NormalMap, *textures,
534 load_texture_cmds, &next_uniform, &desc)) { 534 load_texture_cmds, &next_uniform, &desc)) {
535 goto cleanup; 535 goto cleanup;
536 } 536 }
@@ -557,28 +557,25 @@ cleanup:
557 } 557 }
558 free(materials); 558 free(materials);
559 } 559 }
560 if (*textures) {
561 for (cgltf_size i = 0; i < data->textures_count; ++i) {
562 if ((*textures)[i]) {
563 gfx_destroy_texture(render_backend, &(*textures)[i]);
564 }
565 }
566 free(*textures);
567 *textures = 0;
568 }
560 *num_materials = 0; 569 *num_materials = 0;
561 return 0; 570 return 0;
562} 571}
563 572
564/// Configures the MeshPermutation based on the given material.
565static void configure_material_permutation(
566 MeshPermutation* perm, cgltf_material* material) {
567 assert(perm);
568 assert(material);
569
570 perm->has_normal_map = material->normal_texture.texture != 0;
571 perm->has_occlusion_texture = material->occlusion_texture.texture != 0;
572 perm->has_emissive_texture = material->emissive_texture.texture != 0;
573}
574
575/// Load all meshes from the glTF scene. 573/// Load all meshes from the glTF scene.
576static SceneObject** load_meshes( 574static SceneObject** load_meshes(
577 const cgltf_data* data, Gfx* gfx, Buffer** buffers, 575 const cgltf_data* data, Gfx* gfx, Buffer** buffers,
578 Buffer** tangent_buffers, Material** materials, 576 Buffer** tangent_buffers, const cgltfTangentBuffer* cgltf_tangent_buffers,
579 const cgltfTangentBuffer* cgltf_tangent_buffers, 577 cgltf_size num_tangent_buffers, Material** materials, ShaderProgram* shader,
580 cgltf_size num_tangent_buffers, Geometry*** geometries, Mesh*** meshes, 578 Geometry*** geometries, Mesh*** meshes, cgltf_size* num_geometries,
581 MeshPermutation** mesh_permutations, cgltf_size* num_geometries,
582 cgltf_size* num_meshes, cgltf_size* num_scene_objects) { 579 cgltf_size* num_meshes, cgltf_size* num_scene_objects) {
583 // Walk through the mesh primitives to create Meshes. A GLTF mesh primitive 580 // Walk through the mesh primitives to create Meshes. A GLTF mesh primitive
584 // has a material (Mesh) and vertex data (Geometry). A GLTF mesh maps to 581 // has a material (Mesh) and vertex data (Geometry). A GLTF mesh maps to
@@ -594,9 +591,9 @@ static SceneObject** load_meshes(
594 assert(gfx); 591 assert(gfx);
595 assert(buffers); 592 assert(buffers);
596 assert(materials); 593 assert(materials);
594 assert(shader);
597 assert(geometries); 595 assert(geometries);
598 assert(meshes); 596 assert(meshes);
599 assert(mesh_permutations);
600 assert(num_geometries); 597 assert(num_geometries);
601 assert(num_meshes); 598 assert(num_meshes);
602 assert(num_scene_objects); 599 assert(num_scene_objects);
@@ -618,10 +615,6 @@ static SceneObject** load_meshes(
618 if (!*meshes) { 615 if (!*meshes) {
619 goto cleanup; 616 goto cleanup;
620 } 617 }
621 *mesh_permutations = calloc(primitive_count, sizeof(MeshPermutation));
622 if (!*mesh_permutations) {
623 goto cleanup;
624 }
625 SceneObject** objects = calloc(data->meshes_count, sizeof(SceneObject*)); 618 SceneObject** objects = calloc(data->meshes_count, sizeof(SceneObject*));
626 if (!objects) { 619 if (!objects) {
627 goto cleanup; 620 goto cleanup;
@@ -642,7 +635,12 @@ static SceneObject** load_meshes(
642 for (cgltf_size p = 0; p < mesh->primitives_count; ++p) { 635 for (cgltf_size p = 0; p < mesh->primitives_count; ++p) {
643 assert(next_mesh < primitive_count); 636 assert(next_mesh < primitive_count);
644 const cgltf_primitive* prim = &mesh->primitives[p]; 637 const cgltf_primitive* prim = &mesh->primitives[p];
645 MeshPermutation* perm = mesh_permutations[next_mesh]; 638 const cgltf_material* mat = prim->material;
639
640 MeshPermutation perm = {0};
641 perm.has_normal_map = mat->normal_texture.texture != 0;
642 perm.has_occlusion_texture = mat->occlusion_texture.texture != 0;
643 perm.has_emissive_texture = mat->emissive_texture.texture != 0;
646 644
647 GeometryDesc geometry_desc = { 645 GeometryDesc geometry_desc = {
648 .type = from_gltf_primitive_type(prim->type)}; 646 .type = from_gltf_primitive_type(prim->type)};
@@ -693,18 +691,21 @@ static SceneObject** load_meshes(
693 assert(false); 691 assert(false);
694 break; 692 break;
695 } 693 }
694 // It is assumed that meshes have positions, so there is nothing to
695 // do for the mesh permutation in this case.
696 break; 696 break;
697 } 697 }
698 case cgltf_attribute_type_normal: 698 case cgltf_attribute_type_normal:
699 buffer_view_3d = &geometry_desc.normals; 699 buffer_view_3d = &geometry_desc.normals;
700 perm->has_normals = true; 700 perm.has_normals = true;
701 break; 701 break;
702 case cgltf_attribute_type_tangent: 702 case cgltf_attribute_type_tangent:
703 buffer_view_4d = &geometry_desc.tangents; 703 buffer_view_4d = &geometry_desc.tangents;
704 perm->has_tangents = true; 704 perm.has_tangents = true;
705 break; 705 break;
706 case cgltf_attribute_type_texcoord: 706 case cgltf_attribute_type_texcoord:
707 buffer_view_2d = &geometry_desc.texcoords; 707 buffer_view_2d = &geometry_desc.texcoords;
708 perm.has_texcoords = true;
708 break; 709 break;
709 default: 710 default:
710 // Attribute ignored. 711 // Attribute ignored.
@@ -747,8 +748,8 @@ static SceneObject** load_meshes(
747 } 748 }
748 749
749 // Set the number of vertices in the geometry. Since a geometry can have 750 // Set the number of vertices in the geometry. Since a geometry can have
750 // either 2d or 3d positions but not both, here we can perform addition to 751 // either 2d or 3d positions but not both, here we can perform addition
751 // compute the total number of vertices. 752 // to compute the total number of vertices.
752 geometry_desc.num_verts = 753 geometry_desc.num_verts =
753 (geometry_desc.positions2d.size_bytes / sizeof(vec2)) + 754 (geometry_desc.positions2d.size_bytes / sizeof(vec2)) +
754 (geometry_desc.positions3d.size_bytes / sizeof(vec3)); 755 (geometry_desc.positions3d.size_bytes / sizeof(vec3));
@@ -771,17 +772,40 @@ static SceneObject** load_meshes(
771 geometry_desc.num_verts); 772 geometry_desc.num_verts);
772 } 773 }
773 774
774 (*geometries)[next_mesh] =
775 gfx_make_geometry(render_backend, &geometry_desc);
776
777 const cgltf_size material_index = prim->material - data->materials; 775 const cgltf_size material_index = prim->material - data->materials;
778 assert(material_index < data->materials_count); 776 assert(material_index < data->materials_count);
779 const Material* material = materials[material_index]; 777 Material* material = materials[material_index];
778
779 // TODO: We need a better way to handle clean-up, specifically of
780 // materials. One is to add materials to a dynamically-allocated list or
781 // vector. Another is to expose some kind of scene purge that deletes the
782 // resources of a given scene. The latter would make clean-up much simpler
783 // in general, not just for materials.
784
785 (*geometries)[next_mesh] =
786 gfx_make_geometry(render_backend, &geometry_desc);
787 if (!(*geometries)[next_mesh]) {
788 goto cleanup;
789 }
780 790
781 configure_material_permutation(perm, prim->material); 791 // If the user specifies a custom shader, use that instead.
792 // else TODO: Build a shader based on permutation.
793 //
794 // TODO: We should cache shader permutations to re-use shader programs.
795 // Caching should not be done locally here because a caller may call
796 // gfx_load_scene() multiple times to load multiple scenes, and we want
797 // shader re-use across scenes too.
798 //
799 // On the other hand, caching materials is not necessary since, provided
800 // they can share shaders, the renderer can check later whether uniforms
801 // have the same values. Also, changing uniforms is much faster than
802 // swapping shaders, so shader caching is the most important thing here.
803 assert(shader);
782 804
783 (*meshes)[next_mesh] = gfx_make_mesh(&(MeshDesc){ 805 (*meshes)[next_mesh] = gfx_make_mesh(&(MeshDesc){
784 .geometry = (*geometries)[next_mesh], .material = material}); 806 .geometry = (*geometries)[next_mesh],
807 .material = material,
808 .shader = shader});
785 809
786 gfx_add_object_mesh(objects[m], (*meshes)[next_mesh]); 810 gfx_add_object_mesh(objects[m], (*meshes)[next_mesh]);
787 811
@@ -814,10 +838,6 @@ cleanup:
814 free(*meshes); 838 free(*meshes);
815 *meshes = 0; 839 *meshes = 0;
816 } 840 }
817 if (*mesh_permutations) {
818 free(*mesh_permutations);
819 mesh_permutations = 0;
820 }
821 if (objects) { 841 if (objects) {
822 for (cgltf_size i = 0; i < data->meshes_count; ++i) { 842 for (cgltf_size i = 0; i < data->meshes_count; ++i) {
823 if (objects[i]) { 843 if (objects[i]) {
@@ -939,8 +959,8 @@ static SceneNode** load_nodes(
939 gfx_set_node_transform(nodes[n], &transform); 959 gfx_set_node_transform(nodes[n], &transform);
940 960
941 // By default, set nodes as children of the root node. The second pass will 961 // By default, set nodes as children of the root node. The second pass will
942 // properly set the parent of the relevant nodes, and leave the root node 962 // properly set the parent of the relevant nodes, and leave the root node as
943 // as the parent for top-level nodes. 963 // the parent for top-level nodes.
944 gfx_set_node_parent(nodes[n], root_node); 964 gfx_set_node_parent(nodes[n], root_node);
945 } // SceneNode. 965 } // SceneNode.
946 966
@@ -1010,26 +1030,28 @@ static bool load_scene(
1010 LOGD("Filepath: %s", filepath); 1030 LOGD("Filepath: %s", filepath);
1011 LOGD("Directory: %s", mstring_cstring(&directory)); 1031 LOGD("Directory: %s", mstring_cstring(&directory));
1012 1032
1013 Buffer** buffers = 0; 1033 Buffer** buffers = 0;
1014 Buffer** tangent_buffers = 0; 1034 Buffer** tangent_buffers = 0;
1015 Geometry** geometries = 0; 1035 Geometry** geometries = 0;
1016 Material** materials = 0; 1036 Material** materials = 0;
1017 Mesh** meshes = 0; 1037 Mesh** meshes = 0;
1018 SceneObject** scene_objects = 0; 1038 SceneObject** scene_objects = 0;
1019 SceneCamera** scene_cameras = 0; 1039 SceneCamera** scene_cameras = 0;
1020 SceneNode** scene_nodes = 0; 1040 SceneNode** scene_nodes = 0;
1021 Texture** textures = 0; 1041 Texture** textures = 0;
1022 LoadTextureCmd* load_texture_cmds = 0; 1042 LoadTextureCmd* load_texture_cmds = 0;
1023 MeshPermutation* mesh_permutations = 0; 1043 cgltf_size num_buffers = 0;
1024 cgltf_size num_buffers = 0; 1044 cgltf_size num_geometries = 0;
1025 cgltf_size num_geometries = 0; 1045 cgltf_size num_materials = 0;
1026 cgltf_size num_materials = 0; 1046 cgltf_size num_meshes = 0;
1027 cgltf_size num_meshes = 0; 1047 cgltf_size num_scene_objects = 0;
1028 cgltf_size num_scene_objects = 0; 1048 cgltf_size num_scene_cameras = 0;
1029 cgltf_size num_scene_cameras = 0; 1049 cgltf_size num_scene_nodes = 0;
1030 cgltf_size num_scene_nodes = 0; 1050 cgltf_size num_textures = 0;
1031 cgltf_size num_textures = 0; 1051
1032 1052 // TODO: Let this function handle all the cleanup. Let the other functions
1053 // return pass/failure booleans and the arrays as in/out parameters. This way
1054 // we do not need the individual functions to duplicate cleanup code.
1033 buffers = load_buffers(data, render_backend, &num_buffers); 1055 buffers = load_buffers(data, render_backend, &num_buffers);
1034 if (!buffers) { 1056 if (!buffers) {
1035 goto cleanup; 1057 goto cleanup;
@@ -1043,23 +1065,21 @@ static bool load_scene(
1043 } 1065 }
1044 } 1066 }
1045 1067
1046 textures = load_textures( 1068 if (!load_textures_lazy(
1047 data, render_backend, mstring_cstring(&directory), &load_texture_cmds, 1069 data, render_backend, mstring_cstring(&directory), &load_texture_cmds,
1048 &num_textures); 1070 &num_textures)) {
1049 if (!textures || !load_texture_cmds) {
1050 goto cleanup; 1071 goto cleanup;
1051 } 1072 }
1052 1073
1053 materials = load_materials( 1074 materials = load_materials(
1054 data, render_backend, shader, textures, load_texture_cmds, 1075 data, render_backend, load_texture_cmds, &textures, &num_materials);
1055 &num_materials);
1056 if (!materials) { 1076 if (!materials) {
1057 goto cleanup; 1077 goto cleanup;
1058 } 1078 }
1059 1079
1060 scene_objects = load_meshes( 1080 scene_objects = load_meshes(
1061 data, gfx, buffers, tangent_buffers, materials, cgltf_tangent_buffers, 1081 data, gfx, buffers, tangent_buffers, cgltf_tangent_buffers,
1062 num_tangent_buffers, &geometries, &meshes, &mesh_permutations, 1082 num_tangent_buffers, materials, shader, &geometries, &meshes,
1063 &num_geometries, &num_meshes, &num_scene_objects); 1083 &num_geometries, &num_meshes, &num_scene_objects);
1064 if (!scene_objects) { 1084 if (!scene_objects) {
1065 goto cleanup; 1085 goto cleanup;
@@ -1132,9 +1152,6 @@ cleanup:
1132 if (load_texture_cmds) { 1152 if (load_texture_cmds) {
1133 free(load_texture_cmds); 1153 free(load_texture_cmds);
1134 } 1154 }
1135 if (mesh_permutations) {
1136 free(mesh_permutations);
1137 }
1138 return false; 1155 return false;
1139} 1156}
1140 1157
@@ -1143,7 +1160,6 @@ bool gfx_load_scene(
1143 const LoadSceneCmd* cmd) { 1160 const LoadSceneCmd* cmd) {
1144 assert(gfx); 1161 assert(gfx);
1145 assert(root_node); 1162 assert(root_node);
1146 assert(shader);
1147 assert(cmd); 1163 assert(cmd);
1148 1164
1149 bool success = false; 1165 bool success = false;
diff --git a/gfx/src/util/skyquad.c b/gfx/src/util/skyquad.c
index 51c250b..2461f8c 100644
--- a/gfx/src/util/skyquad.c
+++ b/gfx/src/util/skyquad.c
@@ -22,11 +22,11 @@ SceneObject* gfx_make_skyquad(Gfx* gfx, Scene* scene, const Texture* texture) {
22 RenderBackend* render_backend = gfx_get_render_backend(gfx); 22 RenderBackend* render_backend = gfx_get_render_backend(gfx);
23 assert(render_backend); 23 assert(render_backend);
24 24
25 ShaderProgram* shader = 0; 25 ShaderProgram* shader = 0;
26 Geometry* geometry = 0; 26 Geometry* geometry = 0;
27 Material* material = 0; 27 Material* material = 0;
28 Mesh* mesh = 0; 28 Mesh* mesh = 0;
29 SceneObject* object = 0; 29 SceneObject* object = 0;
30 30
31 shader = gfx_make_skyquad_shader(render_backend); 31 shader = gfx_make_skyquad_shader(render_backend);
32 if (!shader) { 32 if (!shader) {
@@ -39,12 +39,12 @@ SceneObject* gfx_make_skyquad(Gfx* gfx, Scene* scene, const Texture* texture) {
39 } 39 }
40 40
41 MaterialDesc material_desc = (MaterialDesc){0}; 41 MaterialDesc material_desc = (MaterialDesc){0};
42 material_desc.shader = shader; 42 material_desc.uniforms[0] = (ShaderUniform){
43 material_desc.uniforms[0] = (ShaderUniform){.type = UniformTexture, 43 .type = UniformTexture,
44 .value.texture = texture, 44 .value.texture = texture,
45 .name = sstring_make("Skyquad")}; 45 .name = sstring_make("Skyquad")};
46 material_desc.num_uniforms = 1; 46 material_desc.num_uniforms = 1;
47 material = gfx_make_material(&material_desc); 47 material = gfx_make_material(&material_desc);
48 if (!material) { 48 if (!material) {
49 goto cleanup; 49 goto cleanup;
50 } 50 }
@@ -52,7 +52,8 @@ SceneObject* gfx_make_skyquad(Gfx* gfx, Scene* scene, const Texture* texture) {
52 MeshDesc mesh_desc = (MeshDesc){0}; 52 MeshDesc mesh_desc = (MeshDesc){0};
53 mesh_desc.geometry = geometry; 53 mesh_desc.geometry = geometry;
54 mesh_desc.material = material; 54 mesh_desc.material = material;
55 mesh = gfx_make_mesh(&mesh_desc); 55 mesh_desc.shader = shader;
56 mesh = gfx_make_mesh(&mesh_desc);
56 if (!mesh) { 57 if (!mesh) {
57 goto cleanup; 58 goto cleanup;
58 } 59 }
diff --git a/gltfview/src/game.c b/gltfview/src/game.c
index d7352d4..f2e5a88 100644
--- a/gltfview/src/game.c
+++ b/gltfview/src/game.c
@@ -24,7 +24,7 @@
24#include <unistd.h> // usleep; TODO Remove. 24#include <unistd.h> // usleep; TODO Remove.
25 25
26// Paths to various scene files. 26// Paths to various scene files.
27static const char* BOX = "/assets/models/box.gltf"; 27static const char* BOX = "/assets/models/box.gltf";
28static const char* SUZANNE = "/assets/models/suzanne.gltf"; 28static const char* SUZANNE = "/assets/models/suzanne.gltf";
29static const char* SPONZA = 29static const char* SPONZA =
30 "/assets/glTF-Sample-Models/2.0/Sponza/glTF/Sponza.gltf"; 30 "/assets/glTF-Sample-Models/2.0/Sponza/glTF/Sponza.gltf";
@@ -37,8 +37,8 @@ static const char* DAMAGED_HELMET =
37 37
38static const char* CLOUDS1_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp"; 38static const char* CLOUDS1_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp";
39 39
40static ShaderProgram* load_shader(RenderBackend* render_backend, 40static ShaderProgram* load_shader(
41 const char* view_mode) { 41 RenderBackend* render_backend, const char* view_mode) {
42 ShaderProgram* shader = 0; 42 ShaderProgram* shader = 0;
43 if (strcmp(view_mode, "debug") == 0) { 43 if (strcmp(view_mode, "debug") == 0) {
44 shader = gfx_make_debug3d_shader(render_backend); 44 shader = gfx_make_debug3d_shader(render_backend);
@@ -59,23 +59,24 @@ static Texture* load_environment_map(RenderBackend* render_backend) {
59 return gfx_load_texture( 59 return gfx_load_texture(
60 render_backend, 60 render_backend,
61 &(LoadTextureCmd){ 61 &(LoadTextureCmd){
62 .origin = TextureFromFile, 62 .origin = TextureFromFile,
63 .type = LoadCubemap, 63 .type = LoadCubemap,
64 .colour_space = sRGB, 64 .colour_space = sRGB,
65 .filtering = NearestFiltering, 65 .filtering = NearestFiltering,
66 .mipmaps = false, 66 .mipmaps = false,
67 .data.cubemap.filepaths = { 67 .data.cubemap.filepaths = {
68 mstring_make("/assets/skybox/clouds1/clouds1_east.bmp"), 68 mstring_make("/assets/skybox/clouds1/clouds1_east.bmp"),
69 mstring_make("/assets/skybox/clouds1/clouds1_west.bmp"), 69 mstring_make("/assets/skybox/clouds1/clouds1_west.bmp"),
70 mstring_make("/assets/skybox/clouds1/clouds1_up.bmp"), 70 mstring_make("/assets/skybox/clouds1/clouds1_up.bmp"),
71 mstring_make("/assets/skybox/clouds1/clouds1_down.bmp"), 71 mstring_make("/assets/skybox/clouds1/clouds1_down.bmp"),
72 mstring_make("/assets/skybox/clouds1/clouds1_north.bmp"), 72 mstring_make("/assets/skybox/clouds1/clouds1_north.bmp"),
73 mstring_make("/assets/skybox/clouds1/clouds1_south.bmp")}}); 73 mstring_make("/assets/skybox/clouds1/clouds1_south.bmp")}
74 });
74} 75}
75 76
76/// Creates an object to render the skyquad in the background. 77/// Creates an object to render the skyquad in the background.
77static SceneNode* make_skyquad_object_node(Game* game, 78static SceneNode* make_skyquad_object_node(
78 const Texture* environment_map) { 79 Game* game, const Texture* environment_map) {
79 assert(game); 80 assert(game);
80 81
81 SceneObject* skyquad_object = 82 SceneObject* skyquad_object =
@@ -92,12 +93,12 @@ static SceneNode* make_skyquad_object_node(Game* game,
92} 93}
93 94
94/// Creates an environment light. 95/// Creates an environment light.
95static SceneNode* make_environment_light(Game* game, 96static SceneNode* make_environment_light(
96 const Texture* environment_light) { 97 Game* game, const Texture* environment_light) {
97 assert(game); 98 assert(game);
98 99
99 Light* light = gfx_make_light(&(LightDesc){ 100 Light* light = gfx_make_light(&(LightDesc){
100 .type = EnvironmentLightType, 101 .type = EnvironmentLightType,
101 .light = (EnvironmentLightDesc){.environment_map = environment_light}}); 102 .light = (EnvironmentLightDesc){.environment_map = environment_light}});
102 if (!light) { 103 if (!light) {
103 return 0; 104 return 0;
@@ -127,8 +128,8 @@ static bool load_skyquad(Game* game, SceneNode** node) {
127} 128}
128 129
129/// Loads the 3D scene. 130/// Loads the 3D scene.
130static bool load_scene(Game* game, const char* scene_filepath, 131static bool load_scene(
131 const char* view_mode) { 132 Game* game, const char* scene_filepath, const char* view_mode) {
132 assert(game); 133 assert(game);
133 134
134 game->camera = gfx_make_camera(); 135 game->camera = gfx_make_camera();
@@ -151,9 +152,10 @@ static bool load_scene(Game* game, const char* scene_filepath,
151 return false; 152 return false;
152 } 153 }
153 154
154 if (!gfx_load_scene(game->gfx, sky_node, shader, 155 if (!gfx_load_scene(
155 &(LoadSceneCmd){.origin = SceneFromFile, 156 game->gfx, sky_node, shader,
156 .filepath = scene_filepath})) { 157 &(LoadSceneCmd){
158 .origin = SceneFromFile, .filepath = scene_filepath})) {
157 return false; 159 return false;
158 } 160 }
159 161
@@ -164,14 +166,14 @@ static bool load_scene(Game* game, const char* scene_filepath,
164static bool load_texture_debugger_scene(Game* game) { 166static bool load_texture_debugger_scene(Game* game) {
165 assert(game); 167 assert(game);
166 168
167 Texture* texture = 169 Texture* texture = gfx_load_texture(
168 gfx_load_texture(game->render_backend, 170 game->render_backend,
169 &(LoadTextureCmd){.origin = TextureFromFile, 171 &(LoadTextureCmd){
170 .type = LoadTexture, 172 .origin = TextureFromFile,
171 .filtering = LinearFiltering, 173 .type = LoadTexture,
172 .mipmaps = false, 174 .filtering = LinearFiltering,
173 .data.texture.filepath = 175 .mipmaps = false,
174 mstring_make(CLOUDS1_TEXTURE)}); 176 .data.texture.filepath = mstring_make(CLOUDS1_TEXTURE)});
175 177
176 game->camera = gfx_make_camera(); 178 game->camera = gfx_make_camera();
177 if (!game->camera) { 179 if (!game->camera) {
@@ -191,12 +193,12 @@ static bool load_texture_debugger_scene(Game* game) {
191 } 193 }
192 194
193 MaterialDesc material_desc = (MaterialDesc){0}; 195 MaterialDesc material_desc = (MaterialDesc){0};
194 material_desc.shader = shader; 196 material_desc.uniforms[0] = (ShaderUniform){
195 material_desc.uniforms[0] = (ShaderUniform){.type = UniformTexture, 197 .type = UniformTexture,
196 .value.texture = texture, 198 .value.texture = texture,
197 .name = sstring_make("Texture")}; 199 .name = sstring_make("Texture")};
198 material_desc.num_uniforms = 1; 200 material_desc.num_uniforms = 1;
199 Material* material = gfx_make_material(&material_desc); 201 Material* material = gfx_make_material(&material_desc);
200 if (!material) { 202 if (!material) {
201 return false; 203 return false;
202 } 204 }
@@ -204,7 +206,8 @@ static bool load_texture_debugger_scene(Game* game) {
204 MeshDesc mesh_desc = (MeshDesc){0}; 206 MeshDesc mesh_desc = (MeshDesc){0};
205 mesh_desc.geometry = geometry; 207 mesh_desc.geometry = geometry;
206 mesh_desc.material = material; 208 mesh_desc.material = material;
207 Mesh* mesh = gfx_make_mesh(&mesh_desc); 209 mesh_desc.shader = shader;
210 Mesh* mesh = gfx_make_mesh(&mesh_desc);
208 if (!mesh) { 211 if (!mesh) {
209 return false; 212 return false;
210 } 213 }
@@ -224,7 +227,7 @@ static bool load_texture_debugger_scene(Game* game) {
224 227
225bool game_new(Game* game, int argc, const char** argv) { 228bool game_new(Game* game, int argc, const char** argv) {
226 // TODO: getopt() to implement proper argument parsing. 229 // TODO: getopt() to implement proper argument parsing.
227 const char* view_mode = argc > 1 ? argv[1] : ""; 230 const char* view_mode = argc > 1 ? argv[1] : "";
228 const char* scene_filepath = argc > 2 ? argv[2] : DEFAULT_SCENE_FILE; 231 const char* scene_filepath = argc > 2 ? argv[2] : DEFAULT_SCENE_FILE;
229 232
230 game->gfx = gfx_init(); 233 game->gfx = gfx_init();
@@ -233,7 +236,7 @@ bool game_new(Game* game, int argc, const char** argv) {
233 } 236 }
234 237
235 game->render_backend = gfx_get_render_backend(game->gfx); 238 game->render_backend = gfx_get_render_backend(game->gfx);
236 game->renderer = gfx_get_renderer(game->gfx); 239 game->renderer = gfx_get_renderer(game->gfx);
237 240
238 game->scene = gfx_make_scene(game->gfx); 241 game->scene = gfx_make_scene(game->gfx);
239 if (!game->scene) { 242 if (!game->scene) {
@@ -270,26 +273,27 @@ void game_update(Game* game, double t, double dt) {
270 game->elapsed -= 1.0; 273 game->elapsed -= 1.0;
271 } 274 }
272 Camera* camera = gfx_get_camera_camera(game->camera); 275 Camera* camera = gfx_get_camera_camera(game->camera);
273 spatial3_orbit(&camera->spatial, vec3_make(0, 0, 0), 276 spatial3_orbit(
274 /*radius=*/2, 277 &camera->spatial, vec3_make(0, 0, 0),
275 /*azimuth=*/t * 0.5, /*zenith=*/0); 278 /*radius=*/2,
279 /*azimuth=*/t * 0.5, /*zenith=*/0);
276 spatial3_lookat(&camera->spatial, vec3_make(0, 0, 0)); 280 spatial3_lookat(&camera->spatial, vec3_make(0, 0, 0));
277} 281}
278 282
279void game_render(const Game* game) { 283void game_render(const Game* game) {
280 gfx_render_scene(game->renderer, game->render_backend, game->scene, 284 gfx_render_scene(
281 game->camera); 285 game->renderer, game->render_backend, game->scene, game->camera);
282} 286}
283 287
284void game_set_viewport(Game* game, int width, int height) { 288void game_set_viewport(Game* game, int width, int height) {
285 gfx_set_viewport(game->render_backend, width, height); 289 gfx_set_viewport(game->render_backend, width, height);
286 290
287 const R fovy = 90 * TO_RAD; 291 const R fovy = 90 * TO_RAD;
288 const R aspect = (R)width / (R)height; 292 const R aspect = (R)width / (R)height;
289 const R near = 0.1; 293 const R near = 0.1;
290 const R far = 1000; 294 const R far = 1000;
291 const mat4 projection = mat4_perspective(fovy, aspect, near, far); 295 const mat4 projection = mat4_perspective(fovy, aspect, near, far);
292 296
293 Camera* camera = gfx_get_camera_camera(game->camera); 297 Camera* camera = gfx_get_camera_camera(game->camera);
294 camera->projection = projection; 298 camera->projection = projection;
295} 299}