From 6d5e336c5f1b249404d29bc4349b9cf95f058dbf Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Sat, 17 Feb 2024 14:04:46 -0800 Subject: Rename scene.h/c -> model.h/c. --- gfx/CMakeLists.txt | 2 +- gfx/src/asset/asset_cache.c | 2 +- gfx/src/asset/model.c | 1792 +++++++++++++++++++++++++++++++++++++++++++ gfx/src/asset/model.h | 12 + gfx/src/asset/scene.c | 1792 ------------------------------------------- gfx/src/asset/scene.h | 12 - 6 files changed, 1806 insertions(+), 1806 deletions(-) create mode 100644 gfx/src/asset/model.c create mode 100644 gfx/src/asset/model.h delete mode 100644 gfx/src/asset/scene.c delete mode 100644 gfx/src/asset/scene.h diff --git a/gfx/CMakeLists.txt b/gfx/CMakeLists.txt index b423990..6c8640c 100644 --- a/gfx/CMakeLists.txt +++ b/gfx/CMakeLists.txt @@ -34,7 +34,7 @@ add_shader_library(shaders add_library(gfx SHARED src/asset/asset_cache.c - src/asset/scene.c + src/asset/model.c src/asset/texture.c src/render/buffer.c src/render/framebuffer.c diff --git a/gfx/src/asset/asset_cache.c b/gfx/src/asset/asset_cache.c index e17de9d..0a9b1f2 100644 --- a/gfx/src/asset/asset_cache.c +++ b/gfx/src/asset/asset_cache.c @@ -1,6 +1,6 @@ #include "asset_cache.h" -#include "scene.h" +#include "model.h" #include "scene/animation_impl.h" #include "scene/model_impl.h" #include "scene/node_impl.h" diff --git a/gfx/src/asset/model.c b/gfx/src/asset/model.c new file mode 100644 index 0000000..e0df078 --- /dev/null +++ b/gfx/src/asset/model.c @@ -0,0 +1,1792 @@ +/// Loads scenes from memory and files. +/// +/// Only the GLTF scene format is current supported. +/// +/// ---------------------------------------------------------------------------- +/// glTF File Format Documentation +/// ---------------------------------------------------------------------------- +/// +/// cgltf: +/// https://github.com/jkuhlmann/cgltf +/// +/// gltf overview: +/// https://raw.githubusercontent.com/KhronosGroup/glTF/master/specification/2.0/figures/gltfOverview-2.0.0b.png +/// +/// gltf spec: +/// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md +/// +/// Sample models: +/// https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0 +/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Sponza/glTF/Sponza.gltf +/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AlphaBlendModeTest/glTF/AlphaBlendModeTest.gltf +/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Buggy/glTF/Buggy.gltf +/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AntiqueCamera/glTF/AntiqueCamera.gltf +/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf +/// +/// ---------------------------------------------------------------------------- +/// Implementation Notes +/// ---------------------------------------------------------------------------- +/// +/// # glTF and the gfx library +/// +/// glTF has concepts that are similar to those in the gfx library, but there +/// isn't an exact 1-1 mapping. Concepts map as follows: +/// +/// glTF gfx +/// ---- --- +/// buffer Buffer +/// accessor + buffer view BufferView +/// mesh primitive (geom + mat) Mesh (also geom + mat) +/// mesh SceneObject +/// node SceneNode +/// +/// glTF buffers map 1-1 with gfx Buffers. glTF scenes make heavy re-use of +/// buffers across views/accessors/meshes, so it is important to make that same +/// re-use in the gfx library to use the data effectively and without +/// duplication. The Sponza scene, for example, has all of its data in one giant +/// buffer. +/// +/// glTF accessors and buffer views are combined and mapped to gfx BufferViews. +/// The glTF buffer view's offset/length/stride are combined with the accessor's +/// offset, and together with the remaining information of both data structures +/// baked into a BufferView. Internally, this information is fed into +/// glVertexAttribPointer() calls, wrapped in a VAO (index view/accessor +/// information is fed into glDrawElements()). This baking should not hurt +/// re-use, at least in the OpenGL world. +/// +/// A glTF mesh primitive contains a piece of geometry and a material. This maps +/// directly to a gfx Mesh. +/// +/// A glTF mesh is a list of mesh primitives. This maps nicely to a gfx +/// SceneObject, with the only inconvenience that terminology gets a little +/// confusing. +/// +/// Finally, glTF nodes map directly to gfx SceneNodes. Both enforce a strict +/// tree hierarchy; DAGs are not supported. +/// +/// # Materials +/// +/// glTF uses the metallic-roughness material model. However, an extension +/// allows a scene to use the specular-glossiness model as well and cgltf +/// supports it: +/// +/// https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/ +/// +/// From the docs, the specular-glossiness model can represent more materials +/// than the metallic-roughness model, but it is also more computationally +/// expensive. Furthermore, a material in glTF can specify parameters for both +/// models, leaving it up to the implementation to decide which one to use. +/// In our case, we use the specular-glosiness model if parameters for it are +/// provided, otherwise we use the metallic-roughness model. + +#include "asset/model.h" + +#include "asset/texture.h" +#include "gfx/gfx.h" +#include "gfx/render_backend.h" +#include "gfx/scene/animation.h" +#include "gfx/scene/camera.h" +#include "gfx/scene/material.h" +#include "gfx/scene/mesh.h" +#include "gfx/scene/node.h" +#include "gfx/scene/object.h" +#include "gfx/scene/scene.h" +#include "gfx/sizes.h" +#include "gfx/util/shader.h" + +#include "scene/model_impl.h" + +#include "cstring.h" +#include "error.h" +#include "log/log.h" +#include "math/camera.h" +#include "math/defs.h" +#include "math/mat4.h" +#include "math/quat.h" +#include "math/vec2.h" +#include "math/vec3.h" + +#include "cgltf_tangents.h" +#define CGLTF_IMPLEMENTATION +#include "cgltf.h" + +#include +#include +#include + +// Taken from the GL header file. +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 + +// Uniforms names. Must match the names in shaders. +#define UNIFORM_BASE_COLOR_FACTOR "BaseColorFactor" +#define UNIFORM_METALLIC_FACTOR "MetallicFactor" +#define UNIFORM_ROUGHNESS_FACTOR "RoughnessFactor" +#define UNIFORM_EMISSIVE_FACTOR "EmissiveFactor" +#define UNIFORM_BASE_COLOR_TEXTURE "BaseColorTexture" +#define UNIFORM_METALLIC_ROUGHNESS_TEXTURE "MetallicRoughnessTexture" +#define UNIFORM_EMISSIVE_TEXTURE "EmissiveTexture" +#define UNIFORM_AMBIENT_OCCLUSION_TEXTURE "AmbientOcclusionTexture" +#define UNIFORM_NORMAL_MAP "NormalMap" + +// Shader compiler defines. Must match the names in shaders. +#define DEFINE_HAS_TEXCOORDS "HAS_TEXCOORDS" +#define DEFINE_HAS_NORMALS "HAS_NORMALS" +#define DEFINE_HAS_TANGENTS "HAS_TANGENTS" +#define DEFINE_HAS_ALBEDO_MAP "HAS_ALBEDO_MAP" +#define DEFINE_HAS_METALLIC_ROUGHNESS_MAP "HAS_METALLIC_ROUGHNESS_MAP" +#define DEFINE_HAS_NORMAL_MAP "HAS_NORMAL_MAP" +#define DEFINE_HAS_OCCLUSION_MAP "HAS_OCCLUSION_MAP" +#define DEFINE_HAS_EMISSIVE_MAP "HAS_EMISSIVE_MAP" +#define DEFINE_HAS_JOINTS "HAS_JOINTS" +#define DEFINE_MAX_JOINTS "MAX_JOINTS" + +typedef enum TextureType { + BaseColorTexture, + MetallicRoughnessTexture, + EmissiveTexture, + AmbientOcclusionTexture, + NormalMap, +} TextureType; + +/// Describes the properties of a mesh. +/// This is used to create shader permutations. +typedef struct MeshPermutation { + union { + struct { + // Vertex attributes. + bool has_texcoords : 1; + bool has_normals : 1; + bool has_tangents : 1; + bool has_joints : 1; + bool has_weights : 1; + // Textures. + bool has_albedo_map : 1; + bool has_metallic_roughness_map : 1; + bool has_normal_map : 1; + bool has_occlusion_map : 1; + bool has_emissive_map : 1; + }; + int32_t all; + }; +} MeshPermutation; + +/// Build shader compiler defines from a mesh permutation. +static size_t make_defines( + MeshPermutation perm, ShaderCompilerDefine* defines) { + static const char* str_true = "1"; + size_t next = 0; + +#define check(field, define) \ + if (perm.field) { \ + defines[next].name = sstring_make(define); \ + defines[next].value = sstring_make(str_true); \ + next++; \ + } + check(has_texcoords, DEFINE_HAS_TEXCOORDS); + check(has_normals, DEFINE_HAS_NORMALS); + check(has_tangents, DEFINE_HAS_TANGENTS); + check(has_joints, DEFINE_HAS_JOINTS); + check(has_albedo_map, DEFINE_HAS_ALBEDO_MAP); + check(has_metallic_roughness_map, DEFINE_HAS_METALLIC_ROUGHNESS_MAP); + check(has_normal_map, DEFINE_HAS_NORMAL_MAP); + check(has_occlusion_map, DEFINE_HAS_OCCLUSION_MAP); + check(has_emissive_map, DEFINE_HAS_EMISSIVE_MAP); + + if (perm.has_joints) { + defines[next].name = sstring_make(DEFINE_MAX_JOINTS); + defines[next].value = sstring_itoa(GFX_MAX_NUM_JOINTS); + next++; + } + + return next; +} + +/// Compile a shader permutation. +static ShaderProgram* make_shader_permutation( + RenderBackend* render_backend, MeshPermutation perm) { + LOGD( + "Compiling Cook-Torrance shader permutation: texcoords: %d, normals: " + "%d, tangents: %d, joints: %d, weights: %d, albedo map: %d, " + "metallic-roughness map: " + "%d, normal " + "map: %d, AO map: %d, emissive map: %d", + perm.has_texcoords, perm.has_normals, perm.has_tangents, perm.has_joints, + perm.has_weights, perm.has_albedo_map, perm.has_metallic_roughness_map, + perm.has_normal_map, perm.has_occlusion_map, perm.has_emissive_map); + + ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES]; + const size_t num_defines = make_defines(perm, defines); + return gfx_make_cook_torrance_shader_perm( + render_backend, defines, num_defines); +} + +/// Map a texture type to the name of the shader uniform used to access the +/// texture. +static const char* get_texture_uniform_name(TextureType type) { + switch (type) { + case BaseColorTexture: + return UNIFORM_BASE_COLOR_TEXTURE; + case MetallicRoughnessTexture: + return UNIFORM_METALLIC_ROUGHNESS_TEXTURE; + case EmissiveTexture: + return UNIFORM_EMISSIVE_TEXTURE; + case AmbientOcclusionTexture: + return UNIFORM_AMBIENT_OCCLUSION_TEXTURE; + case NormalMap: + return UNIFORM_NORMAL_MAP; + } + assert(false); + return 0; +} + +/// Map a glTF primitive type to a gfx primitive type. +static PrimitiveType from_gltf_primitive_type(cgltf_primitive_type type) { + switch (type) { + case cgltf_primitive_type_triangles: + return Triangles; + case cgltf_primitive_type_triangle_fan: + return TriangleFan; + case cgltf_primitive_type_triangle_strip: + return TriangleStrip; + // Not yet implemented. + case cgltf_primitive_type_lines: + case cgltf_primitive_type_line_loop: + case cgltf_primitive_type_line_strip: + case cgltf_primitive_type_points: + break; + } + LOGE("Unsupported primitive type: %d", type); + assert(false); + return 0; +} + +/// Map a glTF animation path type to its Gfx equivalent. +static ChannelType from_gltf_animation_path_type( + cgltf_animation_path_type type) { + switch (type) { + case cgltf_animation_path_type_translation: + return TranslationChannel; + case cgltf_animation_path_type_rotation: + return RotationChannel; + case cgltf_animation_path_type_scale: + return ScaleChannel; + case cgltf_animation_path_type_weights: + return WeightsChannel; + case cgltf_animation_path_type_invalid: + assert(false); + break; + } + assert(false); + return 0; +} + +/// Map a glTF interpolation to its Gfx equivalent. +static AnimationInterpolation from_gltf_interpolation_type( + cgltf_interpolation_type type) { + switch (type) { + case cgltf_interpolation_type_linear: + return LinearInterpolation; + case cgltf_interpolation_type_step: + return StepInterpolation; + case cgltf_interpolation_type_cubic_spline: + return CubicSplineInterpolation; + } + assert(false); + return 0; +} + +/// Return the component's size in bytes. +static cgltf_size get_component_size(cgltf_component_type type) { + switch (type) { + case cgltf_component_type_r_8: + return 1; + case cgltf_component_type_r_8u: + return 1; + case cgltf_component_type_r_16: + return 2; + case cgltf_component_type_r_16u: + return 2; + case cgltf_component_type_r_32u: + return 4; + case cgltf_component_type_r_32f: + return 4; + case cgltf_component_type_invalid: + assert(false); + break; + } + assert(false); + return 0; +} + +/// Read a float from the given data pointer and accessor. +/// +/// This function uses the normalization equations from the spec. See the +/// animation section: +/// +/// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#animations +static float read_float(const void* data, const cgltf_accessor* accessor) { + assert(data); + assert(accessor); + + switch (accessor->component_type) { + case cgltf_component_type_r_8: { + assert(accessor->normalized); + const int8_t c = *((int8_t*)data); + return max((float)c / 127.0, -1.0); + } + case cgltf_component_type_r_8u: { + assert(accessor->normalized); + const uint8_t c = *((uint8_t*)data); + return (float)c / 255.0; + } + case cgltf_component_type_r_16: { + assert(accessor->normalized); + const int16_t c = *((int16_t*)data); + return max((float)c / 32767.0, -1.0); + } + case cgltf_component_type_r_16u: { + assert(accessor->normalized); + const uint16_t c = *((uint16_t*)data); + return (float)c / 65535.0; + } + case cgltf_component_type_r_32u: { + assert(accessor->normalized); + const uint32_t c = *((uint32_t*)data); + return (float)c / 4294967295.0; + } + case cgltf_component_type_r_32f: { + const float c = *((float*)data); + return c; + } + case cgltf_component_type_invalid: + assert(false); + break; + } + assert(false); + return 0; +} + +/// Iterate over the vectors in an accessor. +#define ACCESSOR_FOREACH_VEC(dimensions, accessor, body) \ + { \ + assert((1 <= dimensions) && (dimensions <= 4)); \ + assert( \ + ((dimensions == 1) && (accessor->type == cgltf_type_scalar)) || \ + ((dimensions == 2) && (accessor->type == cgltf_type_vec2)) || \ + ((dimensions == 3) && (accessor->type == cgltf_type_vec3)) || \ + ((dimensions == 4) && (accessor->type == cgltf_type_vec4))); \ + const cgltf_buffer_view* view = accessor->buffer_view; \ + const cgltf_buffer* buffer = view->buffer; \ + const cgltf_size offset = accessor->offset + view->offset; \ + const uint8_t* bytes = (const uint8_t*)buffer->data + offset; \ + /* Component size in bytes. */ \ + const cgltf_size comp_size = get_component_size(accessor->component_type); \ + /* Element size in bytes. */ \ + const cgltf_size elem_size = dimensions * comp_size; \ + /* Stride in bytes. If the view stride is 0, then the elements are tightly \ + * packed. */ \ + const cgltf_size stride = view->stride != 0 ? view->stride : elem_size; \ + /* There isn't an accessor stride in the spec, but cgltf still specifies \ + * one. */ \ + assert(accessor->stride == elem_size); \ + /* Accessor data must fit inside the buffer. */ \ + assert( \ + (offset + (accessor->count * elem_size) + \ + ((accessor->count - 1) * view->stride)) <= buffer->size); \ + /* Accessor data must fit inside the view. */ \ + assert(accessor->count * accessor->stride <= view->size); \ + cgltf_float x = 0, y = 0, z = 0, w = 0; \ + /* Silence unused variable warnings. */ \ + (void)y; \ + (void)z; \ + (void)w; \ + /* The {component type} X {dimensions} combinations are a pain to handle. \ + For floats, we switch on type first and then lay out a loop for each \ + dimension to get a tight loop with a possibly inlined body. For other \ + types, we take the performance hit and perform checks and conversions \ + inside the loop for simplicity. */ \ + if (accessor->component_type == cgltf_component_type_r_32f) { \ + switch (dimensions) { \ + case 1: \ + assert(accessor->type == cgltf_type_scalar); \ + for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ + const cgltf_float* floats = (const cgltf_float*)bytes; \ + x = *floats; \ + body; \ + } \ + break; \ + case 2: \ + assert(accessor->type == cgltf_type_vec2); \ + for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ + const cgltf_float* floats = (const cgltf_float*)bytes; \ + x = *floats++; \ + y = *floats; \ + body; \ + } \ + break; \ + case 3: \ + assert(accessor->type == cgltf_type_vec3); \ + for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ + const cgltf_float* floats = (const cgltf_float*)bytes; \ + x = *floats++; \ + y = *floats++; \ + z = *floats; \ + body; \ + } \ + break; \ + case 4: \ + assert(accessor->type == cgltf_type_vec4); \ + for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ + const cgltf_float* floats = (const cgltf_float*)bytes; \ + x = *floats++; \ + y = *floats++; \ + z = *floats++; \ + w = *floats; \ + body; \ + } \ + break; \ + } \ + } else { \ + for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ + const uint8_t* component = bytes; \ + \ + x = read_float(component, accessor); \ + component += comp_size; \ + if (dimensions > 1) { \ + y = read_float(component, accessor); \ + component += comp_size; \ + } \ + if (dimensions > 2) { \ + z = read_float(component, accessor); \ + component += comp_size; \ + } \ + if (dimensions > 3) { \ + w = read_float(component, accessor); \ + component += comp_size; \ + } \ + body; \ + } \ + } \ + } + +/// Iterate over the matrices in an accessor. +#define ACCESSOR_FOREACH_MAT(dimensions, accessor, body) \ + { \ + assert((2 <= dimensions) && (dimensions <= 4)); \ + assert(!(dimensions == 2) || (accessor->type == cgltf_type_mat2)); \ + assert(!(dimensions == 3) || (accessor->type == cgltf_type_mat3)); \ + assert(!(dimensions == 4) || (accessor->type == cgltf_type_mat4)); \ + const cgltf_buffer_view* view = accessor->buffer_view; \ + const cgltf_buffer* buffer = view->buffer; \ + const cgltf_size offset = accessor->offset + view->offset; \ + const cgltf_size comp_size = get_component_size(accessor->component_type); \ + const uint8_t* bytes = (const uint8_t*)buffer->data + offset; \ + assert( \ + (offset + accessor->count * dimensions * comp_size) < buffer->size); \ + /* From the spec: */ \ + /* "Buffer views with other types of data MUST NOT not define */ \ + /* byteStride (unless such layout is explicitly enabled by an */ \ + /* extension)."*/ \ + assert(view->stride == 0); \ + assert(accessor->stride == dimensions * dimensions * comp_size); \ + assert(accessor->component_type == cgltf_component_type_r_32f); \ + const cgltf_float* floats = (const cgltf_float*)bytes; \ + switch (dimensions) { \ + case 2: \ + assert(accessor->type == cgltf_type_mat2); \ + for (cgltf_size i = 0; i < accessor->count; ++i) { \ + body; \ + floats += 4; \ + } \ + break; \ + case 3: \ + assert(accessor->type == cgltf_type_mat3); \ + for (cgltf_size i = 0; i < accessor->count; ++i) { \ + body; \ + floats += 9; \ + } \ + break; \ + case 4: \ + assert(accessor->type == cgltf_type_mat4); \ + for (cgltf_size i = 0; i < accessor->count; ++i) { \ + body; \ + floats += 16; \ + } \ + break; \ + } \ + } + +/// Return the total number of primitives in the scene. Each mesh may contain +/// multiple primitives. +/// +/// Note that this function scans all of the scenes in the glTF data. +static size_t get_total_primitives(const cgltf_data* data) { + size_t total = 0; + for (cgltf_size i = 0; i < data->meshes_count; ++i) { + total += data->meshes[i].primitives_count; + } + return total; +} + +/// Load all buffers from the glTF scene. +/// +/// If buffer data is loaded from memory, set filepath = null. +/// +/// Return an array of Buffers such that the index of each glTF buffer in the +/// original array matches the same Buffer in the resulting array. +/// +/// TODO: There is no need to load the inverse bind matrices buffer into the +/// GPU. Might need to lazily load buffers. +static bool load_buffers( + const cgltf_data* data, RenderBackend* render_backend, Buffer** buffers) { + assert(data); + assert(render_backend); + assert(buffers); + + for (cgltf_size i = 0; i < data->buffers_count; ++i) { + const cgltf_buffer* buffer = &data->buffers[i]; + assert(buffer->data); + buffers[i] = gfx_make_buffer( + render_backend, &(BufferDesc){ + .usage = BufferStatic, + .type = BufferUntyped, + .data.data = buffer->data, + .data.count = buffer->size}); + if (!buffers[i]) { + return false; + } + } + + return true; +} + +/// Load tangent buffers. +static bool load_tangent_buffers( + const cgltfTangentBuffer* cgltf_tangent_buffers, + cgltf_size num_tangent_buffers, RenderBackend* render_backend, + Buffer** tangent_buffers) { + assert(cgltf_tangent_buffers); + assert(render_backend); + assert(tangent_buffers); + + for (cgltf_size i = 0; i < num_tangent_buffers; ++i) { + const cgltfTangentBuffer* buffer = &cgltf_tangent_buffers[i]; + assert(buffer->data); + tangent_buffers[i] = gfx_make_buffer( + render_backend, &(BufferDesc){ + .usage = BufferStatic, + .type = BufferUntyped, + .data.data = buffer->data, + .data.count = buffer->size_bytes}); + if (!tangent_buffers[i]) { + return false; + } + } + + return true; +} + +/// Lazily load all textures from the glTF scene. +/// +/// 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. +/// +/// Return an array of LoadTextureCmds such that the index of each cmd matches +/// the index of each glTF texture in the scene. +static void load_textures_lazy( + const cgltf_data* data, RenderBackend* render_backend, + const char* directory, LoadTextureCmd* load_texture_cmds) { + assert(data); + assert(render_backend); + assert(load_texture_cmds); + + for (cgltf_size i = 0; i < data->textures_count; ++i) { + const cgltf_texture* texture = &data->textures[i]; + const cgltf_image* image = texture->image; + const cgltf_sampler* sampler = texture->sampler; + + // glTF models might not specify a sampler. In such case, the client can + // pick its own defaults. + // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#samplers + bool mipmaps = true; + TextureFiltering filtering = LinearFiltering; + TextureWrapping wrap = Repeat; + + if (sampler) { + // The gfx library does not distinguish between sampling the texture and + // combining the mipmap levels. + const cgltf_int filter = + sampler->min_filter == 0 ? sampler->mag_filter : sampler->min_filter; + + switch (filter) { + case GL_NEAREST_MIPMAP_NEAREST: + mipmaps = true; + filtering = NearestFiltering; + break; + case GL_NEAREST_MIPMAP_LINEAR: + case GL_LINEAR_MIPMAP_NEAREST: + case GL_LINEAR_MIPMAP_LINEAR: + mipmaps = true; + filtering = LinearFiltering; + break; + case GL_NEAREST: + filtering = NearestFiltering; + break; + case GL_LINEAR: + filtering = LinearFiltering; + break; + default: + break; + } + } + + // Currently only supporting loading textures from files. + assert(image->uri); + assert(directory); + mstring fullpath = + mstring_concat_path(mstring_make(directory), mstring_make(image->uri)); + + load_texture_cmds[i] = (LoadTextureCmd){ + .origin = AssetFromFile, + .type = LoadTexture, + .colour_space = sRGB, + .filtering = filtering, + .wrap = wrap, + .mipmaps = mipmaps, + .data.texture.filepath = fullpath}; + } +} + +/// 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_and_uniform( + const cgltf_data* data, Gfx* gfx, const cgltf_texture_view* texture_view, + TextureType texture_type, Texture** textures, + LoadTextureCmd* load_texture_cmds, int* next_uniform, MaterialDesc* desc) { + assert(data); + assert(gfx); + assert(texture_view); + assert(textures); + assert(next_uniform); + assert(desc); + assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); + + const size_t texture_index = texture_view->texture - data->textures; + assert(texture_index < data->textures_count); + + // 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]) { + 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_cstr(&cmd->data.texture.filepath), cmd->mipmaps, + cmd->filtering); + + textures[texture_index] = gfx_load_texture(gfx, cmd); + if (!textures[texture_index]) { + prepend_error( + "Failed to load texture: %s", + mstring_cstr(&cmd->data.texture.filepath)); + return false; + } + } + + assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc->uniforms[(*next_uniform)++] = (ShaderUniform){ + .name = sstring_make(get_texture_uniform_name(texture_type)), + .type = UniformTexture, + .value.texture = textures[texture_index]}; + + return true; +} + +/// Load all materials from the glTF scene. +/// +/// 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 bool load_materials( + const cgltf_data* data, Gfx* gfx, LoadTextureCmd* load_texture_cmds, + Texture** textures, Material** materials) { + assert(data); + assert(gfx); + assert(materials); + if (data->textures_count > 0) { + assert(load_texture_cmds); + assert(textures); + } + + for (cgltf_size i = 0; i < data->materials_count; ++i) { + const cgltf_material* mat = &data->materials[i]; + + int next_uniform = 0; + MaterialDesc desc = {0}; + + // TODO: specular/glossiness and other material parameters. + if (mat->has_pbr_metallic_roughness) { + const cgltf_pbr_metallic_roughness* pbr = &mat->pbr_metallic_roughness; + + 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_and_uniform( + data, gfx, &pbr->base_color_texture, BaseColorTexture, textures, + load_texture_cmds, &next_uniform, &desc)) { + return false; + } + } + + if (pbr->metallic_roughness_texture.texture) { + if (!load_texture_and_uniform( + data, gfx, &pbr->metallic_roughness_texture, + MetallicRoughnessTexture, textures, load_texture_cmds, + &next_uniform, &desc)) { + return false; + } + } + } + + if (mat->emissive_texture.texture) { + if (!load_texture_and_uniform( + data, gfx, &mat->emissive_texture, EmissiveTexture, textures, + load_texture_cmds, &next_uniform, &desc)) { + return false; + } + } + + if (mat->occlusion_texture.texture) { + if (!load_texture_and_uniform( + data, gfx, &mat->occlusion_texture, AmbientOcclusionTexture, + textures, load_texture_cmds, &next_uniform, &desc)) { + return false; + } + } + + if (mat->normal_texture.texture) { + if (!load_texture_and_uniform( + data, gfx, &mat->normal_texture, NormalMap, textures, + load_texture_cmds, &next_uniform, &desc)) { + return false; + } + } + + assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.num_uniforms = next_uniform; + + materials[i] = gfx_make_material(&desc); + if (!materials[i]) { + return false; + } + } + + return true; +} + +/// Create a default material for meshes that do not have a material. +static Material* make_default_material() { + MaterialDesc desc = (MaterialDesc){0}; + + assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), + .type = UniformVec4, + .value.vec4 = vec4_make(1, 1, 1, 1)}; + + assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_METALLIC_FACTOR), + .type = UniformFloat, + .value.scalar = 0}; + + assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), + .type = UniformFloat, + .value.scalar = 1}; + + assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); + desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ + .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), + .type = UniformVec3, + .value.vec3 = vec3_make(0, 0, 0)}; + + return gfx_make_material(&desc); +} + +/// Compute the bounding box of the vertices pointed to by the accessor. +/// 'dim' is the dimension of the vertices (2D or 3D). +aabb3 compute_aabb(const cgltf_accessor* accessor, int dim) { + aabb3 box = {0}; + if (accessor->has_min && accessor->has_max) { + box = aabb3_make( + vec3_from_array(accessor->min), vec3_from_array(accessor->max)); + } else { + ACCESSOR_FOREACH_VEC(dim, accessor, { + const vec3 p = vec3_make(x, y, z); + if (i == 0) { + box = aabb3_make(p, p); + } else { + box = aabb3_add(box, p); + } + }); + } + return box; +} + +/// Load all meshes from the glTF scene. +static bool load_meshes( + const cgltf_data* data, RenderBackend* render_backend, Buffer** buffers, + Buffer** tangent_buffers, const cgltfTangentBuffer* cgltf_tangent_buffers, + cgltf_size num_tangent_buffers, Material** materials, + ShaderProgram* const shader, size_t primitive_count, Geometry** geometries, + Mesh** meshes, SceneObject** 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 + // a SceneObject. + // + // glTF gfx + // ---- --- + // Mesh SceneObject + // Mesh primitive Mesh / Geometry + // Accessor + buffer view BufferView + // Buffer Buffer + assert(data); + assert(render_backend); + assert(buffers); + assert(materials); + assert(geometries); + assert(meshes); + assert(scene_objects); + if (num_tangent_buffers > 0) { + assert(tangent_buffers); + assert(cgltf_tangent_buffers); + } + + // Points to the next available Mesh and also the next available Geometry. + // There is one (Mesh, Geometry) pair per glTF mesh primitive. + size_t next_mesh = 0; + + for (cgltf_size m = 0; m < data->meshes_count; ++m) { + const cgltf_mesh* mesh = &data->meshes[m]; + + scene_objects[m] = gfx_make_object(); + if (!scene_objects[m]) { + return false; + } + + for (cgltf_size p = 0; p < mesh->primitives_count; ++p) { + assert(next_mesh < primitive_count); + const cgltf_primitive* prim = &mesh->primitives[p]; + const cgltf_material* mat = prim->material; + + MeshPermutation perm = {0}; + if (mat) { + perm.has_normal_map = mat->normal_texture.texture != 0; + perm.has_occlusion_map = mat->occlusion_texture.texture != 0; + perm.has_emissive_map = mat->emissive_texture.texture != 0; + + if (mat->has_pbr_metallic_roughness) { + const cgltf_pbr_metallic_roughness* pbr = + &mat->pbr_metallic_roughness; + perm.has_albedo_map = pbr->base_color_texture.texture != 0; + perm.has_metallic_roughness_map = + pbr->metallic_roughness_texture.texture != 0; + } else { + // TODO: specular/glossiness and other material parameters. + } + } + + GeometryDesc geometry_desc = { + .type = from_gltf_primitive_type(prim->type), + .buffer_usage = BufferStatic}; + + // Vertex indices. + if (prim->indices) { + const cgltf_accessor* accessor = prim->indices; + const cgltf_buffer_view* view = prim->indices->buffer_view; + const cgltf_size buffer_index = view->buffer - data->buffers; + assert(buffer_index < data->buffers_count); + const Buffer* buffer = buffers[buffer_index]; + const cgltf_size component_size = + get_component_size(accessor->component_type); + switch (component_size) { + case 1: { + BufferViewIdx8* indices = &geometry_desc.indices8; + // TODO: discards const qualifier. + indices->buffer = buffer; + indices->offset_bytes = accessor->offset + view->offset; + indices->size_bytes = view->size; + indices->stride_bytes = view->stride; + geometry_desc.num_indices = prim->indices->count; + break; + } + case 2: { + BufferViewIdx16* indices = &geometry_desc.indices16; + indices->buffer = buffer; + indices->offset_bytes = accessor->offset + view->offset; + indices->size_bytes = view->size; + indices->stride_bytes = view->stride; + geometry_desc.num_indices = prim->indices->count; + break; + } + default: + // TODO: Handle 32-bit indices. + assert(false); + break; + } + } + + // Vertex attributes. + for (cgltf_size a = 0; a < prim->attributes_count; ++a) { + const cgltf_attribute* attrib = &prim->attributes[a]; + const cgltf_accessor* accessor = attrib->data; + const cgltf_buffer_view* view = accessor->buffer_view; + const cgltf_size offset = accessor->offset + view->offset; + const cgltf_size buffer_index = view->buffer - data->buffers; + assert(buffer_index < data->buffers_count); + const Buffer* buffer = buffers[buffer_index]; + + BufferView2d* buffer_view_2d = 0; + BufferView3d* buffer_view_3d = 0; + BufferView4d* buffer_view_4d = 0; + BufferViewFloat* buffer_view_float = 0; + BufferViewU8* buffer_view_u8 = 0; + BufferViewU16* buffer_view_u16 = 0; + + switch (attrib->type) { + case cgltf_attribute_type_position: { + switch (accessor->type) { + case cgltf_type_vec2: + assert(geometry_desc.positions3d.buffer == 0); + buffer_view_2d = &geometry_desc.positions2d; + geometry_desc.aabb = compute_aabb(accessor, 2); + break; + case cgltf_type_vec3: + assert(geometry_desc.positions2d.buffer == 0); + buffer_view_3d = &geometry_desc.positions3d; + geometry_desc.aabb = compute_aabb(accessor, 3); + break; + default: + LOGE( + "Unhandled accessor type %d in vertex positions", + accessor->type); + assert(false); + return false; + } + // 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; + break; + case cgltf_attribute_type_tangent: + buffer_view_4d = &geometry_desc.tangents; + perm.has_tangents = true; + break; + case cgltf_attribute_type_texcoord: + buffer_view_2d = &geometry_desc.texcoords; + perm.has_texcoords = true; + break; + case cgltf_attribute_type_color: + // TODO: Add support for color. + break; + case cgltf_attribute_type_joints: + // Joints can be either u8 or u16. + switch (accessor->component_type) { + case cgltf_component_type_r_8u: + buffer_view_u8 = &geometry_desc.joints.u8; + break; + case cgltf_component_type_r_16u: + buffer_view_u16 = &geometry_desc.joints.u16; + break; + default: + assert(false); + return false; + } + perm.has_joints = true; + break; + case cgltf_attribute_type_weights: + // Weights can be either u8, u16, or float. + switch (accessor->component_type) { + case cgltf_component_type_r_8u: + buffer_view_u8 = &geometry_desc.weights.u8; + break; + case cgltf_component_type_r_16u: + buffer_view_u16 = &geometry_desc.weights.u16; + break; + case cgltf_component_type_r_32f: + buffer_view_float = &geometry_desc.weights.floats; + break; + default: + assert(false); + return false; + } + perm.has_weights = true; + break; + case cgltf_attribute_type_invalid: + assert(false); + break; + } + +#define CONFIGURE_BUFFER(buf) \ + if (buf) { \ + buf->buffer = buffer; \ + buf->offset_bytes = offset; \ + buf->size_bytes = view->size; \ + buf->stride_bytes = view->stride; \ + } + CONFIGURE_BUFFER(buffer_view_2d); + CONFIGURE_BUFFER(buffer_view_3d); + CONFIGURE_BUFFER(buffer_view_4d); + CONFIGURE_BUFFER(buffer_view_u8); + CONFIGURE_BUFFER(buffer_view_u16); + CONFIGURE_BUFFER(buffer_view_float); + } // Vertex attributes. + + assert( + (perm.has_joints && perm.has_weights) || + (!perm.has_joints && !perm.has_weights)); + + // If the mesh primitive has no tangents, see if they were computed + // separately. + if (!geometry_desc.tangents.buffer) { + for (cgltf_size t = 0; t < num_tangent_buffers; ++t) { + const cgltfTangentBuffer* cgltf_buffer = &cgltf_tangent_buffers[t]; + + if (cgltf_buffer->primitive == prim) { + BufferView4d* view = &geometry_desc.tangents; + view->buffer = tangent_buffers[t]; + view->offset_bytes = 0; + view->size_bytes = cgltf_buffer->size_bytes; + view->stride_bytes = 0; // Tightly packed. + break; + } + } + } + + // 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. + geometry_desc.num_verts = + (geometry_desc.positions2d.size_bytes / sizeof(vec2)) + + (geometry_desc.positions3d.size_bytes / sizeof(vec3)); + +#define CHECK_COUNT(buffer_view, type, num_components) \ + if (geometry_desc.buffer_view.buffer) { \ + assert( \ + (geometry_desc.buffer_view.size_bytes / \ + (num_components * sizeof(type))) == geometry_desc.num_verts); \ + } + + // Check that the number of vertices is consistent across all vertex + // attributes. + CHECK_COUNT(normals, vec3, 1); + CHECK_COUNT(tangents, vec4, 1); + CHECK_COUNT(texcoords, vec2, 1); + CHECK_COUNT(joints.u8, uint8_t, 4); + CHECK_COUNT(joints.u16, uint16_t, 4); + CHECK_COUNT(weights.u8, uint8_t, 4); + CHECK_COUNT(weights.u16, uint16_t, 4); + CHECK_COUNT(weights.floats, float, 4); + + Material* material = 0; + if (mat) { + const cgltf_size material_index = mat - data->materials; + assert(material_index < data->materials_count); + material = materials[material_index]; + } else { + // Create a default material for meshes that do not specify one. + material = make_default_material(); + } + assert(material); + + geometries[next_mesh] = gfx_make_geometry(render_backend, &geometry_desc); + if (!geometries[next_mesh]) { + return false; + } + + // If the user specifies a custom shader, use that instead. Otherwise + // compile a shader based on the mesh's permutation. + // + // Note that Gfx takes care of caching shaders and shader programs. + // + // Caching materials could be useful, but, 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. + ShaderProgram* mesh_shader = + shader ? shader : make_shader_permutation(render_backend, perm); + assert(mesh_shader); + + meshes[next_mesh] = gfx_make_mesh(&(MeshDesc){ + .geometry = geometries[next_mesh], + .material = material, + .shader = mesh_shader}); + + if (!meshes[next_mesh]) { + return false; + } + + gfx_add_object_mesh(scene_objects[m], meshes[next_mesh]); + + ++next_mesh; + } // glTF mesh primitive / gfx Mesh. + } // glTF mesh / gfx SceneObject. + + return true; +} + +/// Find the joint node with the smallest index across all skeletons. +/// +/// The channels in glTF may target arbitrary nodes in the scene (those nodes +/// are the joints). However, we want to map the "base joint" (the joint/node +/// with the smallest index) to 0 in the AnimaDesc's joint array. We can do this +/// by subtracting the "base node index" from every joint index or channel +/// target. +/// +/// There is an assumption in the animation library that joints are contiguous +/// anyway, so this "base joint index" works provided the joint nodes are also +/// contiguous in the glTF. The glTF does not guarantee this, but I think it's +/// a reasonable assumption that exporters write glTF files in such a way, and +/// Blender does appear to do so. +cgltf_size find_base_joint_index(const cgltf_data* data) { + assert(data); + + cgltf_size base_joint_index = (cgltf_size)-1; + + for (cgltf_size s = 0; s < data->skins_count; ++s) { + const cgltf_skin* skin = &data->skins[s]; + for (cgltf_size j = 0; j < skin->joints_count; ++j) { + // Joint is an index/pointer into the nodes array. + const cgltf_size node_index = skin->joints[j] - data->nodes; + assert(node_index < data->nodes_count); + // Min. + if (node_index < base_joint_index) { + base_joint_index = node_index; + } + } + } + + return base_joint_index; +} + +/// Load all skins (Gfx skeletons) from the glTF scene. +/// Return the total number of joints. +static size_t load_skins( + const cgltf_data* data, Buffer* const* buffers, cgltf_size base_joint_index, + AnimaDesc* anima_desc) { + assert(data); + assert(buffers); + assert(anima_desc); + assert(base_joint_index < data->nodes_count); + + // Determines whether the ith joint in the node hierarchy is a joint node. + // This is then used to determine whether a joint is a root of the joint + // hierarchy. + bool is_joint_node[GFX_MAX_NUM_JOINTS] = {false}; + + size_t num_joints = 0; + + for (cgltf_size s = 0; s < data->skins_count; ++s) { + const cgltf_skin* skin = &data->skins[s]; + const cgltf_accessor* matrices_accessor = skin->inverse_bind_matrices; + assert(matrices_accessor->count == skin->joints_count); + + num_joints += skin->joints_count; + assert(num_joints < GFX_MAX_NUM_JOINTS); + + SkeletonDesc* skeleton_desc = &anima_desc->skeletons[s]; + *skeleton_desc = (SkeletonDesc){.num_joints = skin->joints_count}; + + // for (cgltf_size j = 0; j < skin->joints_count; ++j) { + ACCESSOR_FOREACH_MAT(4, matrices_accessor, { + const mat4 inv_bind_matrix = mat4_from_array(floats); + + // Joint is an index/pointer into the nodes array. + const cgltf_size node_index = skin->joints[i] - data->nodes; + assert(node_index < data->nodes_count); + + const cgltf_size parent_node_index = + skin->joints[i]->parent - data->nodes; + assert(parent_node_index < data->nodes_count); + + // Subtract the base index to pack the joints as tightly as possible in + // the AnimaDesc. + assert(node_index >= base_joint_index); + const cgltf_size joint_index = node_index - base_joint_index; + + assert(parent_node_index >= base_joint_index); + const cgltf_size parent_index = parent_node_index - base_joint_index; + + skeleton_desc->joints[i] = joint_index; + + JointDesc* joint_desc = &anima_desc->joints[joint_index]; + joint_desc->parent = parent_index; + joint_desc->inv_bind_matrix = inv_bind_matrix; + + is_joint_node[joint_index] = true; + }); + + // glTF may specify a "skeleton", which is the root of the skin's + // (skeleton's) node hierarchy. + // if (skin->skeleton) { + // // cgltf_size root_index = skin->skeleton - data->nodes; + // // assert(root_index <= data->nodes_count); + // // root_node = nodes[root_index]; + // assert(false); + //} + } + + // Animation library assumes that joints are contiguous. + for (size_t i = 0; i < num_joints; ++i) { + assert(is_joint_node[i]); + } + + // Insert the root joint. + // This is the root of all skeletons. It is, specifically, the root of all + // joints that do not have a parent; skins (skeletons) in glTF are not + // guaranteed to have a common parent, but are generally a set of disjoint + // trees. + const size_t root_index = num_joints; + assert(root_index < GFX_MAX_NUM_JOINTS); + anima_desc->joints[root_index] = (JointDesc){.parent = INDEX_NONE}; + num_joints++; + + // Make root joints point to the root joint at index N. + // The root joints are the ones that have a non-joint node in the glTF as a + // parent. + for (size_t i = 0; i < root_index; ++i) { + JointDesc* joint = &anima_desc->joints[i]; + if ((joint->parent >= root_index) || !is_joint_node[joint->parent]) { + joint->parent = root_index; + } + } + + return num_joints; +} + +/// Load all animations from the glTF scene. +static void load_animations( + const cgltf_data* data, cgltf_size base_joint_index, + AnimaDesc* anima_desc) { + assert(data); + assert(anima_desc); + assert(base_joint_index < data->nodes_count); + assert(data->animations_count <= GFX_MAX_NUM_ANIMATIONS); + + for (cgltf_size a = 0; a < data->animations_count; ++a) { + const cgltf_animation* animation = &data->animations[a]; + AnimationDesc* animation_desc = &anima_desc->animations[a]; + + *animation_desc = (AnimationDesc){ + .name = sstring_make(animation->name), + .num_channels = animation->channels_count}; + + assert(animation->channels_count <= GFX_MAX_NUM_CHANNELS); + for (cgltf_size c = 0; c < animation->channels_count; ++c) { + const cgltf_animation_channel* channel = &animation->channels[c]; + ChannelDesc* channel_desc = &animation_desc->channels[c]; + const cgltf_animation_sampler* sampler = channel->sampler; + + const size_t target_index = channel->target_node - data->nodes; + assert(target_index < data->nodes_count); + + assert(target_index >= base_joint_index); + const size_t tight_target_index = target_index - base_joint_index; + assert(tight_target_index < anima_desc->num_joints); + + *channel_desc = (ChannelDesc){ + .target = tight_target_index, + .type = from_gltf_animation_path_type(channel->target_path), + .interpolation = from_gltf_interpolation_type(sampler->interpolation), + .num_keyframes = 0}; + + // Read time inputs. + ACCESSOR_FOREACH_VEC(1, sampler->input, { + channel_desc->keyframes[i].time = x; + channel_desc->num_keyframes++; + }); + + // Read transform outputs. + switch (channel->target_path) { + case cgltf_animation_path_type_translation: + ACCESSOR_FOREACH_VEC(3, sampler->output, { + channel_desc->keyframes[i].translation = vec3_make(x, y, z); + }); + break; + case cgltf_animation_path_type_rotation: + ACCESSOR_FOREACH_VEC(4, sampler->output, { + channel_desc->keyframes[i].rotation = qmake(x, y, z, w); + }); + break; + default: + // TODO: Handle other channel transformations. + break; + } + } + } +} + +/// Load all nodes from the glTF scene. +/// +/// This function ignores the many scenes and default scene of the glTF spec +/// and instead just loads all nodes into a single gfx Scene. +static void load_nodes( + const cgltf_data* data, SceneNode* root_node, SceneObject** objects, + SceneCamera** cameras, const Anima* anima, SceneNode** nodes) { + // Note that with glTF 2.0, nodes do not form a DAG / scene graph but a + // disjount union of strict trees: + // + // "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." + // + // This matches the gfx library implementation, where every node can have at + // most one parent. + assert(data); + assert(root_node); + assert(objects); + assert(cameras); + assert(nodes); + + cgltf_size next_camera = 0; + + for (cgltf_size n = 0; n < data->nodes_count; ++n) { + const cgltf_node* node = &data->nodes[n]; + + // Add SceneObject, SceneCamera or Lights. + // TODO: Handle lights once they are implemented in the gfx library. + if (node->mesh) { + const cgltf_size mesh_index = node->mesh - data->meshes; + assert(mesh_index < data->meshes_count); + SceneObject* object = objects[mesh_index]; + gfx_construct_object_node(nodes[n], object); + + if (node->skin) { + assert(anima); + + const cgltf_size skin_index = node->skin - data->skins; + assert(skin_index < data->skins_count); + const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index); + gfx_set_object_skeleton(object, skeleton); + + // TODO: Compute AABBs/OOBBs for the skeleton's joints here. Iterate + // over the mesh's primitives, its vertices, their joint indices, and + // add the vertex to the AABB/OOBB. + } + } else if (node->camera) { + assert(next_camera < data->cameras_count); + + Camera camera; + const cgltf_camera* cam = node->camera; + + // TODO: We could define a function load_cameras() the same way we load + // every mesh and then remove this ad-hoc loading of cameras here, as well + // as remove 'next_camera'. + switch (cam->type) { + case cgltf_camera_type_orthographic: + camera = camera_orthographic( + 0, cam->data.orthographic.xmag, 0, cam->data.orthographic.ymag, + cam->data.orthographic.znear, cam->data.orthographic.zfar); + break; + case cgltf_camera_type_perspective: + camera = camera_perspective( + cam->data.perspective.yfov, cam->data.perspective.aspect_ratio, + cam->data.perspective.znear, cam->data.perspective.zfar); + break; + case cgltf_camera_type_invalid: + break; + } + + gfx_set_camera_camera(cameras[next_camera], &camera); + gfx_construct_camera_node(nodes[n], cameras[next_camera]); + ++next_camera; + } else { + // TODO: implementation for missing node types. + // These nodes currently default to logical nodes. + } + assert(nodes[n]); + + // Set transform. + mat4 transform; + if (node->has_matrix) { + transform = mat4_from_array(node->matrix); + } else { + transform = mat4_id(); + if (node->has_scale) { + const mat4 scale = mat4_scale(vec3_from_array(node->scale)); + transform = mat4_mul(transform, scale); + } + if (node->has_rotation) { + const quat q = quat_from_array(node->rotation); + const mat4 rotate = mat4_from_quat(q); + transform = mat4_mul(transform, rotate); + } + if (node->has_translation) { + const mat4 translate = + mat4_translate(vec3_from_array(node->translation)); + transform = mat4_mul(translate, transform); + } + } + gfx_set_node_transform(nodes[n], &transform); + + // If this is a top-level node in the glTF scene, set its parent to the + // given root node. + if (!node->parent) { + gfx_set_node_parent(nodes[n], root_node); + } else { + const cgltf_size parent_index = node->parent - data->nodes; + assert(parent_index < data->nodes_count); + SceneNode* parent = nodes[parent_index]; + assert(parent); + gfx_set_node_parent(nodes[n], parent); + } + } // SceneNode. +} + +/// Load all scenes from the glTF file. +/// +/// If the scene is loaded from memory, set filepath = null. +/// +/// This function ignores the many scenes and default scene of the glTF spec +/// and instead just loads all scenes into a single Gfx Scene. +static Model* load_scene( + cgltf_data* data, Gfx* gfx, const mstring* filepath, ShaderProgram* shader, + const cgltfTangentBuffer* cgltf_tangent_buffers, + cgltf_size num_tangent_buffers) { + // In a GLTF scene, buffers can be shared among meshes, meshes among nodes, + // etc. Each object is referenced by its index in the relevant array. Here we + // do a button-up construction, first allocating our own graphics objects in + // the same quantities and then re-using the GLTF indices to index these + // arrays. + // + // For simplicity, this function also handles all of the cleanup. Arrays are + // allocated up front, and the helper functions construct their elements. If + // an error is encountered, the helper functions can simply return and this + // function cleans up any intermediate objects that had been created up until + // the point of failure. + // + // Loading animation data: + // - Buffers with animation sampler data need to stay on the CPU, not + // uploaded to the GPU. We could try to implement GPU animation at a later + // stage. + assert(data); + assert(gfx); + assert(filepath); + assert((num_tangent_buffers == 0) || (cgltf_tangent_buffers != 0)); + + bool success = false; + + RenderBackend* render_backend = gfx_get_render_backend(gfx); + const size_t primitive_count = get_total_primitives(data); + + const mstring directory = mstring_dirname(*filepath); + LOGD("Filepath: %s", mstring_cstr(filepath)); + LOGD("Directory: %s", mstring_cstr(&directory)); + + Buffer** tangent_buffers = 0; + Buffer** buffers = 0; + LoadTextureCmd* load_texture_cmds = 0; + Texture** textures = 0; + Material** materials = 0; + Geometry** geometries = 0; + Mesh** meshes = 0; + AnimaDesc* anima_desc = 0; + SceneObject** scene_objects = 0; + SceneCamera** scene_cameras = 0; + SceneNode** scene_nodes = 0; + Anima* anima = 0; + SceneNode* root_node = 0; + Model* model = 0; + + tangent_buffers = calloc(num_tangent_buffers, sizeof(Buffer*)); + buffers = calloc(data->buffers_count, sizeof(Buffer*)); + textures = calloc(data->textures_count, sizeof(Texture*)); + materials = calloc(data->materials_count, sizeof(Material*)); + geometries = calloc(primitive_count, sizeof(Geometry*)); + meshes = calloc(primitive_count, sizeof(Mesh*)); + scene_objects = calloc(data->meshes_count, sizeof(SceneObject*)); + scene_cameras = calloc(data->cameras_count, sizeof(SceneCamera**)); + scene_nodes = calloc(data->nodes_count, sizeof(SceneNode**)); + // A glTF scene does not necessarily have textures. Materials can be given + // as constants, for example. + if (data->textures_count > 0) { + load_texture_cmds = calloc(data->textures_count, sizeof(LoadTextureCmd)); + } + + if (!buffers || !tangent_buffers || + ((data->textures_count > 0) && !load_texture_cmds) || !textures || + !materials || !geometries || !meshes || !scene_objects || + !scene_cameras || !scene_nodes) { + goto cleanup; + } + + if ((num_tangent_buffers > 0) && + !load_tangent_buffers( + cgltf_tangent_buffers, num_tangent_buffers, render_backend, + tangent_buffers)) { + goto cleanup; + } + + if (!load_buffers(data, render_backend, buffers)) { + goto cleanup; + } + + if (data->textures_count > 0) { + load_textures_lazy( + data, render_backend, mstring_cstr(&directory), load_texture_cmds); + } + + if (!load_materials(data, gfx, load_texture_cmds, textures, materials)) { + goto cleanup; + } + + if (!load_meshes( + data, render_backend, buffers, tangent_buffers, cgltf_tangent_buffers, + num_tangent_buffers, materials, shader, primitive_count, geometries, + meshes, scene_objects)) { + goto cleanup; + } + + // Skins refer to nodes, and nodes may refer to skins. To break this circular + // dependency, glTF defines skins in terms of node indices. We could do the + // same if Gfx allowed allocating nodes contiguously in memory. For now, + // create the nodes up front and use the indices of the array to map to the + // node_idx. + for (cgltf_size i = 0; i < data->nodes_count; ++i) { + scene_nodes[i] = gfx_make_node(); + } + + // Create the scene's root node. + // This is an anima node if the scene has skins; otherwise it is a logical + // node. + root_node = gfx_make_node(); + if (data->skins_count > 0) { + anima_desc = calloc(1, sizeof(AnimaDesc)); + if (!anima_desc) { + goto cleanup; + } + + const cgltf_size base = find_base_joint_index(data); + + anima_desc->num_skeletons = data->skins_count; + anima_desc->num_animations = data->animations_count; + anima_desc->num_joints = load_skins(data, buffers, base, anima_desc); + load_animations(data, base, anima_desc); + + anima = gfx_make_anima(anima_desc); + gfx_construct_anima_node(root_node, anima); + } + + // The root node becomes the root of all scene nodes. + load_nodes(data, root_node, scene_objects, scene_cameras, anima, scene_nodes); + + // TODO: Clean up scene nodes that correspond to joints in the glTF. + + model = gfx_make_model(root_node); + + success = true; + +cleanup: + // The arrays of resources are no longer needed. The resources themselves are + // destroyed only if this function fails. + if (tangent_buffers) { + if (!success) { + for (cgltf_size i = 0; i < num_tangent_buffers; ++i) { + if (tangent_buffers[i]) { + gfx_destroy_buffer(render_backend, &tangent_buffers[i]); + } + } + } + free(tangent_buffers); + } + if (buffers) { + if (!success) { + for (cgltf_size i = 0; i < data->buffers_count; ++i) { + if (buffers[i]) { + gfx_destroy_buffer(render_backend, &buffers[i]); + } + } + } + free(buffers); + } + if (load_texture_cmds) { + free(load_texture_cmds); + } + if (textures) { + if (!success) { + for (cgltf_size i = 0; i < data->textures_count; ++i) { + if (textures[i]) { + gfx_destroy_texture(render_backend, &textures[i]); + } + } + } + free(textures); + } + if (materials) { + if (!success) { + for (cgltf_size i = 0; i < data->materials_count; ++i) { + if (materials[i]) { + gfx_destroy_material(&materials[i]); + } + } + } + free(materials); + } + if (geometries) { + if (!success) { + for (size_t i = 0; i < primitive_count; ++i) { + if (geometries[i]) { + gfx_destroy_geometry(render_backend, &geometries[i]); + } + } + } + free(geometries); + } + if (meshes) { + if (!success) { + for (size_t i = 0; i < primitive_count; ++i) { + if (meshes[i]) { + gfx_destroy_mesh(&meshes[i]); + } + } + } + free(meshes); + } + if (anima_desc) { + free(anima_desc); + } + if (scene_objects) { + if (!success) { + for (cgltf_size i = 0; i < data->meshes_count; ++i) { + if (scene_objects[i]) { + gfx_destroy_object(&scene_objects[i]); + } + } + } + free(scene_objects); + } + if (scene_cameras) { + if (!success) { + for (cgltf_size i = 0; i < data->cameras_count; ++i) { + if (scene_cameras[i]) { + gfx_destroy_camera(&scene_cameras[i]); + } + } + } + free(scene_cameras); + } + if (scene_nodes) { + if (!success) { + for (cgltf_size i = 0; i < data->nodes_count; ++i) { + if (scene_nodes[i]) { + gfx_destroy_node(&scene_nodes[i]); + } + } + } + free(scene_nodes); + } + if (!success) { + if (root_node) { + gfx_destroy_node(&root_node); // Node owns the anima. + } else if (anima) { + gfx_destroy_anima(&anima); + } + } + return model; +} + +Model* gfx_model_load(Gfx* gfx, const LoadModelCmd* cmd) { + assert(gfx); + assert(cmd); + + Model* model = 0; + + cgltf_options options = {0}; + cgltf_data* data = NULL; + cgltfTangentBuffer* tangent_buffers = 0; + + cgltf_result result; + switch (cmd->origin) { + case AssetFromFile: + result = cgltf_parse_file(&options, mstring_cstr(&cmd->filepath), &data); + break; + case AssetFromMemory: + result = cgltf_parse(&options, cmd->data, cmd->size_bytes, &data); + break; + } + if (result != cgltf_result_success) { + goto cleanup; + } + + if (cmd->origin == AssetFromFile) { + // Must call cgltf_load_buffers() to load buffer data. + result = cgltf_load_buffers(&options, data, mstring_cstr(&cmd->filepath)); + if (result != cgltf_result_success) { + goto cleanup; + } + } + + // Compute tangents for normal-mapped models that are missing them. + cgltf_size num_tangent_buffers = 0; + cgltf_compute_tangents( + &options, data, &tangent_buffers, &num_tangent_buffers); + + model = load_scene( + data, gfx, &cmd->filepath, cmd->shader, tangent_buffers, + num_tangent_buffers); + +cleanup: + if (data) { + cgltf_free(data); + } + if (tangent_buffers) { + free(tangent_buffers); + } + return model; +} diff --git a/gfx/src/asset/model.h b/gfx/src/asset/model.h new file mode 100644 index 0000000..d6399b1 --- /dev/null +++ b/gfx/src/asset/model.h @@ -0,0 +1,12 @@ +/// Load scene files. +#pragma once + +#include + +typedef struct Gfx Gfx; +typedef struct Model Model; + +/// Load a model. +/// +/// Currently only supports the GLTF format. +Model* gfx_model_load(Gfx*, const LoadModelCmd*); diff --git a/gfx/src/asset/scene.c b/gfx/src/asset/scene.c deleted file mode 100644 index 290ade9..0000000 --- a/gfx/src/asset/scene.c +++ /dev/null @@ -1,1792 +0,0 @@ -/// Loads scenes from memory and files. -/// -/// Only the GLTF scene format is current supported. -/// -/// ---------------------------------------------------------------------------- -/// glTF File Format Documentation -/// ---------------------------------------------------------------------------- -/// -/// cgltf: -/// https://github.com/jkuhlmann/cgltf -/// -/// gltf overview: -/// https://raw.githubusercontent.com/KhronosGroup/glTF/master/specification/2.0/figures/gltfOverview-2.0.0b.png -/// -/// gltf spec: -/// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md -/// -/// Sample models: -/// https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0 -/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Sponza/glTF/Sponza.gltf -/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AlphaBlendModeTest/glTF/AlphaBlendModeTest.gltf -/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Buggy/glTF/Buggy.gltf -/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AntiqueCamera/glTF/AntiqueCamera.gltf -/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf -/// -/// ---------------------------------------------------------------------------- -/// Implementation Notes -/// ---------------------------------------------------------------------------- -/// -/// # glTF and the gfx library -/// -/// glTF has concepts that are similar to those in the gfx library, but there -/// isn't an exact 1-1 mapping. Concepts map as follows: -/// -/// glTF gfx -/// ---- --- -/// buffer Buffer -/// accessor + buffer view BufferView -/// mesh primitive (geom + mat) Mesh (also geom + mat) -/// mesh SceneObject -/// node SceneNode -/// -/// glTF buffers map 1-1 with gfx Buffers. glTF scenes make heavy re-use of -/// buffers across views/accessors/meshes, so it is important to make that same -/// re-use in the gfx library to use the data effectively and without -/// duplication. The Sponza scene, for example, has all of its data in one giant -/// buffer. -/// -/// glTF accessors and buffer views are combined and mapped to gfx BufferViews. -/// The glTF buffer view's offset/length/stride are combined with the accessor's -/// offset, and together with the remaining information of both data structures -/// baked into a BufferView. Internally, this information is fed into -/// glVertexAttribPointer() calls, wrapped in a VAO (index view/accessor -/// information is fed into glDrawElements()). This baking should not hurt -/// re-use, at least in the OpenGL world. -/// -/// A glTF mesh primitive contains a piece of geometry and a material. This maps -/// directly to a gfx Mesh. -/// -/// A glTF mesh is a list of mesh primitives. This maps nicely to a gfx -/// SceneObject, with the only inconvenience that terminology gets a little -/// confusing. -/// -/// Finally, glTF nodes map directly to gfx SceneNodes. Both enforce a strict -/// tree hierarchy; DAGs are not supported. -/// -/// # Materials -/// -/// glTF uses the metallic-roughness material model. However, an extension -/// allows a scene to use the specular-glossiness model as well and cgltf -/// supports it: -/// -/// https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/ -/// -/// From the docs, the specular-glossiness model can represent more materials -/// than the metallic-roughness model, but it is also more computationally -/// expensive. Furthermore, a material in glTF can specify parameters for both -/// models, leaving it up to the implementation to decide which one to use. -/// In our case, we use the specular-glosiness model if parameters for it are -/// provided, otherwise we use the metallic-roughness model. - -#include "asset/scene.h" - -#include "asset/texture.h" -#include "gfx/gfx.h" -#include "gfx/render_backend.h" -#include "gfx/scene/animation.h" -#include "gfx/scene/camera.h" -#include "gfx/scene/material.h" -#include "gfx/scene/mesh.h" -#include "gfx/scene/node.h" -#include "gfx/scene/object.h" -#include "gfx/scene/scene.h" -#include "gfx/sizes.h" -#include "gfx/util/shader.h" - -#include "scene/model_impl.h" - -#include "cstring.h" -#include "error.h" -#include "log/log.h" -#include "math/camera.h" -#include "math/defs.h" -#include "math/mat4.h" -#include "math/quat.h" -#include "math/vec2.h" -#include "math/vec3.h" - -#include "cgltf_tangents.h" -#define CGLTF_IMPLEMENTATION -#include "cgltf.h" - -#include -#include -#include - -// Taken from the GL header file. -#define GL_NEAREST 0x2600 -#define GL_LINEAR 0x2601 -#define GL_NEAREST_MIPMAP_NEAREST 0x2700 -#define GL_LINEAR_MIPMAP_NEAREST 0x2701 -#define GL_NEAREST_MIPMAP_LINEAR 0x2702 -#define GL_LINEAR_MIPMAP_LINEAR 0x2703 - -// Uniforms names. Must match the names in shaders. -#define UNIFORM_BASE_COLOR_FACTOR "BaseColorFactor" -#define UNIFORM_METALLIC_FACTOR "MetallicFactor" -#define UNIFORM_ROUGHNESS_FACTOR "RoughnessFactor" -#define UNIFORM_EMISSIVE_FACTOR "EmissiveFactor" -#define UNIFORM_BASE_COLOR_TEXTURE "BaseColorTexture" -#define UNIFORM_METALLIC_ROUGHNESS_TEXTURE "MetallicRoughnessTexture" -#define UNIFORM_EMISSIVE_TEXTURE "EmissiveTexture" -#define UNIFORM_AMBIENT_OCCLUSION_TEXTURE "AmbientOcclusionTexture" -#define UNIFORM_NORMAL_MAP "NormalMap" - -// Shader compiler defines. Must match the names in shaders. -#define DEFINE_HAS_TEXCOORDS "HAS_TEXCOORDS" -#define DEFINE_HAS_NORMALS "HAS_NORMALS" -#define DEFINE_HAS_TANGENTS "HAS_TANGENTS" -#define DEFINE_HAS_ALBEDO_MAP "HAS_ALBEDO_MAP" -#define DEFINE_HAS_METALLIC_ROUGHNESS_MAP "HAS_METALLIC_ROUGHNESS_MAP" -#define DEFINE_HAS_NORMAL_MAP "HAS_NORMAL_MAP" -#define DEFINE_HAS_OCCLUSION_MAP "HAS_OCCLUSION_MAP" -#define DEFINE_HAS_EMISSIVE_MAP "HAS_EMISSIVE_MAP" -#define DEFINE_HAS_JOINTS "HAS_JOINTS" -#define DEFINE_MAX_JOINTS "MAX_JOINTS" - -typedef enum TextureType { - BaseColorTexture, - MetallicRoughnessTexture, - EmissiveTexture, - AmbientOcclusionTexture, - NormalMap, -} TextureType; - -/// Describes the properties of a mesh. -/// This is used to create shader permutations. -typedef struct MeshPermutation { - union { - struct { - // Vertex attributes. - bool has_texcoords : 1; - bool has_normals : 1; - bool has_tangents : 1; - bool has_joints : 1; - bool has_weights : 1; - // Textures. - bool has_albedo_map : 1; - bool has_metallic_roughness_map : 1; - bool has_normal_map : 1; - bool has_occlusion_map : 1; - bool has_emissive_map : 1; - }; - int32_t all; - }; -} MeshPermutation; - -/// Build shader compiler defines from a mesh permutation. -static size_t make_defines( - MeshPermutation perm, ShaderCompilerDefine* defines) { - static const char* str_true = "1"; - size_t next = 0; - -#define check(field, define) \ - if (perm.field) { \ - defines[next].name = sstring_make(define); \ - defines[next].value = sstring_make(str_true); \ - next++; \ - } - check(has_texcoords, DEFINE_HAS_TEXCOORDS); - check(has_normals, DEFINE_HAS_NORMALS); - check(has_tangents, DEFINE_HAS_TANGENTS); - check(has_joints, DEFINE_HAS_JOINTS); - check(has_albedo_map, DEFINE_HAS_ALBEDO_MAP); - check(has_metallic_roughness_map, DEFINE_HAS_METALLIC_ROUGHNESS_MAP); - check(has_normal_map, DEFINE_HAS_NORMAL_MAP); - check(has_occlusion_map, DEFINE_HAS_OCCLUSION_MAP); - check(has_emissive_map, DEFINE_HAS_EMISSIVE_MAP); - - if (perm.has_joints) { - defines[next].name = sstring_make(DEFINE_MAX_JOINTS); - defines[next].value = sstring_itoa(GFX_MAX_NUM_JOINTS); - next++; - } - - return next; -} - -/// Compile a shader permutation. -static ShaderProgram* make_shader_permutation( - RenderBackend* render_backend, MeshPermutation perm) { - LOGD( - "Compiling Cook-Torrance shader permutation: texcoords: %d, normals: " - "%d, tangents: %d, joints: %d, weights: %d, albedo map: %d, " - "metallic-roughness map: " - "%d, normal " - "map: %d, AO map: %d, emissive map: %d", - perm.has_texcoords, perm.has_normals, perm.has_tangents, perm.has_joints, - perm.has_weights, perm.has_albedo_map, perm.has_metallic_roughness_map, - perm.has_normal_map, perm.has_occlusion_map, perm.has_emissive_map); - - ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES]; - const size_t num_defines = make_defines(perm, defines); - return gfx_make_cook_torrance_shader_perm( - render_backend, defines, num_defines); -} - -/// Map a texture type to the name of the shader uniform used to access the -/// texture. -static const char* get_texture_uniform_name(TextureType type) { - switch (type) { - case BaseColorTexture: - return UNIFORM_BASE_COLOR_TEXTURE; - case MetallicRoughnessTexture: - return UNIFORM_METALLIC_ROUGHNESS_TEXTURE; - case EmissiveTexture: - return UNIFORM_EMISSIVE_TEXTURE; - case AmbientOcclusionTexture: - return UNIFORM_AMBIENT_OCCLUSION_TEXTURE; - case NormalMap: - return UNIFORM_NORMAL_MAP; - } - assert(false); - return 0; -} - -/// Map a glTF primitive type to a gfx primitive type. -static PrimitiveType from_gltf_primitive_type(cgltf_primitive_type type) { - switch (type) { - case cgltf_primitive_type_triangles: - return Triangles; - case cgltf_primitive_type_triangle_fan: - return TriangleFan; - case cgltf_primitive_type_triangle_strip: - return TriangleStrip; - // Not yet implemented. - case cgltf_primitive_type_lines: - case cgltf_primitive_type_line_loop: - case cgltf_primitive_type_line_strip: - case cgltf_primitive_type_points: - break; - } - LOGE("Unsupported primitive type: %d", type); - assert(false); - return 0; -} - -/// Map a glTF animation path type to its Gfx equivalent. -static ChannelType from_gltf_animation_path_type( - cgltf_animation_path_type type) { - switch (type) { - case cgltf_animation_path_type_translation: - return TranslationChannel; - case cgltf_animation_path_type_rotation: - return RotationChannel; - case cgltf_animation_path_type_scale: - return ScaleChannel; - case cgltf_animation_path_type_weights: - return WeightsChannel; - case cgltf_animation_path_type_invalid: - assert(false); - break; - } - assert(false); - return 0; -} - -/// Map a glTF interpolation to its Gfx equivalent. -static AnimationInterpolation from_gltf_interpolation_type( - cgltf_interpolation_type type) { - switch (type) { - case cgltf_interpolation_type_linear: - return LinearInterpolation; - case cgltf_interpolation_type_step: - return StepInterpolation; - case cgltf_interpolation_type_cubic_spline: - return CubicSplineInterpolation; - } - assert(false); - return 0; -} - -/// Return the component's size in bytes. -static cgltf_size get_component_size(cgltf_component_type type) { - switch (type) { - case cgltf_component_type_r_8: - return 1; - case cgltf_component_type_r_8u: - return 1; - case cgltf_component_type_r_16: - return 2; - case cgltf_component_type_r_16u: - return 2; - case cgltf_component_type_r_32u: - return 4; - case cgltf_component_type_r_32f: - return 4; - case cgltf_component_type_invalid: - assert(false); - break; - } - assert(false); - return 0; -} - -/// Read a float from the given data pointer and accessor. -/// -/// This function uses the normalization equations from the spec. See the -/// animation section: -/// -/// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#animations -static float read_float(const void* data, const cgltf_accessor* accessor) { - assert(data); - assert(accessor); - - switch (accessor->component_type) { - case cgltf_component_type_r_8: { - assert(accessor->normalized); - const int8_t c = *((int8_t*)data); - return max((float)c / 127.0, -1.0); - } - case cgltf_component_type_r_8u: { - assert(accessor->normalized); - const uint8_t c = *((uint8_t*)data); - return (float)c / 255.0; - } - case cgltf_component_type_r_16: { - assert(accessor->normalized); - const int16_t c = *((int16_t*)data); - return max((float)c / 32767.0, -1.0); - } - case cgltf_component_type_r_16u: { - assert(accessor->normalized); - const uint16_t c = *((uint16_t*)data); - return (float)c / 65535.0; - } - case cgltf_component_type_r_32u: { - assert(accessor->normalized); - const uint32_t c = *((uint32_t*)data); - return (float)c / 4294967295.0; - } - case cgltf_component_type_r_32f: { - const float c = *((float*)data); - return c; - } - case cgltf_component_type_invalid: - assert(false); - break; - } - assert(false); - return 0; -} - -/// Iterate over the vectors in an accessor. -#define ACCESSOR_FOREACH_VEC(dimensions, accessor, body) \ - { \ - assert((1 <= dimensions) && (dimensions <= 4)); \ - assert( \ - ((dimensions == 1) && (accessor->type == cgltf_type_scalar)) || \ - ((dimensions == 2) && (accessor->type == cgltf_type_vec2)) || \ - ((dimensions == 3) && (accessor->type == cgltf_type_vec3)) || \ - ((dimensions == 4) && (accessor->type == cgltf_type_vec4))); \ - const cgltf_buffer_view* view = accessor->buffer_view; \ - const cgltf_buffer* buffer = view->buffer; \ - const cgltf_size offset = accessor->offset + view->offset; \ - const uint8_t* bytes = (const uint8_t*)buffer->data + offset; \ - /* Component size in bytes. */ \ - const cgltf_size comp_size = get_component_size(accessor->component_type); \ - /* Element size in bytes. */ \ - const cgltf_size elem_size = dimensions * comp_size; \ - /* Stride in bytes. If the view stride is 0, then the elements are tightly \ - * packed. */ \ - const cgltf_size stride = view->stride != 0 ? view->stride : elem_size; \ - /* There isn't an accessor stride in the spec, but cgltf still specifies \ - * one. */ \ - assert(accessor->stride == elem_size); \ - /* Accessor data must fit inside the buffer. */ \ - assert( \ - (offset + (accessor->count * elem_size) + \ - ((accessor->count - 1) * view->stride)) <= buffer->size); \ - /* Accessor data must fit inside the view. */ \ - assert(accessor->count * accessor->stride <= view->size); \ - cgltf_float x = 0, y = 0, z = 0, w = 0; \ - /* Silence unused variable warnings. */ \ - (void)y; \ - (void)z; \ - (void)w; \ - /* The {component type} X {dimensions} combinations are a pain to handle. \ - For floats, we switch on type first and then lay out a loop for each \ - dimension to get a tight loop with a possibly inlined body. For other \ - types, we take the performance hit and perform checks and conversions \ - inside the loop for simplicity. */ \ - if (accessor->component_type == cgltf_component_type_r_32f) { \ - switch (dimensions) { \ - case 1: \ - assert(accessor->type == cgltf_type_scalar); \ - for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ - const cgltf_float* floats = (const cgltf_float*)bytes; \ - x = *floats; \ - body; \ - } \ - break; \ - case 2: \ - assert(accessor->type == cgltf_type_vec2); \ - for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ - const cgltf_float* floats = (const cgltf_float*)bytes; \ - x = *floats++; \ - y = *floats; \ - body; \ - } \ - break; \ - case 3: \ - assert(accessor->type == cgltf_type_vec3); \ - for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ - const cgltf_float* floats = (const cgltf_float*)bytes; \ - x = *floats++; \ - y = *floats++; \ - z = *floats; \ - body; \ - } \ - break; \ - case 4: \ - assert(accessor->type == cgltf_type_vec4); \ - for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ - const cgltf_float* floats = (const cgltf_float*)bytes; \ - x = *floats++; \ - y = *floats++; \ - z = *floats++; \ - w = *floats; \ - body; \ - } \ - break; \ - } \ - } else { \ - for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ - const uint8_t* component = bytes; \ - \ - x = read_float(component, accessor); \ - component += comp_size; \ - if (dimensions > 1) { \ - y = read_float(component, accessor); \ - component += comp_size; \ - } \ - if (dimensions > 2) { \ - z = read_float(component, accessor); \ - component += comp_size; \ - } \ - if (dimensions > 3) { \ - w = read_float(component, accessor); \ - component += comp_size; \ - } \ - body; \ - } \ - } \ - } - -/// Iterate over the matrices in an accessor. -#define ACCESSOR_FOREACH_MAT(dimensions, accessor, body) \ - { \ - assert((2 <= dimensions) && (dimensions <= 4)); \ - assert(!(dimensions == 2) || (accessor->type == cgltf_type_mat2)); \ - assert(!(dimensions == 3) || (accessor->type == cgltf_type_mat3)); \ - assert(!(dimensions == 4) || (accessor->type == cgltf_type_mat4)); \ - const cgltf_buffer_view* view = accessor->buffer_view; \ - const cgltf_buffer* buffer = view->buffer; \ - const cgltf_size offset = accessor->offset + view->offset; \ - const cgltf_size comp_size = get_component_size(accessor->component_type); \ - const uint8_t* bytes = (const uint8_t*)buffer->data + offset; \ - assert( \ - (offset + accessor->count * dimensions * comp_size) < buffer->size); \ - /* From the spec: */ \ - /* "Buffer views with other types of data MUST NOT not define */ \ - /* byteStride (unless such layout is explicitly enabled by an */ \ - /* extension)."*/ \ - assert(view->stride == 0); \ - assert(accessor->stride == dimensions * dimensions * comp_size); \ - assert(accessor->component_type == cgltf_component_type_r_32f); \ - const cgltf_float* floats = (const cgltf_float*)bytes; \ - switch (dimensions) { \ - case 2: \ - assert(accessor->type == cgltf_type_mat2); \ - for (cgltf_size i = 0; i < accessor->count; ++i) { \ - body; \ - floats += 4; \ - } \ - break; \ - case 3: \ - assert(accessor->type == cgltf_type_mat3); \ - for (cgltf_size i = 0; i < accessor->count; ++i) { \ - body; \ - floats += 9; \ - } \ - break; \ - case 4: \ - assert(accessor->type == cgltf_type_mat4); \ - for (cgltf_size i = 0; i < accessor->count; ++i) { \ - body; \ - floats += 16; \ - } \ - break; \ - } \ - } - -/// Return the total number of primitives in the scene. Each mesh may contain -/// multiple primitives. -/// -/// Note that this function scans all of the scenes in the glTF data. -static size_t get_total_primitives(const cgltf_data* data) { - size_t total = 0; - for (cgltf_size i = 0; i < data->meshes_count; ++i) { - total += data->meshes[i].primitives_count; - } - return total; -} - -/// Load all buffers from the glTF scene. -/// -/// If buffer data is loaded from memory, set filepath = null. -/// -/// Return an array of Buffers such that the index of each glTF buffer in the -/// original array matches the same Buffer in the resulting array. -/// -/// TODO: There is no need to load the inverse bind matrices buffer into the -/// GPU. Might need to lazily load buffers. -static bool load_buffers( - const cgltf_data* data, RenderBackend* render_backend, Buffer** buffers) { - assert(data); - assert(render_backend); - assert(buffers); - - for (cgltf_size i = 0; i < data->buffers_count; ++i) { - const cgltf_buffer* buffer = &data->buffers[i]; - assert(buffer->data); - buffers[i] = gfx_make_buffer( - render_backend, &(BufferDesc){ - .usage = BufferStatic, - .type = BufferUntyped, - .data.data = buffer->data, - .data.count = buffer->size}); - if (!buffers[i]) { - return false; - } - } - - return true; -} - -/// Load tangent buffers. -static bool load_tangent_buffers( - const cgltfTangentBuffer* cgltf_tangent_buffers, - cgltf_size num_tangent_buffers, RenderBackend* render_backend, - Buffer** tangent_buffers) { - assert(cgltf_tangent_buffers); - assert(render_backend); - assert(tangent_buffers); - - for (cgltf_size i = 0; i < num_tangent_buffers; ++i) { - const cgltfTangentBuffer* buffer = &cgltf_tangent_buffers[i]; - assert(buffer->data); - tangent_buffers[i] = gfx_make_buffer( - render_backend, &(BufferDesc){ - .usage = BufferStatic, - .type = BufferUntyped, - .data.data = buffer->data, - .data.count = buffer->size_bytes}); - if (!tangent_buffers[i]) { - return false; - } - } - - return true; -} - -/// Lazily load all textures from the glTF scene. -/// -/// 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. -/// -/// Return an array of LoadTextureCmds such that the index of each cmd matches -/// the index of each glTF texture in the scene. -static void load_textures_lazy( - const cgltf_data* data, RenderBackend* render_backend, - const char* directory, LoadTextureCmd* load_texture_cmds) { - assert(data); - assert(render_backend); - assert(load_texture_cmds); - - for (cgltf_size i = 0; i < data->textures_count; ++i) { - const cgltf_texture* texture = &data->textures[i]; - const cgltf_image* image = texture->image; - const cgltf_sampler* sampler = texture->sampler; - - // glTF models might not specify a sampler. In such case, the client can - // pick its own defaults. - // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#samplers - bool mipmaps = true; - TextureFiltering filtering = LinearFiltering; - TextureWrapping wrap = Repeat; - - if (sampler) { - // The gfx library does not distinguish between sampling the texture and - // combining the mipmap levels. - const cgltf_int filter = - sampler->min_filter == 0 ? sampler->mag_filter : sampler->min_filter; - - switch (filter) { - case GL_NEAREST_MIPMAP_NEAREST: - mipmaps = true; - filtering = NearestFiltering; - break; - case GL_NEAREST_MIPMAP_LINEAR: - case GL_LINEAR_MIPMAP_NEAREST: - case GL_LINEAR_MIPMAP_LINEAR: - mipmaps = true; - filtering = LinearFiltering; - break; - case GL_NEAREST: - filtering = NearestFiltering; - break; - case GL_LINEAR: - filtering = LinearFiltering; - break; - default: - break; - } - } - - // Currently only supporting loading textures from files. - assert(image->uri); - assert(directory); - mstring fullpath = - mstring_concat_path(mstring_make(directory), mstring_make(image->uri)); - - load_texture_cmds[i] = (LoadTextureCmd){ - .origin = AssetFromFile, - .type = LoadTexture, - .colour_space = sRGB, - .filtering = filtering, - .wrap = wrap, - .mipmaps = mipmaps, - .data.texture.filepath = fullpath}; - } -} - -/// 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_and_uniform( - const cgltf_data* data, Gfx* gfx, const cgltf_texture_view* texture_view, - TextureType texture_type, Texture** textures, - LoadTextureCmd* load_texture_cmds, int* next_uniform, MaterialDesc* desc) { - assert(data); - assert(gfx); - assert(texture_view); - assert(textures); - assert(next_uniform); - assert(desc); - assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); - - const size_t texture_index = texture_view->texture - data->textures; - assert(texture_index < data->textures_count); - - // 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]) { - 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_cstr(&cmd->data.texture.filepath), cmd->mipmaps, - cmd->filtering); - - textures[texture_index] = gfx_load_texture(gfx, cmd); - if (!textures[texture_index]) { - prepend_error( - "Failed to load texture: %s", - mstring_cstr(&cmd->data.texture.filepath)); - return false; - } - } - - assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); - desc->uniforms[(*next_uniform)++] = (ShaderUniform){ - .name = sstring_make(get_texture_uniform_name(texture_type)), - .type = UniformTexture, - .value.texture = textures[texture_index]}; - - return true; -} - -/// Load all materials from the glTF scene. -/// -/// 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 bool load_materials( - const cgltf_data* data, Gfx* gfx, LoadTextureCmd* load_texture_cmds, - Texture** textures, Material** materials) { - assert(data); - assert(gfx); - assert(materials); - if (data->textures_count > 0) { - assert(load_texture_cmds); - assert(textures); - } - - for (cgltf_size i = 0; i < data->materials_count; ++i) { - const cgltf_material* mat = &data->materials[i]; - - int next_uniform = 0; - MaterialDesc desc = {0}; - - // TODO: specular/glossiness and other material parameters. - if (mat->has_pbr_metallic_roughness) { - const cgltf_pbr_metallic_roughness* pbr = &mat->pbr_metallic_roughness; - - 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_and_uniform( - data, gfx, &pbr->base_color_texture, BaseColorTexture, textures, - load_texture_cmds, &next_uniform, &desc)) { - return false; - } - } - - if (pbr->metallic_roughness_texture.texture) { - if (!load_texture_and_uniform( - data, gfx, &pbr->metallic_roughness_texture, - MetallicRoughnessTexture, textures, load_texture_cmds, - &next_uniform, &desc)) { - return false; - } - } - } - - if (mat->emissive_texture.texture) { - if (!load_texture_and_uniform( - data, gfx, &mat->emissive_texture, EmissiveTexture, textures, - load_texture_cmds, &next_uniform, &desc)) { - return false; - } - } - - if (mat->occlusion_texture.texture) { - if (!load_texture_and_uniform( - data, gfx, &mat->occlusion_texture, AmbientOcclusionTexture, - textures, load_texture_cmds, &next_uniform, &desc)) { - return false; - } - } - - if (mat->normal_texture.texture) { - if (!load_texture_and_uniform( - data, gfx, &mat->normal_texture, NormalMap, textures, - load_texture_cmds, &next_uniform, &desc)) { - return false; - } - } - - assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL); - desc.num_uniforms = next_uniform; - - materials[i] = gfx_make_material(&desc); - if (!materials[i]) { - return false; - } - } - - return true; -} - -/// Create a default material for meshes that do not have a material. -static Material* make_default_material() { - MaterialDesc desc = (MaterialDesc){0}; - - assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); - desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ - .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR), - .type = UniformVec4, - .value.vec4 = vec4_make(1, 1, 1, 1)}; - - assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); - desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ - .name = sstring_make(UNIFORM_METALLIC_FACTOR), - .type = UniformFloat, - .value.scalar = 0}; - - assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); - desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ - .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR), - .type = UniformFloat, - .value.scalar = 1}; - - assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL); - desc.uniforms[desc.num_uniforms++] = (ShaderUniform){ - .name = sstring_make(UNIFORM_EMISSIVE_FACTOR), - .type = UniformVec3, - .value.vec3 = vec3_make(0, 0, 0)}; - - return gfx_make_material(&desc); -} - -/// Compute the bounding box of the vertices pointed to by the accessor. -/// 'dim' is the dimension of the vertices (2D or 3D). -aabb3 compute_aabb(const cgltf_accessor* accessor, int dim) { - aabb3 box = {0}; - if (accessor->has_min && accessor->has_max) { - box = aabb3_make( - vec3_from_array(accessor->min), vec3_from_array(accessor->max)); - } else { - ACCESSOR_FOREACH_VEC(dim, accessor, { - const vec3 p = vec3_make(x, y, z); - if (i == 0) { - box = aabb3_make(p, p); - } else { - box = aabb3_add(box, p); - } - }); - } - return box; -} - -/// Load all meshes from the glTF scene. -static bool load_meshes( - const cgltf_data* data, RenderBackend* render_backend, Buffer** buffers, - Buffer** tangent_buffers, const cgltfTangentBuffer* cgltf_tangent_buffers, - cgltf_size num_tangent_buffers, Material** materials, - ShaderProgram* const shader, size_t primitive_count, Geometry** geometries, - Mesh** meshes, SceneObject** 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 - // a SceneObject. - // - // glTF gfx - // ---- --- - // Mesh SceneObject - // Mesh primitive Mesh / Geometry - // Accessor + buffer view BufferView - // Buffer Buffer - assert(data); - assert(render_backend); - assert(buffers); - assert(materials); - assert(geometries); - assert(meshes); - assert(scene_objects); - if (num_tangent_buffers > 0) { - assert(tangent_buffers); - assert(cgltf_tangent_buffers); - } - - // Points to the next available Mesh and also the next available Geometry. - // There is one (Mesh, Geometry) pair per glTF mesh primitive. - size_t next_mesh = 0; - - for (cgltf_size m = 0; m < data->meshes_count; ++m) { - const cgltf_mesh* mesh = &data->meshes[m]; - - scene_objects[m] = gfx_make_object(); - if (!scene_objects[m]) { - return false; - } - - for (cgltf_size p = 0; p < mesh->primitives_count; ++p) { - assert(next_mesh < primitive_count); - const cgltf_primitive* prim = &mesh->primitives[p]; - const cgltf_material* mat = prim->material; - - MeshPermutation perm = {0}; - if (mat) { - perm.has_normal_map = mat->normal_texture.texture != 0; - perm.has_occlusion_map = mat->occlusion_texture.texture != 0; - perm.has_emissive_map = mat->emissive_texture.texture != 0; - - if (mat->has_pbr_metallic_roughness) { - const cgltf_pbr_metallic_roughness* pbr = - &mat->pbr_metallic_roughness; - perm.has_albedo_map = pbr->base_color_texture.texture != 0; - perm.has_metallic_roughness_map = - pbr->metallic_roughness_texture.texture != 0; - } else { - // TODO: specular/glossiness and other material parameters. - } - } - - GeometryDesc geometry_desc = { - .type = from_gltf_primitive_type(prim->type), - .buffer_usage = BufferStatic}; - - // Vertex indices. - if (prim->indices) { - const cgltf_accessor* accessor = prim->indices; - const cgltf_buffer_view* view = prim->indices->buffer_view; - const cgltf_size buffer_index = view->buffer - data->buffers; - assert(buffer_index < data->buffers_count); - const Buffer* buffer = buffers[buffer_index]; - const cgltf_size component_size = - get_component_size(accessor->component_type); - switch (component_size) { - case 1: { - BufferViewIdx8* indices = &geometry_desc.indices8; - // TODO: discards const qualifier. - indices->buffer = buffer; - indices->offset_bytes = accessor->offset + view->offset; - indices->size_bytes = view->size; - indices->stride_bytes = view->stride; - geometry_desc.num_indices = prim->indices->count; - break; - } - case 2: { - BufferViewIdx16* indices = &geometry_desc.indices16; - indices->buffer = buffer; - indices->offset_bytes = accessor->offset + view->offset; - indices->size_bytes = view->size; - indices->stride_bytes = view->stride; - geometry_desc.num_indices = prim->indices->count; - break; - } - default: - // TODO: Handle 32-bit indices. - assert(false); - break; - } - } - - // Vertex attributes. - for (cgltf_size a = 0; a < prim->attributes_count; ++a) { - const cgltf_attribute* attrib = &prim->attributes[a]; - const cgltf_accessor* accessor = attrib->data; - const cgltf_buffer_view* view = accessor->buffer_view; - const cgltf_size offset = accessor->offset + view->offset; - const cgltf_size buffer_index = view->buffer - data->buffers; - assert(buffer_index < data->buffers_count); - const Buffer* buffer = buffers[buffer_index]; - - BufferView2d* buffer_view_2d = 0; - BufferView3d* buffer_view_3d = 0; - BufferView4d* buffer_view_4d = 0; - BufferViewFloat* buffer_view_float = 0; - BufferViewU8* buffer_view_u8 = 0; - BufferViewU16* buffer_view_u16 = 0; - - switch (attrib->type) { - case cgltf_attribute_type_position: { - switch (accessor->type) { - case cgltf_type_vec2: - assert(geometry_desc.positions3d.buffer == 0); - buffer_view_2d = &geometry_desc.positions2d; - geometry_desc.aabb = compute_aabb(accessor, 2); - break; - case cgltf_type_vec3: - assert(geometry_desc.positions2d.buffer == 0); - buffer_view_3d = &geometry_desc.positions3d; - geometry_desc.aabb = compute_aabb(accessor, 3); - break; - default: - LOGE( - "Unhandled accessor type %d in vertex positions", - accessor->type); - assert(false); - return false; - } - // 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; - break; - case cgltf_attribute_type_tangent: - buffer_view_4d = &geometry_desc.tangents; - perm.has_tangents = true; - break; - case cgltf_attribute_type_texcoord: - buffer_view_2d = &geometry_desc.texcoords; - perm.has_texcoords = true; - break; - case cgltf_attribute_type_color: - // TODO: Add support for color. - break; - case cgltf_attribute_type_joints: - // Joints can be either u8 or u16. - switch (accessor->component_type) { - case cgltf_component_type_r_8u: - buffer_view_u8 = &geometry_desc.joints.u8; - break; - case cgltf_component_type_r_16u: - buffer_view_u16 = &geometry_desc.joints.u16; - break; - default: - assert(false); - return false; - } - perm.has_joints = true; - break; - case cgltf_attribute_type_weights: - // Weights can be either u8, u16, or float. - switch (accessor->component_type) { - case cgltf_component_type_r_8u: - buffer_view_u8 = &geometry_desc.weights.u8; - break; - case cgltf_component_type_r_16u: - buffer_view_u16 = &geometry_desc.weights.u16; - break; - case cgltf_component_type_r_32f: - buffer_view_float = &geometry_desc.weights.floats; - break; - default: - assert(false); - return false; - } - perm.has_weights = true; - break; - case cgltf_attribute_type_invalid: - assert(false); - break; - } - -#define CONFIGURE_BUFFER(buf) \ - if (buf) { \ - buf->buffer = buffer; \ - buf->offset_bytes = offset; \ - buf->size_bytes = view->size; \ - buf->stride_bytes = view->stride; \ - } - CONFIGURE_BUFFER(buffer_view_2d); - CONFIGURE_BUFFER(buffer_view_3d); - CONFIGURE_BUFFER(buffer_view_4d); - CONFIGURE_BUFFER(buffer_view_u8); - CONFIGURE_BUFFER(buffer_view_u16); - CONFIGURE_BUFFER(buffer_view_float); - } // Vertex attributes. - - assert( - (perm.has_joints && perm.has_weights) || - (!perm.has_joints && !perm.has_weights)); - - // If the mesh primitive has no tangents, see if they were computed - // separately. - if (!geometry_desc.tangents.buffer) { - for (cgltf_size t = 0; t < num_tangent_buffers; ++t) { - const cgltfTangentBuffer* cgltf_buffer = &cgltf_tangent_buffers[t]; - - if (cgltf_buffer->primitive == prim) { - BufferView4d* view = &geometry_desc.tangents; - view->buffer = tangent_buffers[t]; - view->offset_bytes = 0; - view->size_bytes = cgltf_buffer->size_bytes; - view->stride_bytes = 0; // Tightly packed. - break; - } - } - } - - // 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. - geometry_desc.num_verts = - (geometry_desc.positions2d.size_bytes / sizeof(vec2)) + - (geometry_desc.positions3d.size_bytes / sizeof(vec3)); - -#define CHECK_COUNT(buffer_view, type, num_components) \ - if (geometry_desc.buffer_view.buffer) { \ - assert( \ - (geometry_desc.buffer_view.size_bytes / \ - (num_components * sizeof(type))) == geometry_desc.num_verts); \ - } - - // Check that the number of vertices is consistent across all vertex - // attributes. - CHECK_COUNT(normals, vec3, 1); - CHECK_COUNT(tangents, vec4, 1); - CHECK_COUNT(texcoords, vec2, 1); - CHECK_COUNT(joints.u8, uint8_t, 4); - CHECK_COUNT(joints.u16, uint16_t, 4); - CHECK_COUNT(weights.u8, uint8_t, 4); - CHECK_COUNT(weights.u16, uint16_t, 4); - CHECK_COUNT(weights.floats, float, 4); - - Material* material = 0; - if (mat) { - const cgltf_size material_index = mat - data->materials; - assert(material_index < data->materials_count); - material = materials[material_index]; - } else { - // Create a default material for meshes that do not specify one. - material = make_default_material(); - } - assert(material); - - geometries[next_mesh] = gfx_make_geometry(render_backend, &geometry_desc); - if (!geometries[next_mesh]) { - return false; - } - - // If the user specifies a custom shader, use that instead. Otherwise - // compile a shader based on the mesh's permutation. - // - // Note that Gfx takes care of caching shaders and shader programs. - // - // Caching materials could be useful, but, 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. - ShaderProgram* mesh_shader = - shader ? shader : make_shader_permutation(render_backend, perm); - assert(mesh_shader); - - meshes[next_mesh] = gfx_make_mesh(&(MeshDesc){ - .geometry = geometries[next_mesh], - .material = material, - .shader = mesh_shader}); - - if (!meshes[next_mesh]) { - return false; - } - - gfx_add_object_mesh(scene_objects[m], meshes[next_mesh]); - - ++next_mesh; - } // glTF mesh primitive / gfx Mesh. - } // glTF mesh / gfx SceneObject. - - return true; -} - -/// Find the joint node with the smallest index across all skeletons. -/// -/// The channels in glTF may target arbitrary nodes in the scene (those nodes -/// are the joints). However, we want to map the "base joint" (the joint/node -/// with the smallest index) to 0 in the AnimaDesc's joint array. We can do this -/// by subtracting the "base node index" from every joint index or channel -/// target. -/// -/// There is an assumption in the animation library that joints are contiguous -/// anyway, so this "base joint index" works provided the joint nodes are also -/// contiguous in the glTF. The glTF does not guarantee this, but I think it's -/// a reasonable assumption that exporters write glTF files in such a way, and -/// Blender does appear to do so. -cgltf_size find_base_joint_index(const cgltf_data* data) { - assert(data); - - cgltf_size base_joint_index = (cgltf_size)-1; - - for (cgltf_size s = 0; s < data->skins_count; ++s) { - const cgltf_skin* skin = &data->skins[s]; - for (cgltf_size j = 0; j < skin->joints_count; ++j) { - // Joint is an index/pointer into the nodes array. - const cgltf_size node_index = skin->joints[j] - data->nodes; - assert(node_index < data->nodes_count); - // Min. - if (node_index < base_joint_index) { - base_joint_index = node_index; - } - } - } - - return base_joint_index; -} - -/// Load all skins (Gfx skeletons) from the glTF scene. -/// Return the total number of joints. -static size_t load_skins( - const cgltf_data* data, Buffer* const* buffers, cgltf_size base_joint_index, - AnimaDesc* anima_desc) { - assert(data); - assert(buffers); - assert(anima_desc); - assert(base_joint_index < data->nodes_count); - - // Determines whether the ith joint in the node hierarchy is a joint node. - // This is then used to determine whether a joint is a root of the joint - // hierarchy. - bool is_joint_node[GFX_MAX_NUM_JOINTS] = {false}; - - size_t num_joints = 0; - - for (cgltf_size s = 0; s < data->skins_count; ++s) { - const cgltf_skin* skin = &data->skins[s]; - const cgltf_accessor* matrices_accessor = skin->inverse_bind_matrices; - assert(matrices_accessor->count == skin->joints_count); - - num_joints += skin->joints_count; - assert(num_joints < GFX_MAX_NUM_JOINTS); - - SkeletonDesc* skeleton_desc = &anima_desc->skeletons[s]; - *skeleton_desc = (SkeletonDesc){.num_joints = skin->joints_count}; - - // for (cgltf_size j = 0; j < skin->joints_count; ++j) { - ACCESSOR_FOREACH_MAT(4, matrices_accessor, { - const mat4 inv_bind_matrix = mat4_from_array(floats); - - // Joint is an index/pointer into the nodes array. - const cgltf_size node_index = skin->joints[i] - data->nodes; - assert(node_index < data->nodes_count); - - const cgltf_size parent_node_index = - skin->joints[i]->parent - data->nodes; - assert(parent_node_index < data->nodes_count); - - // Subtract the base index to pack the joints as tightly as possible in - // the AnimaDesc. - assert(node_index >= base_joint_index); - const cgltf_size joint_index = node_index - base_joint_index; - - assert(parent_node_index >= base_joint_index); - const cgltf_size parent_index = parent_node_index - base_joint_index; - - skeleton_desc->joints[i] = joint_index; - - JointDesc* joint_desc = &anima_desc->joints[joint_index]; - joint_desc->parent = parent_index; - joint_desc->inv_bind_matrix = inv_bind_matrix; - - is_joint_node[joint_index] = true; - }); - - // glTF may specify a "skeleton", which is the root of the skin's - // (skeleton's) node hierarchy. - // if (skin->skeleton) { - // // cgltf_size root_index = skin->skeleton - data->nodes; - // // assert(root_index <= data->nodes_count); - // // root_node = nodes[root_index]; - // assert(false); - //} - } - - // Animation library assumes that joints are contiguous. - for (size_t i = 0; i < num_joints; ++i) { - assert(is_joint_node[i]); - } - - // Insert the root joint. - // This is the root of all skeletons. It is, specifically, the root of all - // joints that do not have a parent; skins (skeletons) in glTF are not - // guaranteed to have a common parent, but are generally a set of disjoint - // trees. - const size_t root_index = num_joints; - assert(root_index < GFX_MAX_NUM_JOINTS); - anima_desc->joints[root_index] = (JointDesc){.parent = INDEX_NONE}; - num_joints++; - - // Make root joints point to the root joint at index N. - // The root joints are the ones that have a non-joint node in the glTF as a - // parent. - for (size_t i = 0; i < root_index; ++i) { - JointDesc* joint = &anima_desc->joints[i]; - if ((joint->parent >= root_index) || !is_joint_node[joint->parent]) { - joint->parent = root_index; - } - } - - return num_joints; -} - -/// Load all animations from the glTF scene. -static void load_animations( - const cgltf_data* data, cgltf_size base_joint_index, - AnimaDesc* anima_desc) { - assert(data); - assert(anima_desc); - assert(base_joint_index < data->nodes_count); - assert(data->animations_count <= GFX_MAX_NUM_ANIMATIONS); - - for (cgltf_size a = 0; a < data->animations_count; ++a) { - const cgltf_animation* animation = &data->animations[a]; - AnimationDesc* animation_desc = &anima_desc->animations[a]; - - *animation_desc = (AnimationDesc){ - .name = sstring_make(animation->name), - .num_channels = animation->channels_count}; - - assert(animation->channels_count <= GFX_MAX_NUM_CHANNELS); - for (cgltf_size c = 0; c < animation->channels_count; ++c) { - const cgltf_animation_channel* channel = &animation->channels[c]; - ChannelDesc* channel_desc = &animation_desc->channels[c]; - const cgltf_animation_sampler* sampler = channel->sampler; - - const size_t target_index = channel->target_node - data->nodes; - assert(target_index < data->nodes_count); - - assert(target_index >= base_joint_index); - const size_t tight_target_index = target_index - base_joint_index; - assert(tight_target_index < anima_desc->num_joints); - - *channel_desc = (ChannelDesc){ - .target = tight_target_index, - .type = from_gltf_animation_path_type(channel->target_path), - .interpolation = from_gltf_interpolation_type(sampler->interpolation), - .num_keyframes = 0}; - - // Read time inputs. - ACCESSOR_FOREACH_VEC(1, sampler->input, { - channel_desc->keyframes[i].time = x; - channel_desc->num_keyframes++; - }); - - // Read transform outputs. - switch (channel->target_path) { - case cgltf_animation_path_type_translation: - ACCESSOR_FOREACH_VEC(3, sampler->output, { - channel_desc->keyframes[i].translation = vec3_make(x, y, z); - }); - break; - case cgltf_animation_path_type_rotation: - ACCESSOR_FOREACH_VEC(4, sampler->output, { - channel_desc->keyframes[i].rotation = qmake(x, y, z, w); - }); - break; - default: - // TODO: Handle other channel transformations. - break; - } - } - } -} - -/// Load all nodes from the glTF scene. -/// -/// This function ignores the many scenes and default scene of the glTF spec -/// and instead just loads all nodes into a single gfx Scene. -static void load_nodes( - const cgltf_data* data, SceneNode* root_node, SceneObject** objects, - SceneCamera** cameras, const Anima* anima, SceneNode** nodes) { - // Note that with glTF 2.0, nodes do not form a DAG / scene graph but a - // disjount union of strict trees: - // - // "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." - // - // This matches the gfx library implementation, where every node can have at - // most one parent. - assert(data); - assert(root_node); - assert(objects); - assert(cameras); - assert(nodes); - - cgltf_size next_camera = 0; - - for (cgltf_size n = 0; n < data->nodes_count; ++n) { - const cgltf_node* node = &data->nodes[n]; - - // Add SceneObject, SceneCamera or Lights. - // TODO: Handle lights once they are implemented in the gfx library. - if (node->mesh) { - const cgltf_size mesh_index = node->mesh - data->meshes; - assert(mesh_index < data->meshes_count); - SceneObject* object = objects[mesh_index]; - gfx_construct_object_node(nodes[n], object); - - if (node->skin) { - assert(anima); - - const cgltf_size skin_index = node->skin - data->skins; - assert(skin_index < data->skins_count); - const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index); - gfx_set_object_skeleton(object, skeleton); - - // TODO: Compute AABBs/OOBBs for the skeleton's joints here. Iterate - // over the mesh's primitives, its vertices, their joint indices, and - // add the vertex to the AABB/OOBB. - } - } else if (node->camera) { - assert(next_camera < data->cameras_count); - - Camera camera; - const cgltf_camera* cam = node->camera; - - // TODO: We could define a function load_cameras() the same way we load - // every mesh and then remove this ad-hoc loading of cameras here, as well - // as remove 'next_camera'. - switch (cam->type) { - case cgltf_camera_type_orthographic: - camera = camera_orthographic( - 0, cam->data.orthographic.xmag, 0, cam->data.orthographic.ymag, - cam->data.orthographic.znear, cam->data.orthographic.zfar); - break; - case cgltf_camera_type_perspective: - camera = camera_perspective( - cam->data.perspective.yfov, cam->data.perspective.aspect_ratio, - cam->data.perspective.znear, cam->data.perspective.zfar); - break; - case cgltf_camera_type_invalid: - break; - } - - gfx_set_camera_camera(cameras[next_camera], &camera); - gfx_construct_camera_node(nodes[n], cameras[next_camera]); - ++next_camera; - } else { - // TODO: implementation for missing node types. - // These nodes currently default to logical nodes. - } - assert(nodes[n]); - - // Set transform. - mat4 transform; - if (node->has_matrix) { - transform = mat4_from_array(node->matrix); - } else { - transform = mat4_id(); - if (node->has_scale) { - const mat4 scale = mat4_scale(vec3_from_array(node->scale)); - transform = mat4_mul(transform, scale); - } - if (node->has_rotation) { - const quat q = quat_from_array(node->rotation); - const mat4 rotate = mat4_from_quat(q); - transform = mat4_mul(transform, rotate); - } - if (node->has_translation) { - const mat4 translate = - mat4_translate(vec3_from_array(node->translation)); - transform = mat4_mul(translate, transform); - } - } - gfx_set_node_transform(nodes[n], &transform); - - // If this is a top-level node in the glTF scene, set its parent to the - // given root node. - if (!node->parent) { - gfx_set_node_parent(nodes[n], root_node); - } else { - const cgltf_size parent_index = node->parent - data->nodes; - assert(parent_index < data->nodes_count); - SceneNode* parent = nodes[parent_index]; - assert(parent); - gfx_set_node_parent(nodes[n], parent); - } - } // SceneNode. -} - -/// Load all scenes from the glTF file. -/// -/// If the scene is loaded from memory, set filepath = null. -/// -/// This function ignores the many scenes and default scene of the glTF spec -/// and instead just loads all scenes into a single Gfx Scene. -static Model* load_scene( - cgltf_data* data, Gfx* gfx, const mstring* filepath, ShaderProgram* shader, - const cgltfTangentBuffer* cgltf_tangent_buffers, - cgltf_size num_tangent_buffers) { - // In a GLTF scene, buffers can be shared among meshes, meshes among nodes, - // etc. Each object is referenced by its index in the relevant array. Here we - // do a button-up construction, first allocating our own graphics objects in - // the same quantities and then re-using the GLTF indices to index these - // arrays. - // - // For simplicity, this function also handles all of the cleanup. Arrays are - // allocated up front, and the helper functions construct their elements. If - // an error is encountered, the helper functions can simply return and this - // function cleans up any intermediate objects that had been created up until - // the point of failure. - // - // Loading animation data: - // - Buffers with animation sampler data need to stay on the CPU, not - // uploaded to the GPU. We could try to implement GPU animation at a later - // stage. - assert(data); - assert(gfx); - assert(filepath); - assert((num_tangent_buffers == 0) || (cgltf_tangent_buffers != 0)); - - bool success = false; - - RenderBackend* render_backend = gfx_get_render_backend(gfx); - const size_t primitive_count = get_total_primitives(data); - - const mstring directory = mstring_dirname(*filepath); - LOGD("Filepath: %s", mstring_cstr(filepath)); - LOGD("Directory: %s", mstring_cstr(&directory)); - - Buffer** tangent_buffers = 0; - Buffer** buffers = 0; - LoadTextureCmd* load_texture_cmds = 0; - Texture** textures = 0; - Material** materials = 0; - Geometry** geometries = 0; - Mesh** meshes = 0; - AnimaDesc* anima_desc = 0; - SceneObject** scene_objects = 0; - SceneCamera** scene_cameras = 0; - SceneNode** scene_nodes = 0; - Anima* anima = 0; - SceneNode* root_node = 0; - Model* model = 0; - - tangent_buffers = calloc(num_tangent_buffers, sizeof(Buffer*)); - buffers = calloc(data->buffers_count, sizeof(Buffer*)); - textures = calloc(data->textures_count, sizeof(Texture*)); - materials = calloc(data->materials_count, sizeof(Material*)); - geometries = calloc(primitive_count, sizeof(Geometry*)); - meshes = calloc(primitive_count, sizeof(Mesh*)); - scene_objects = calloc(data->meshes_count, sizeof(SceneObject*)); - scene_cameras = calloc(data->cameras_count, sizeof(SceneCamera**)); - scene_nodes = calloc(data->nodes_count, sizeof(SceneNode**)); - // A glTF scene does not necessarily have textures. Materials can be given - // as constants, for example. - if (data->textures_count > 0) { - load_texture_cmds = calloc(data->textures_count, sizeof(LoadTextureCmd)); - } - - if (!buffers || !tangent_buffers || - ((data->textures_count > 0) && !load_texture_cmds) || !textures || - !materials || !geometries || !meshes || !scene_objects || - !scene_cameras || !scene_nodes) { - goto cleanup; - } - - if ((num_tangent_buffers > 0) && - !load_tangent_buffers( - cgltf_tangent_buffers, num_tangent_buffers, render_backend, - tangent_buffers)) { - goto cleanup; - } - - if (!load_buffers(data, render_backend, buffers)) { - goto cleanup; - } - - if (data->textures_count > 0) { - load_textures_lazy( - data, render_backend, mstring_cstr(&directory), load_texture_cmds); - } - - if (!load_materials(data, gfx, load_texture_cmds, textures, materials)) { - goto cleanup; - } - - if (!load_meshes( - data, render_backend, buffers, tangent_buffers, cgltf_tangent_buffers, - num_tangent_buffers, materials, shader, primitive_count, geometries, - meshes, scene_objects)) { - goto cleanup; - } - - // Skins refer to nodes, and nodes may refer to skins. To break this circular - // dependency, glTF defines skins in terms of node indices. We could do the - // same if Gfx allowed allocating nodes contiguously in memory. For now, - // create the nodes up front and use the indices of the array to map to the - // node_idx. - for (cgltf_size i = 0; i < data->nodes_count; ++i) { - scene_nodes[i] = gfx_make_node(); - } - - // Create the scene's root node. - // This is an anima node if the scene has skins; otherwise it is a logical - // node. - root_node = gfx_make_node(); - if (data->skins_count > 0) { - anima_desc = calloc(1, sizeof(AnimaDesc)); - if (!anima_desc) { - goto cleanup; - } - - const cgltf_size base = find_base_joint_index(data); - - anima_desc->num_skeletons = data->skins_count; - anima_desc->num_animations = data->animations_count; - anima_desc->num_joints = load_skins(data, buffers, base, anima_desc); - load_animations(data, base, anima_desc); - - anima = gfx_make_anima(anima_desc); - gfx_construct_anima_node(root_node, anima); - } - - // The root node becomes the root of all scene nodes. - load_nodes(data, root_node, scene_objects, scene_cameras, anima, scene_nodes); - - // TODO: Clean up scene nodes that correspond to joints in the glTF. - - model = gfx_make_model(root_node); - - success = true; - -cleanup: - // The arrays of resources are no longer needed. The resources themselves are - // destroyed only if this function fails. - if (tangent_buffers) { - if (!success) { - for (cgltf_size i = 0; i < num_tangent_buffers; ++i) { - if (tangent_buffers[i]) { - gfx_destroy_buffer(render_backend, &tangent_buffers[i]); - } - } - } - free(tangent_buffers); - } - if (buffers) { - if (!success) { - for (cgltf_size i = 0; i < data->buffers_count; ++i) { - if (buffers[i]) { - gfx_destroy_buffer(render_backend, &buffers[i]); - } - } - } - free(buffers); - } - if (load_texture_cmds) { - free(load_texture_cmds); - } - if (textures) { - if (!success) { - for (cgltf_size i = 0; i < data->textures_count; ++i) { - if (textures[i]) { - gfx_destroy_texture(render_backend, &textures[i]); - } - } - } - free(textures); - } - if (materials) { - if (!success) { - for (cgltf_size i = 0; i < data->materials_count; ++i) { - if (materials[i]) { - gfx_destroy_material(&materials[i]); - } - } - } - free(materials); - } - if (geometries) { - if (!success) { - for (size_t i = 0; i < primitive_count; ++i) { - if (geometries[i]) { - gfx_destroy_geometry(render_backend, &geometries[i]); - } - } - } - free(geometries); - } - if (meshes) { - if (!success) { - for (size_t i = 0; i < primitive_count; ++i) { - if (meshes[i]) { - gfx_destroy_mesh(&meshes[i]); - } - } - } - free(meshes); - } - if (anima_desc) { - free(anima_desc); - } - if (scene_objects) { - if (!success) { - for (cgltf_size i = 0; i < data->meshes_count; ++i) { - if (scene_objects[i]) { - gfx_destroy_object(&scene_objects[i]); - } - } - } - free(scene_objects); - } - if (scene_cameras) { - if (!success) { - for (cgltf_size i = 0; i < data->cameras_count; ++i) { - if (scene_cameras[i]) { - gfx_destroy_camera(&scene_cameras[i]); - } - } - } - free(scene_cameras); - } - if (scene_nodes) { - if (!success) { - for (cgltf_size i = 0; i < data->nodes_count; ++i) { - if (scene_nodes[i]) { - gfx_destroy_node(&scene_nodes[i]); - } - } - } - free(scene_nodes); - } - if (!success) { - if (root_node) { - gfx_destroy_node(&root_node); // Node owns the anima. - } else if (anima) { - gfx_destroy_anima(&anima); - } - } - return model; -} - -Model* gfx_model_load(Gfx* gfx, const LoadModelCmd* cmd) { - assert(gfx); - assert(cmd); - - Model* model = 0; - - cgltf_options options = {0}; - cgltf_data* data = NULL; - cgltfTangentBuffer* tangent_buffers = 0; - - cgltf_result result; - switch (cmd->origin) { - case AssetFromFile: - result = cgltf_parse_file(&options, mstring_cstr(&cmd->filepath), &data); - break; - case AssetFromMemory: - result = cgltf_parse(&options, cmd->data, cmd->size_bytes, &data); - break; - } - if (result != cgltf_result_success) { - goto cleanup; - } - - if (cmd->origin == AssetFromFile) { - // Must call cgltf_load_buffers() to load buffer data. - result = cgltf_load_buffers(&options, data, mstring_cstr(&cmd->filepath)); - if (result != cgltf_result_success) { - goto cleanup; - } - } - - // Compute tangents for normal-mapped models that are missing them. - cgltf_size num_tangent_buffers = 0; - cgltf_compute_tangents( - &options, data, &tangent_buffers, &num_tangent_buffers); - - model = load_scene( - data, gfx, &cmd->filepath, cmd->shader, tangent_buffers, - num_tangent_buffers); - -cleanup: - if (data) { - cgltf_free(data); - } - if (tangent_buffers) { - free(tangent_buffers); - } - return model; -} diff --git a/gfx/src/asset/scene.h b/gfx/src/asset/scene.h deleted file mode 100644 index d6399b1..0000000 --- a/gfx/src/asset/scene.h +++ /dev/null @@ -1,12 +0,0 @@ -/// Load scene files. -#pragma once - -#include - -typedef struct Gfx Gfx; -typedef struct Model Model; - -/// Load a model. -/// -/// Currently only supports the GLTF format. -Model* gfx_model_load(Gfx*, const LoadModelCmd*); -- cgit v1.2.3