From 27ff505b6daaf5b0ec5f6af422f727a032f83c6b Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Wed, 4 Jan 2023 15:46:22 -0800 Subject: Move ShaderProgram from Material to Mesh in preparation for shader permutations. --- gfx/include/gfx/scene/README.md | 8 +- gfx/include/gfx/scene/material.h | 7 +- gfx/include/gfx/scene/mesh.h | 6 +- gfx/include/gfx/util/scene.h | 5 +- gfx/shaders/cook_torrance.frag | 1 + gfx/shaders/cook_torrance.vert | 1 + gfx/src/renderer/renderer.c | 103 ++++++------- gfx/src/scene/material.c | 6 +- gfx/src/scene/material_impl.h | 5 +- gfx/src/scene/mesh.c | 2 + gfx/src/scene/mesh_impl.h | 1 + gfx/src/util/scene.c | 310 ++++++++++++++++++++------------------- gfx/src/util/skyquad.c | 23 +-- 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. The strict tree hierarchy should not be that restrictive in practice. Even the glTF 2.0 spec [enforces this](https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#nodes-and-hierarchy): -*For Version 2.0 conformance, the glTF node hierarchy is not a directed acyclic -graph (DAG) or scene graph, but a disjoint union of strict trees. That is, no -node may be a direct descendant of more than one node. This restriction is meant -to simplify implementation and facilitate conformance.* +> *For Version 2.0 conformance, the glTF node hierarchy is not a directed +> acyclic graph (DAG) or scene graph, but a disjoint union of strict trees. That +> is, no node may be a direct descendant of more than one node. This restriction +> 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; /// variables. Two materials can share the same shader, but shader parameters /// generally give two materials a different appearance. typedef struct MaterialDesc { - ShaderProgram* shader; // TODO: Move to Mesh? Cannot fully determine shader - // permutation without geometry. Or move the creation - // of permutations to the renderer? A multi-pass - // renderer will juggle multiple shader programs - // anyway. ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL]; - int num_uniforms; + int num_uniforms; } MaterialDesc; /// 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 @@ #pragma once -typedef struct Geometry Geometry; -typedef struct Material Material; +typedef struct Geometry Geometry; +typedef struct Material Material; +typedef struct ShaderProgram ShaderProgram; typedef struct Mesh Mesh; @@ -9,6 +10,7 @@ typedef struct Mesh Mesh; typedef struct MeshDesc { const Geometry* geometry; const Material* material; + ShaderProgram* shader; } MeshDesc; /// 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 { /// Load a scene. /// /// |root_node| is the node under which scene elements are loaded. -/// |shader| is the shader program assigned to the loaded scene objects. +/// +/// |shader| is an optional shader program assigned to the loaded scene objects. +/// If no shader is given, a Cook-Torrance shader based on the object's +/// characteristics (presence of normals, tangents, etc) is assigned. /// /// Currently only supports the GLTF format. 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; uniform sampler2D AmbientOcclusionTexture; uniform sampler2D NormalMap; +// TODO: Handle case in which there is no sky. Pass a boolean. uniform samplerCube Sky; uniform samplerCube IrradianceMap; 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; uniform mat4 MVP; layout (location = 0) in vec3 vPosition; +// TODO: Add HAS_NORMALS layout (location = 1) in vec3 vNormal; #ifdef HAS_TANGENTS 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 @@ #include -static const int IRRADIANCE_MAP_WIDTH = 1024; -static const int IRRADIANCE_MAP_HEIGHT = 1024; -static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128; +static const int IRRADIANCE_MAP_WIDTH = 1024; +static const int IRRADIANCE_MAP_HEIGHT = 1024; +static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128; static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128; -static const int BRDF_INTEGRATION_MAP_WIDTH = 512; -static const int BRDF_INTEGRATION_MAP_HEIGHT = 512; +static const int BRDF_INTEGRATION_MAP_WIDTH = 512; +static const int BRDF_INTEGRATION_MAP_HEIGHT = 512; bool renderer_make(Renderer* renderer, RenderBackend* render_backend) { assert(renderer); @@ -63,9 +63,9 @@ void renderer_destroy(Renderer* renderer, RenderBackend* render_backend) { /// Computes irradiance and prefiltered environment maps for the light if they /// have not been already computed. -static bool setup_environment_light(Renderer* renderer, - RenderBackend* render_backend, - EnvironmentLight* light) { +static bool setup_environment_light( + Renderer* renderer, RenderBackend* render_backend, + EnvironmentLight* light) { assert(renderer); assert(light); assert(renderer->ibl); @@ -75,7 +75,7 @@ static bool setup_environment_light(Renderer* renderer, return true; } - Texture* irradiance_map = 0; + Texture* irradiance_map = 0; Texture* prefiltered_environment_map = 0; if (!(irradiance_map = gfx_make_irradiance_map( @@ -92,9 +92,9 @@ static bool setup_environment_light(Renderer* renderer, goto cleanup; } - light->irradiance_map = irradiance_map; + light->irradiance_map = irradiance_map; light->prefiltered_environment_map = prefiltered_environment_map; - light->max_reflection_lod = max_mip_level; + light->max_reflection_lod = max_mip_level; return true; @@ -110,21 +110,21 @@ cleanup: typedef struct RenderState { RenderBackend* render_backend; - Renderer* renderer; - const Scene* scene; - const Camera* camera; - const mat4* view_matrix; - const mat4* projection; - Light* environment_light; - const float fovy; - const float aspect; + Renderer* renderer; + const Scene* scene; + const Camera* camera; + const mat4* view_matrix; + const mat4* projection; + Light* environment_light; + const float fovy; + const float aspect; } RenderState; -static void draw_recursively(RenderState* state, mat4 parent_transform, - node_idx node_index) { +static void draw_recursively( + RenderState* state, mat4 parent_transform, node_idx node_index) { assert(state); - const SceneNode* node = mem_get_node(node_index); - const mat4 node_transform = mat4_mul(parent_transform, node->transform); + const SceneNode* node = mem_get_node(node_index); + const mat4 node_transform = mat4_mul(parent_transform, node->transform); // Activate light. if (node->type == LightNode) { @@ -145,13 +145,13 @@ static void draw_recursively(RenderState* state, mat4 parent_transform, assert(object); const mat4 model_matrix = mat4_mul(node_transform, object->transform); - const mat4 modelview = mat4_mul(*state->view_matrix, model_matrix); - const mat4 mvp = mat4_mul(*state->projection, modelview); + const mat4 modelview = mat4_mul(*state->view_matrix, model_matrix); + const mat4 mvp = mat4_mul(*state->projection, modelview); for (mesh_link_idx mesh_link_index = object->mesh_link; mesh_link_index.val;) { const MeshLink* mesh_link = mem_get_mesh_link(mesh_link_index); - mesh_link_index = mesh_link->next; + mesh_link_index = mesh_link->next; const Mesh* mesh = mem_get_mesh(mesh_link->mesh); if (!mesh) { @@ -159,11 +159,10 @@ static void draw_recursively(RenderState* state, mat4 parent_transform, } assert(mesh->geometry); assert(mesh->material); - material_activate(mesh->material); // Apply common shader uniforms not captured by materials. // TODO: Avoid computing matrices like Modelview or MVP if the shader does // not use them. - ShaderProgram* shader = mesh->material->shader; + ShaderProgram* shader = mesh->shader; gfx_set_mat4_uniform(shader, "ModelMatrix", &model_matrix); gfx_set_mat4_uniform(shader, "Modelview", &modelview); gfx_set_mat4_uniform(shader, "Projection", state->projection); @@ -177,19 +176,19 @@ static void draw_recursively(RenderState* state, mat4 parent_transform, assert(light->irradiance_map); assert(light->prefiltered_environment_map); assert(state->renderer->brdf_integration_map); - gfx_set_texture_uniform(shader, "BRDFIntegrationMap", - state->renderer->brdf_integration_map); + gfx_set_texture_uniform( + shader, "BRDFIntegrationMap", + state->renderer->brdf_integration_map); gfx_set_texture_uniform(shader, "Sky", light->environment_map); gfx_set_texture_uniform(shader, "IrradianceMap", light->irradiance_map); - gfx_set_texture_uniform(shader, "PrefilteredEnvironmentMap", - light->prefiltered_environment_map); - gfx_set_float_uniform(shader, "MaxReflectionLOD", - light->max_reflection_lod); + gfx_set_texture_uniform( + shader, "PrefilteredEnvironmentMap", + light->prefiltered_environment_map); + gfx_set_float_uniform( + shader, "MaxReflectionLOD", light->max_reflection_lod); } - // TODO: Remove this altogether. - // This is not needed because material_activate() already activates the - // shader. - // gfx_activate_shader_program(shader); + material_activate(shader, mesh->material); + gfx_activate_shader_program(shader); gfx_apply_uniforms(shader); gfx_render_geometry(mesh->geometry); } @@ -200,12 +199,13 @@ static void draw_recursively(RenderState* state, mat4 parent_transform, draw_recursively(state, node_transform, child_index); const SceneNode* child = mem_get_node(child_index); - child_index = child->next; + child_index = child->next; } } -void gfx_render_scene(Renderer* renderer, RenderBackend* render_backend, - const Scene* scene, const SceneCamera* camera) { +void gfx_render_scene( + Renderer* renderer, RenderBackend* render_backend, const Scene* scene, + const SceneCamera* camera) { assert(renderer); assert(render_backend); @@ -221,16 +221,17 @@ void gfx_render_scene(Renderer* renderer, RenderBackend* render_backend, gfx_get_viewport(render_backend, &width, &height); const float aspect = (float)width / (float)height; - RenderState state = {.render_backend = render_backend, - .renderer = renderer, - .scene = scene, - .camera = &camera->camera, - .view_matrix = &view_matrix, - .projection = &projection, - .environment_light = 0, - // Assuming a perspective matrix. - .fovy = atan(1.0 / (mat4_at(projection, 1, 1))) * 2, - .aspect = aspect}; + RenderState state = { + .render_backend = render_backend, + .renderer = renderer, + .scene = scene, + .camera = &camera->camera, + .view_matrix = &view_matrix, + .projection = &projection, + .environment_light = 0, + // Assuming a perspective matrix. + .fovy = atan(1.0 / (mat4_at(projection, 1, 1))) * 2, + .aspect = aspect}; gfx_start_frame(render_backend); 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) { assert(material); assert(desc); assert(desc->num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); - material->shader = desc->shader; material->num_uniforms = desc->num_uniforms; for (int i = 0; i < desc->num_uniforms; ++i) { material->uniforms[i] = desc->uniforms[i]; @@ -47,11 +46,10 @@ static void set_uniform(ShaderProgram* prog, const ShaderUniform* uniform) { } } -void material_activate(const Material* material) { +void material_activate(ShaderProgram* shader, const Material* material) { assert(material); - gfx_activate_shader_program(material->shader); for (int i = 0; i < material->num_uniforms; ++i) { const ShaderUniform* uniform = &material->uniforms[i]; - set_uniform(material->shader, uniform); + set_uniform(shader, uniform); } } 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 @@ typedef struct ShaderProgram ShaderProgram; typedef struct Material { - ShaderProgram* shader; ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL]; - int num_uniforms; + int num_uniforms; } Material; /// Activate the material. /// /// This activates the material's shader and configures the shader uniforms that /// are specific to the material. -void material_activate(const Material* material); +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) { assert(desc); assert(desc->geometry); assert(desc->material); + assert(desc->shader); mesh->geometry = desc->geometry; mesh->material = desc->material; + mesh->shader = desc->shader; } 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 @@ typedef struct Mesh { const Geometry* geometry; const Material* material; + ShaderProgram* shader; } Mesh; // 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 { union { struct { // Vertex attributes. - bool has_normals : 1; - bool has_tangents : 1; + bool has_texcoords : 1; + bool has_normals : 1; + bool has_tangents : 1; // Textures. bool has_normal_map : 1; bool has_occlusion_texture : 1; @@ -282,36 +283,31 @@ cleanup: return 0; } -/// Load all textures from the glTF scene. +/// Lazily load all textures from the glTF scene. /// -/// Return an array of Textures such that the index of each glTF texture in the -/// original array matches the same Texture in the resulting array. +/// Colour textures like albedo are in sRGB colour space. Non-colour textures +/// like normal maps are in linear space (e.g. DamagedHelmet sample). Since we +/// don't know how the texture is going to be used at this point, we can't tell +/// what colour space it should be loaded in (ideally this would be part of the +/// image file format, but not all formats specify colour space.) Therefore, we +/// load the textures lazily and don't actually commit them to GPU memory until +/// we know their colour space when loading glTF materials. /// -/// Most textures are in sRGB colour space. One exception is normal maps, which -/// are typically authored in linear space (e.g. DamagedHelmet sample). Since -/// we don't know what colour space we should use at this point, we load the -/// textures lazily and don't actually commit them to GPU memory until we know -/// their colour space. +/// Return an array of LoadTextureCmds such that the index of each cmd matches +/// the index of each glTF texture in the scene. Also return the number of +/// textures. /// -/// This function returns an array of Texture objects, all of which are null due -/// to lazy loading. It also returns an array of LoadTextureCmd which describes -/// how each Texture in the first array should be loaded. -static Texture** load_textures( +/// Return true on success (all textures processed or no textures in the +/// scene), false otherwise. +static bool load_textures_lazy( const cgltf_data* data, RenderBackend* render_backend, const char* directory, LoadTextureCmd** load_texture_cmds, cgltf_size* num_textures) { assert(data); assert(render_backend); + assert(load_texture_cmds); assert(num_textures); - Texture** textures = 0; - *load_texture_cmds = 0; - - textures = calloc(data->textures_count, sizeof(Texture*)); - if (!textures) { - goto cleanup; - } - *load_texture_cmds = calloc(data->textures_count, sizeof(LoadTextureCmd)); if (!*load_texture_cmds) { goto cleanup; @@ -374,29 +370,21 @@ static Texture** load_textures( } *num_textures = data->textures_count; - return textures; + return true; cleanup: - if (textures) { - for (cgltf_size i = 0; i < data->textures_count; ++i) { - if (textures[i]) { - gfx_destroy_texture(render_backend, &textures[i]); - } - } - free(textures); - } if (*load_texture_cmds) { free(*load_texture_cmds); } *num_textures = 0; - return 0; + return false; } /// Load a texture uniform. /// /// This determines a texture's colour space based on its intended use, loads /// the texture, and then defines the sampler shader uniform. -static bool load_texture_uniform( +static bool load_texture_and_uniform( const cgltf_data* data, RenderBackend* render_backend, const cgltf_texture_view* texture_view, TextureType texture_type, Texture** textures, LoadTextureCmd* load_texture_cmds, int* next_uniform, @@ -412,24 +400,31 @@ static bool load_texture_uniform( const size_t texture_index = texture_view->texture - data->textures; assert(texture_index < data->textures_count); - LoadTextureCmd* cmd = &load_texture_cmds[texture_index]; - if (texture_type == NormalMap) { - cmd->colour_space = LinearColourSpace; - } - - LOGD( - "Load texture: %s (mipmaps: %d, filtering: %d)", - mstring_cstring(&cmd->data.texture.filepath), cmd->mipmaps, - cmd->filtering); - - textures[texture_index] = gfx_load_texture(render_backend, cmd); + // Here we are assuming that if a texture is re-used, it is re-used with the + // same texture view. This should be fine because, e.g., a normal map would + // not be used as albedo and vice versa. if (!textures[texture_index]) { - gfx_prepend_error( - "Failed to load texture: %s", - mstring_cstring(&cmd->data.texture.filepath)); - return false; + LoadTextureCmd* cmd = &load_texture_cmds[texture_index]; + // TODO: Check for colour textures and default to LinearColourSpace instead. + if (texture_type == NormalMap) { + cmd->colour_space = LinearColourSpace; + } + + LOGD( + "Load texture: %s (mipmaps: %d, filtering: %d)", + mstring_cstring(&cmd->data.texture.filepath), cmd->mipmaps, + cmd->filtering); + + textures[texture_index] = gfx_load_texture(render_backend, cmd); + if (!textures[texture_index]) { + gfx_prepend_error( + "Failed to load texture: %s", + mstring_cstring(&cmd->data.texture.filepath)); + return false; + } } + assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc->uniforms[(*next_uniform)++] = (ShaderUniform){ .name = sstring_make(TextureUniformName(texture_type)), .type = UniformTexture, @@ -440,97 +435,102 @@ static bool load_texture_uniform( /// Load all materials from the glTF scene. /// -/// Return an array of Materials such that the index of each glTF material in -/// the original array matches the same Material in the resulting array. +/// Return an array of Materials such that the index of each descriptor matches +/// the index of each glTF material in the scene. Also return the number of +/// materials and the textures used by them. static Material** load_materials( const cgltf_data* data, RenderBackend* render_backend, - ShaderProgram* shader, Texture** textures, - LoadTextureCmd* load_texture_cmds, cgltf_size* num_materials) { + LoadTextureCmd* load_texture_cmds, Texture*** textures, + cgltf_size* num_materials) { assert(data); assert(render_backend); - assert(shader); - assert(textures); assert(load_texture_cmds); + assert(textures); assert(num_materials); - Material** materials = 0; - - materials = calloc(data->materials_count, sizeof(Material*)); + Material** materials = calloc(data->materials_count, sizeof(Material*)); if (!materials) { goto cleanup; } + *textures = calloc(data->textures_count, sizeof(Texture*)); + if (!*textures) { + goto cleanup; + } + for (cgltf_size i = 0; i < data->materials_count; ++i) { const cgltf_material* mat = &data->materials[i]; int next_uniform = 0; - MaterialDesc desc = (MaterialDesc){.shader = shader}; + MaterialDesc desc = {0}; // TODO: emissive texture/factor and other material parameters. if (mat->has_pbr_metallic_roughness) { const cgltf_pbr_metallic_roughness* pbr = &mat->pbr_metallic_roughness; - assert(next_uniform + 3 < GFX_MAX_UNIFORMS_PER_MATERIAL); - + assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[next_uniform++] = (ShaderUniform){ .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), .type = UniformVec4, .value.vec4 = vec4_from_array(pbr->base_color_factor)}; + assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[next_uniform++] = (ShaderUniform){ .name = sstring_make(UNIFORM_METALLIC_FACTOR), .type = UniformFloat, .value.scalar = pbr->metallic_factor}; + assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); desc.uniforms[next_uniform++] = (ShaderUniform){ .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), .type = UniformFloat, .value.scalar = pbr->roughness_factor}; + assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[next_uniform++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), + .type = UniformVec3, + .value.vec3 = vec3_from_array(mat->emissive_factor)}; + if (pbr->base_color_texture.texture) { - if (!load_texture_uniform( + if (!load_texture_and_uniform( data, render_backend, &pbr->base_color_texture, - BaseColorTexture, textures, load_texture_cmds, &next_uniform, + BaseColorTexture, *textures, load_texture_cmds, &next_uniform, &desc)) { goto cleanup; } } if (pbr->metallic_roughness_texture.texture) { - if (!load_texture_uniform( + if (!load_texture_and_uniform( data, render_backend, &pbr->metallic_roughness_texture, - MetallicRoughnessTexture, textures, load_texture_cmds, + MetallicRoughnessTexture, *textures, load_texture_cmds, &next_uniform, &desc)) { goto cleanup; } } } - desc.uniforms[next_uniform++] = (ShaderUniform){ - .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), - .type = UniformVec3, - .value.vec3 = vec3_from_array(mat->emissive_factor)}; - if (mat->emissive_texture.texture) { - if (!load_texture_uniform( + if (!load_texture_and_uniform( data, render_backend, &mat->emissive_texture, EmissiveTexture, - textures, load_texture_cmds, &next_uniform, &desc)) { + *textures, load_texture_cmds, &next_uniform, &desc)) { goto cleanup; } } if (mat->occlusion_texture.texture) { - if (!load_texture_uniform( + if (!load_texture_and_uniform( data, render_backend, &mat->occlusion_texture, - AmbientOcclusionTexture, textures, load_texture_cmds, + AmbientOcclusionTexture, *textures, load_texture_cmds, &next_uniform, &desc)) { goto cleanup; } } if (mat->normal_texture.texture) { - if (!load_texture_uniform( - data, render_backend, &mat->normal_texture, NormalMap, textures, + if (!load_texture_and_uniform( + data, render_backend, &mat->normal_texture, NormalMap, *textures, load_texture_cmds, &next_uniform, &desc)) { goto cleanup; } @@ -557,28 +557,25 @@ cleanup: } free(materials); } + if (*textures) { + for (cgltf_size i = 0; i < data->textures_count; ++i) { + if ((*textures)[i]) { + gfx_destroy_texture(render_backend, &(*textures)[i]); + } + } + free(*textures); + *textures = 0; + } *num_materials = 0; return 0; } -/// Configures the MeshPermutation based on the given material. -static void configure_material_permutation( - MeshPermutation* perm, cgltf_material* material) { - assert(perm); - assert(material); - - perm->has_normal_map = material->normal_texture.texture != 0; - perm->has_occlusion_texture = material->occlusion_texture.texture != 0; - perm->has_emissive_texture = material->emissive_texture.texture != 0; -} - /// Load all meshes from the glTF scene. static SceneObject** load_meshes( const cgltf_data* data, Gfx* gfx, Buffer** buffers, - Buffer** tangent_buffers, Material** materials, - const cgltfTangentBuffer* cgltf_tangent_buffers, - cgltf_size num_tangent_buffers, Geometry*** geometries, Mesh*** meshes, - MeshPermutation** mesh_permutations, cgltf_size* num_geometries, + Buffer** tangent_buffers, const cgltfTangentBuffer* cgltf_tangent_buffers, + cgltf_size num_tangent_buffers, Material** materials, ShaderProgram* shader, + Geometry*** geometries, Mesh*** meshes, cgltf_size* num_geometries, cgltf_size* num_meshes, cgltf_size* num_scene_objects) { // Walk through the mesh primitives to create Meshes. A GLTF mesh primitive // has a material (Mesh) and vertex data (Geometry). A GLTF mesh maps to @@ -594,9 +591,9 @@ static SceneObject** load_meshes( assert(gfx); assert(buffers); assert(materials); + assert(shader); assert(geometries); assert(meshes); - assert(mesh_permutations); assert(num_geometries); assert(num_meshes); assert(num_scene_objects); @@ -618,10 +615,6 @@ static SceneObject** load_meshes( if (!*meshes) { goto cleanup; } - *mesh_permutations = calloc(primitive_count, sizeof(MeshPermutation)); - if (!*mesh_permutations) { - goto cleanup; - } SceneObject** objects = calloc(data->meshes_count, sizeof(SceneObject*)); if (!objects) { goto cleanup; @@ -642,7 +635,12 @@ static SceneObject** load_meshes( for (cgltf_size p = 0; p < mesh->primitives_count; ++p) { assert(next_mesh < primitive_count); const cgltf_primitive* prim = &mesh->primitives[p]; - MeshPermutation* perm = mesh_permutations[next_mesh]; + const cgltf_material* mat = prim->material; + + MeshPermutation perm = {0}; + perm.has_normal_map = mat->normal_texture.texture != 0; + perm.has_occlusion_texture = mat->occlusion_texture.texture != 0; + perm.has_emissive_texture = mat->emissive_texture.texture != 0; GeometryDesc geometry_desc = { .type = from_gltf_primitive_type(prim->type)}; @@ -693,18 +691,21 @@ static SceneObject** load_meshes( assert(false); break; } + // It is assumed that meshes have positions, so there is nothing to + // do for the mesh permutation in this case. break; } case cgltf_attribute_type_normal: - buffer_view_3d = &geometry_desc.normals; - perm->has_normals = true; + buffer_view_3d = &geometry_desc.normals; + perm.has_normals = true; break; case cgltf_attribute_type_tangent: - buffer_view_4d = &geometry_desc.tangents; - perm->has_tangents = true; + buffer_view_4d = &geometry_desc.tangents; + perm.has_tangents = true; break; case cgltf_attribute_type_texcoord: - buffer_view_2d = &geometry_desc.texcoords; + buffer_view_2d = &geometry_desc.texcoords; + perm.has_texcoords = true; break; default: // Attribute ignored. @@ -747,8 +748,8 @@ static SceneObject** load_meshes( } // Set the number of vertices in the geometry. Since a geometry can have - // either 2d or 3d positions but not both, here we can perform addition to - // compute the total number of vertices. + // either 2d or 3d positions but not both, here we can perform addition + // to compute the total number of vertices. geometry_desc.num_verts = (geometry_desc.positions2d.size_bytes / sizeof(vec2)) + (geometry_desc.positions3d.size_bytes / sizeof(vec3)); @@ -771,17 +772,40 @@ static SceneObject** load_meshes( geometry_desc.num_verts); } - (*geometries)[next_mesh] = - gfx_make_geometry(render_backend, &geometry_desc); - const cgltf_size material_index = prim->material - data->materials; assert(material_index < data->materials_count); - const Material* material = materials[material_index]; + Material* material = materials[material_index]; + + // TODO: We need a better way to handle clean-up, specifically of + // materials. One is to add materials to a dynamically-allocated list or + // vector. Another is to expose some kind of scene purge that deletes the + // resources of a given scene. The latter would make clean-up much simpler + // in general, not just for materials. + + (*geometries)[next_mesh] = + gfx_make_geometry(render_backend, &geometry_desc); + if (!(*geometries)[next_mesh]) { + goto cleanup; + } - configure_material_permutation(perm, prim->material); + // If the user specifies a custom shader, use that instead. + // else TODO: Build a shader based on permutation. + // + // TODO: We should cache shader permutations to re-use shader programs. + // Caching should not be done locally here because a caller may call + // gfx_load_scene() multiple times to load multiple scenes, and we want + // shader re-use across scenes too. + // + // On the other hand, caching materials is not necessary since, provided + // they can share shaders, the renderer can check later whether uniforms + // have the same values. Also, changing uniforms is much faster than + // swapping shaders, so shader caching is the most important thing here. + assert(shader); (*meshes)[next_mesh] = gfx_make_mesh(&(MeshDesc){ - .geometry = (*geometries)[next_mesh], .material = material}); + .geometry = (*geometries)[next_mesh], + .material = material, + .shader = shader}); gfx_add_object_mesh(objects[m], (*meshes)[next_mesh]); @@ -814,10 +838,6 @@ cleanup: free(*meshes); *meshes = 0; } - if (*mesh_permutations) { - free(*mesh_permutations); - mesh_permutations = 0; - } if (objects) { for (cgltf_size i = 0; i < data->meshes_count; ++i) { if (objects[i]) { @@ -939,8 +959,8 @@ static SceneNode** load_nodes( gfx_set_node_transform(nodes[n], &transform); // By default, set nodes as children of the root node. The second pass will - // properly set the parent of the relevant nodes, and leave the root node - // as the parent for top-level nodes. + // properly set the parent of the relevant nodes, and leave the root node as + // the parent for top-level nodes. gfx_set_node_parent(nodes[n], root_node); } // SceneNode. @@ -1010,26 +1030,28 @@ static bool load_scene( LOGD("Filepath: %s", filepath); LOGD("Directory: %s", mstring_cstring(&directory)); - Buffer** buffers = 0; - Buffer** tangent_buffers = 0; - Geometry** geometries = 0; - Material** materials = 0; - Mesh** meshes = 0; - SceneObject** scene_objects = 0; - SceneCamera** scene_cameras = 0; - SceneNode** scene_nodes = 0; - Texture** textures = 0; - LoadTextureCmd* load_texture_cmds = 0; - MeshPermutation* mesh_permutations = 0; - cgltf_size num_buffers = 0; - cgltf_size num_geometries = 0; - cgltf_size num_materials = 0; - cgltf_size num_meshes = 0; - cgltf_size num_scene_objects = 0; - cgltf_size num_scene_cameras = 0; - cgltf_size num_scene_nodes = 0; - cgltf_size num_textures = 0; - + Buffer** buffers = 0; + Buffer** tangent_buffers = 0; + Geometry** geometries = 0; + Material** materials = 0; + Mesh** meshes = 0; + SceneObject** scene_objects = 0; + SceneCamera** scene_cameras = 0; + SceneNode** scene_nodes = 0; + Texture** textures = 0; + LoadTextureCmd* load_texture_cmds = 0; + cgltf_size num_buffers = 0; + cgltf_size num_geometries = 0; + cgltf_size num_materials = 0; + cgltf_size num_meshes = 0; + cgltf_size num_scene_objects = 0; + cgltf_size num_scene_cameras = 0; + cgltf_size num_scene_nodes = 0; + cgltf_size num_textures = 0; + + // TODO: Let this function handle all the cleanup. Let the other functions + // return pass/failure booleans and the arrays as in/out parameters. This way + // we do not need the individual functions to duplicate cleanup code. buffers = load_buffers(data, render_backend, &num_buffers); if (!buffers) { goto cleanup; @@ -1043,23 +1065,21 @@ static bool load_scene( } } - textures = load_textures( - data, render_backend, mstring_cstring(&directory), &load_texture_cmds, - &num_textures); - if (!textures || !load_texture_cmds) { + if (!load_textures_lazy( + data, render_backend, mstring_cstring(&directory), &load_texture_cmds, + &num_textures)) { goto cleanup; } materials = load_materials( - data, render_backend, shader, textures, load_texture_cmds, - &num_materials); + data, render_backend, load_texture_cmds, &textures, &num_materials); if (!materials) { goto cleanup; } scene_objects = load_meshes( - data, gfx, buffers, tangent_buffers, materials, cgltf_tangent_buffers, - num_tangent_buffers, &geometries, &meshes, &mesh_permutations, + data, gfx, buffers, tangent_buffers, cgltf_tangent_buffers, + num_tangent_buffers, materials, shader, &geometries, &meshes, &num_geometries, &num_meshes, &num_scene_objects); if (!scene_objects) { goto cleanup; @@ -1132,9 +1152,6 @@ cleanup: if (load_texture_cmds) { free(load_texture_cmds); } - if (mesh_permutations) { - free(mesh_permutations); - } return false; } @@ -1143,7 +1160,6 @@ bool gfx_load_scene( const LoadSceneCmd* cmd) { assert(gfx); assert(root_node); - assert(shader); assert(cmd); 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) { RenderBackend* render_backend = gfx_get_render_backend(gfx); assert(render_backend); - ShaderProgram* shader = 0; - Geometry* geometry = 0; - Material* material = 0; - Mesh* mesh = 0; - SceneObject* object = 0; + ShaderProgram* shader = 0; + Geometry* geometry = 0; + Material* material = 0; + Mesh* mesh = 0; + SceneObject* object = 0; shader = gfx_make_skyquad_shader(render_backend); if (!shader) { @@ -39,12 +39,12 @@ SceneObject* gfx_make_skyquad(Gfx* gfx, Scene* scene, const Texture* texture) { } MaterialDesc material_desc = (MaterialDesc){0}; - material_desc.shader = shader; - material_desc.uniforms[0] = (ShaderUniform){.type = UniformTexture, - .value.texture = texture, - .name = sstring_make("Skyquad")}; + material_desc.uniforms[0] = (ShaderUniform){ + .type = UniformTexture, + .value.texture = texture, + .name = sstring_make("Skyquad")}; material_desc.num_uniforms = 1; - material = gfx_make_material(&material_desc); + material = gfx_make_material(&material_desc); if (!material) { goto cleanup; } @@ -52,7 +52,8 @@ SceneObject* gfx_make_skyquad(Gfx* gfx, Scene* scene, const Texture* texture) { MeshDesc mesh_desc = (MeshDesc){0}; mesh_desc.geometry = geometry; mesh_desc.material = material; - mesh = gfx_make_mesh(&mesh_desc); + mesh_desc.shader = shader; + mesh = gfx_make_mesh(&mesh_desc); if (!mesh) { goto cleanup; } 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 @@ #include // usleep; TODO Remove. // Paths to various scene files. -static const char* BOX = "/assets/models/box.gltf"; +static const char* BOX = "/assets/models/box.gltf"; static const char* SUZANNE = "/assets/models/suzanne.gltf"; static const char* SPONZA = "/assets/glTF-Sample-Models/2.0/Sponza/glTF/Sponza.gltf"; @@ -37,8 +37,8 @@ static const char* DAMAGED_HELMET = static const char* CLOUDS1_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp"; -static ShaderProgram* load_shader(RenderBackend* render_backend, - const char* view_mode) { +static ShaderProgram* load_shader( + RenderBackend* render_backend, const char* view_mode) { ShaderProgram* shader = 0; if (strcmp(view_mode, "debug") == 0) { shader = gfx_make_debug3d_shader(render_backend); @@ -59,23 +59,24 @@ static Texture* load_environment_map(RenderBackend* render_backend) { return gfx_load_texture( render_backend, &(LoadTextureCmd){ - .origin = TextureFromFile, - .type = LoadCubemap, - .colour_space = sRGB, - .filtering = NearestFiltering, - .mipmaps = false, + .origin = TextureFromFile, + .type = LoadCubemap, + .colour_space = sRGB, + .filtering = NearestFiltering, + .mipmaps = false, .data.cubemap.filepaths = { - mstring_make("/assets/skybox/clouds1/clouds1_east.bmp"), - mstring_make("/assets/skybox/clouds1/clouds1_west.bmp"), - mstring_make("/assets/skybox/clouds1/clouds1_up.bmp"), - mstring_make("/assets/skybox/clouds1/clouds1_down.bmp"), - mstring_make("/assets/skybox/clouds1/clouds1_north.bmp"), - mstring_make("/assets/skybox/clouds1/clouds1_south.bmp")}}); + mstring_make("/assets/skybox/clouds1/clouds1_east.bmp"), + mstring_make("/assets/skybox/clouds1/clouds1_west.bmp"), + mstring_make("/assets/skybox/clouds1/clouds1_up.bmp"), + mstring_make("/assets/skybox/clouds1/clouds1_down.bmp"), + mstring_make("/assets/skybox/clouds1/clouds1_north.bmp"), + mstring_make("/assets/skybox/clouds1/clouds1_south.bmp")} + }); } /// Creates an object to render the skyquad in the background. -static SceneNode* make_skyquad_object_node(Game* game, - const Texture* environment_map) { +static SceneNode* make_skyquad_object_node( + Game* game, const Texture* environment_map) { assert(game); SceneObject* skyquad_object = @@ -92,12 +93,12 @@ static SceneNode* make_skyquad_object_node(Game* game, } /// Creates an environment light. -static SceneNode* make_environment_light(Game* game, - const Texture* environment_light) { +static SceneNode* make_environment_light( + Game* game, const Texture* environment_light) { assert(game); Light* light = gfx_make_light(&(LightDesc){ - .type = EnvironmentLightType, + .type = EnvironmentLightType, .light = (EnvironmentLightDesc){.environment_map = environment_light}}); if (!light) { return 0; @@ -127,8 +128,8 @@ static bool load_skyquad(Game* game, SceneNode** node) { } /// Loads the 3D scene. -static bool load_scene(Game* game, const char* scene_filepath, - const char* view_mode) { +static bool load_scene( + Game* game, const char* scene_filepath, const char* view_mode) { assert(game); game->camera = gfx_make_camera(); @@ -151,9 +152,10 @@ static bool load_scene(Game* game, const char* scene_filepath, return false; } - if (!gfx_load_scene(game->gfx, sky_node, shader, - &(LoadSceneCmd){.origin = SceneFromFile, - .filepath = scene_filepath})) { + if (!gfx_load_scene( + game->gfx, sky_node, shader, + &(LoadSceneCmd){ + .origin = SceneFromFile, .filepath = scene_filepath})) { return false; } @@ -164,14 +166,14 @@ static bool load_scene(Game* game, const char* scene_filepath, static bool load_texture_debugger_scene(Game* game) { assert(game); - Texture* texture = - gfx_load_texture(game->render_backend, - &(LoadTextureCmd){.origin = TextureFromFile, - .type = LoadTexture, - .filtering = LinearFiltering, - .mipmaps = false, - .data.texture.filepath = - mstring_make(CLOUDS1_TEXTURE)}); + Texture* texture = gfx_load_texture( + game->render_backend, + &(LoadTextureCmd){ + .origin = TextureFromFile, + .type = LoadTexture, + .filtering = LinearFiltering, + .mipmaps = false, + .data.texture.filepath = mstring_make(CLOUDS1_TEXTURE)}); game->camera = gfx_make_camera(); if (!game->camera) { @@ -191,12 +193,12 @@ static bool load_texture_debugger_scene(Game* game) { } MaterialDesc material_desc = (MaterialDesc){0}; - material_desc.shader = shader; - material_desc.uniforms[0] = (ShaderUniform){.type = UniformTexture, - .value.texture = texture, - .name = sstring_make("Texture")}; + material_desc.uniforms[0] = (ShaderUniform){ + .type = UniformTexture, + .value.texture = texture, + .name = sstring_make("Texture")}; material_desc.num_uniforms = 1; - Material* material = gfx_make_material(&material_desc); + Material* material = gfx_make_material(&material_desc); if (!material) { return false; } @@ -204,7 +206,8 @@ static bool load_texture_debugger_scene(Game* game) { MeshDesc mesh_desc = (MeshDesc){0}; mesh_desc.geometry = geometry; mesh_desc.material = material; - Mesh* mesh = gfx_make_mesh(&mesh_desc); + mesh_desc.shader = shader; + Mesh* mesh = gfx_make_mesh(&mesh_desc); if (!mesh) { return false; } @@ -224,7 +227,7 @@ static bool load_texture_debugger_scene(Game* game) { bool game_new(Game* game, int argc, const char** argv) { // TODO: getopt() to implement proper argument parsing. - const char* view_mode = argc > 1 ? argv[1] : ""; + const char* view_mode = argc > 1 ? argv[1] : ""; const char* scene_filepath = argc > 2 ? argv[2] : DEFAULT_SCENE_FILE; game->gfx = gfx_init(); @@ -233,7 +236,7 @@ bool game_new(Game* game, int argc, const char** argv) { } game->render_backend = gfx_get_render_backend(game->gfx); - game->renderer = gfx_get_renderer(game->gfx); + game->renderer = gfx_get_renderer(game->gfx); game->scene = gfx_make_scene(game->gfx); if (!game->scene) { @@ -270,26 +273,27 @@ void game_update(Game* game, double t, double dt) { game->elapsed -= 1.0; } Camera* camera = gfx_get_camera_camera(game->camera); - spatial3_orbit(&camera->spatial, vec3_make(0, 0, 0), - /*radius=*/2, - /*azimuth=*/t * 0.5, /*zenith=*/0); + spatial3_orbit( + &camera->spatial, vec3_make(0, 0, 0), + /*radius=*/2, + /*azimuth=*/t * 0.5, /*zenith=*/0); spatial3_lookat(&camera->spatial, vec3_make(0, 0, 0)); } void game_render(const Game* game) { - gfx_render_scene(game->renderer, game->render_backend, game->scene, - game->camera); + gfx_render_scene( + game->renderer, game->render_backend, game->scene, game->camera); } void game_set_viewport(Game* game, int width, int height) { gfx_set_viewport(game->render_backend, width, height); - const R fovy = 90 * TO_RAD; - const R aspect = (R)width / (R)height; - const R near = 0.1; - const R far = 1000; + const R fovy = 90 * TO_RAD; + const R aspect = (R)width / (R)height; + const R near = 0.1; + const R far = 1000; const mat4 projection = mat4_perspective(fovy, aspect, near, far); - Camera* camera = gfx_get_camera_camera(game->camera); + Camera* camera = gfx_get_camera_camera(game->camera); camera->projection = projection; } -- cgit v1.2.3