aboutsummaryrefslogtreecommitdiff
path: root/src/asset
diff options
context:
space:
mode:
Diffstat (limited to 'src/asset')
-rw-r--r--src/asset/asset_cache.c252
-rw-r--r--src/asset/asset_cache.h37
-rw-r--r--src/asset/model.c1968
-rw-r--r--src/asset/model.h12
-rw-r--r--src/asset/texture.c177
-rw-r--r--src/asset/texture.h7
6 files changed, 2453 insertions, 0 deletions
diff --git a/src/asset/asset_cache.c b/src/asset/asset_cache.c
new file mode 100644
index 0000000..16c4d5c
--- /dev/null
+++ b/src/asset/asset_cache.c
@@ -0,0 +1,252 @@
1#include "asset_cache.h"
2
3#include "model.h"
4#include "scene/animation_impl.h"
5#include "scene/model_impl.h"
6#include "scene/node_impl.h"
7#include "scene/scene_memory.h"
8#include "texture.h"
9
10#include <gfx/asset.h>
11#include <gfx/gfx.h>
12#include <gfx/scene/node.h>
13#include <gfx_assert.h>
14
15#include <cstring.h>
16#include <error.h>
17#include <log/log.h>
18
19static void log_model_load_failure(const LoadModelCmd* cmd) {
20 assert(cmd);
21
22 switch (cmd->origin) {
23 case AssetFromFile:
24 log_error("Failed to load model: %s", mstring_cstr(&cmd->filepath));
25 break;
26 case AssetFromMemory:
27 log_error("Failed to load model: %p", cmd->data);
28 break;
29 }
30}
31
32static void log_texture_load_failure(const LoadTextureCmd* cmd) {
33 assert(cmd);
34
35 switch (cmd->origin) {
36 case AssetFromFile:
37 switch (cmd->type) {
38 case LoadTexture:
39 log_error(
40 "Failed to load texture: %s",
41 mstring_cstr(&cmd->data.texture.filepath));
42 break;
43 case LoadCubemap:
44 log_error(
45 "Failed to load cubemap texture: %s",
46 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_x));
47 break;
48 }
49 break;
50 case AssetFromMemory:
51 switch (cmd->type) {
52 case LoadTexture:
53 log_error("Failed to load texture: %p", cmd->data.texture.data);
54 break;
55 case LoadCubemap:
56 log_error(
57 "Failed to load texture: %p", cmd->data.cubemap.buffers.data_pos_x);
58 break;
59 }
60 break;
61 }
62}
63
64static Hash calc_model_hash(const LoadModelCmd* cmd) {
65 assert(cmd);
66 switch (cmd->origin) {
67 case AssetFromFile:
68 return cstring_hash(mstring_cstr(&cmd->filepath));
69 case AssetFromMemory:
70 return (Hash)cmd->data;
71 }
72 FAIL("Unhandled model asset origin");
73 return 0;
74}
75
76static Hash calc_texture_hash(const LoadTextureCmd* cmd) {
77 assert(cmd);
78 switch (cmd->origin) {
79 case AssetFromFile:
80 switch (cmd->type) {
81 case LoadTexture:
82 return cstring_hash(mstring_cstr(&cmd->data.texture.filepath));
83 case LoadCubemap:
84 return cstring_hash(
85 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_x)) ^
86 cstring_hash(
87 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_neg_x)) ^
88 cstring_hash(
89 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_y)) ^
90 cstring_hash(
91 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_neg_y)) ^
92 cstring_hash(
93 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_z)) ^
94 cstring_hash(
95 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_neg_z));
96 }
97 break;
98 case AssetFromMemory:
99 switch (cmd->type) {
100 case LoadTexture:
101 return (Hash)cmd->data.texture.data;
102 case LoadCubemap:
103 return (Hash)cmd->data.cubemap.buffers.data_pos_x ^
104 (Hash)cmd->data.cubemap.buffers.data_neg_x ^
105 (Hash)cmd->data.cubemap.buffers.data_pos_y ^
106 (Hash)cmd->data.cubemap.buffers.data_neg_y ^
107 (Hash)cmd->data.cubemap.buffers.data_pos_z ^
108 (Hash)cmd->data.cubemap.buffers.data_neg_z;
109 }
110 break;
111 }
112 FAIL("Unhandled texture asset origin");
113 return 0;
114}
115
116static Asset* lookup_cache(AssetCache* cache, Hash hash) {
117 assert(cache);
118 mempool_foreach(&cache->assets, asset, {
119 if (asset->hash == hash) {
120 return asset;
121 }
122 });
123 return 0;
124}
125
126static void log_model_cache_hit(const LoadModelCmd* cmd, Hash hash) {
127 assert(cmd);
128 switch (cmd->origin) {
129 case AssetFromFile:
130 LOGD(
131 "Found asset [%s] in cache with hash [%lu]",
132 mstring_cstr(&cmd->filepath), hash);
133 break;
134 case AssetFromMemory:
135 LOGD("Found asset [%p] in cache with hash [%lu]", cmd->data, hash);
136 break;
137 }
138}
139
140static void log_model_loaded(const LoadModelCmd* cmd) {
141 assert(cmd);
142 switch (cmd->origin) {
143 case AssetFromFile:
144 LOGD("Loaded asset from file: [%s]", mstring_cstr(&cmd->filepath));
145 break;
146 case AssetFromMemory:
147 LOGD("Loaded asset from memory: [%p]", cmd->data);
148 break;
149 }
150}
151
152static Model* clone_model(const Model* model) {
153 assert(model);
154
155 // Only the Anima needs to be cloned since everything else in the model is
156 // static.
157 //
158 // The Anima can be partially shallow-cloned. Skeletons and animations are
159 // static and can be shared with the original Anima. Other members are
160 // deep-cloned. Skeletons in particular point back to their Anima, so need to
161 // be deep-cloned.
162 const SceneNode* root = mem_get_node(model->root);
163 if (gfx_get_node_type(root) == AnimaNode) {
164 const Anima* anima = gfx_get_node_anima(root);
165 Anima* anima_copy = mem_alloc_anima();
166 *anima_copy = *anima; // Shallow copy.
167
168 SceneNode* root_copy = gfx_clone_scene_shallow(root);
169 root_copy->anima = mem_get_anima_index(anima_copy);
170 anima_copy->parent = mem_get_node_index(root_copy);
171
172 Model* copy = mem_alloc_model();
173 copy->root = mem_get_node_index(root_copy);
174 return copy;
175 } else {
176 return (Model*)model; // Static model, can't be mutated.
177 }
178}
179
180void gfx_init_asset_cache(AssetCache* cache) {
181 assert(cache);
182 mempool_make(&cache->assets);
183
184 // Allocate a dummy asset at index 0 to guarantee that no assets allocated by
185 // the caller map to index 0.
186 const Asset* dummy = mempool_alloc(&cache->assets);
187 assert(mempool_get_block_index(&cache->assets, dummy) == 0);
188}
189
190void gfx_destroy_asset_cache(AssetCache* cache) {
191 assert(cache);
192 mempool_del(&cache->assets);
193}
194
195Model* gfx_load_model(Gfx* gfx, const LoadModelCmd* cmd) {
196 assert(gfx);
197
198 AssetCache* cache = gfx_get_asset_cache(gfx);
199
200 // First search for the asset in the cache.
201 const uint64_t hash = calc_model_hash(cmd);
202 Asset* asset = lookup_cache(cache, hash);
203 if (asset) {
204 log_model_cache_hit(cmd, hash);
205 return clone_model(asset->model);
206 }
207
208 // Asset not found in the cache.
209 // Load it, insert it into the cache, and return it.
210 Model* model = gfx_model_load(gfx, cmd);
211 if (model) {
212 *(Asset*)mempool_alloc(&cache->assets) = (Asset){
213 .type = ModelAsset,
214 .hash = hash,
215 .model = model,
216 };
217 log_model_loaded(cmd);
218 return clone_model(model);
219 } else {
220 log_model_load_failure(cmd);
221 return 0;
222 }
223}
224
225const Texture* gfx_load_texture(Gfx* gfx, const LoadTextureCmd* cmd) {
226 assert(gfx);
227 assert(cmd);
228
229 AssetCache* cache = gfx_get_asset_cache(gfx);
230
231 // First search for the asset in the cache.
232 const uint64_t hash = calc_texture_hash(cmd);
233 Asset* asset = lookup_cache(cache, hash);
234 if (asset) {
235 return asset->texture;
236 }
237
238 // Asset not found in the cache.
239 // Load it, insert it into the cache, and return it.
240 GfxCore* gfxcore = gfx_get_core(gfx);
241 const Texture* texture = gfx_texture_load(gfxcore, cmd);
242 if (texture) {
243 *(Asset*)mempool_alloc(&cache->assets) = (Asset){
244 .type = TextureAsset,
245 .hash = hash,
246 .texture = texture,
247 };
248 } else {
249 log_texture_load_failure(cmd);
250 }
251 return texture;
252}
diff --git a/src/asset/asset_cache.h b/src/asset/asset_cache.h
new file mode 100644
index 0000000..b2a35ed
--- /dev/null
+++ b/src/asset/asset_cache.h
@@ -0,0 +1,37 @@
1#pragma once
2
3#include <gfx/sizes.h>
4
5#include <cstring.h>
6#include <mempool.h>
7
8typedef struct Model Model;
9typedef struct Texture Texture;
10
11typedef uint64_t Hash;
12
13typedef enum AssetType {
14 ModelAsset,
15 TextureAsset,
16} AssetType;
17
18typedef struct Asset {
19 AssetType type;
20 Hash hash;
21 union {
22 Model* model;
23 const Texture* texture;
24 };
25} Asset;
26
27DEF_MEMPOOL(asset_pool, Asset, GFX_MAX_NUM_ASSETS)
28
29typedef struct AssetCache {
30 asset_pool assets;
31} AssetCache;
32
33/// Create a new asset cache.
34void gfx_init_asset_cache(AssetCache*);
35
36/// Destroy the asset cache.
37void gfx_destroy_asset_cache(AssetCache*);
diff --git a/src/asset/model.c b/src/asset/model.c
new file mode 100644
index 0000000..25f2780
--- /dev/null
+++ b/src/asset/model.c
@@ -0,0 +1,1968 @@
1/// Loads scenes from memory and files.
2///
3/// Only the GLTF scene format is current supported.
4///
5/// ----------------------------------------------------------------------------
6/// glTF File Format Documentation
7/// ----------------------------------------------------------------------------
8///
9/// cgltf:
10/// https://github.com/jkuhlmann/cgltf
11///
12/// gltf overview:
13/// https://raw.githubusercontent.com/KhronosGroup/glTF/master/specification/2.0/figures/gltfOverview-2.0.0b.png
14///
15/// gltf spec:
16/// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md
17///
18/// Sample models:
19/// https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0
20/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Sponza/glTF/Sponza.gltf
21/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AlphaBlendModeTest/glTF/AlphaBlendModeTest.gltf
22/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Buggy/glTF/Buggy.gltf
23/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AntiqueCamera/glTF/AntiqueCamera.gltf
24/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf
25///
26/// ----------------------------------------------------------------------------
27/// Implementation Notes
28/// ----------------------------------------------------------------------------
29///
30/// # glTF and the gfx library
31///
32/// glTF has concepts that are similar to those in the gfx library, but there
33/// isn't an exact 1-1 mapping. Concepts map as follows:
34///
35/// glTF gfx
36/// ---- ---
37/// buffer Buffer
38/// accessor + buffer view BufferView
39/// mesh primitive (geom + mat) Mesh (also geom + mat)
40/// mesh SceneObject
41/// node SceneNode
42///
43/// glTF buffers map 1-1 with gfx Buffers. glTF scenes make heavy re-use of
44/// buffers across views/accessors/meshes, so it is important to make that same
45/// re-use in the gfx library to use the data effectively and without
46/// duplication. The Sponza scene, for example, has all of its data in one giant
47/// buffer.
48///
49/// glTF accessors and buffer views are combined and mapped to gfx BufferViews.
50/// The glTF buffer view's offset/length/stride are combined with the accessor's
51/// offset, and together with the remaining information of both data structures
52/// baked into a BufferView. Internally, this information is fed into
53/// glVertexAttribPointer() calls, wrapped in a VAO (index view/accessor
54/// information is fed into glDrawElements()). This baking should not hurt
55/// re-use, at least in the OpenGL world.
56///
57/// A glTF mesh primitive contains a piece of geometry and a material. This maps
58/// directly to a gfx Mesh.
59///
60/// A glTF mesh is a list of mesh primitives. This maps nicely to a gfx
61/// SceneObject, with the only inconvenience that terminology gets a little
62/// confusing.
63///
64/// Finally, glTF nodes map directly to gfx SceneNodes. Both enforce a strict
65/// tree hierarchy; DAGs are not supported.
66///
67/// # Materials
68///
69/// glTF uses the metallic-roughness material model. However, an extension
70/// allows a scene to use the specular-glossiness model as well and cgltf
71/// supports it:
72///
73/// https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/
74///
75/// From the docs, the specular-glossiness model can represent more materials
76/// than the metallic-roughness model, but it is also more computationally
77/// expensive. Furthermore, a material in glTF can specify parameters for both
78/// models, leaving it up to the implementation to decide which one to use.
79/// In our case, we use the specular-glosiness model if parameters for it are
80/// provided, otherwise we use the metallic-roughness model.
81
82#include "asset/model.h"
83
84#include "asset/texture.h"
85#include "gfx/core.h"
86#include "gfx/gfx.h"
87#include "gfx/scene/animation.h"
88#include "gfx/scene/camera.h"
89#include "gfx/scene/material.h"
90#include "gfx/scene/mesh.h"
91#include "gfx/scene/node.h"
92#include "gfx/scene/object.h"
93#include "gfx/scene/scene.h"
94#include "gfx/sizes.h"
95#include "gfx/util/shader.h"
96
97#include "gfx_assert.h"
98#include "scene/model_impl.h"
99
100#include "cstring.h"
101#include "error.h"
102#include "log/log.h"
103#include "math/camera.h"
104#include "math/defs.h"
105#include "math/mat4.h"
106#include "math/quat.h"
107#include "math/vec2.h"
108#include "math/vec3.h"
109
110#include "cgltf_tangents.h"
111#define CGLTF_IMPLEMENTATION
112#include "cgltf.h"
113
114#include <stdbool.h>
115#include <stdlib.h>
116
117// Taken from the GL header file.
118#define GL_NEAREST 0x2600
119#define GL_LINEAR 0x2601
120#define GL_NEAREST_MIPMAP_NEAREST 0x2700
121#define GL_LINEAR_MIPMAP_NEAREST 0x2701
122#define GL_NEAREST_MIPMAP_LINEAR 0x2702
123#define GL_LINEAR_MIPMAP_LINEAR 0x2703
124
125// Uniforms names. Must match the names in shaders.
126#define UNIFORM_BASE_COLOR_FACTOR "BaseColorFactor"
127#define UNIFORM_METALLIC_FACTOR "MetallicFactor"
128#define UNIFORM_ROUGHNESS_FACTOR "RoughnessFactor"
129#define UNIFORM_EMISSIVE_FACTOR "EmissiveFactor"
130#define UNIFORM_BASE_COLOR_TEXTURE "BaseColorTexture"
131#define UNIFORM_METALLIC_ROUGHNESS_TEXTURE "MetallicRoughnessTexture"
132#define UNIFORM_EMISSIVE_TEXTURE "EmissiveTexture"
133#define UNIFORM_AMBIENT_OCCLUSION_TEXTURE "AmbientOcclusionTexture"
134#define UNIFORM_NORMAL_MAP "NormalMap"
135
136// Shader compiler defines. Must match the names in shaders.
137#define DEFINE_HAS_TEXCOORDS "HAS_TEXCOORDS"
138#define DEFINE_HAS_NORMALS "HAS_NORMALS"
139#define DEFINE_HAS_TANGENTS "HAS_TANGENTS"
140#define DEFINE_HAS_ALBEDO_MAP "HAS_ALBEDO_MAP"
141#define DEFINE_HAS_METALLIC_ROUGHNESS_MAP "HAS_METALLIC_ROUGHNESS_MAP"
142#define DEFINE_HAS_NORMAL_MAP "HAS_NORMAL_MAP"
143#define DEFINE_HAS_OCCLUSION_MAP "HAS_OCCLUSION_MAP"
144#define DEFINE_HAS_EMISSIVE_MAP "HAS_EMISSIVE_MAP"
145#define DEFINE_HAS_JOINTS "HAS_JOINTS"
146#define DEFINE_MAX_JOINTS "MAX_JOINTS"
147
148typedef enum TextureType {
149 BaseColorTexture,
150 MetallicRoughnessTexture,
151 EmissiveTexture,
152 AmbientOcclusionTexture,
153 NormalMap,
154} TextureType;
155
156/// Describes the properties of a mesh.
157/// This is used to create shader permutations.
158typedef struct MeshPermutation {
159 union {
160 struct {
161 // Vertex attributes.
162 bool has_texcoords : 1;
163 bool has_normals : 1;
164 bool has_tangents : 1;
165 bool has_joints : 1;
166 bool has_weights : 1;
167 // Textures.
168 bool has_albedo_map : 1;
169 bool has_metallic_roughness_map : 1;
170 bool has_normal_map : 1;
171 bool has_occlusion_map : 1;
172 bool has_emissive_map : 1;
173 };
174 int32_t all;
175 };
176} MeshPermutation;
177
178/// Build shader compiler defines from a mesh permutation.
179static size_t make_defines(
180 MeshPermutation perm, ShaderCompilerDefine* defines) {
181 static const char* str_true = "1";
182 size_t next = 0;
183
184#define check(field, define) \
185 if (perm.field) { \
186 defines[next].name = sstring_make(define); \
187 defines[next].value = sstring_make(str_true); \
188 next++; \
189 }
190 check(has_texcoords, DEFINE_HAS_TEXCOORDS);
191 check(has_normals, DEFINE_HAS_NORMALS);
192 check(has_tangents, DEFINE_HAS_TANGENTS);
193 check(has_joints, DEFINE_HAS_JOINTS);
194 check(has_albedo_map, DEFINE_HAS_ALBEDO_MAP);
195 check(has_metallic_roughness_map, DEFINE_HAS_METALLIC_ROUGHNESS_MAP);
196 check(has_normal_map, DEFINE_HAS_NORMAL_MAP);
197 check(has_occlusion_map, DEFINE_HAS_OCCLUSION_MAP);
198 check(has_emissive_map, DEFINE_HAS_EMISSIVE_MAP);
199
200 if (perm.has_joints) {
201 defines[next].name = sstring_make(DEFINE_MAX_JOINTS);
202 defines[next].value = sstring_itoa(GFX_MAX_NUM_JOINTS);
203 next++;
204 }
205
206 return next;
207}
208
209/// Compile a shader permutation.
210static ShaderProgram* make_shader_permutation(
211 GfxCore* gfxcore, MeshPermutation perm) {
212 LOGD(
213 "Compiling Cook-Torrance shader permutation: texcoords: %d, normals: "
214 "%d, tangents: %d, joints: %d, weights: %d, albedo map: %d, "
215 "metallic-roughness map: "
216 "%d, normal "
217 "map: %d, AO map: %d, emissive map: %d",
218 perm.has_texcoords, perm.has_normals, perm.has_tangents, perm.has_joints,
219 perm.has_weights, perm.has_albedo_map, perm.has_metallic_roughness_map,
220 perm.has_normal_map, perm.has_occlusion_map, perm.has_emissive_map);
221
222 ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES];
223 const size_t num_defines = make_defines(perm, defines);
224 return gfx_make_cook_torrance_shader_perm(gfxcore, defines, num_defines);
225}
226
227/// Map a texture type to the name of the shader uniform used to access the
228/// texture.
229static const char* get_texture_uniform_name(TextureType type) {
230 switch (type) {
231 case BaseColorTexture:
232 return UNIFORM_BASE_COLOR_TEXTURE;
233 case MetallicRoughnessTexture:
234 return UNIFORM_METALLIC_ROUGHNESS_TEXTURE;
235 case EmissiveTexture:
236 return UNIFORM_EMISSIVE_TEXTURE;
237 case AmbientOcclusionTexture:
238 return UNIFORM_AMBIENT_OCCLUSION_TEXTURE;
239 case NormalMap:
240 return UNIFORM_NORMAL_MAP;
241 }
242 assert(false);
243 return 0;
244}
245
246/// Map a glTF primitive type to a gfx primitive type.
247static PrimitiveType from_gltf_primitive_type(cgltf_primitive_type type) {
248 switch (type) {
249 case cgltf_primitive_type_triangles:
250 return Triangles;
251 case cgltf_primitive_type_triangle_fan:
252 return TriangleFan;
253 case cgltf_primitive_type_triangle_strip:
254 return TriangleStrip;
255 // Not yet implemented.
256 case cgltf_primitive_type_lines:
257 case cgltf_primitive_type_line_loop:
258 case cgltf_primitive_type_line_strip:
259 case cgltf_primitive_type_points:
260 break;
261 }
262 LOGE("Unsupported primitive type: %d", type);
263 assert(false);
264 return 0;
265}
266
267/// Map a glTF animation path type to its Gfx equivalent.
268static ChannelType from_gltf_animation_path_type(
269 cgltf_animation_path_type type) {
270 switch (type) {
271 case cgltf_animation_path_type_translation:
272 return TranslationChannel;
273 case cgltf_animation_path_type_rotation:
274 return RotationChannel;
275 case cgltf_animation_path_type_scale:
276 return ScaleChannel;
277 case cgltf_animation_path_type_weights:
278 return WeightsChannel;
279 case cgltf_animation_path_type_invalid:
280 assert(false);
281 break;
282 }
283 assert(false);
284 return 0;
285}
286
287/// Map a glTF interpolation to its Gfx equivalent.
288static AnimationInterpolation from_gltf_interpolation_type(
289 cgltf_interpolation_type type) {
290 switch (type) {
291 case cgltf_interpolation_type_linear:
292 return LinearInterpolation;
293 case cgltf_interpolation_type_step:
294 return StepInterpolation;
295 case cgltf_interpolation_type_cubic_spline:
296 return CubicSplineInterpolation;
297 }
298 assert(false);
299 return 0;
300}
301
302/// Return the component's size in bytes.
303static cgltf_size get_component_size(cgltf_component_type type) {
304 switch (type) {
305 case cgltf_component_type_r_8:
306 return 1;
307 case cgltf_component_type_r_8u:
308 return 1;
309 case cgltf_component_type_r_16:
310 return 2;
311 case cgltf_component_type_r_16u:
312 return 2;
313 case cgltf_component_type_r_32u:
314 return 4;
315 case cgltf_component_type_r_32f:
316 return 4;
317 case cgltf_component_type_invalid:
318 assert(false);
319 break;
320 }
321 assert(false);
322 return 0;
323}
324
325/// Return the number dimensionality of the given data type.
326int get_num_dimensions(cgltf_type type) {
327 switch (type) {
328 case cgltf_type_scalar:
329 return 1;
330 case cgltf_type_vec2:
331 return 2;
332 case cgltf_type_vec3:
333 return 3;
334 case cgltf_type_vec4:
335 return 4;
336 case cgltf_type_mat2:
337 return 4; // 2x2
338 case cgltf_type_mat3:
339 return 9; // 3x3
340 case cgltf_type_mat4:
341 return 16; // 4x4
342 case cgltf_type_invalid:
343 FAIL();
344 break;
345 }
346 FAIL();
347 return 0;
348}
349
350/// Read an int64 from the given data pointer and accessor.
351/// The largest integer in glTF is u32, so we can fit all integers in an int64.
352static int64_t read_int(const void* component, const cgltf_accessor* accessor) {
353 assert(component);
354 assert(accessor);
355
356 switch (accessor->component_type) {
357 case cgltf_component_type_r_8: {
358 const int8_t c = *((int8_t*)component);
359 return c;
360 }
361 case cgltf_component_type_r_8u: {
362 const uint8_t c = *((uint8_t*)component);
363 return c;
364 }
365 case cgltf_component_type_r_16: {
366 const int16_t c = *((int16_t*)component);
367 return c;
368 }
369 case cgltf_component_type_r_16u: {
370 const uint16_t c = *((uint16_t*)component);
371 return c;
372 }
373 case cgltf_component_type_r_32u: {
374 const uint32_t c = *((uint32_t*)component);
375 return c;
376 }
377 case cgltf_component_type_r_32f: {
378 const float c = *((float*)component);
379 return (int64_t)c;
380 }
381 case cgltf_component_type_invalid:
382 FAIL();
383 break;
384 }
385 FAIL();
386 return 0;
387}
388
389/// Read a float from the given data pointer and accessor.
390///
391/// This function uses the normalization equations from the spec. See the
392/// animation section:
393///
394/// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#animations
395static float read_float(const void* component, const cgltf_accessor* accessor) {
396 assert(component);
397 assert(accessor);
398
399 switch (accessor->component_type) {
400 case cgltf_component_type_r_8: {
401 // assert(accessor->normalized);
402 const int8_t c = *((int8_t*)component);
403 return max((float)c / 127.0, -1.0);
404 }
405 case cgltf_component_type_r_8u: {
406 // assert(accessor->normalized);
407 const uint8_t c = *((uint8_t*)component);
408 return (float)c / 255.0;
409 }
410 case cgltf_component_type_r_16: {
411 // assert(accessor->normalized);
412 const int16_t c = *((int16_t*)component);
413 return max((float)c / 32767.0, -1.0);
414 }
415 case cgltf_component_type_r_16u: {
416 // assert(accessor->normalized);
417 const uint16_t c = *((uint16_t*)component);
418 return (float)c / 65535.0;
419 }
420 case cgltf_component_type_r_32u: {
421 // assert(accessor->normalized);
422 const uint32_t c = *((uint32_t*)component);
423 return (float)c / 4294967295.0;
424 }
425 case cgltf_component_type_r_32f: {
426 const float c = *((float*)component);
427 return c;
428 }
429 case cgltf_component_type_invalid:
430 FAIL();
431 break;
432 }
433 FAIL();
434 return 0;
435}
436
437typedef struct AccessorIter {
438 const cgltf_accessor* accessor;
439 const uint8_t* next_element;
440 cgltf_size comp_size; // Component size in bytes.
441 cgltf_size stride; // ELement stride in bytes.
442 cgltf_size index; // Index of the next element.
443 bool is_matrix;
444} AccessorIter;
445
446typedef struct AccessorData {
447 union {
448 struct {
449 float x, y, z, w; // Possibly normalized.
450 int64_t xi, yi, zi, wi; // Always unnormalized.
451 };
452 const float* floats;
453 };
454} AccessorData;
455
456bool accessor_iter_next(AccessorIter* iter, AccessorData* data) {
457 assert(iter);
458 assert(data);
459
460 if (iter->index < iter->accessor->count) {
461 const int dimensions = get_num_dimensions(iter->accessor->type);
462 const uint8_t* component = iter->next_element;
463
464 // So that the caller can access the element's components as an array.
465 data->floats = (const float*)component;
466
467 if (!iter->is_matrix) { // Scalar or vector.
468 // x
469 data->x = read_float(component, iter->accessor);
470 data->xi = read_int(component, iter->accessor);
471 component += iter->comp_size;
472 // y
473 if (dimensions > 1) {
474 data->y = read_float(component, iter->accessor);
475 data->yi = read_int(component, iter->accessor);
476 component += iter->comp_size;
477 }
478 // z
479 if (dimensions > 2) {
480 data->z = read_float(component, iter->accessor);
481 data->zi = read_int(component, iter->accessor);
482 component += iter->comp_size;
483 }
484 // w
485 if (dimensions > 3) {
486 data->w = read_float(component, iter->accessor);
487 data->wi = read_int(component, iter->accessor);
488 component += iter->comp_size;
489 }
490 }
491
492 iter->next_element += iter->stride;
493 iter->index++;
494 return true;
495 }
496
497 return false;
498}
499
500AccessorIter make_accessor_iter(const cgltf_accessor* accessor) {
501 assert(accessor);
502
503 const bool is_matrix = (accessor->type == cgltf_type_mat2) ||
504 (accessor->type == cgltf_type_mat3) ||
505 (accessor->type == cgltf_type_mat4);
506
507 const int dimensions = get_num_dimensions(accessor->type);
508 assert(
509 ((dimensions == 1) && (accessor->type == cgltf_type_scalar)) ||
510 ((dimensions == 2) && (accessor->type == cgltf_type_vec2)) ||
511 ((dimensions == 3) && (accessor->type == cgltf_type_vec3)) ||
512 ((dimensions == 4) && (accessor->type == cgltf_type_vec4)) ||
513 ((dimensions == 4) && (accessor->type == cgltf_type_mat2)) ||
514 ((dimensions == 9) && (accessor->type == cgltf_type_mat3)) ||
515 ((dimensions == 16) && (accessor->type == cgltf_type_mat4)));
516
517 const cgltf_buffer_view* view = accessor->buffer_view;
518 const cgltf_buffer* buffer = view->buffer;
519 const cgltf_size offset = accessor->offset + view->offset;
520 const uint8_t* bytes = (const uint8_t*)buffer->data + offset;
521 // Component size in bytes.
522 const cgltf_size comp_size = get_component_size(accessor->component_type);
523 // Element size in bytes.
524 const cgltf_size elem_size = dimensions * comp_size;
525 // Stride in bytes. If the view stride is 0, then the elements are tightly
526 // packed.
527 const cgltf_size stride = view->stride != 0 ? view->stride : elem_size;
528
529 // There isn't an accessor stride in the spec, but cgltf still specifies one.
530 assert(accessor->stride == elem_size);
531
532 // Accessor data must fit inside the view.
533 assert(accessor->offset + (accessor->count * accessor->stride) <= view->size);
534
535 // Accessor data must fit inside the buffer.
536 assert(
537 (offset + (accessor->count * elem_size) +
538 ((accessor->count - 1) * view->stride)) <= buffer->size);
539
540 return (AccessorIter){
541 .accessor = accessor,
542 .next_element = bytes,
543 .comp_size = comp_size,
544 .stride = stride,
545 .index = 0,
546 .is_matrix = is_matrix,
547 };
548}
549
550/// Return the total number of primitives in the scene. Each mesh may contain
551/// multiple primitives.
552///
553/// Note that this function scans all of the scenes in the glTF data.
554static size_t get_total_primitives(const cgltf_data* data) {
555 size_t total = 0;
556 for (cgltf_size i = 0; i < data->meshes_count; ++i) {
557 total += data->meshes[i].primitives_count;
558 }
559 return total;
560}
561
562/// Load all buffers from the glTF scene.
563///
564/// If buffer data is loaded from memory, set filepath = null.
565///
566/// Return an array of Buffers such that the index of each glTF buffer in the
567/// original array matches the same Buffer in the resulting array.
568///
569/// TODO: There is no need to load the inverse bind matrices buffer into the
570/// GPU. Might need to lazily load buffers.
571static bool load_buffers(
572 const cgltf_data* data, GfxCore* gfxcore, Buffer** buffers) {
573 assert(data);
574 assert(gfxcore);
575 assert(buffers);
576
577 for (cgltf_size i = 0; i < data->buffers_count; ++i) {
578 const cgltf_buffer* buffer = &data->buffers[i];
579 assert(buffer->data);
580 buffers[i] = gfx_make_buffer(
581 gfxcore, &(BufferDesc){
582 .usage = BufferStatic,
583 .type = BufferUntyped,
584 .data.data = buffer->data,
585 .data.count = buffer->size});
586 if (!buffers[i]) {
587 return false;
588 }
589 }
590
591 return true;
592}
593
594/// Load tangent buffers.
595static bool load_tangent_buffers(
596 const cgltfTangentBuffer* cgltf_tangent_buffers,
597 cgltf_size num_tangent_buffers, GfxCore* gfxcore,
598 Buffer** tangent_buffers) {
599 assert(cgltf_tangent_buffers);
600 assert(gfxcore);
601 assert(tangent_buffers);
602
603 for (cgltf_size i = 0; i < num_tangent_buffers; ++i) {
604 const cgltfTangentBuffer* buffer = &cgltf_tangent_buffers[i];
605 assert(buffer->data);
606 tangent_buffers[i] = gfx_make_buffer(
607 gfxcore, &(BufferDesc){
608 .usage = BufferStatic,
609 .type = BufferUntyped,
610 .data.data = buffer->data,
611 .data.count = buffer->size_bytes});
612 if (!tangent_buffers[i]) {
613 return false;
614 }
615 }
616
617 return true;
618}
619
620/// Lazily load all textures from the glTF scene.
621///
622/// Colour textures like albedo are in sRGB colour space. Non-colour textures
623/// like normal maps are in linear space (e.g. DamagedHelmet sample). Since we
624/// don't know how the texture is going to be used at this point, we can't tell
625/// what colour space it should be loaded in (ideally this would be part of the
626/// image file format, but not all formats specify colour space.) Therefore, we
627/// load the textures lazily and don't actually commit them to GPU memory until
628/// we know their colour space when loading glTF materials.
629///
630/// Return an array of LoadTextureCmds such that the index of each cmd matches
631/// the index of each glTF texture in the scene.
632static void load_textures_lazy(
633 const cgltf_data* data, GfxCore* gfxcore, const char* directory,
634 LoadTextureCmd* load_texture_cmds) {
635 assert(data);
636 assert(gfxcore);
637 assert(load_texture_cmds);
638
639 for (cgltf_size i = 0; i < data->textures_count; ++i) {
640 const cgltf_texture* texture = &data->textures[i];
641 const cgltf_image* image = texture->image;
642 const cgltf_sampler* sampler = texture->sampler;
643
644 // glTF models might not specify a sampler. In such case, the client can
645 // pick its own defaults.
646 // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#samplers
647 bool mipmaps = true;
648 TextureFiltering filtering = LinearFiltering;
649 TextureWrapping wrap = Repeat;
650
651 if (sampler) {
652 // The gfx library does not distinguish between sampling the texture and
653 // combining the mipmap levels.
654 const cgltf_int filter =
655 sampler->min_filter == 0 ? sampler->mag_filter : sampler->min_filter;
656
657 switch (filter) {
658 case GL_NEAREST_MIPMAP_NEAREST:
659 mipmaps = true;
660 filtering = NearestFiltering;
661 break;
662 case GL_NEAREST_MIPMAP_LINEAR:
663 case GL_LINEAR_MIPMAP_NEAREST:
664 case GL_LINEAR_MIPMAP_LINEAR:
665 mipmaps = true;
666 filtering = LinearFiltering;
667 break;
668 case GL_NEAREST:
669 filtering = NearestFiltering;
670 break;
671 case GL_LINEAR:
672 filtering = LinearFiltering;
673 break;
674 default:
675 break;
676 }
677 }
678
679 // Currently only supporting loading textures from files.
680 assert(image->uri);
681 assert(directory);
682 mstring fullpath =
683 mstring_concat_path(mstring_make(directory), mstring_make(image->uri));
684
685 load_texture_cmds[i] = (LoadTextureCmd){
686 .origin = AssetFromFile,
687 .type = LoadTexture,
688 .colour_space = sRGB,
689 .filtering = filtering,
690 .wrap = wrap,
691 .mipmaps = mipmaps,
692 .data.texture.filepath = fullpath};
693 }
694}
695
696/// Load a texture uniform.
697///
698/// This determines a texture's colour space based on its intended use, loads
699/// the texture, and then defines the sampler shader uniform.
700static bool load_texture_and_uniform(
701 const cgltf_data* data, Gfx* gfx, const cgltf_texture_view* texture_view,
702 TextureType texture_type, const Texture** textures,
703 LoadTextureCmd* load_texture_cmds, int* next_uniform, MaterialDesc* desc) {
704 assert(data);
705 assert(gfx);
706 assert(texture_view);
707 assert(textures);
708 assert(next_uniform);
709 assert(desc);
710 assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
711
712 const size_t texture_index = texture_view->texture - data->textures;
713 assert(texture_index < data->textures_count);
714
715 // Here we are assuming that if a texture is re-used, it is re-used with the
716 // same texture view. This should be fine because, e.g., a normal map would
717 // not be used as albedo and vice versa.
718 if (!textures[texture_index]) {
719 LoadTextureCmd* cmd = &load_texture_cmds[texture_index];
720 // TODO: Check for colour textures and default to LinearColourSpace instead.
721 if (texture_type == NormalMap) {
722 cmd->colour_space = LinearColourSpace;
723 }
724
725 LOGD(
726 "Load texture: %s (mipmaps: %d, filtering: %d)",
727 mstring_cstr(&cmd->data.texture.filepath), cmd->mipmaps,
728 cmd->filtering);
729
730 textures[texture_index] = gfx_load_texture(gfx, cmd);
731 if (!textures[texture_index]) {
732 log_error(
733 "Failed to load texture: %s",
734 mstring_cstr(&cmd->data.texture.filepath));
735 return false;
736 }
737 }
738
739 assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
740 desc->uniforms[(*next_uniform)++] = (ShaderUniform){
741 .name = sstring_make(get_texture_uniform_name(texture_type)),
742 .type = UniformTexture,
743 .value.texture = textures[texture_index]};
744
745 return true;
746}
747
748/// Load all materials from the glTF scene.
749///
750/// Return an array of Materials such that the index of each descriptor matches
751/// the index of each glTF material in the scene. Also return the number of
752/// materials and the textures used by them.
753static bool load_materials(
754 const cgltf_data* data, Gfx* gfx, LoadTextureCmd* load_texture_cmds,
755 const Texture** textures, Material** materials) {
756 assert(data);
757 assert(gfx);
758 assert(materials);
759 if (data->textures_count > 0) {
760 assert(load_texture_cmds);
761 assert(textures);
762 }
763
764 for (cgltf_size i = 0; i < data->materials_count; ++i) {
765 const cgltf_material* mat = &data->materials[i];
766
767 int next_uniform = 0;
768 MaterialDesc desc = {0};
769
770 // TODO: specular/glossiness and other material parameters.
771 if (mat->has_pbr_metallic_roughness) {
772 const cgltf_pbr_metallic_roughness* pbr = &mat->pbr_metallic_roughness;
773
774 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
775 desc.uniforms[next_uniform++] = (ShaderUniform){
776 .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR),
777 .type = UniformVec4,
778 .value.vec4 = vec4_from_array(pbr->base_color_factor)};
779
780 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
781 desc.uniforms[next_uniform++] = (ShaderUniform){
782 .name = sstring_make(UNIFORM_METALLIC_FACTOR),
783 .type = UniformFloat,
784 .value.scalar = pbr->metallic_factor};
785
786 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
787 desc.uniforms[next_uniform++] = (ShaderUniform){
788 .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR),
789 .type = UniformFloat,
790 .value.scalar = pbr->roughness_factor};
791
792 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
793 desc.uniforms[next_uniform++] = (ShaderUniform){
794 .name = sstring_make(UNIFORM_EMISSIVE_FACTOR),
795 .type = UniformVec3,
796 .value.vec3 = vec3_from_array(mat->emissive_factor)};
797
798 if (pbr->base_color_texture.texture) {
799 if (!load_texture_and_uniform(
800 data, gfx, &pbr->base_color_texture, BaseColorTexture, textures,
801 load_texture_cmds, &next_uniform, &desc)) {
802 return false;
803 }
804 }
805
806 if (pbr->metallic_roughness_texture.texture) {
807 if (!load_texture_and_uniform(
808 data, gfx, &pbr->metallic_roughness_texture,
809 MetallicRoughnessTexture, textures, load_texture_cmds,
810 &next_uniform, &desc)) {
811 return false;
812 }
813 }
814 }
815
816 if (mat->emissive_texture.texture) {
817 if (!load_texture_and_uniform(
818 data, gfx, &mat->emissive_texture, EmissiveTexture, textures,
819 load_texture_cmds, &next_uniform, &desc)) {
820 return false;
821 }
822 }
823
824 if (mat->occlusion_texture.texture) {
825 if (!load_texture_and_uniform(
826 data, gfx, &mat->occlusion_texture, AmbientOcclusionTexture,
827 textures, load_texture_cmds, &next_uniform, &desc)) {
828 return false;
829 }
830 }
831
832 if (mat->normal_texture.texture) {
833 if (!load_texture_and_uniform(
834 data, gfx, &mat->normal_texture, NormalMap, textures,
835 load_texture_cmds, &next_uniform, &desc)) {
836 return false;
837 }
838 }
839
840 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
841 desc.num_uniforms = next_uniform;
842
843 materials[i] = gfx_make_material(&desc);
844 if (!materials[i]) {
845 return false;
846 }
847 }
848
849 return true;
850}
851
852/// Create a default material for meshes that do not have a material.
853static Material* make_default_material() {
854 MaterialDesc desc = (MaterialDesc){0};
855
856 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
857 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){
858 .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR),
859 .type = UniformVec4,
860 .value.vec4 = vec4_make(1, 1, 1, 1)};
861
862 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
863 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){
864 .name = sstring_make(UNIFORM_METALLIC_FACTOR),
865 .type = UniformFloat,
866 .value.scalar = 0};
867
868 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
869 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){
870 .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR),
871 .type = UniformFloat,
872 .value.scalar = 1};
873
874 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
875 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){
876 .name = sstring_make(UNIFORM_EMISSIVE_FACTOR),
877 .type = UniformVec3,
878 .value.vec3 = vec3_make(0, 0, 0)};
879
880 return gfx_make_material(&desc);
881}
882
883/// Compute the bounding box of the vertices pointed to by the accessor.
884/// 'dim' is the dimension of the vertices (2D or 3D).
885aabb3 compute_aabb(const cgltf_accessor* accessor) {
886 aabb3 box = {0};
887 if (accessor->has_min && accessor->has_max) {
888 box = aabb3_make(
889 vec3_from_array(accessor->min), vec3_from_array(accessor->max));
890 } else {
891 AccessorIter iter = make_accessor_iter(accessor);
892 AccessorData vertex = {0};
893 cgltf_size i = 0;
894
895 while (accessor_iter_next(&iter, &vertex)) {
896 const vec3 p = vec3_make(vertex.x, vertex.y, vertex.z);
897 if (i == 0) {
898 box = aabb3_make(p, p);
899 } else {
900 box = aabb3_add(box, p);
901 }
902 ++i;
903 }
904 }
905 return box;
906}
907
908/// Load all meshes from the glTF scene.
909static bool load_meshes(
910 const cgltf_data* data, GfxCore* gfxcore, Buffer** buffers,
911 Buffer** tangent_buffers, const cgltfTangentBuffer* cgltf_tangent_buffers,
912 cgltf_size num_tangent_buffers, Material** materials,
913 ShaderProgram* const shader, size_t primitive_count, Geometry** geometries,
914 Mesh** meshes, SceneObject** scene_objects) {
915 // Walk through the mesh primitives to create Meshes. A GLTF mesh primitive
916 // has a material (Mesh) and vertex data (Geometry). A GLTF mesh maps to
917 // a SceneObject.
918 //
919 // glTF gfx
920 // ---- ---
921 // Mesh SceneObject
922 // Mesh primitive Mesh / Geometry
923 // Accessor + buffer view BufferView
924 // Buffer Buffer
925 assert(data);
926 assert(gfxcore);
927 assert(buffers);
928 assert(materials);
929 assert(geometries);
930 assert(meshes);
931 assert(scene_objects);
932 if (num_tangent_buffers > 0) {
933 assert(tangent_buffers);
934 assert(cgltf_tangent_buffers);
935 }
936
937 // Points to the next available Mesh and also the next available Geometry.
938 // There is one (Mesh, Geometry) pair per glTF mesh primitive.
939 size_t next_mesh = 0;
940
941 for (cgltf_size m = 0; m < data->meshes_count; ++m) {
942 const cgltf_mesh* mesh = &data->meshes[m];
943
944 ObjectDesc object_desc = {0};
945
946 for (cgltf_size p = 0; p < mesh->primitives_count; ++p) {
947 assert(next_mesh < primitive_count);
948 const cgltf_primitive* prim = &mesh->primitives[p];
949 const cgltf_material* mat = prim->material;
950
951 MeshPermutation perm = {0};
952 if (mat) {
953 perm.has_normal_map = mat->normal_texture.texture != 0;
954 perm.has_occlusion_map = mat->occlusion_texture.texture != 0;
955 perm.has_emissive_map = mat->emissive_texture.texture != 0;
956
957 if (mat->has_pbr_metallic_roughness) {
958 const cgltf_pbr_metallic_roughness* pbr =
959 &mat->pbr_metallic_roughness;
960 perm.has_albedo_map = pbr->base_color_texture.texture != 0;
961 perm.has_metallic_roughness_map =
962 pbr->metallic_roughness_texture.texture != 0;
963 } else {
964 // TODO: specular/glossiness and other material parameters.
965 }
966 }
967
968 GeometryDesc geometry_desc = {
969 .type = from_gltf_primitive_type(prim->type),
970 .buffer_usage = BufferStatic};
971
972 // Vertex indices.
973 if (prim->indices) {
974 const cgltf_accessor* accessor = prim->indices;
975 const cgltf_buffer_view* view = prim->indices->buffer_view;
976 const cgltf_size buffer_index = view->buffer - data->buffers;
977
978 assert(buffer_index < data->buffers_count);
979 Buffer* buffer = buffers[buffer_index];
980
981 const cgltf_size component_size =
982 get_component_size(accessor->component_type);
983 switch (component_size) {
984 case 1: {
985 BufferViewIdx8* indices = &geometry_desc.indices8;
986 // TODO: discards const qualifier.
987 indices->buffer = buffer;
988 indices->offset_bytes = accessor->offset + view->offset;
989 indices->size_bytes = view->size;
990 indices->stride_bytes = view->stride;
991 geometry_desc.num_indices = prim->indices->count;
992 break;
993 }
994 case 2: {
995 BufferViewIdx16* indices = &geometry_desc.indices16;
996 indices->buffer = buffer;
997 indices->offset_bytes = accessor->offset + view->offset;
998 indices->size_bytes = view->size;
999 indices->stride_bytes = view->stride;
1000 geometry_desc.num_indices = prim->indices->count;
1001 break;
1002 }
1003 default:
1004 // TODO: Handle 32-bit indices.
1005 assert(false);
1006 break;
1007 }
1008 }
1009
1010 // Vertex attributes.
1011 for (cgltf_size a = 0; a < prim->attributes_count; ++a) {
1012 const cgltf_attribute* attrib = &prim->attributes[a];
1013 const cgltf_accessor* accessor = attrib->data;
1014 const cgltf_buffer_view* view = accessor->buffer_view;
1015 const cgltf_size offset = accessor->offset + view->offset;
1016 const cgltf_size buffer_index = view->buffer - data->buffers;
1017
1018 assert(buffer_index < data->buffers_count);
1019 Buffer* buffer = buffers[buffer_index];
1020
1021 BufferView2d* buffer_view_2d = 0;
1022 BufferView3d* buffer_view_3d = 0;
1023 BufferView4d* buffer_view_4d = 0;
1024 BufferViewFloat* buffer_view_float = 0;
1025 BufferViewU8* buffer_view_u8 = 0;
1026 BufferViewU16* buffer_view_u16 = 0;
1027
1028 switch (attrib->type) {
1029 case cgltf_attribute_type_position: {
1030 switch (accessor->type) {
1031 case cgltf_type_vec2:
1032 assert(geometry_desc.positions3d.buffer == 0);
1033 buffer_view_2d = &geometry_desc.positions2d;
1034 geometry_desc.aabb = compute_aabb(accessor);
1035 break;
1036 case cgltf_type_vec3:
1037 assert(geometry_desc.positions2d.buffer == 0);
1038 buffer_view_3d = &geometry_desc.positions3d;
1039 geometry_desc.aabb = compute_aabb(accessor);
1040 break;
1041 default:
1042 FAIL(
1043 "Unhandled accessor type %d in vertex positions",
1044 accessor->type);
1045 assert(false);
1046 return false;
1047 }
1048 // It is assumed that meshes have positions, so there is nothing to
1049 // do for the mesh permutation in this case.
1050 break;
1051 }
1052 case cgltf_attribute_type_normal:
1053 buffer_view_3d = &geometry_desc.normals;
1054 perm.has_normals = true;
1055 break;
1056 case cgltf_attribute_type_tangent:
1057 buffer_view_4d = &geometry_desc.tangents;
1058 perm.has_tangents = true;
1059 break;
1060 case cgltf_attribute_type_texcoord:
1061 buffer_view_2d = &geometry_desc.texcoords;
1062 perm.has_texcoords = true;
1063 break;
1064 case cgltf_attribute_type_color:
1065 // TODO: Add support for color.
1066 break;
1067 case cgltf_attribute_type_joints:
1068 // Joints can be either u8 or u16.
1069 switch (accessor->component_type) {
1070 case cgltf_component_type_r_8u:
1071 buffer_view_u8 = &geometry_desc.joints.u8;
1072 break;
1073 case cgltf_component_type_r_16u:
1074 buffer_view_u16 = &geometry_desc.joints.u16;
1075 break;
1076 default:
1077 assert(false);
1078 return false;
1079 }
1080 perm.has_joints = true;
1081 break;
1082 case cgltf_attribute_type_weights:
1083 // Weights can be either u8, u16, or float.
1084 switch (accessor->component_type) {
1085 case cgltf_component_type_r_8u:
1086 buffer_view_u8 = &geometry_desc.weights.u8;
1087 break;
1088 case cgltf_component_type_r_16u:
1089 buffer_view_u16 = &geometry_desc.weights.u16;
1090 break;
1091 case cgltf_component_type_r_32f:
1092 buffer_view_float = &geometry_desc.weights.floats;
1093 break;
1094 default:
1095 assert(false);
1096 return false;
1097 }
1098 perm.has_weights = true;
1099 break;
1100 case cgltf_attribute_type_invalid:
1101 assert(false);
1102 break;
1103 }
1104
1105#define CONFIGURE_BUFFER(buf) \
1106 if (buf) { \
1107 buf->buffer = buffer; \
1108 buf->offset_bytes = offset; \
1109 buf->size_bytes = view->size; \
1110 buf->stride_bytes = view->stride; \
1111 }
1112 CONFIGURE_BUFFER(buffer_view_2d);
1113 CONFIGURE_BUFFER(buffer_view_3d);
1114 CONFIGURE_BUFFER(buffer_view_4d);
1115 CONFIGURE_BUFFER(buffer_view_u8);
1116 CONFIGURE_BUFFER(buffer_view_u16);
1117 CONFIGURE_BUFFER(buffer_view_float);
1118 } // Vertex attributes.
1119
1120 assert(
1121 (perm.has_joints && perm.has_weights) ||
1122 (!perm.has_joints && !perm.has_weights));
1123
1124 // If the mesh primitive has no tangents, see if they were computed
1125 // separately.
1126 if (!geometry_desc.tangents.buffer) {
1127 for (cgltf_size t = 0; t < num_tangent_buffers; ++t) {
1128 const cgltfTangentBuffer* cgltf_buffer = &cgltf_tangent_buffers[t];
1129
1130 if (cgltf_buffer->primitive == prim) {
1131 BufferView4d* view = &geometry_desc.tangents;
1132 view->buffer = tangent_buffers[t];
1133 view->offset_bytes = 0;
1134 view->size_bytes = cgltf_buffer->size_bytes;
1135 view->stride_bytes = 0; // Tightly packed.
1136 break;
1137 }
1138 }
1139 }
1140
1141 // Set the number of vertices in the geometry. Since a geometry can have
1142 // either 2d or 3d positions but not both, here we can perform addition
1143 // to compute the total number of vertices.
1144 geometry_desc.num_verts =
1145 (geometry_desc.positions2d.size_bytes / sizeof(vec2)) +
1146 (geometry_desc.positions3d.size_bytes / sizeof(vec3));
1147
1148#define CHECK_COUNT(buffer_view, type, num_components) \
1149 if (geometry_desc.buffer_view.buffer) { \
1150 assert( \
1151 (geometry_desc.buffer_view.size_bytes / \
1152 (num_components * sizeof(type))) == geometry_desc.num_verts); \
1153 }
1154
1155 // Check that the number of vertices is consistent across all vertex
1156 // attributes.
1157 CHECK_COUNT(normals, vec3, 1);
1158 CHECK_COUNT(tangents, vec4, 1);
1159 CHECK_COUNT(texcoords, vec2, 1);
1160 CHECK_COUNT(joints.u8, uint8_t, 4);
1161 CHECK_COUNT(joints.u16, uint16_t, 4);
1162 CHECK_COUNT(weights.u8, uint8_t, 4);
1163 CHECK_COUNT(weights.u16, uint16_t, 4);
1164 CHECK_COUNT(weights.floats, float, 4);
1165
1166 Material* material = 0;
1167 if (mat) {
1168 const cgltf_size material_index = mat - data->materials;
1169 assert(material_index < data->materials_count);
1170 material = materials[material_index];
1171 } else {
1172 // Create a default material for meshes that do not specify one.
1173 material = make_default_material();
1174 }
1175 assert(material);
1176
1177 geometries[next_mesh] = gfx_make_geometry(gfxcore, &geometry_desc);
1178 if (!geometries[next_mesh]) {
1179 return false;
1180 }
1181
1182 // If the user specifies a custom shader, use that instead. Otherwise
1183 // compile a shader based on the mesh's permutation.
1184 //
1185 // Note that Gfx takes care of caching shaders and shader programs.
1186 //
1187 // Caching materials could be useful, but, provided they can share
1188 // shaders, the renderer can check later whether uniforms have the same
1189 // values. Also, changing uniforms is much faster than swapping shaders,
1190 // so shader caching is the most important thing here.
1191 ShaderProgram* mesh_shader =
1192 shader ? shader : make_shader_permutation(gfxcore, perm);
1193 assert(mesh_shader);
1194
1195 meshes[next_mesh] = gfx_make_mesh(&(MeshDesc){
1196 .geometry = geometries[next_mesh],
1197 .material = material,
1198 .shader = mesh_shader});
1199
1200 if (!meshes[next_mesh]) {
1201 return false;
1202 }
1203
1204 assert(object_desc.num_meshes < GFX_MAX_NUM_MESHES);
1205 object_desc.meshes[object_desc.num_meshes] = meshes[next_mesh];
1206 object_desc.num_meshes++;
1207
1208 ++next_mesh;
1209 } // glTF mesh primitive / gfx Mesh.
1210
1211 scene_objects[m] = gfx_make_object(&object_desc);
1212 if (!scene_objects[m]) {
1213 return false;
1214 }
1215 } // glTF mesh / gfx SceneObject.
1216
1217 return true;
1218}
1219
1220/// Compute bounding boxes for the joints in the model.
1221static void compute_joint_bounding_boxes(
1222 const cgltf_data* data, size_t num_joints, JointDesc* joint_descs) {
1223 assert(data);
1224 assert(joint_descs);
1225 assert(num_joints <= GFX_MAX_NUM_JOINTS);
1226
1227 // Initialize bounding boxes so that we can compute unions below.
1228 for (size_t i = 0; i < num_joints; ++i) {
1229 joint_descs[i].box = aabb3_make_empty();
1230 }
1231
1232 // Iterate over the meshes -> primitives -> vertices -> joint indices, and add
1233 // the vertex to the joint's bounding box.
1234 for (cgltf_size n = 0; n < data->nodes_count; ++n) {
1235 const cgltf_node* node = &data->nodes[n];
1236
1237 if (node->skin) {
1238 if (node->mesh) {
1239 const cgltf_mesh* mesh = node->mesh;
1240
1241 for (cgltf_size pr = 0; pr < mesh->primitives_count; ++pr) {
1242 const cgltf_primitive* prim = &mesh->primitives[pr];
1243
1244 // Find the indices of the positions and joints arrays in the
1245 // primitive's attributes.
1246 int positions_index = -1;
1247 int joints_index = -1;
1248 for (int a = 0; a < (int)prim->attributes_count; ++a) {
1249 const cgltf_attribute* attrib = &prim->attributes[a];
1250
1251 if (attrib->type == cgltf_attribute_type_position) {
1252 positions_index = a;
1253 } else if (attrib->type == cgltf_attribute_type_joints) {
1254 joints_index = a;
1255 }
1256 }
1257
1258 if ((positions_index != -1) && (joints_index != -1)) {
1259 const cgltf_accessor* positions =
1260 prim->attributes[positions_index].data;
1261 const cgltf_accessor* joints = prim->attributes[joints_index].data;
1262
1263 assert(positions->count == joints->count);
1264
1265 AccessorIter positions_iter = make_accessor_iter(positions);
1266 AccessorIter joints_iter = make_accessor_iter(joints);
1267 AccessorData position = {0}, joint = {0};
1268
1269 while (accessor_iter_next(&positions_iter, &position)) {
1270 const bool advance = accessor_iter_next(&joints_iter, &joint);
1271 assert(advance); // Counts should match.
1272
1273 const vec3 p = vec3_make(position.x, position.y, position.z);
1274 const int64_t j[4] = {joint.xi, joint.yi, joint.wi, joint.zi};
1275
1276 for (int i = 0; i < 4; ++i) {
1277 const size_t joint_index = j[i];
1278 assert((size_t)joint_index < num_joints);
1279
1280 joint_descs[joint_index].box =
1281 aabb3_add(joint_descs[joint_index].box, p);
1282 }
1283 }
1284 }
1285 }
1286 }
1287 }
1288 }
1289}
1290
1291/// Find the joint node with the smallest index across all skeletons.
1292///
1293/// The channels in glTF may target arbitrary nodes in the scene (those nodes
1294/// are the joints). However, we want to map the "base joint" (the joint/node
1295/// with the smallest index) to 0 in the AnimaDesc's joint array. We can do this
1296/// by subtracting the "base node index" from every joint index or channel
1297/// target.
1298///
1299/// There is an assumption in the animation library that joints are contiguous
1300/// anyway, so this "base joint index" works provided the joint nodes are also
1301/// contiguous in the glTF. The glTF does not guarantee this, but I think it's
1302/// a reasonable assumption that exporters write glTF files in such a way, and
1303/// Blender does appear to do so.
1304cgltf_size find_base_joint_index(const cgltf_data* data) {
1305 assert(data);
1306
1307 cgltf_size base_joint_index = (cgltf_size)-1;
1308
1309 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1310 const cgltf_skin* skin = &data->skins[s];
1311 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1312 // Joint is an index/pointer into the nodes array.
1313 const cgltf_size node_index = skin->joints[j] - data->nodes;
1314 assert(node_index < data->nodes_count);
1315 // Min.
1316 if (node_index < base_joint_index) {
1317 base_joint_index = node_index;
1318 }
1319 }
1320 }
1321
1322 return base_joint_index;
1323}
1324
1325/// Load all skins (Gfx skeletons) from the glTF scene.
1326/// Return the total number of joints.
1327static size_t load_skins(
1328 const cgltf_data* data, Buffer* const* buffers, cgltf_size base_joint_index,
1329 AnimaDesc* anima_desc) {
1330 assert(data);
1331 assert(buffers);
1332 assert(anima_desc);
1333 assert(base_joint_index < data->nodes_count);
1334
1335 // Determines whether the ith joint in the node hierarchy is a joint node.
1336 // This is then used to determine whether a joint is a root of the joint
1337 // hierarchy.
1338 bool is_joint_node[GFX_MAX_NUM_JOINTS] = {false};
1339
1340 size_t num_joints = 0;
1341
1342 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1343 const cgltf_skin* skin = &data->skins[s];
1344 const cgltf_accessor* matrices_accessor = skin->inverse_bind_matrices;
1345 assert(matrices_accessor->count == skin->joints_count);
1346
1347 num_joints += skin->joints_count;
1348 assert(num_joints < GFX_MAX_NUM_JOINTS);
1349
1350 SkeletonDesc* skeleton_desc = &anima_desc->skeletons[s];
1351 *skeleton_desc = (SkeletonDesc){.num_joints = skin->joints_count};
1352
1353 // for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1354 AccessorIter iter = make_accessor_iter(matrices_accessor);
1355 AccessorData matrix = {0};
1356 for (cgltf_size i = 0; accessor_iter_next(&iter, &matrix); ++i) {
1357 const mat4 inv_bind_matrix = mat4_from_array(matrix.floats);
1358
1359 // Joint is an index/pointer into the nodes array.
1360 const cgltf_size node_index = skin->joints[i] - data->nodes;
1361 assert(node_index < data->nodes_count);
1362
1363 const cgltf_size parent_node_index =
1364 skin->joints[i]->parent - data->nodes;
1365 assert(parent_node_index < data->nodes_count);
1366
1367 // Subtract the base index to pack the joints as tightly as possible in
1368 // the AnimaDesc.
1369 assert(node_index >= base_joint_index);
1370 const cgltf_size joint_index = node_index - base_joint_index;
1371
1372 assert(parent_node_index >= base_joint_index);
1373 const cgltf_size parent_index = parent_node_index - base_joint_index;
1374
1375 skeleton_desc->joints[i] = joint_index;
1376
1377 JointDesc* joint_desc = &anima_desc->joints[joint_index];
1378 joint_desc->parent = parent_index;
1379 joint_desc->inv_bind_matrix = inv_bind_matrix;
1380
1381 is_joint_node[joint_index] = true;
1382 };
1383
1384 // glTF may specify a "skeleton", which is the root of the skin's
1385 // (skeleton's) node hierarchy.
1386 // if (skin->skeleton) {
1387 // // cgltf_size root_index = skin->skeleton - data->nodes;
1388 // // assert(root_index <= data->nodes_count);
1389 // // root_node = nodes[root_index];
1390 // assert(false);
1391 //}
1392 }
1393
1394 // Animation library assumes that joints are contiguous.
1395 for (size_t i = 0; i < num_joints; ++i) {
1396 assert(is_joint_node[i]);
1397 }
1398
1399 // Insert the root joint.
1400 // This is the root of all skeletons. It is, specifically, the root of all
1401 // joints that do not have a parent; skins (skeletons) in glTF are not
1402 // guaranteed to have a common parent, but are generally a set of disjoint
1403 // trees.
1404 const size_t root_index = num_joints;
1405 assert(root_index < GFX_MAX_NUM_JOINTS);
1406 anima_desc->joints[root_index] = (JointDesc){.parent = INDEX_NONE};
1407 num_joints++;
1408
1409 // Make root joints point to the root joint at index N.
1410 // The root joints are the ones that have a non-joint node in the glTF as a
1411 // parent.
1412 for (size_t i = 0; i < root_index; ++i) {
1413 JointDesc* joint = &anima_desc->joints[i];
1414 if ((joint->parent >= root_index) || !is_joint_node[joint->parent]) {
1415 joint->parent = root_index;
1416 }
1417 }
1418
1419 return num_joints;
1420}
1421
1422/// Load all animations from the glTF scene.
1423static void load_animations(
1424 const cgltf_data* data, cgltf_size base_joint_index,
1425 AnimaDesc* anima_desc) {
1426 assert(data);
1427 assert(anima_desc);
1428 assert(base_joint_index < data->nodes_count);
1429 assert(data->animations_count <= GFX_MAX_NUM_ANIMATIONS);
1430
1431 for (cgltf_size a = 0; a < data->animations_count; ++a) {
1432 const cgltf_animation* animation = &data->animations[a];
1433 AnimationDesc* animation_desc = &anima_desc->animations[a];
1434
1435 *animation_desc = (AnimationDesc){
1436 .name = sstring_make(animation->name),
1437 .num_channels = animation->channels_count};
1438
1439 assert(animation->channels_count <= GFX_MAX_NUM_CHANNELS);
1440 for (cgltf_size c = 0; c < animation->channels_count; ++c) {
1441 const cgltf_animation_channel* channel = &animation->channels[c];
1442 ChannelDesc* channel_desc = &animation_desc->channels[c];
1443 const cgltf_animation_sampler* sampler = channel->sampler;
1444
1445 const size_t target_index = channel->target_node - data->nodes;
1446 assert(target_index < data->nodes_count);
1447
1448 assert(target_index >= base_joint_index);
1449 const size_t tight_target_index = target_index - base_joint_index;
1450 assert(tight_target_index < anima_desc->num_joints);
1451
1452 *channel_desc = (ChannelDesc){
1453 .target = tight_target_index,
1454 .type = from_gltf_animation_path_type(channel->target_path),
1455 .interpolation = from_gltf_interpolation_type(sampler->interpolation),
1456 .num_keyframes = 0};
1457
1458 // Read time inputs.
1459 AccessorIter iter = make_accessor_iter(sampler->input);
1460 AccessorData input = {0};
1461 for (cgltf_size i = 0; accessor_iter_next(&iter, &input); ++i) {
1462 channel_desc->keyframes[i].time = input.x;
1463 channel_desc->num_keyframes++;
1464 }
1465
1466 // Read transform outputs.
1467 AccessorData output = {0};
1468 switch (channel->target_path) {
1469 case cgltf_animation_path_type_translation: {
1470 iter = make_accessor_iter(sampler->output);
1471 for (cgltf_size i = 0; accessor_iter_next(&iter, &output); ++i) {
1472 channel_desc->keyframes[i].translation =
1473 vec3_make(output.x, output.y, output.z);
1474 }
1475 break;
1476 }
1477 case cgltf_animation_path_type_rotation: {
1478 iter = make_accessor_iter(sampler->output);
1479 for (cgltf_size i = 0; accessor_iter_next(&iter, &output); ++i) {
1480 channel_desc->keyframes[i].rotation =
1481 qmake(output.x, output.y, output.z, output.w);
1482 }
1483 break;
1484 }
1485 default:
1486 // TODO: Handle other channel transformations.
1487 break;
1488 }
1489 }
1490 }
1491}
1492
1493/// Load all nodes from the glTF scene.
1494///
1495/// This function ignores the many scenes and default scene of the glTF spec
1496/// and instead just loads all nodes into a single gfx Scene.
1497static void load_nodes(
1498 const cgltf_data* data, SceneNode* root_node, SceneObject** objects,
1499 SceneCamera** cameras, const Anima* anima, SceneNode** nodes) {
1500 // Note that with glTF 2.0, nodes do not form a DAG / scene graph but a
1501 // disjount union of strict trees:
1502 //
1503 // "For Version 2.0 conformance, the glTF node hierarchy is not a directed
1504 // acyclic graph (DAG) or scene graph, but a disjoint union of strict trees.
1505 // That is, no node may be a direct descendant of more than one node. This
1506 // restriction is meant to simplify implementation and facilitate
1507 // conformance."
1508 //
1509 // This matches the gfx library implementation, where every node can have at
1510 // most one parent.
1511 assert(data);
1512 assert(root_node);
1513 assert(objects);
1514 assert(cameras);
1515 assert(nodes);
1516
1517 cgltf_size next_camera = 0;
1518
1519 for (cgltf_size n = 0; n < data->nodes_count; ++n) {
1520 const cgltf_node* node = &data->nodes[n];
1521
1522 // Add SceneObject, SceneCamera or Lights.
1523 // TODO: Handle lights once they are implemented in the gfx library.
1524 if (node->mesh) {
1525 const cgltf_size mesh_index = node->mesh - data->meshes;
1526 assert(mesh_index < data->meshes_count);
1527 SceneObject* object = objects[mesh_index];
1528 gfx_construct_object_node(nodes[n], object);
1529
1530 if (node->skin) {
1531 assert(anima);
1532
1533 const cgltf_size skin_index = node->skin - data->skins;
1534 assert(skin_index < data->skins_count);
1535 const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index);
1536 gfx_set_object_skeleton(object, skeleton);
1537 }
1538 } else if (node->camera) {
1539 assert(next_camera < data->cameras_count);
1540
1541 Camera camera;
1542 const cgltf_camera* cam = node->camera;
1543
1544 // TODO: We could define a function load_cameras() the same way we load
1545 // every mesh and then remove this ad-hoc loading of cameras here, as well
1546 // as remove 'next_camera'.
1547 switch (cam->type) {
1548 case cgltf_camera_type_orthographic:
1549 camera = camera_orthographic(
1550 0, cam->data.orthographic.xmag, 0, cam->data.orthographic.ymag,
1551 cam->data.orthographic.znear, cam->data.orthographic.zfar);
1552 break;
1553 case cgltf_camera_type_perspective:
1554 camera = camera_perspective(
1555 cam->data.perspective.yfov, cam->data.perspective.aspect_ratio,
1556 cam->data.perspective.znear, cam->data.perspective.zfar);
1557 break;
1558 case cgltf_camera_type_invalid:
1559 break;
1560 }
1561
1562 gfx_set_camera_camera(cameras[next_camera], &camera);
1563 gfx_construct_camera_node(nodes[n], cameras[next_camera]);
1564 ++next_camera;
1565 } else {
1566 // TODO: implementation for missing node types.
1567 // These nodes currently default to logical nodes.
1568 }
1569 assert(nodes[n]);
1570
1571 // Set transform.
1572 mat4 transform;
1573 if (node->has_matrix) {
1574 transform = mat4_from_array(node->matrix);
1575 } else {
1576 transform = mat4_id();
1577 if (node->has_scale) {
1578 const mat4 scale = mat4_scale(vec3_from_array(node->scale));
1579 transform = mat4_mul(transform, scale);
1580 }
1581 if (node->has_rotation) {
1582 const quat q = quat_from_array(node->rotation);
1583 const mat4 rotate = mat4_from_quat(q);
1584 transform = mat4_mul(transform, rotate);
1585 }
1586 if (node->has_translation) {
1587 const mat4 translate =
1588 mat4_translate(vec3_from_array(node->translation));
1589 transform = mat4_mul(translate, transform);
1590 }
1591 }
1592 gfx_set_node_transform(nodes[n], &transform);
1593
1594 // If this is a top-level node in the glTF scene, set its parent to the
1595 // given root node.
1596 if (!node->parent) {
1597 gfx_set_node_parent(nodes[n], root_node);
1598 } else {
1599 const cgltf_size parent_index = node->parent - data->nodes;
1600 assert(parent_index < data->nodes_count);
1601 SceneNode* parent = nodes[parent_index];
1602 assert(parent);
1603 gfx_set_node_parent(nodes[n], parent);
1604 }
1605 } // SceneNode.
1606}
1607
1608/// Remove joint nodes from the Gfx Scene.
1609///
1610/// Joint nodes are not needed because joints are packed into the Anima.
1611static void remove_joint_nodes(
1612 const cgltf_data* data, SceneNode** scene_nodes) {
1613 assert(data);
1614 assert(scene_nodes);
1615
1616 // This works assuming the joint nodes are contiguous. Contiguity is checked
1617 // when loading skins. See load_skins().
1618 size_t min_joint_index = (size_t)-1;
1619 size_t max_joint_index = 0;
1620
1621 // First get the minimum and maximum indices of all joint nodes.
1622 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1623 const cgltf_skin* skin = &data->skins[s];
1624
1625 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1626 // Joint is an index/pointer into the nodes array.
1627 const cgltf_size joint_index = skin->joints[j] - data->nodes;
1628 assert(joint_index < data->nodes_count);
1629
1630 if (joint_index < min_joint_index) {
1631 min_joint_index = joint_index;
1632 }
1633 if (joint_index > max_joint_index) {
1634 max_joint_index = joint_index;
1635 }
1636 }
1637 }
1638
1639 assert(min_joint_index < data->nodes_count);
1640 assert(max_joint_index < data->nodes_count);
1641
1642 // Now walk over the joint nodes. If a joint's parent is itself not a joint
1643 // node, then that joint is a root of a joint hierarchy (skins in glTF may
1644 // have multiple roots). In such case, delete the root joint recursively.
1645 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1646 const cgltf_skin* skin = &data->skins[s];
1647
1648 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1649 // Joint is an index/pointer into the nodes array.
1650 const cgltf_size joint_index = skin->joints[j] - data->nodes;
1651 assert(joint_index < data->nodes_count);
1652
1653 const cgltf_node* joint = &data->nodes[joint_index];
1654
1655 // Parent node index.
1656 const cgltf_size parent_index = joint->parent - data->nodes;
1657 assert(parent_index < data->nodes_count);
1658
1659 // If the parent is not a joint node, recursively delete this joint node.
1660 if ((parent_index < min_joint_index) ||
1661 (parent_index > max_joint_index)) {
1662 gfx_destroy_node(&scene_nodes[joint_index]);
1663 }
1664 }
1665 }
1666}
1667
1668/// Load all scenes from the glTF file.
1669///
1670/// If the scene is loaded from memory, set filepath = null.
1671///
1672/// This function ignores the many scenes and default scene of the glTF spec
1673/// and instead just loads all scenes into a single Gfx Scene.
1674static Model* load_scene(
1675 cgltf_data* data, Gfx* gfx, const mstring* filepath, ShaderProgram* shader,
1676 const cgltfTangentBuffer* cgltf_tangent_buffers,
1677 cgltf_size num_tangent_buffers) {
1678 // In a GLTF scene, buffers can be shared among meshes, meshes among nodes,
1679 // etc. Each object is referenced by its index in the relevant array. Here we
1680 // do a button-up construction, first allocating our own graphics objects in
1681 // the same quantities and then re-using the GLTF indices to index these
1682 // arrays.
1683 //
1684 // For simplicity, this function also handles all of the cleanup. Arrays are
1685 // allocated up front, and the helper functions construct their elements. If
1686 // an error is encountered, the helper functions can simply return and this
1687 // function cleans up any intermediate objects that had been created up until
1688 // the point of failure.
1689 //
1690 // Loading animation data:
1691 // - Buffers with animation sampler data need to stay on the CPU, not
1692 // uploaded to the GPU. We could try to implement GPU animation at a later
1693 // stage.
1694 assert(data);
1695 assert(gfx);
1696 assert(filepath);
1697 assert((num_tangent_buffers == 0) || (cgltf_tangent_buffers != 0));
1698
1699 bool success = false;
1700
1701 GfxCore* gfxcore = gfx_get_core(gfx);
1702 const size_t primitive_count = get_total_primitives(data);
1703
1704 const mstring directory = mstring_dirname(*filepath);
1705 LOGD("Filepath: %s", mstring_cstr(filepath));
1706 LOGD("Directory: %s", mstring_cstr(&directory));
1707
1708 Buffer** tangent_buffers = 0;
1709 Buffer** buffers = 0;
1710 LoadTextureCmd* load_texture_cmds = 0;
1711 const Texture** textures = 0; // Textures are owned by asset cache.
1712 Material** materials = 0;
1713 Geometry** geometries = 0;
1714 Mesh** meshes = 0;
1715 AnimaDesc* anima_desc = 0;
1716 SceneObject** scene_objects = 0;
1717 SceneCamera** scene_cameras = 0;
1718 SceneNode** scene_nodes = 0;
1719 Anima* anima = 0;
1720 SceneNode* root_node = 0;
1721 Model* model = 0;
1722
1723 tangent_buffers = calloc(num_tangent_buffers, sizeof(Buffer*));
1724 buffers = calloc(data->buffers_count, sizeof(Buffer*));
1725 textures = calloc(data->textures_count, sizeof(Texture*));
1726 materials = calloc(data->materials_count, sizeof(Material*));
1727 geometries = calloc(primitive_count, sizeof(Geometry*));
1728 meshes = calloc(primitive_count, sizeof(Mesh*));
1729 scene_objects = calloc(data->meshes_count, sizeof(SceneObject*));
1730 scene_cameras = calloc(data->cameras_count, sizeof(SceneCamera**));
1731 scene_nodes = calloc(data->nodes_count, sizeof(SceneNode**));
1732 // A glTF scene does not necessarily have textures. Materials can be given
1733 // as constants, for example.
1734 if (data->textures_count > 0) {
1735 load_texture_cmds = calloc(data->textures_count, sizeof(LoadTextureCmd));
1736 }
1737
1738 if (!buffers || !tangent_buffers ||
1739 ((data->textures_count > 0) && !load_texture_cmds) || !textures ||
1740 !materials || !geometries || !meshes || !scene_objects ||
1741 !scene_cameras || !scene_nodes) {
1742 goto cleanup;
1743 }
1744
1745 if ((num_tangent_buffers > 0) &&
1746 !load_tangent_buffers(
1747 cgltf_tangent_buffers, num_tangent_buffers, gfxcore,
1748 tangent_buffers)) {
1749 goto cleanup;
1750 }
1751
1752 if (!load_buffers(data, gfxcore, buffers)) {
1753 goto cleanup;
1754 }
1755
1756 if (data->textures_count > 0) {
1757 load_textures_lazy(
1758 data, gfxcore, mstring_cstr(&directory), load_texture_cmds);
1759 }
1760
1761 if (!load_materials(data, gfx, load_texture_cmds, textures, materials)) {
1762 goto cleanup;
1763 }
1764
1765 if (!load_meshes(
1766 data, gfxcore, buffers, tangent_buffers, cgltf_tangent_buffers,
1767 num_tangent_buffers, materials, shader, primitive_count, geometries,
1768 meshes, scene_objects)) {
1769 goto cleanup;
1770 }
1771
1772 // Skins refer to nodes, and nodes may refer to skins. To break this circular
1773 // dependency, glTF defines skins in terms of node indices. We could do the
1774 // same if Gfx allowed allocating nodes contiguously in memory. For now,
1775 // create the nodes up front and use the indices of the array to map to the
1776 // node_idx.
1777 for (cgltf_size i = 0; i < data->nodes_count; ++i) {
1778 scene_nodes[i] = gfx_make_node();
1779 }
1780
1781 // Create the scene's root node.
1782 // This is an anima node if the scene has skins; otherwise it is a logical
1783 // node.
1784 root_node = gfx_make_node();
1785 if (data->skins_count > 0) {
1786 anima_desc = calloc(1, sizeof(AnimaDesc));
1787 if (!anima_desc) {
1788 goto cleanup;
1789 }
1790
1791 const cgltf_size base = find_base_joint_index(data);
1792
1793 anima_desc->num_skeletons = data->skins_count;
1794 anima_desc->num_animations = data->animations_count;
1795 anima_desc->num_joints = load_skins(data, buffers, base, anima_desc);
1796 load_animations(data, base, anima_desc);
1797
1798 compute_joint_bounding_boxes(
1799 data, anima_desc->num_joints, anima_desc->joints);
1800
1801 anima = gfx_make_anima(anima_desc);
1802 gfx_construct_anima_node(root_node, anima);
1803 }
1804
1805 // The root node becomes the root of all scene nodes.
1806 load_nodes(data, root_node, scene_objects, scene_cameras, anima, scene_nodes);
1807
1808 // Clean up scene nodes that correspond to joints in the glTF. These are
1809 // not needed anymore.
1810 if (data->skins_count > 0) {
1811 remove_joint_nodes(data, scene_nodes);
1812 }
1813
1814 model = gfx_make_model(root_node);
1815
1816 success = true;
1817
1818cleanup:
1819 // The arrays of resources are no longer needed. The resources themselves are
1820 // destroyed only if this function fails.
1821 if (tangent_buffers) {
1822 if (!success) {
1823 for (cgltf_size i = 0; i < num_tangent_buffers; ++i) {
1824 if (tangent_buffers[i]) {
1825 gfx_destroy_buffer(gfxcore, &tangent_buffers[i]);
1826 }
1827 }
1828 }
1829 free(tangent_buffers);
1830 }
1831 if (buffers) {
1832 if (!success) {
1833 for (cgltf_size i = 0; i < data->buffers_count; ++i) {
1834 if (buffers[i]) {
1835 gfx_destroy_buffer(gfxcore, &buffers[i]);
1836 }
1837 }
1838 }
1839 free(buffers);
1840 }
1841 if (load_texture_cmds) {
1842 free(load_texture_cmds);
1843 }
1844 if (textures) {
1845 free(textures);
1846 }
1847 if (materials) {
1848 if (!success) {
1849 for (cgltf_size i = 0; i < data->materials_count; ++i) {
1850 if (materials[i]) {
1851 gfx_destroy_material(&materials[i]);
1852 }
1853 }
1854 }
1855 free(materials);
1856 }
1857 if (geometries) {
1858 if (!success) {
1859 for (size_t i = 0; i < primitive_count; ++i) {
1860 if (geometries[i]) {
1861 gfx_destroy_geometry(gfxcore, &geometries[i]);
1862 }
1863 }
1864 }
1865 free(geometries);
1866 }
1867 if (meshes) {
1868 if (!success) {
1869 for (size_t i = 0; i < primitive_count; ++i) {
1870 if (meshes[i]) {
1871 gfx_destroy_mesh(&meshes[i]);
1872 }
1873 }
1874 }
1875 free(meshes);
1876 }
1877 if (anima_desc) {
1878 free(anima_desc);
1879 }
1880 if (scene_objects) {
1881 if (!success) {
1882 for (cgltf_size i = 0; i < data->meshes_count; ++i) {
1883 if (scene_objects[i]) {
1884 gfx_destroy_object(&scene_objects[i]);
1885 }
1886 }
1887 }
1888 free(scene_objects);
1889 }
1890 if (scene_cameras) {
1891 if (!success) {
1892 for (cgltf_size i = 0; i < data->cameras_count; ++i) {
1893 if (scene_cameras[i]) {
1894 gfx_destroy_camera(&scene_cameras[i]);
1895 }
1896 }
1897 }
1898 free(scene_cameras);
1899 }
1900 if (scene_nodes) {
1901 if (!success) {
1902 for (cgltf_size i = 0; i < data->nodes_count; ++i) {
1903 if (scene_nodes[i]) {
1904 gfx_destroy_node(&scene_nodes[i]);
1905 }
1906 }
1907 }
1908 free(scene_nodes);
1909 }
1910 if (!success) {
1911 if (root_node) {
1912 gfx_destroy_node(&root_node); // Node owns the anima.
1913 } else if (anima) {
1914 gfx_destroy_anima(&anima);
1915 }
1916 }
1917 return model;
1918}
1919
1920Model* gfx_model_load(Gfx* gfx, const LoadModelCmd* cmd) {
1921 assert(gfx);
1922 assert(cmd);
1923
1924 Model* model = 0;
1925
1926 cgltf_options options = {0};
1927 cgltf_data* data = NULL;
1928 cgltfTangentBuffer* tangent_buffers = 0;
1929
1930 cgltf_result result;
1931 switch (cmd->origin) {
1932 case AssetFromFile:
1933 result = cgltf_parse_file(&options, mstring_cstr(&cmd->filepath), &data);
1934 break;
1935 case AssetFromMemory:
1936 result = cgltf_parse(&options, cmd->data, cmd->size_bytes, &data);
1937 break;
1938 }
1939 if (result != cgltf_result_success) {
1940 goto cleanup;
1941 }
1942
1943 if (cmd->origin == AssetFromFile) {
1944 // Must call cgltf_load_buffers() to load buffer data.
1945 result = cgltf_load_buffers(&options, data, mstring_cstr(&cmd->filepath));
1946 if (result != cgltf_result_success) {
1947 goto cleanup;
1948 }
1949 }
1950
1951 // Compute tangents for normal-mapped models that are missing them.
1952 cgltf_size num_tangent_buffers = 0;
1953 cgltf_compute_tangents(
1954 &options, data, &tangent_buffers, &num_tangent_buffers);
1955
1956 model = load_scene(
1957 data, gfx, &cmd->filepath, cmd->shader, tangent_buffers,
1958 num_tangent_buffers);
1959
1960cleanup:
1961 if (data) {
1962 cgltf_free(data);
1963 }
1964 if (tangent_buffers) {
1965 free(tangent_buffers);
1966 }
1967 return model;
1968}
diff --git a/src/asset/model.h b/src/asset/model.h
new file mode 100644
index 0000000..d6399b1
--- /dev/null
+++ b/src/asset/model.h
@@ -0,0 +1,12 @@
1/// Load scene files.
2#pragma once
3
4#include <gfx/asset.h>
5
6typedef struct Gfx Gfx;
7typedef struct Model Model;
8
9/// Load a model.
10///
11/// Currently only supports the GLTF format.
12Model* gfx_model_load(Gfx*, const LoadModelCmd*);
diff --git a/src/asset/texture.c b/src/asset/texture.c
new file mode 100644
index 0000000..c790394
--- /dev/null
+++ b/src/asset/texture.c
@@ -0,0 +1,177 @@
1#include "texture.h"
2
3#include "gfx/core.h"
4
5#include "error.h"
6
7#define STB_IMAGE_IMPLEMENTATION
8#include "stb_image.h"
9
10#include <assert.h>
11
12static void flip_horizontally(
13 unsigned char* pixels, int width, int height, int components) {
14 assert(pixels);
15
16 for (int y = 0; y < height; ++y) {
17 for (int x = 0; x < width / 2; ++x) {
18 unsigned char* p1 = &pixels[(y * width + x) * components];
19 unsigned char* p2 = &pixels[(y * width + (width - x - 1)) * components];
20
21 for (int c = 0; c < components; ++c) {
22 unsigned char tmp = *p1;
23 *p1 = *p2;
24 *p2 = tmp;
25 p1++;
26 p2++;
27 }
28 }
29 }
30}
31
32// Note that the cubemap coordinate system uses the one in RenderMan:
33//
34// https://www.khronos.org/opengl/wiki/Cubemap_Texture
35//
36// This is what happens:
37//
38// - Cubemaps follow a left-handed coordinate system. Say, +X is right, +Y is
39// up, and +Z is forward.
40// - The texture coordinate system follow's DirectX's, so +V goes down, not up
41// like it does in OpenGL.
42//
43// For this reason, we do X and Y flips when doing cubemap textures so that we
44// can sample cubemaps as if they were given in the usual OpenGL coordinate
45// system.
46Texture* gfx_texture_load(GfxCore* gfxcore, const LoadTextureCmd* cmd) {
47 assert(gfxcore);
48 assert(cmd);
49 assert(cmd->origin == AssetFromFile || cmd->origin == AssetFromMemory);
50 assert(cmd->type == LoadTexture || cmd->type == LoadCubemap);
51
52 int width, height, components, old_components;
53 unsigned char* pixels[6] = {0};
54
55 switch (cmd->origin) {
56 case AssetFromFile:
57 switch (cmd->type) {
58 case LoadTexture: {
59 const char* filepath = mstring_cstr(&cmd->data.texture.filepath);
60 stbi_set_flip_vertically_on_load(0);
61 pixels[0] = stbi_load(filepath, &width, &height, &components, 0);
62 if (!pixels[0]) {
63 log_error("Failed to load texture file: %s", filepath);
64 }
65 break;
66 }
67 case LoadCubemap:
68 for (int i = 0; i < 6; ++i) {
69 // Flip +Y and -Y textures vertically.
70 stbi_set_flip_vertically_on_load(((i == 2) || (i == 3)) ? 1 : 0);
71 const char* filepath =
72 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_x + i);
73 stbi_uc* image_pixels =
74 stbi_load(filepath, &width, &height, &components, 0);
75 if (!image_pixels) {
76 log_error("Failed to load texture file: %s", filepath);
77 break;
78 }
79 if (i > 0 && components != old_components) {
80 log_error("All textures in a cubemap must have the same number of "
81 "components");
82 break;
83 }
84 if ((i != 2) && (i != 3)) {
85 flip_horizontally(image_pixels, width, height, components);
86 }
87 pixels[i] = image_pixels;
88 old_components = components;
89 }
90 break;
91 }
92 break;
93 case AssetFromMemory:
94 // TODO: Load textures from memory.
95 log_error("Loading textures from memory is not yet implemented");
96 return 0;
97 }
98
99 // Error out if we failed to load a texture.
100 if (!pixels[0] ||
101 (cmd->type == LoadCubemap &&
102 (!pixels[1] || !pixels[2] || !pixels[3] || !pixels[4] || !pixels[5]))) {
103 for (int i = 0; i < 6; ++i) {
104 if (pixels[i]) {
105 stbi_image_free(pixels[i]);
106 }
107 }
108 return 0;
109 }
110
111 TextureDesc desc = (TextureDesc){0};
112 desc.width = width;
113 desc.height = height;
114
115 switch (cmd->type) {
116 case LoadTexture:
117 desc.dimension = Texture2D;
118 break;
119 case LoadCubemap:
120 desc.dimension = TextureCubeMap;
121 break;
122 }
123
124 switch (components) {
125 case 3:
126 switch (cmd->colour_space) {
127 case LinearColourSpace:
128 desc.format = TextureRGB8;
129 break;
130 case sRGB:
131 desc.format = TextureSRGB8;
132 break;
133 default:
134 log_error("Unsupported texture colour space: %d", cmd->colour_space);
135 return 0;
136 }
137 break;
138 case 4:
139 switch (cmd->colour_space) {
140 case LinearColourSpace:
141 desc.format = TextureRGBA8;
142 break;
143 case sRGB:
144 desc.format = TextureSRGBA8;
145 break;
146 default:
147 log_error("Unsupported texture colour space: %d", cmd->colour_space);
148 return 0;
149 }
150 break;
151 default:
152 log_error("Unsupported number of texture components: %d", components);
153 return 0;
154 }
155
156 desc.filtering = cmd->filtering;
157 desc.mipmaps = cmd->mipmaps;
158
159 switch (cmd->type) {
160 case LoadTexture:
161 desc.data.pixels = pixels[0];
162 break;
163 case LoadCubemap:
164 for (int i = 0; i < 6; ++i) {
165 *(&desc.data.cubemap.pixels_pos_x + i) = pixels[i];
166 }
167 break;
168 }
169
170 Texture* texture = gfx_make_texture(gfxcore, &desc);
171 for (int i = 0; i < 6; ++i) {
172 if (pixels[i]) {
173 stbi_image_free(pixels[i]);
174 }
175 }
176 return texture;
177}
diff --git a/src/asset/texture.h b/src/asset/texture.h
new file mode 100644
index 0000000..0d38bd9
--- /dev/null
+++ b/src/asset/texture.h
@@ -0,0 +1,7 @@
1/// Load textures from images.
2#pragma once
3
4#include <gfx/asset.h>
5
6/// Load a texture.
7Texture* gfx_texture_load(GfxCore*, const LoadTextureCmd*);