diff options
author | 3gg <3gg@shellblade.net> | 2023-01-04 15:46:22 -0800 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2023-01-04 15:46:22 -0800 |
commit | 27ff505b6daaf5b0ec5f6af422f727a032f83c6b (patch) | |
tree | ca005986bfd02b760e2e8363627258f37f4de320 | |
parent | 1e3fcf5b38d67fb54102786be74af42be5c6792f (diff) |
Move ShaderProgram from Material to Mesh in preparation for shader permutations.
-rw-r--r-- | gfx/include/gfx/scene/README.md | 8 | ||||
-rw-r--r-- | gfx/include/gfx/scene/material.h | 7 | ||||
-rw-r--r-- | gfx/include/gfx/scene/mesh.h | 6 | ||||
-rw-r--r-- | gfx/include/gfx/util/scene.h | 5 | ||||
-rw-r--r-- | gfx/shaders/cook_torrance.frag | 1 | ||||
-rw-r--r-- | gfx/shaders/cook_torrance.vert | 1 | ||||
-rw-r--r-- | gfx/src/renderer/renderer.c | 103 | ||||
-rw-r--r-- | gfx/src/scene/material.c | 6 | ||||
-rw-r--r-- | gfx/src/scene/material_impl.h | 5 | ||||
-rw-r--r-- | gfx/src/scene/mesh.c | 2 | ||||
-rw-r--r-- | gfx/src/scene/mesh_impl.h | 1 | ||||
-rw-r--r-- | gfx/src/util/scene.c | 310 | ||||
-rw-r--r-- | gfx/src/util/skyquad.c | 23 | ||||
-rw-r--r-- | gltfview/src/game.c | 104 |
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. | |||
21 | The strict tree hierarchy should not be that restrictive in practice. Even the | 21 | The strict tree hierarchy should not be that restrictive in practice. Even the |
22 | glTF 2.0 spec [enforces this](https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#nodes-and-hierarchy): | 22 | glTF 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 |
25 | graph (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 |
26 | node 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 |
27 | to 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. |
13 | typedef struct MaterialDesc { | 13 | typedef 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 | ||
3 | typedef struct Geometry Geometry; | 3 | typedef struct Geometry Geometry; |
4 | typedef struct Material Material; | 4 | typedef struct Material Material; |
5 | typedef struct ShaderProgram ShaderProgram; | ||
5 | 6 | ||
6 | typedef struct Mesh Mesh; | 7 | typedef struct Mesh Mesh; |
7 | 8 | ||
@@ -9,6 +10,7 @@ typedef struct Mesh Mesh; | |||
9 | typedef struct MeshDesc { | 10 | typedef 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. |
30 | bool gfx_load_scene( | 33 | bool 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; | |||
11 | uniform sampler2D AmbientOcclusionTexture; | 11 | uniform sampler2D AmbientOcclusionTexture; |
12 | uniform sampler2D NormalMap; | 12 | uniform sampler2D NormalMap; |
13 | 13 | ||
14 | // TODO: Handle case in which there is no sky. Pass a boolean. | ||
14 | uniform samplerCube Sky; | 15 | uniform samplerCube Sky; |
15 | uniform samplerCube IrradianceMap; | 16 | uniform samplerCube IrradianceMap; |
16 | uniform samplerCube PrefilteredEnvironmentMap; | 17 | uniform 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; | |||
4 | uniform mat4 MVP; | 4 | uniform mat4 MVP; |
5 | 5 | ||
6 | layout (location = 0) in vec3 vPosition; | 6 | layout (location = 0) in vec3 vPosition; |
7 | // TODO: Add HAS_NORMALS | ||
7 | layout (location = 1) in vec3 vNormal; | 8 | layout (location = 1) in vec3 vNormal; |
8 | #ifdef HAS_TANGENTS | 9 | #ifdef HAS_TANGENTS |
9 | layout (location = 2) in vec4 vTangent; | 10 | layout (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 | ||
21 | static const int IRRADIANCE_MAP_WIDTH = 1024; | 21 | static const int IRRADIANCE_MAP_WIDTH = 1024; |
22 | static const int IRRADIANCE_MAP_HEIGHT = 1024; | 22 | static const int IRRADIANCE_MAP_HEIGHT = 1024; |
23 | static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128; | 23 | static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128; |
24 | static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128; | 24 | static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128; |
25 | static const int BRDF_INTEGRATION_MAP_WIDTH = 512; | 25 | static const int BRDF_INTEGRATION_MAP_WIDTH = 512; |
26 | static const int BRDF_INTEGRATION_MAP_HEIGHT = 512; | 26 | static const int BRDF_INTEGRATION_MAP_HEIGHT = 512; |
27 | 27 | ||
28 | bool renderer_make(Renderer* renderer, RenderBackend* render_backend) { | 28 | bool 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. |
66 | static bool setup_environment_light(Renderer* renderer, | 66 | static 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 | ||
111 | typedef struct RenderState { | 111 | typedef 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 | ||
123 | static void draw_recursively(RenderState* state, mat4 parent_transform, | 123 | static 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 | ||
207 | void gfx_render_scene(Renderer* renderer, RenderBackend* render_backend, | 206 | void 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 | ||
50 | void material_activate(const Material* material) { | 49 | void 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 @@ | |||
5 | typedef struct ShaderProgram ShaderProgram; | 5 | typedef struct ShaderProgram ShaderProgram; |
6 | 6 | ||
7 | typedef struct Material { | 7 | typedef 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. |
17 | void material_activate(const Material* material); | 16 | void 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 | ||
16 | Mesh* gfx_make_mesh(const MeshDesc* desc) { | 18 | Mesh* 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 @@ | |||
5 | typedef struct Mesh { | 5 | typedef 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. | 302 | static bool load_textures_lazy( |
299 | static 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 | ||
379 | cleanup: | 375 | cleanup: |
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. |
399 | static bool load_texture_uniform( | 387 | static 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. | ||
445 | static Material** load_materials( | 441 | static 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. | ||
565 | static 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. |
576 | static SceneObject** load_meshes( | 574 | static 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. |
27 | static const char* BOX = "/assets/models/box.gltf"; | 27 | static const char* BOX = "/assets/models/box.gltf"; |
28 | static const char* SUZANNE = "/assets/models/suzanne.gltf"; | 28 | static const char* SUZANNE = "/assets/models/suzanne.gltf"; |
29 | static const char* SPONZA = | 29 | static 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 | ||
38 | static const char* CLOUDS1_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp"; | 38 | static const char* CLOUDS1_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp"; |
39 | 39 | ||
40 | static ShaderProgram* load_shader(RenderBackend* render_backend, | 40 | static 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. |
77 | static SceneNode* make_skyquad_object_node(Game* game, | 78 | static 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. |
95 | static SceneNode* make_environment_light(Game* game, | 96 | static 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. |
130 | static bool load_scene(Game* game, const char* scene_filepath, | 131 | static 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, | |||
164 | static bool load_texture_debugger_scene(Game* game) { | 166 | static 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 | ||
225 | bool game_new(Game* game, int argc, const char** argv) { | 228 | bool 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 | ||
279 | void game_render(const Game* game) { | 283 | void 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 | ||
284 | void game_set_viewport(Game* game, int width, int height) { | 288 | void 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 | } |