diff options
| -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 | } |
