aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/animation.c517
-rw-r--r--src/animation_impl.h96
-rw-r--r--src/asset/asset_cache.c253
-rw-r--r--src/asset/asset_cache.h37
-rw-r--r--src/asset/model.c1908
-rw-r--r--src/asset/model.h12
-rw-r--r--src/asset/texture.c199
-rw-r--r--src/asset/texture.h7
-rw-r--r--src/core/buffer.c85
-rw-r--r--src/core/buffer.h24
-rw-r--r--src/core/constants.h9
-rw-r--r--src/core/core.c437
-rw-r--r--src/core/core_impl.h70
-rw-r--r--src/core/framebuffer.c151
-rw-r--r--src/core/framebuffer.h15
-rw-r--r--src/core/geometry.c321
-rw-r--r--src/core/geometry.h28
-rw-r--r--src/core/gl_util.h45
-rw-r--r--src/core/renderbuffer.c35
-rw-r--r--src/core/renderbuffer.h15
-rw-r--r--src/core/shader.c92
-rw-r--r--src/core/shader.h17
-rw-r--r--src/core/shader_program.c355
-rw-r--r--src/core/shader_program.h22
-rw-r--r--src/core/texture.c228
-rw-r--r--src/core/texture.h35
-rw-r--r--src/gfx.c83
-rw-r--r--src/gfx_assert.h5
-rw-r--r--src/memory.c178
-rw-r--r--src/memory.h41
-rw-r--r--src/render/imm.c194
-rw-r--r--src/render/imm_impl.h43
-rw-r--r--src/render/llr.c441
-rw-r--r--src/render/llr_impl.h85
-rw-r--r--src/render/renderer.c282
-rw-r--r--src/render/renderer_impl.h25
-rw-r--r--src/scene/camera.c18
-rw-r--r--src/scene/light.c39
-rw-r--r--src/scene/light_impl.h20
-rw-r--r--src/scene/material.c24
-rw-r--r--src/scene/material_impl.h13
-rw-r--r--src/scene/mesh.c26
-rw-r--r--src/scene/mesh_impl.h11
-rw-r--r--src/scene/model.c45
-rw-r--r--src/scene/model_impl.h16
-rw-r--r--src/scene/node.c352
-rw-r--r--src/scene/node_impl.h40
-rw-r--r--src/scene/object.c80
-rw-r--r--src/scene/object_impl.h23
-rw-r--r--src/scene/scene.c31
-rw-r--r--src/scene/scene_graph.h144
-rw-r--r--src/scene/scene_impl.h9
-rw-r--r--src/types.h24
-rw-r--r--src/util/geometry.c45
-rw-r--r--src/util/ibl.c328
-rw-r--r--src/util/shader.c136
-rw-r--r--src/util/skyquad.c153
57 files changed, 7967 insertions, 0 deletions
diff --git a/src/animation.c b/src/animation.c
new file mode 100644
index 0000000..c52df73
--- /dev/null
+++ b/src/animation.c
@@ -0,0 +1,517 @@
1#include "animation_impl.h"
2
3#include "memory.h"
4
5#include <string.h>
6
7// #include <log/log.h> // Debugging.
8
9static const R PLAYBACK_UNINITIALIZED = -1;
10
11static joint_idx get_anima_root_joint_index(Anima* anima) {
12 assert(anima);
13 assert(anima->num_joints > 0);
14 assert(anima->num_joints < GFX_MAX_NUM_JOINTS);
15 return anima->num_joints - 1;
16}
17
18static Joint* get_anima_root_joint(Anima* anima) {
19 assert(anima);
20 return &anima->joints[get_anima_root_joint_index(anima)];
21}
22
23static const Joint* get_anima_joint(const Anima* anima, joint_idx index) {
24 assert(anima);
25 assert(index < GFX_MAX_NUM_JOINTS);
26 assert(index != INDEX_NONE);
27 assert(index < anima->num_joints);
28 return &anima->joints[index];
29}
30
31static Joint* get_anima_joint_mut(Anima* anima, joint_idx index) {
32 return (Joint*)get_anima_joint(anima, index);
33}
34
35static const Joint* get_skeleton_joint(
36 const Anima* anima, const Skeleton* skeleton, joint_idx index) {
37 assert(anima);
38 assert(skeleton);
39 return get_anima_joint(anima, skeleton->joints[index]);
40}
41
42static void set_joint_parent(
43 Anima* anima, joint_idx joint_index, joint_idx parent_index) {
44 assert(anima);
45 assert(joint_index != INDEX_NONE);
46 assert(joint_index != get_anima_root_joint_index(anima));
47 assert(parent_index != INDEX_NONE);
48
49 Joint* parent = get_anima_joint_mut(anima, parent_index);
50
51 if (parent->child == INDEX_NONE) {
52 parent->child = joint_index;
53 } else {
54 // Find the last child in the chain of children.
55 Joint* child = get_anima_joint_mut(anima, parent->child);
56 while (child->next != INDEX_NONE) {
57 child = get_anima_joint_mut(anima, child->next);
58 }
59 // Wire up this joint as the last child's sibling.
60 child->next = joint_index;
61 }
62}
63
64static void make_joint(Anima* anima, const JointDesc* desc, Joint* joint) {
65 assert(anima);
66 assert(desc);
67 assert(joint);
68
69 // The joint matrix needs to be initialized so that meshes look right even if
70 // no animation is played. Initializing joint matrices to the identity makes
71 // meshes appear in their bind pose.
72 joint->child = INDEX_NONE;
73 joint->next = INDEX_NONE;
74 joint->transform = mat4_id();
75 joint->inv_bind_matrix = desc->inv_bind_matrix;
76 joint->joint_matrix = mat4_id();
77 joint->box = desc->box;
78}
79
80static Skeleton* make_skeleton(const SkeletonDesc* desc) {
81 assert(desc);
82 assert(desc->num_joints <= GFX_MAX_NUM_JOINTS);
83
84 Skeleton* skeleton = mem_alloc_skeleton();
85 skeleton->num_joints = desc->num_joints;
86 memcpy(
87 skeleton->joints, desc->joints,
88 desc->num_joints * sizeof(skeleton->joints[0]));
89 return skeleton;
90}
91
92static Animation* make_animation(const AnimationDesc* desc) {
93 assert(desc);
94 assert(desc->num_channels < GFX_MAX_NUM_CHANNELS);
95
96 Animation* animation = mem_alloc_animation();
97 animation->name = desc->name;
98 animation->duration = 0;
99 animation->num_channels = desc->num_channels;
100 R start_time = 0;
101 R end_time = 0;
102
103 for (size_t c = 0; c < desc->num_channels; ++c) {
104 const ChannelDesc* channel_desc = &desc->channels[c];
105 Channel* channel = &animation->channels[c];
106
107 channel->target = channel_desc->target;
108 channel->type = channel_desc->type;
109 channel->interpolation = channel_desc->interpolation;
110 channel->num_keyframes = channel_desc->num_keyframes;
111 assert(channel_desc->num_keyframes < GFX_MAX_NUM_KEYFRAMES);
112
113 for (size_t k = 0; k < channel_desc->num_keyframes; ++k) {
114 const KeyframeDesc* keyframe_desc = &channel_desc->keyframes[k];
115 Keyframe* keyframe = &channel->keyframes[k];
116
117 keyframe->time = keyframe_desc->time;
118 keyframe->translation = keyframe_desc->translation;
119 keyframe->rotation = keyframe_desc->rotation;
120
121 start_time = keyframe->time < start_time ? keyframe->time : start_time;
122 end_time = keyframe->time > end_time ? keyframe->time : end_time;
123 }
124 }
125
126 // LOGD("Animation start/end: %f / %f", start_time, end_time);
127 animation->duration = end_time - start_time;
128 assert(animation->duration >= 0);
129 return animation;
130}
131
132Anima* gfx_make_anima(const AnimaDesc* desc) {
133 assert(desc);
134 assert(desc->num_joints > 0);
135 assert(desc->num_joints <= GFX_MAX_NUM_JOINTS);
136 // All joints should have a parent except for the root.
137 for (size_t i = 0; i < desc->num_joints - 1; ++i) {
138 const joint_idx parent = desc->joints[i].parent;
139 assert(parent != INDEX_NONE);
140 assert(parent < desc->num_joints);
141 }
142 // The root should have no parent.
143 assert(desc->joints[desc->num_joints - 1].parent == INDEX_NONE);
144
145 Anima* anima = mem_alloc_anima();
146
147 // Wire the skeletons in the same order they are given in the descriptor.
148 Skeleton* last_skeleton = 0;
149 for (size_t i = 0; i < desc->num_skeletons; ++i) {
150 Skeleton* skeleton = make_skeleton(&desc->skeletons[i]);
151 const skeleton_idx skeleton_index = mem_get_skeleton_index(skeleton);
152 if (last_skeleton == 0) {
153 anima->skeleton = skeleton_index;
154 } else {
155 last_skeleton->next = skeleton_index;
156 }
157 last_skeleton = skeleton;
158 }
159
160 // Wire the animations in the same order they are given in the descriptor.
161 Animation* last_animation = 0;
162 for (size_t i = 0; i < desc->num_animations; ++i) {
163 Animation* animation = make_animation(&desc->animations[i]);
164 const animation_idx animation_index = mem_get_animation_index(animation);
165 if (last_animation == 0) {
166 anima->animation = animation_index;
167 } else {
168 last_animation->next = animation_index;
169 }
170 last_animation = animation;
171 }
172
173 // Create joints.
174 anima->num_joints = desc->num_joints;
175 // Initialize all joints.
176 // Child and sibling pointers must be initialized before wiring up the
177 // hierarchy.
178 for (size_t i = 0; i < desc->num_joints; ++i) {
179 Joint* joint = get_anima_joint_mut(anima, i);
180 make_joint(anima, &desc->joints[i], joint);
181 }
182 // Wire up joints to their parents. -1 to skip the root.
183 for (size_t i = 0; i < desc->num_joints - 1; ++i) {
184 set_joint_parent(anima, i, desc->joints[i].parent);
185 }
186
187 return anima;
188}
189
190void gfx_destroy_anima(Anima** anima) {
191 assert(anima);
192
193 if (*anima) {
194 for (skeleton_idx i = (*anima)->skeleton; i.val != 0;) {
195 Skeleton* skeleton = mem_get_skeleton(i);
196 i = skeleton->next;
197 mem_free_skeleton(&skeleton);
198 }
199
200 for (animation_idx i = (*anima)->animation; i.val != 0;) {
201 Animation* animation = mem_get_animation(i);
202 i = animation->next;
203 mem_free_animation(&animation);
204 }
205
206 mem_free_anima(anima);
207 }
208}
209
210static Animation* find_animation(animation_idx index, const char* name) {
211 assert(name);
212
213 while (index.val != 0) {
214 Animation* animation = mem_get_animation(index);
215 if (sstring_eq_cstr(animation->name, name)) {
216 // LOGD(
217 // "Found animation at index %u, %s - %s", index.val,
218 // sstring_cstr(&animation->name), name);
219 // LOGD("Animation has duration %f", animation->duration);
220 return animation;
221 }
222 index = animation->next;
223 }
224
225 return 0;
226}
227
228bool gfx_play_animation(Anima* anima, const AnimationPlaySettings* settings) {
229 assert(anima);
230 assert(settings);
231
232 // TODO: Should we animate at t=0 here to kickstart the animation? Otherwise
233 // the client is forced to call gfx_update_animation() to do this.
234 Animation* animation = find_animation(anima->animation, settings->name);
235 if (!animation) {
236 return false;
237 }
238 // Playback initialized on first call to update().
239 AnimationState* state = &anima->state;
240 state->start_time = PLAYBACK_UNINITIALIZED;
241 state->animation = mem_get_animation_index(animation);
242 state->loop = settings->loop;
243 return true;
244}
245
246static void gfx_set_joint_position(Joint* joint, vec3 position) {
247 assert(joint);
248 mat4_set_v3(&joint->transform, position);
249}
250
251static void gfx_set_joint_rotation(Joint* joint, quat rotation) {
252 assert(joint);
253 mat4_set_3x3(&joint->transform, mat4_from_quat(rotation));
254}
255
256static void find_keyframes(const Channel* channel, R t, int* prev, int* next) {
257 assert(channel);
258 assert(prev);
259 assert(next);
260
261 *prev = -1;
262 *next = 0;
263 while (((*next + 1) < (int)channel->num_keyframes) &&
264 (t >= channel->keyframes[*next + 1].time)) {
265 (*prev)++;
266 (*next)++;
267 }
268}
269
270static R normalize_time(R a, R b, R t) {
271 assert(a <= t);
272 assert(t <= b);
273 return (t - a) / (b - a);
274}
275
276static quat interpolate_rotation(
277 const Channel* channel, int prev, int next, R t) {
278 assert(channel);
279
280 if (next == 0) {
281 // Animation has not started at this point in time yet.
282 return channel->keyframes[next].rotation;
283 } else {
284 switch (channel->interpolation) {
285 case StepInterpolation:
286 return channel->keyframes[prev].rotation;
287 case LinearInterpolation: {
288 const R normalized_t = normalize_time(
289 channel->keyframes[prev].time, channel->keyframes[next].time, t);
290 return qnormalize(qslerp(
291 channel->keyframes[prev].rotation, channel->keyframes[next].rotation,
292 normalized_t));
293 break;
294 }
295 case CubicSplineInterpolation:
296 assert(false); // TODO
297 return qmake(0, 0, 0, 0);
298 default:
299 assert(false);
300 return qmake(0, 0, 0, 0);
301 }
302 }
303}
304
305static vec3 interpolate_translation(
306 const Channel* channel, int prev, int next, R t) {
307 assert(channel);
308
309 if (next == 0) {
310 // Animation has not started at this point in time yet.
311 return channel->keyframes[next].translation;
312 } else {
313 switch (channel->interpolation) {
314 case StepInterpolation:
315 return channel->keyframes[prev].translation;
316 case LinearInterpolation: {
317 const R normalized_t = normalize_time(
318 channel->keyframes[prev].time, channel->keyframes[next].time, t);
319 return vec3_lerp(
320 channel->keyframes[prev].translation,
321 channel->keyframes[next].translation, normalized_t);
322 break;
323 }
324 case CubicSplineInterpolation:
325 assert(false); // TODO
326 return vec3_make(0, 0, 0);
327 default:
328 assert(false);
329 return vec3_make(0, 0, 0);
330 }
331 }
332}
333
334static void animate_channel(Anima* anima, const Channel* channel, R t) {
335 assert(anima);
336 assert(channel);
337 assert(channel->target < anima->num_joints);
338
339 int prev, next;
340 find_keyframes(channel, t, &prev, &next);
341
342 // Note that not all channels extend to the duration of an animation; some
343 // channels may stop animating their targets earlier. Clamp the animation time
344 // to the channel's end keyframe to make the rest of the math (normalize_time)
345 // work.
346 t = t > channel->keyframes[next].time ? channel->keyframes[next].time : t;
347
348 Joint* target = get_anima_joint_mut(anima, channel->target);
349
350 switch (channel->type) {
351 case RotationChannel: {
352 const quat rotation = interpolate_rotation(channel, prev, next, t);
353 gfx_set_joint_rotation(target, rotation);
354 break;
355 }
356 case TranslationChannel: {
357 const vec3 translation = interpolate_translation(channel, prev, next, t);
358 gfx_set_joint_position(target, translation);
359 break;
360 }
361 // Not yet supported.
362 case ScaleChannel:
363 case WeightsChannel:
364 default:
365 // TODO: Add back the assertion or add support for scaling.
366 // assert(false);
367 break;
368 }
369}
370
371static void compute_joint_matrices_rec(
372 Anima* anima, Joint* joint, const mat4* parent_global_joint_transform,
373 const mat4* root_inv_global_transform) {
374 assert(anima);
375 assert(joint);
376 assert(parent_global_joint_transform);
377 assert(root_inv_global_transform);
378
379 const mat4 global_joint_transform =
380 mat4_mul(*parent_global_joint_transform, joint->transform);
381
382 // Compute this joint's matrix.
383 joint->joint_matrix = mat4_mul(
384 *root_inv_global_transform,
385 mat4_mul(global_joint_transform, joint->inv_bind_matrix));
386
387 // Recursively compute the joint matrices for this joint's siblings.
388 if (joint->next != INDEX_NONE) {
389 Joint* sibling = get_anima_joint_mut(anima, joint->next);
390
391 compute_joint_matrices_rec(
392 anima, sibling, parent_global_joint_transform,
393 root_inv_global_transform);
394 }
395
396 // Recursively compute the joint matrices for this joint's children.
397 if (joint->child != INDEX_NONE) {
398 Joint* child = get_anima_joint_mut(anima, joint->child);
399
400 compute_joint_matrices_rec(
401 anima, child, &global_joint_transform, root_inv_global_transform);
402 }
403}
404
405void gfx_update_animation(Anima* anima, R t) {
406 assert(anima);
407
408 AnimationState* state = &anima->state;
409 if (state->animation.val == 0) {
410 return; // No active animation.
411 }
412 const Animation* animation = mem_get_animation(state->animation);
413 assert(animation);
414
415 // On a call to play(), the start time is set to -1 to signal that the
416 // animation playback has not yet been initialized.
417 if (state->start_time == PLAYBACK_UNINITIALIZED) {
418 state->start_time = t;
419 }
420 // Locate the current time point inside the animation's timeline.
421 assert(t >= state->start_time);
422 assert(animation->duration >= 0.0);
423 const R local_time = t - state->start_time;
424 const R animation_time = state->loop
425 ? rmod(local_time, animation->duration)
426 : clamp(local_time, 0.0, animation->duration);
427
428 // LOGD(
429 // "animation_time = %f, animation duration: %f", animation_time,
430 // animation->duration);
431
432 // Play through the animation to transform skeleton nodes.
433 for (size_t i = 0; i < animation->num_channels; ++i) {
434 const Channel* channel = &animation->channels[i];
435 animate_channel(anima, channel, animation_time);
436 }
437
438 // Compute joint matrices after having transformed the skeletons.
439 //
440 // The anima's parent node is the common ancestor of all skeletons, and its
441 // transform maps the skeletons from object space to world space. This is the
442 // transform used as the "global transform" in the joint matrix equations.
443 //
444 // Joint matrix calculation begins by descending from the anima's root joint,
445 // which we have constructed to be the common root of all skeletons.
446 //
447 // This procedure touches every joint exactly once.
448 const mat4 root_global_transform = mat4_id();
449 const mat4 root_inv_global_transform = mat4_id();
450
451 Joint* root_joint = get_anima_root_joint(anima);
452 compute_joint_matrices_rec(
453 anima, root_joint, &root_global_transform, &root_inv_global_transform);
454}
455
456const Skeleton* gfx_get_anima_skeleton(const Anima* anima, size_t i) {
457 assert(anima);
458
459 skeleton_idx skeleton_index = anima->skeleton;
460 const Skeleton* skeleton = mem_get_skeleton(skeleton_index);
461
462 for (size_t j = 1; j < i; ++j) {
463 skeleton_index = skeleton->next;
464 mem_get_skeleton(skeleton_index);
465 }
466
467 return skeleton;
468}
469
470size_t gfx_get_skeleton_num_joints(const Skeleton* skeleton) {
471 assert(skeleton);
472 return skeleton->num_joints;
473}
474
475bool gfx_joint_has_box(
476 const Anima* anima, const Skeleton* skeleton, size_t joint_index) {
477 assert(anima);
478 assert(skeleton);
479 assert(joint_index < skeleton->num_joints);
480
481 const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index);
482 return !aabb3_is_empty(joint->box);
483}
484
485Box gfx_get_joint_box(
486 const Anima* anima, const Skeleton* skeleton, size_t joint_index) {
487 assert(anima);
488 assert(skeleton);
489
490 const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index);
491
492 // Transform the box to anima space.
493 // Note that joint matrices do not usually have a translation since joints
494 // mostly just rotate with respect to their parent.
495 const vec3 pmin = joint->box.min;
496 const vec3 pmax = joint->box.max;
497 return (Box){
498 .vertices = {
499 mat4_mul_vec3(
500 joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmax.z), 1),
501 mat4_mul_vec3(
502 joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmax.z), 1),
503 mat4_mul_vec3(
504 joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmax.z), 1),
505 mat4_mul_vec3(
506 joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmax.z), 1),
507 mat4_mul_vec3(
508 joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmin.z), 1),
509 mat4_mul_vec3(
510 joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmin.z), 1),
511 mat4_mul_vec3(
512 joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmin.z), 1),
513 mat4_mul_vec3(
514 joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmin.z), 1),
515 }
516 };
517}
diff --git a/src/animation_impl.h b/src/animation_impl.h
new file mode 100644
index 0000000..4929b97
--- /dev/null
+++ b/src/animation_impl.h
@@ -0,0 +1,96 @@
1#pragma once
2
3#include <gfx/animation.h>
4#include <gfx/sizes.h>
5
6#include "types.h"
7
8#include <cstring.h>
9#include <math/defs.h>
10#include <math/mat4.h>
11#include <math/quat.h>
12#include <math/vec3.h>
13
14#include <stddef.h>
15
16typedef struct Buffer Buffer;
17
18// Currently ignoring scale in skinning and animation.
19//
20// TODO: Simultaneous animation of disjoint animations.
21
22/// Skeleton joint.
23/// Joints are mutable and store the transform and joint matrices that result
24/// from animation, aside from the inverse bind matrix.
25typedef struct Joint {
26 joint_idx child; // First child Joint; index into Anima's joints.
27 joint_idx next; // Next sibling Joint; index into Anima's joints.
28 mat4 transform; // Local transform relative to parent.
29 mat4 inv_bind_matrix; // Transforms the mesh into the joint's local space.
30 mat4 joint_matrix; // inv(global) * global joint transform * inv(bind).
31 aabb3 box; // Bounding box of vertices affected by joint.
32} Joint;
33
34/// Animation skeleton.
35typedef struct Skeleton {
36 skeleton_idx next;
37 size_t num_joints;
38 joint_idx joints[GFX_MAX_NUM_JOINTS]; // Indices into Anima's joints array.
39} Skeleton;
40
41/// A keyframe of animation.
42typedef struct Keyframe {
43 R time; // Start time in [0, end animation time]
44 union {
45 vec3 translation;
46 quat rotation;
47 };
48} Keyframe;
49
50/// Animation channel.
51typedef struct Channel {
52 joint_idx target; // Index into Anima's joints array.
53 ChannelType type;
54 AnimationInterpolation interpolation;
55 size_t num_keyframes;
56 Keyframe keyframes[GFX_MAX_NUM_KEYFRAMES];
57} Channel;
58
59/// A skeletal animation.
60typedef struct Animation {
61 animation_idx next;
62 sstring name;
63 R duration;
64 size_t num_channels;
65 Channel channels[GFX_MAX_NUM_CHANNELS];
66} Animation;
67
68/// Animation state.
69///
70/// This represents the current state of an animation.
71typedef struct AnimationState {
72 R start_time; // Time when the current animation started playing. -1 means the
73 // animation playback has not yet been initialized.
74 animation_idx animation; // Current animation. 0 = no animation.
75 bool loop;
76} AnimationState;
77
78/// Animation object.
79///
80/// This is the top-level animation object that encapsulates everything
81/// necessary for animation.
82///
83/// For lack of a better name, this is called an Anima. It is short and the
84/// Latin root of animation.
85///
86/// The last joint of the joints array at index 'num_joints - 1' is the root of
87/// all skeletons; specifically, the root of all joints that otherwise would
88/// have no parent (a skeleton need not have its own root and can be a set of
89/// disjoint node hierarchies).
90typedef struct Anima {
91 skeleton_idx skeleton; // Index of first skeleton.
92 animation_idx animation; // Index of first animation.
93 AnimationState state; // Current animation state.
94 size_t num_joints; // Number of actual joints in the array.
95 Joint joints[GFX_MAX_NUM_JOINTS]; // Shared by all skeletons.
96} Anima;
diff --git a/src/asset/asset_cache.c b/src/asset/asset_cache.c
new file mode 100644
index 0000000..dfaf7c6
--- /dev/null
+++ b/src/asset/asset_cache.c
@@ -0,0 +1,253 @@
1#include "asset_cache.h"
2
3#include "animation_impl.h"
4#include "memory.h"
5#include "model.h"
6#include "scene/model_impl.h"
7#include "scene/node_impl.h"
8#include "texture.h"
9
10#include <gfx/asset.h>
11#include <gfx/gfx.h>
12#include <gfx/scene.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
171 Model* copy = mem_alloc_model();
172 copy->root = mem_get_node_index(root_copy);
173 return copy;
174 } else {
175 return (Model*)model; // Static model, can't be mutated.
176 }
177}
178
179void gfx_init_asset_cache(AssetCache* cache) {
180 assert(cache);
181
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 // TODO: Destroy assets here.
193 mempool_del(&cache->assets);
194}
195
196Model* gfx_load_model(Gfx* gfx, const LoadModelCmd* cmd) {
197 assert(gfx);
198
199 AssetCache* cache = gfx_get_asset_cache(gfx);
200
201 // First search for the asset in the cache.
202 const uint64_t hash = calc_model_hash(cmd);
203 Asset* asset = lookup_cache(cache, hash);
204 if (asset) {
205 log_model_cache_hit(cmd, hash);
206 return clone_model(asset->model);
207 }
208
209 // Asset not found in the cache.
210 // Load it, insert it into the cache, and return it.
211 Model* model = gfx_model_load(gfx, cmd);
212 if (model) {
213 *(Asset*)mempool_alloc(&cache->assets) = (Asset){
214 .type = ModelAsset,
215 .hash = hash,
216 .model = model,
217 };
218 log_model_loaded(cmd);
219 return clone_model(model);
220 } else {
221 log_model_load_failure(cmd);
222 return 0;
223 }
224}
225
226const Texture* gfx_load_texture(Gfx* gfx, const LoadTextureCmd* cmd) {
227 assert(gfx);
228 assert(cmd);
229
230 AssetCache* cache = gfx_get_asset_cache(gfx);
231
232 // First search for the asset in the cache.
233 const uint64_t hash = calc_texture_hash(cmd);
234 Asset* asset = lookup_cache(cache, hash);
235 if (asset) {
236 return asset->texture;
237 }
238
239 // Asset not found in the cache.
240 // Load it, insert it into the cache, and return it.
241 GfxCore* gfxcore = gfx_get_core(gfx);
242 const Texture* texture = gfx_texture_load(gfxcore, cmd);
243 if (texture) {
244 *(Asset*)mempool_alloc(&cache->assets) = (Asset){
245 .type = TextureAsset,
246 .hash = hash,
247 .texture = texture,
248 };
249 } else {
250 log_texture_load_failure(cmd);
251 }
252 return texture;
253}
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..b5c6b0d
--- /dev/null
+++ b/src/asset/model.c
@@ -0,0 +1,1908 @@
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_assert.h"
86#include "scene/model_impl.h"
87
88#include <gfx/animation.h>
89#include <gfx/core.h>
90#include <gfx/gfx.h>
91#include <gfx/render/llr.h>
92#include <gfx/scene.h>
93#include <gfx/sizes.h>
94#include <gfx/util/shader.h>
95
96#include <cstring.h>
97#include <error.h>
98#include <log/log.h>
99#include <math/camera.h>
100#include <math/defs.h>
101#include <math/mat4.h>
102#include <math/quat.h>
103#include <math/vec2.h>
104#include <math/vec3.h>
105
106#include "cgltf_tangents.h"
107#define CGLTF_IMPLEMENTATION
108#include "cgltf.h"
109
110#include <stdbool.h>
111#include <stdlib.h>
112
113// Taken from the GL header file.
114#define GL_NEAREST 0x2600
115#define GL_LINEAR 0x2601
116#define GL_NEAREST_MIPMAP_NEAREST 0x2700
117#define GL_LINEAR_MIPMAP_NEAREST 0x2701
118#define GL_NEAREST_MIPMAP_LINEAR 0x2702
119#define GL_LINEAR_MIPMAP_LINEAR 0x2703
120
121// Uniforms names. Must match the names in shaders.
122#define UNIFORM_BASE_COLOR_FACTOR "BaseColorFactor"
123#define UNIFORM_METALLIC_FACTOR "MetallicFactor"
124#define UNIFORM_ROUGHNESS_FACTOR "RoughnessFactor"
125#define UNIFORM_EMISSIVE_FACTOR "EmissiveFactor"
126#define UNIFORM_BASE_COLOR_TEXTURE "BaseColorTexture"
127#define UNIFORM_METALLIC_ROUGHNESS_TEXTURE "MetallicRoughnessTexture"
128#define UNIFORM_EMISSIVE_TEXTURE "EmissiveTexture"
129#define UNIFORM_AMBIENT_OCCLUSION_TEXTURE "AmbientOcclusionTexture"
130#define UNIFORM_NORMAL_MAP "NormalMap"
131
132// Shader compiler defines. Must match the names in shaders.
133#define DEFINE_HAS_TEXCOORDS "HAS_TEXCOORDS"
134#define DEFINE_HAS_NORMALS "HAS_NORMALS"
135#define DEFINE_HAS_TANGENTS "HAS_TANGENTS"
136#define DEFINE_HAS_ALBEDO_MAP "HAS_ALBEDO_MAP"
137#define DEFINE_HAS_METALLIC_ROUGHNESS_MAP "HAS_METALLIC_ROUGHNESS_MAP"
138#define DEFINE_HAS_NORMAL_MAP "HAS_NORMAL_MAP"
139#define DEFINE_HAS_OCCLUSION_MAP "HAS_OCCLUSION_MAP"
140#define DEFINE_HAS_EMISSIVE_MAP "HAS_EMISSIVE_MAP"
141#define DEFINE_HAS_TRANSPARENCY "HAS_TRANSPARENCY"
142#define DEFINE_HAS_JOINTS "HAS_JOINTS"
143#define DEFINE_MAX_JOINTS "MAX_JOINTS"
144
145typedef enum TextureType {
146 BaseColorTexture,
147 MetallicRoughnessTexture,
148 EmissiveTexture,
149 AmbientOcclusionTexture,
150 NormalMap,
151} TextureType;
152
153/// Describes the properties of a mesh.
154/// This is used to create shader permutations.
155typedef struct MeshPermutation {
156 union {
157 struct {
158 // Vertex attributes.
159 bool has_texcoords : 1;
160 bool has_normals : 1;
161 bool has_tangents : 1;
162 bool has_joints : 1;
163 bool has_weights : 1;
164 // Textures.
165 bool has_albedo_map : 1;
166 bool has_metallic_roughness_map : 1;
167 bool has_normal_map : 1;
168 bool has_occlusion_map : 1;
169 bool has_emissive_map : 1;
170 // Material.
171 bool has_transparency : 1;
172 };
173 int32_t all;
174 };
175} MeshPermutation;
176
177/// Build shader compiler defines from a mesh permutation.
178static size_t make_defines(
179 MeshPermutation perm, ShaderCompilerDefine* defines) {
180 static const char* str_true = "1";
181 size_t next = 0;
182
183#define check(field, define) \
184 if (perm.field) { \
185 defines[next].name = sstring_make(define); \
186 defines[next].value = sstring_make(str_true); \
187 next++; \
188 }
189 check(has_texcoords, DEFINE_HAS_TEXCOORDS);
190 check(has_normals, DEFINE_HAS_NORMALS);
191 check(has_tangents, DEFINE_HAS_TANGENTS);
192 check(has_joints, DEFINE_HAS_JOINTS);
193 check(has_albedo_map, DEFINE_HAS_ALBEDO_MAP);
194 check(has_metallic_roughness_map, DEFINE_HAS_METALLIC_ROUGHNESS_MAP);
195 check(has_normal_map, DEFINE_HAS_NORMAL_MAP);
196 check(has_occlusion_map, DEFINE_HAS_OCCLUSION_MAP);
197 check(has_emissive_map, DEFINE_HAS_EMISSIVE_MAP);
198 check(has_transparency, DEFINE_HAS_TRANSPARENCY);
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: %d, normal map: %d, AO map: %d, emissive map: "
216 "%d, has transparency: %d",
217 perm.has_texcoords, perm.has_normals, perm.has_tangents, perm.has_joints,
218 perm.has_weights, perm.has_albedo_map, perm.has_metallic_roughness_map,
219 perm.has_normal_map, perm.has_occlusion_map, perm.has_emissive_map,
220 perm.has_transparency);
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){.usage = BufferStatic,
582 .type = BufferUntyped,
583 .data.data = buffer->data,
584 .data.count = buffer->size});
585 if (!buffers[i]) {
586 return false;
587 }
588 }
589
590 return true;
591}
592
593/// Load tangent buffers.
594static bool load_tangent_buffers(
595 const cgltfTangentBuffer* cgltf_tangent_buffers,
596 cgltf_size num_tangent_buffers, GfxCore* gfxcore,
597 Buffer** tangent_buffers) {
598 assert(cgltf_tangent_buffers);
599 assert(gfxcore);
600 assert(tangent_buffers);
601
602 for (cgltf_size i = 0; i < num_tangent_buffers; ++i) {
603 const cgltfTangentBuffer* buffer = &cgltf_tangent_buffers[i];
604 assert(buffer->data);
605 tangent_buffers[i] = gfx_make_buffer(
606 gfxcore, &(BufferDesc){.usage = BufferStatic,
607 .type = BufferUntyped,
608 .data.data = buffer->data,
609 .data.count = buffer->size_bytes});
610 if (!tangent_buffers[i]) {
611 return false;
612 }
613 }
614
615 return true;
616}
617
618/// Lazily load all textures from the glTF scene.
619///
620/// Colour textures like albedo are in sRGB colour space. Non-colour textures
621/// like normal maps are in linear space (e.g. DamagedHelmet sample). Since we
622/// don't know how the texture is going to be used at this point, we can't tell
623/// what colour space it should be loaded in (ideally this would be part of the
624/// image file format, but not all formats specify colour space.) Therefore, we
625/// load the textures lazily and don't actually commit them to GPU memory until
626/// we know their colour space when loading glTF materials.
627///
628/// Return an array of LoadTextureCmds such that the index of each cmd matches
629/// the index of each glTF texture in the scene.
630static void load_textures_lazy(
631 const cgltf_data* data, GfxCore* gfxcore, const char* directory,
632 LoadTextureCmd* load_texture_cmds) {
633 assert(data);
634 assert(gfxcore);
635 assert(load_texture_cmds);
636
637 for (cgltf_size i = 0; i < data->textures_count; ++i) {
638 const cgltf_texture* texture = &data->textures[i];
639 const cgltf_image* image = texture->image;
640 const cgltf_sampler* sampler = texture->sampler;
641
642 // glTF models might not specify a sampler. In such case, the client can
643 // pick its own defaults.
644 // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#samplers
645 bool mipmaps = true;
646 TextureFiltering filtering = LinearFiltering;
647 TextureWrapping wrap = Repeat;
648
649 if (sampler) {
650 // The gfx library does not distinguish between sampling the texture and
651 // combining the mipmap levels.
652 const cgltf_int filter =
653 sampler->min_filter == 0 ? sampler->mag_filter : sampler->min_filter;
654
655 switch (filter) {
656 case GL_NEAREST_MIPMAP_NEAREST:
657 mipmaps = true;
658 filtering = NearestFiltering;
659 break;
660 case GL_NEAREST_MIPMAP_LINEAR:
661 case GL_LINEAR_MIPMAP_NEAREST:
662 case GL_LINEAR_MIPMAP_LINEAR:
663 mipmaps = true;
664 filtering = LinearFiltering;
665 break;
666 case GL_NEAREST:
667 filtering = NearestFiltering;
668 break;
669 case GL_LINEAR:
670 filtering = LinearFiltering;
671 break;
672 default:
673 break;
674 }
675 }
676
677 // Currently only supporting loading textures from files.
678 assert(image->uri);
679 assert(directory);
680 mstring fullpath =
681 mstring_concat_path(mstring_make(directory), mstring_make(image->uri));
682
683 load_texture_cmds[i] = (LoadTextureCmd){.origin = AssetFromFile,
684 .type = LoadTexture,
685 .colour_space = LinearColourSpace,
686 .filtering = filtering,
687 .wrap = wrap,
688 .mipmaps = mipmaps,
689 .data.texture.filepath = fullpath};
690 }
691}
692
693/// Load a texture uniform.
694///
695/// This determines a texture's colour space based on its intended use, loads
696/// the texture, and then defines the sampler shader uniform.
697static bool load_texture_and_uniform(
698 const cgltf_data* data, Gfx* gfx, const cgltf_texture_view* texture_view,
699 TextureType texture_type, const Texture** textures,
700 LoadTextureCmd* load_texture_cmds, int* next_uniform, MaterialDesc* desc) {
701 assert(data);
702 assert(gfx);
703 assert(texture_view);
704 assert(textures);
705 assert(next_uniform);
706 assert(desc);
707 assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
708
709 const size_t texture_index = texture_view->texture - data->textures;
710 assert(texture_index < data->textures_count);
711
712 // Here we are assuming that if a texture is re-used, it is re-used with the
713 // same texture view. This should be fine because, e.g., a normal map would
714 // not be used as albedo and vice versa.
715 if (!textures[texture_index]) {
716 LoadTextureCmd* cmd = &load_texture_cmds[texture_index];
717 // Albedo and emissive use sRGB. Other textures use a linear colour space.
718 // See the notes on the spec.
719 if ((texture_type == BaseColorTexture) ||
720 (texture_type == EmissiveTexture)) {
721 cmd->colour_space = sRGB;
722 } else {
723 cmd->colour_space = LinearColourSpace;
724 }
725
726 LOGD(
727 "Load texture: %s (mipmaps: %d, filtering: %d)",
728 mstring_cstr(&cmd->data.texture.filepath), cmd->mipmaps,
729 cmd->filtering);
730
731 textures[texture_index] = gfx_load_texture(gfx, cmd);
732 if (!textures[texture_index]) {
733 log_error(
734 "Failed to load texture: %s",
735 mstring_cstr(&cmd->data.texture.filepath));
736 return false;
737 }
738 }
739
740 assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
741 desc->uniforms[(*next_uniform)++] = (ShaderUniform){
742 .name = sstring_make(get_texture_uniform_name(texture_type)),
743 .type = UniformTexture,
744 .value.texture = textures[texture_index]};
745
746 return true;
747}
748
749static AlphaMode to_gfx_alpha_mode(cgltf_alpha_mode mode) {
750 switch (mode) {
751 case cgltf_alpha_mode_opaque:
752 return Opaque;
753 case cgltf_alpha_mode_mask:
754 return Mask;
755 case cgltf_alpha_mode_blend:
756 return Blend;
757 }
758 FAIL("unhandled alpha mode");
759 return Opaque;
760}
761
762/// Load all materials from the glTF scene.
763///
764/// Return an array of Materials such that the index of each descriptor matches
765/// the index of each glTF material in the scene. Also return the number of
766/// materials and the textures used by them.
767static bool load_materials(
768 const cgltf_data* data, Gfx* gfx, LoadTextureCmd* load_texture_cmds,
769 const Texture** textures, Material** materials) {
770 assert(data);
771 assert(gfx);
772 assert(materials);
773 if (data->textures_count > 0) {
774 assert(load_texture_cmds);
775 assert(textures);
776 }
777
778 for (cgltf_size i = 0; i < data->materials_count; ++i) {
779 const cgltf_material* mat = &data->materials[i];
780
781 int next_uniform = 0;
782 MaterialDesc desc = {0};
783
784 // TODO: specular/glossiness and other material parameters.
785 if (mat->has_pbr_metallic_roughness) {
786 const cgltf_pbr_metallic_roughness* pbr = &mat->pbr_metallic_roughness;
787
788 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
789 desc.uniforms[next_uniform++] = (ShaderUniform){
790 .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR),
791 .type = UniformVec4,
792 .value.uniform_vec4 = vec4_from_array(pbr->base_color_factor)};
793
794 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
795 desc.uniforms[next_uniform++] =
796 (ShaderUniform){.name = sstring_make(UNIFORM_METALLIC_FACTOR),
797 .type = UniformFloat,
798 .value.uniform_float = pbr->metallic_factor};
799
800 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
801 desc.uniforms[next_uniform++] =
802 (ShaderUniform){.name = sstring_make(UNIFORM_ROUGHNESS_FACTOR),
803 .type = UniformFloat,
804 .value.uniform_float = pbr->roughness_factor};
805
806 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
807 desc.uniforms[next_uniform++] = (ShaderUniform){
808 .name = sstring_make(UNIFORM_EMISSIVE_FACTOR),
809 .type = UniformVec3,
810 .value.uniform_vec3 = vec3_from_array(mat->emissive_factor)};
811
812 if (pbr->base_color_texture.texture) {
813 if (!load_texture_and_uniform(
814 data, gfx, &pbr->base_color_texture, BaseColorTexture, textures,
815 load_texture_cmds, &next_uniform, &desc)) {
816 return false;
817 }
818 }
819
820 if (pbr->metallic_roughness_texture.texture) {
821 if (!load_texture_and_uniform(
822 data, gfx, &pbr->metallic_roughness_texture,
823 MetallicRoughnessTexture, textures, load_texture_cmds,
824 &next_uniform, &desc)) {
825 return false;
826 }
827 }
828 }
829
830 if (mat->emissive_texture.texture) {
831 if (!load_texture_and_uniform(
832 data, gfx, &mat->emissive_texture, EmissiveTexture, textures,
833 load_texture_cmds, &next_uniform, &desc)) {
834 return false;
835 }
836 }
837
838 if (mat->occlusion_texture.texture) {
839 if (!load_texture_and_uniform(
840 data, gfx, &mat->occlusion_texture, AmbientOcclusionTexture,
841 textures, load_texture_cmds, &next_uniform, &desc)) {
842 return false;
843 }
844 }
845
846 if (mat->normal_texture.texture) {
847 if (!load_texture_and_uniform(
848 data, gfx, &mat->normal_texture, NormalMap, textures,
849 load_texture_cmds, &next_uniform, &desc)) {
850 return false;
851 }
852 }
853
854 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
855 desc.num_uniforms = next_uniform;
856
857 desc.alpha_mode = to_gfx_alpha_mode(mat->alpha_mode);
858 desc.alpha_cutoff = mat->alpha_cutoff;
859
860 materials[i] = gfx_make_material(&desc);
861 if (!materials[i]) {
862 return false;
863 }
864 }
865
866 return true;
867}
868
869/// Create a default material for meshes that do not have a material.
870static Material* make_default_material() {
871 MaterialDesc desc = (MaterialDesc){0};
872
873 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
874 desc.uniforms[desc.num_uniforms++] =
875 (ShaderUniform){.name = sstring_make(UNIFORM_BASE_COLOR_FACTOR),
876 .type = UniformVec4,
877 .value.uniform_vec4 = vec4_make(1, 1, 1, 1)};
878
879 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
880 desc.uniforms[desc.num_uniforms++] =
881 (ShaderUniform){.name = sstring_make(UNIFORM_METALLIC_FACTOR),
882 .type = UniformFloat,
883 .value.uniform_float = 0};
884
885 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
886 desc.uniforms[desc.num_uniforms++] =
887 (ShaderUniform){.name = sstring_make(UNIFORM_ROUGHNESS_FACTOR),
888 .type = UniformFloat,
889 .value.uniform_float = 1};
890
891 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
892 desc.uniforms[desc.num_uniforms++] =
893 (ShaderUniform){.name = sstring_make(UNIFORM_EMISSIVE_FACTOR),
894 .type = UniformVec3,
895 .value.uniform_vec3 = vec3_make(0, 0, 0)};
896
897 return gfx_make_material(&desc);
898}
899
900/// Compute the bounding box of the vertices pointed to by the accessor.
901/// 'dim' is the dimension of the vertices (2D or 3D).
902aabb3 compute_aabb(const cgltf_accessor* accessor) {
903 aabb3 box = {0};
904 if (accessor->has_min && accessor->has_max) {
905 box = aabb3_make(
906 vec3_from_array(accessor->min), vec3_from_array(accessor->max));
907 } else {
908 AccessorIter iter = make_accessor_iter(accessor);
909 AccessorData vertex = {0};
910 cgltf_size i = 0;
911
912 while (accessor_iter_next(&iter, &vertex)) {
913 const vec3 p = vec3_make(vertex.x, vertex.y, vertex.z);
914 if (i == 0) {
915 box = aabb3_make(p, p);
916 } else {
917 box = aabb3_add(box, p);
918 }
919 ++i;
920 }
921 }
922 return box;
923}
924
925/// Load all meshes from the glTF scene.
926static bool load_meshes(
927 const cgltf_data* data, GfxCore* gfxcore, Buffer** buffers,
928 Buffer** tangent_buffers, const cgltfTangentBuffer* cgltf_tangent_buffers,
929 cgltf_size num_tangent_buffers, Material** materials,
930 ShaderProgram* const shader, size_t primitive_count, Geometry** geometries,
931 Mesh** meshes, SceneObject** scene_objects) {
932 // Walk through the mesh primitives to create Meshes. A GLTF mesh primitive
933 // has a material (Mesh) and vertex data (Geometry). A GLTF mesh maps to
934 // a SceneObject.
935 //
936 // glTF gfx
937 // ---- ---
938 // Mesh SceneObject
939 // Mesh primitive Mesh / Geometry
940 // Accessor + buffer view BufferView
941 // Buffer Buffer
942 assert(data);
943 assert(gfxcore);
944 assert(buffers);
945 assert(materials);
946 assert(geometries);
947 assert(meshes);
948 assert(scene_objects);
949 if (num_tangent_buffers > 0) {
950 assert(tangent_buffers);
951 assert(cgltf_tangent_buffers);
952 }
953
954 // Points to the next available Mesh and also the next available Geometry.
955 // There is one (Mesh, Geometry) pair per glTF mesh primitive.
956 size_t next_mesh = 0;
957
958 for (cgltf_size m = 0; m < data->meshes_count; ++m) {
959 const cgltf_mesh* mesh = &data->meshes[m];
960
961 ObjectDesc object_desc = {0};
962
963 for (cgltf_size p = 0; p < mesh->primitives_count; ++p) {
964 assert(next_mesh < primitive_count);
965 const cgltf_primitive* prim = &mesh->primitives[p];
966 const cgltf_material* mat = prim->material;
967
968 MeshPermutation perm = {0};
969 if (mat) {
970 perm.has_normal_map = mat->normal_texture.texture != 0;
971 perm.has_occlusion_map = mat->occlusion_texture.texture != 0;
972 perm.has_emissive_map = mat->emissive_texture.texture != 0;
973 perm.has_transparency = mat->alpha_mode != cgltf_alpha_mode_opaque;
974
975 if (mat->has_pbr_metallic_roughness) {
976 const cgltf_pbr_metallic_roughness* pbr =
977 &mat->pbr_metallic_roughness;
978 perm.has_albedo_map = pbr->base_color_texture.texture != 0;
979 perm.has_metallic_roughness_map =
980 pbr->metallic_roughness_texture.texture != 0;
981 } else {
982 // TODO: specular/glossiness and other material parameters.
983 }
984 }
985
986 GeometryDesc geometry_desc = {
987 .type = from_gltf_primitive_type(prim->type),
988 .buffer_usage = BufferStatic};
989
990 // Vertex indices.
991 if (prim->indices) {
992 const cgltf_accessor* accessor = prim->indices;
993 const cgltf_buffer_view* view = prim->indices->buffer_view;
994 const cgltf_size buffer_index = view->buffer - data->buffers;
995
996 assert(buffer_index < data->buffers_count);
997 Buffer* buffer = buffers[buffer_index];
998
999 const cgltf_size component_size =
1000 get_component_size(accessor->component_type);
1001 switch (component_size) {
1002 case 1: {
1003 BufferViewIdx8* indices = &geometry_desc.indices8;
1004 // TODO: discards const qualifier.
1005 indices->buffer = buffer;
1006 indices->offset_bytes = accessor->offset + view->offset;
1007 indices->size_bytes = view->size;
1008 indices->stride_bytes = view->stride;
1009 geometry_desc.num_indices = prim->indices->count;
1010 break;
1011 }
1012 case 2: {
1013 BufferViewIdx16* indices = &geometry_desc.indices16;
1014 indices->buffer = buffer;
1015 indices->offset_bytes = accessor->offset + view->offset;
1016 indices->size_bytes = view->size;
1017 indices->stride_bytes = view->stride;
1018 geometry_desc.num_indices = prim->indices->count;
1019 break;
1020 }
1021 default:
1022 // TODO: Handle 32-bit indices.
1023 assert(false);
1024 break;
1025 }
1026 }
1027
1028 // Vertex attributes.
1029 for (cgltf_size a = 0; a < prim->attributes_count; ++a) {
1030 const cgltf_attribute* attrib = &prim->attributes[a];
1031 const cgltf_accessor* accessor = attrib->data;
1032 const cgltf_buffer_view* view = accessor->buffer_view;
1033 const cgltf_size buffer_index = view->buffer - data->buffers;
1034
1035 assert(buffer_index < data->buffers_count);
1036 Buffer* buffer = buffers[buffer_index];
1037
1038 BufferView2d* buffer_view_2d = 0;
1039 BufferView3d* buffer_view_3d = 0;
1040 BufferView4d* buffer_view_4d = 0;
1041 BufferViewFloat* buffer_view_float = 0;
1042 BufferViewU8* buffer_view_u8 = 0;
1043 BufferViewU16* buffer_view_u16 = 0;
1044
1045 switch (attrib->type) {
1046 case cgltf_attribute_type_position: {
1047 switch (accessor->type) {
1048 case cgltf_type_vec2:
1049 assert(geometry_desc.positions3d.buffer == 0);
1050 buffer_view_2d = &geometry_desc.positions2d;
1051 geometry_desc.aabb = compute_aabb(accessor);
1052 break;
1053 case cgltf_type_vec3:
1054 assert(geometry_desc.positions2d.buffer == 0);
1055 buffer_view_3d = &geometry_desc.positions3d;
1056 geometry_desc.aabb = compute_aabb(accessor);
1057 break;
1058 default:
1059 FAIL(
1060 "Unhandled accessor type %d in vertex positions",
1061 accessor->type);
1062 assert(false);
1063 return false;
1064 }
1065 // It is assumed that meshes have positions, so there is nothing to
1066 // do for the mesh permutation in this case.
1067 break;
1068 }
1069 case cgltf_attribute_type_normal:
1070 buffer_view_3d = &geometry_desc.normals;
1071 perm.has_normals = true;
1072 break;
1073 case cgltf_attribute_type_tangent:
1074 buffer_view_4d = &geometry_desc.tangents;
1075 perm.has_tangents = true;
1076 break;
1077 case cgltf_attribute_type_texcoord:
1078 buffer_view_2d = &geometry_desc.texcoords;
1079 perm.has_texcoords = true;
1080 break;
1081 case cgltf_attribute_type_color:
1082 // TODO: Add support for color.
1083 break;
1084 case cgltf_attribute_type_joints:
1085 // Joints can be either u8 or u16.
1086 switch (accessor->component_type) {
1087 case cgltf_component_type_r_8u:
1088 buffer_view_u8 = &geometry_desc.joints.u8;
1089 break;
1090 case cgltf_component_type_r_16u:
1091 buffer_view_u16 = &geometry_desc.joints.u16;
1092 break;
1093 default:
1094 assert(false);
1095 return false;
1096 }
1097 perm.has_joints = true;
1098 break;
1099 case cgltf_attribute_type_weights:
1100 // Weights can be either u8, u16, or float.
1101 switch (accessor->component_type) {
1102 case cgltf_component_type_r_8u:
1103 buffer_view_u8 = &geometry_desc.weights.u8;
1104 break;
1105 case cgltf_component_type_r_16u:
1106 buffer_view_u16 = &geometry_desc.weights.u16;
1107 break;
1108 case cgltf_component_type_r_32f:
1109 buffer_view_float = &geometry_desc.weights.floats;
1110 break;
1111 default:
1112 assert(false);
1113 return false;
1114 }
1115 perm.has_weights = true;
1116 break;
1117 case cgltf_attribute_type_invalid:
1118 assert(false);
1119 break;
1120 }
1121
1122 // See comments here for accessor/view/buffer invariants:
1123 // https://github.com/KhronosGroup/glTF-Sample-Assets/issues/242
1124 // Gfx only has Buffer and BufferView, not accessors. We must combine
1125 // the glTF's accessor and view offsets correctly.
1126 const cgltf_size offset = accessor->offset + view->offset;
1127 const cgltf_size size_bytes = view->size - accessor->offset;
1128
1129#define CONFIGURE_BUFFER(buf) \
1130 if (buf) { \
1131 buf->buffer = buffer; \
1132 buf->offset_bytes = offset; \
1133 buf->size_bytes = size_bytes; \
1134 buf->stride_bytes = view->stride; \
1135 buf->count = accessor->count; \
1136 }
1137 CONFIGURE_BUFFER(buffer_view_2d);
1138 CONFIGURE_BUFFER(buffer_view_3d);
1139 CONFIGURE_BUFFER(buffer_view_4d);
1140 CONFIGURE_BUFFER(buffer_view_u8);
1141 CONFIGURE_BUFFER(buffer_view_u16);
1142 CONFIGURE_BUFFER(buffer_view_float);
1143 } // Vertex attributes.
1144
1145 assert(
1146 (perm.has_joints && perm.has_weights) ||
1147 (!perm.has_joints && !perm.has_weights));
1148
1149 // If the mesh primitive has no tangents, see if they were computed
1150 // separately.
1151 if (!geometry_desc.tangents.buffer) {
1152 for (cgltf_size t = 0; t < num_tangent_buffers; ++t) {
1153 const cgltfTangentBuffer* cgltf_buffer = &cgltf_tangent_buffers[t];
1154
1155 if (cgltf_buffer->primitive == prim) {
1156 BufferView4d* view = &geometry_desc.tangents;
1157 view->buffer = tangent_buffers[t];
1158 view->offset_bytes = 0;
1159 view->size_bytes = cgltf_buffer->size_bytes;
1160 view->stride_bytes = 0; // Tightly packed.
1161 break;
1162 }
1163 }
1164 }
1165
1166 // Set the number of vertices in the geometry. Since a geometry can have
1167 // either 2d or 3d positions but not both, here we can perform addition
1168 // to compute the total number of vertices.
1169 geometry_desc.num_verts =
1170 geometry_desc.positions2d.count + geometry_desc.positions3d.count;
1171
1172#define CHECK_COUNT(buffer_view, type, num_components) \
1173 if (geometry_desc.buffer_view.buffer) { \
1174 assert(geometry_desc.buffer_view.count == geometry_desc.num_verts); \
1175 }
1176
1177 // Check that the number of vertices is consistent across all vertex
1178 // attributes.
1179 CHECK_COUNT(normals, vec3, 1);
1180 CHECK_COUNT(tangents, vec4, 1);
1181 CHECK_COUNT(texcoords, vec2, 1);
1182 CHECK_COUNT(joints.u8, uint8_t, 4);
1183 CHECK_COUNT(joints.u16, uint16_t, 4);
1184 CHECK_COUNT(weights.u8, uint8_t, 4);
1185 CHECK_COUNT(weights.u16, uint16_t, 4);
1186 CHECK_COUNT(weights.floats, float, 4);
1187
1188 Material* material = 0;
1189 if (mat) {
1190 const cgltf_size material_index = mat - data->materials;
1191 assert(material_index < data->materials_count);
1192 material = materials[material_index];
1193 } else {
1194 // Create a default material for meshes that do not specify one.
1195 material = make_default_material();
1196 }
1197 assert(material);
1198
1199 geometries[next_mesh] = gfx_make_geometry(gfxcore, &geometry_desc);
1200 if (!geometries[next_mesh]) {
1201 return false;
1202 }
1203
1204 // If the user specifies a custom shader, use that instead. Otherwise
1205 // compile a shader based on the mesh's permutation.
1206 //
1207 // Note that Gfx takes care of caching shaders and shader programs.
1208 //
1209 // Caching materials could be useful, but, provided they can share
1210 // shaders, the renderer can check later whether uniforms have the same
1211 // values. Also, changing uniforms is much faster than swapping shaders,
1212 // so shader caching is the most important thing here.
1213 ShaderProgram* mesh_shader =
1214 shader ? shader : make_shader_permutation(gfxcore, perm);
1215 assert(mesh_shader);
1216
1217 meshes[next_mesh] =
1218 gfx_make_mesh(&(MeshDesc){.geometry = geometries[next_mesh],
1219 .material = material,
1220 .shader = mesh_shader});
1221
1222 if (!meshes[next_mesh]) {
1223 return false;
1224 }
1225
1226 assert(object_desc.num_meshes < GFX_MAX_NUM_MESHES);
1227 object_desc.meshes[object_desc.num_meshes] = meshes[next_mesh];
1228 object_desc.num_meshes++;
1229
1230 ++next_mesh;
1231 } // glTF mesh primitive / gfx Mesh.
1232
1233 scene_objects[m] = gfx_make_object(&object_desc);
1234 if (!scene_objects[m]) {
1235 return false;
1236 }
1237 } // glTF mesh / gfx SceneObject.
1238
1239 return true;
1240}
1241
1242/// Compute bounding boxes for the joints in the model.
1243static void compute_joint_bounding_boxes(
1244 const cgltf_data* data, size_t num_joints, JointDesc* joint_descs) {
1245 assert(data);
1246 assert(joint_descs);
1247 assert(num_joints <= GFX_MAX_NUM_JOINTS);
1248
1249 // Initialize bounding boxes so that we can compute unions below.
1250 for (size_t i = 0; i < num_joints; ++i) {
1251 joint_descs[i].box = aabb3_make_empty();
1252 }
1253
1254 // Iterate over the meshes -> primitives -> vertices -> joint indices, and add
1255 // the vertex to the joint's bounding box.
1256 for (cgltf_size n = 0; n < data->nodes_count; ++n) {
1257 const cgltf_node* node = &data->nodes[n];
1258
1259 if (node->skin) {
1260 if (node->mesh) {
1261 const cgltf_mesh* mesh = node->mesh;
1262
1263 for (cgltf_size pr = 0; pr < mesh->primitives_count; ++pr) {
1264 const cgltf_primitive* prim = &mesh->primitives[pr];
1265
1266 // Find the indices of the positions and joints arrays in the
1267 // primitive's attributes.
1268 int positions_index = -1;
1269 int joints_index = -1;
1270 for (int a = 0; a < (int)prim->attributes_count; ++a) {
1271 const cgltf_attribute* attrib = &prim->attributes[a];
1272
1273 if (attrib->type == cgltf_attribute_type_position) {
1274 positions_index = a;
1275 } else if (attrib->type == cgltf_attribute_type_joints) {
1276 joints_index = a;
1277 }
1278 }
1279
1280 if ((positions_index != -1) && (joints_index != -1)) {
1281 const cgltf_accessor* positions =
1282 prim->attributes[positions_index].data;
1283 const cgltf_accessor* joints = prim->attributes[joints_index].data;
1284
1285 assert(positions->count == joints->count);
1286
1287 AccessorIter positions_iter = make_accessor_iter(positions);
1288 AccessorIter joints_iter = make_accessor_iter(joints);
1289 AccessorData position = {0}, joint = {0};
1290
1291 while (accessor_iter_next(&positions_iter, &position)) {
1292 const bool advance = accessor_iter_next(&joints_iter, &joint);
1293 assert(advance); // Counts should match.
1294
1295 const vec3 p = vec3_make(position.x, position.y, position.z);
1296 const int64_t j[4] = {joint.xi, joint.yi, joint.wi, joint.zi};
1297
1298 for (int i = 0; i < 4; ++i) {
1299 const size_t joint_index = j[i];
1300 assert((size_t)joint_index < num_joints);
1301
1302 joint_descs[joint_index].box =
1303 aabb3_add(joint_descs[joint_index].box, p);
1304 }
1305 }
1306 }
1307 }
1308 }
1309 }
1310 }
1311}
1312
1313/// Find the joint node with the smallest index across all skeletons.
1314///
1315/// The channels in glTF may target arbitrary nodes in the scene (those nodes
1316/// are the joints). However, we want to map the "base joint" (the joint/node
1317/// with the smallest index) to 0 in the AnimaDesc's joint array. We can do this
1318/// by subtracting the "base node index" from every joint index or channel
1319/// target.
1320///
1321/// There is an assumption in the animation library that joints are contiguous
1322/// anyway, so this "base joint index" works provided the joint nodes are also
1323/// contiguous in the glTF. The glTF does not guarantee this, but I think it's
1324/// a reasonable assumption that exporters write glTF files in such a way, and
1325/// Blender does appear to do so.
1326cgltf_size find_base_joint_index(const cgltf_data* data) {
1327 assert(data);
1328
1329 cgltf_size base_joint_index = (cgltf_size)-1;
1330
1331 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1332 const cgltf_skin* skin = &data->skins[s];
1333 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1334 // Joint is an index/pointer into the nodes array.
1335 const cgltf_size node_index = skin->joints[j] - data->nodes;
1336 assert(node_index < data->nodes_count);
1337 // Min.
1338 if (node_index < base_joint_index) {
1339 base_joint_index = node_index;
1340 }
1341 }
1342 }
1343
1344 return base_joint_index;
1345}
1346
1347/// Load all skins (Gfx skeletons) from the glTF scene.
1348/// Return the total number of joints.
1349static size_t load_skins(
1350 const cgltf_data* data, Buffer* const* buffers, cgltf_size base_joint_index,
1351 AnimaDesc* anima_desc) {
1352 assert(data);
1353 assert(buffers);
1354 assert(anima_desc);
1355 assert(base_joint_index < data->nodes_count);
1356
1357 // Determines whether the ith joint in the node hierarchy is a joint node.
1358 // This is then used to determine whether a joint is a root of the joint
1359 // hierarchy.
1360 bool is_joint_node[GFX_MAX_NUM_JOINTS] = {false};
1361
1362 size_t num_joints = 0;
1363
1364 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1365 const cgltf_skin* skin = &data->skins[s];
1366 const cgltf_accessor* matrices_accessor = skin->inverse_bind_matrices;
1367 assert(matrices_accessor->count == skin->joints_count);
1368
1369 num_joints += skin->joints_count;
1370 assert(num_joints < GFX_MAX_NUM_JOINTS);
1371
1372 SkeletonDesc* skeleton_desc = &anima_desc->skeletons[s];
1373 *skeleton_desc = (SkeletonDesc){.num_joints = skin->joints_count};
1374
1375 // for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1376 AccessorIter iter = make_accessor_iter(matrices_accessor);
1377 AccessorData matrix = {0};
1378 for (cgltf_size i = 0; accessor_iter_next(&iter, &matrix); ++i) {
1379 const mat4 inv_bind_matrix = mat4_from_array(matrix.floats);
1380
1381 // Joint is an index/pointer into the nodes array.
1382 const cgltf_size node_index = skin->joints[i] - data->nodes;
1383 assert(node_index < data->nodes_count);
1384
1385 const cgltf_size parent_node_index =
1386 skin->joints[i]->parent - data->nodes;
1387 assert(parent_node_index < data->nodes_count);
1388
1389 // Subtract the base index to pack the joints as tightly as possible in
1390 // the AnimaDesc.
1391 assert(node_index >= base_joint_index);
1392 const cgltf_size joint_index = node_index - base_joint_index;
1393
1394 assert(parent_node_index >= base_joint_index);
1395 const cgltf_size parent_index = parent_node_index - base_joint_index;
1396
1397 skeleton_desc->joints[i] = joint_index;
1398
1399 JointDesc* joint_desc = &anima_desc->joints[joint_index];
1400 joint_desc->parent = parent_index;
1401 joint_desc->inv_bind_matrix = inv_bind_matrix;
1402
1403 is_joint_node[joint_index] = true;
1404 };
1405
1406 // glTF may specify a "skeleton", which is the root of the skin's
1407 // (skeleton's) node hierarchy.
1408 // if (skin->skeleton) {
1409 // // cgltf_size root_index = skin->skeleton - data->nodes;
1410 // // assert(root_index <= data->nodes_count);
1411 // // root_node = nodes[root_index];
1412 // assert(false);
1413 //}
1414 }
1415
1416 // Animation library assumes that joints are contiguous.
1417 for (size_t i = 0; i < num_joints; ++i) {
1418 assert(is_joint_node[i]);
1419 }
1420
1421 // Insert the root joint.
1422 // This is the root of all skeletons. It is, specifically, the root of all
1423 // joints that do not have a parent; skins (skeletons) in glTF are not
1424 // guaranteed to have a common parent, but are generally a set of disjoint
1425 // trees.
1426 const size_t root_index = num_joints;
1427 assert(root_index < GFX_MAX_NUM_JOINTS);
1428 anima_desc->joints[root_index] = (JointDesc){.parent = INDEX_NONE};
1429 num_joints++;
1430
1431 // Make root joints point to the root joint at index N.
1432 // The root joints are the ones that have a non-joint node in the glTF as a
1433 // parent.
1434 for (size_t i = 0; i < root_index; ++i) {
1435 JointDesc* joint = &anima_desc->joints[i];
1436 if ((joint->parent >= root_index) || !is_joint_node[joint->parent]) {
1437 joint->parent = root_index;
1438 }
1439 }
1440
1441 return num_joints;
1442}
1443
1444/// Load all animations from the glTF scene.
1445static void load_animations(
1446 const cgltf_data* data, cgltf_size base_joint_index,
1447 AnimaDesc* anima_desc) {
1448 assert(data);
1449 assert(anima_desc);
1450 assert(base_joint_index < data->nodes_count);
1451 assert(data->animations_count <= GFX_MAX_NUM_ANIMATIONS);
1452
1453 for (cgltf_size a = 0; a < data->animations_count; ++a) {
1454 const cgltf_animation* animation = &data->animations[a];
1455 AnimationDesc* animation_desc = &anima_desc->animations[a];
1456
1457 *animation_desc =
1458 (AnimationDesc){.name = sstring_make(animation->name),
1459 .num_channels = animation->channels_count};
1460
1461 assert(animation->channels_count <= GFX_MAX_NUM_CHANNELS);
1462 for (cgltf_size c = 0; c < animation->channels_count; ++c) {
1463 const cgltf_animation_channel* channel = &animation->channels[c];
1464 ChannelDesc* channel_desc = &animation_desc->channels[c];
1465 const cgltf_animation_sampler* sampler = channel->sampler;
1466
1467 const size_t target_index = channel->target_node - data->nodes;
1468 assert(target_index < data->nodes_count);
1469
1470 assert(target_index >= base_joint_index);
1471 const size_t tight_target_index = target_index - base_joint_index;
1472 assert(tight_target_index < anima_desc->num_joints);
1473
1474 *channel_desc = (ChannelDesc){
1475 .target = tight_target_index,
1476 .type = from_gltf_animation_path_type(channel->target_path),
1477 .interpolation = from_gltf_interpolation_type(sampler->interpolation),
1478 .num_keyframes = 0};
1479
1480 // Read time inputs.
1481 AccessorIter iter = make_accessor_iter(sampler->input);
1482 AccessorData input = {0};
1483 for (cgltf_size i = 0; accessor_iter_next(&iter, &input); ++i) {
1484 channel_desc->keyframes[i].time = input.x;
1485 channel_desc->num_keyframes++;
1486 }
1487
1488 // Read transform outputs.
1489 AccessorData output = {0};
1490 switch (channel->target_path) {
1491 case cgltf_animation_path_type_translation: {
1492 iter = make_accessor_iter(sampler->output);
1493 for (cgltf_size i = 0; accessor_iter_next(&iter, &output); ++i) {
1494 channel_desc->keyframes[i].translation =
1495 vec3_make(output.x, output.y, output.z);
1496 }
1497 break;
1498 }
1499 case cgltf_animation_path_type_rotation: {
1500 iter = make_accessor_iter(sampler->output);
1501 for (cgltf_size i = 0; accessor_iter_next(&iter, &output); ++i) {
1502 channel_desc->keyframes[i].rotation =
1503 qmake(output.x, output.y, output.z, output.w);
1504 }
1505 break;
1506 }
1507 default:
1508 // TODO: Handle other channel transformations.
1509 break;
1510 }
1511 }
1512 }
1513}
1514
1515/// Remove joint nodes from the Gfx Scene.
1516///
1517/// Joint nodes are not needed because joints are packed into the Anima.
1518static void remove_joint_nodes(
1519 const cgltf_data* data, SceneNode** scene_nodes) {
1520 assert(data);
1521 assert(scene_nodes);
1522
1523 // This works assuming the joint nodes are contiguous. Contiguity is checked
1524 // when loading skins. See load_skins().
1525 size_t min_joint_index = (size_t)-1;
1526 size_t max_joint_index = 0;
1527
1528 // First get the minimum and maximum indices of all joint nodes.
1529 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1530 const cgltf_skin* skin = &data->skins[s];
1531
1532 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1533 // Joint is an index/pointer into the nodes array.
1534 const cgltf_size joint_index = skin->joints[j] - data->nodes;
1535 assert(joint_index < data->nodes_count);
1536
1537 if (joint_index < min_joint_index) {
1538 min_joint_index = joint_index;
1539 }
1540 if (joint_index > max_joint_index) {
1541 max_joint_index = joint_index;
1542 }
1543 }
1544 }
1545
1546 assert(min_joint_index < data->nodes_count);
1547 assert(max_joint_index < data->nodes_count);
1548
1549 // Now walk over the joint nodes. If a joint's parent is itself not a joint
1550 // node, then that joint is a root of a joint hierarchy (skins in glTF may
1551 // have multiple roots). In such case, delete the root joint recursively.
1552 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1553 const cgltf_skin* skin = &data->skins[s];
1554
1555 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1556 // Joint is an index/pointer into the nodes array.
1557 const cgltf_size joint_index = skin->joints[j] - data->nodes;
1558 assert(joint_index < data->nodes_count);
1559
1560 const cgltf_node* joint = &data->nodes[joint_index];
1561
1562 // Parent node index.
1563 const cgltf_size parent_index = joint->parent - data->nodes;
1564 assert(parent_index < data->nodes_count);
1565
1566 // If the parent is not a joint node, recursively delete this joint node.
1567 if ((parent_index < min_joint_index) ||
1568 (parent_index > max_joint_index)) {
1569 gfx_destroy_node(&scene_nodes[joint_index]);
1570 }
1571 }
1572 }
1573}
1574
1575/// Load all nodes from the glTF scene.
1576///
1577/// This function ignores the many scenes and default scene of the glTF spec
1578/// and instead just loads all nodes into a single gfx Scene.
1579static void load_nodes(
1580 const cgltf_data* data, SceneNode* root_node, SceneObject** objects,
1581 const Anima* anima, SceneNode** nodes) {
1582 // Note that with glTF 2.0, nodes do not form a DAG / scene graph but a
1583 // disjoint union of strict trees:
1584 //
1585 // "For Version 2.0 conformance, the glTF node hierarchy is not a directed
1586 // acyclic graph (DAG) or scene graph, but a disjoint union of strict trees.
1587 // That is, no node may be a direct descendant of more than one node. This
1588 // restriction is meant to simplify implementation and facilitate
1589 // conformance."
1590 //
1591 // This matches the gfx library implementation, where every node can have at
1592 // most one parent.
1593 assert(data);
1594 assert(root_node);
1595 assert(objects);
1596 assert(nodes);
1597
1598 cgltf_size next_camera = 0;
1599
1600 // First pass: create the nodes.
1601 for (cgltf_size n = 0; n < data->nodes_count; ++n) {
1602 const cgltf_node* node = &data->nodes[n];
1603
1604 // Add SceneObject, Camera or Lights.
1605 // TODO: Handle lights once they are implemented in the gfx library.
1606 if (node->mesh) {
1607 const cgltf_size mesh_index = node->mesh - data->meshes;
1608 assert(mesh_index < data->meshes_count);
1609 SceneObject* object = objects[mesh_index];
1610
1611 nodes[n] = gfx_make_object_node(object);
1612
1613 if (node->skin) {
1614 assert(anima);
1615 const cgltf_size skin_index = node->skin - data->skins;
1616 assert(skin_index < data->skins_count);
1617 const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index);
1618 gfx_set_object_skeleton(object, skeleton);
1619 }
1620 } else if (node->camera) {
1621 assert(next_camera < data->cameras_count);
1622
1623 const cgltf_camera* cam = node->camera;
1624
1625 // TODO: We could define a function load_cameras() the same way we load
1626 // every mesh and then remove this ad-hoc loading of cameras here, as well
1627 // as remove 'next_camera'.
1628 Camera* camera = gfx_make_camera();
1629 switch (cam->type) {
1630 case cgltf_camera_type_orthographic:
1631 *camera = camera_orthographic(
1632 0, cam->data.orthographic.xmag, 0, cam->data.orthographic.ymag,
1633 cam->data.orthographic.znear, cam->data.orthographic.zfar);
1634 break;
1635 case cgltf_camera_type_perspective:
1636 *camera = camera_perspective(
1637 cam->data.perspective.yfov, cam->data.perspective.aspect_ratio,
1638 cam->data.perspective.znear, cam->data.perspective.zfar);
1639 break;
1640 case cgltf_camera_type_invalid:
1641 break;
1642 }
1643 nodes[n] = gfx_make_camera_node(camera);
1644 ++next_camera;
1645 } else {
1646 // TODO: implementation for missing node types.
1647 // These nodes currently default to logical nodes.
1648 nodes[n] = gfx_make_node();
1649 }
1650 assert(nodes[n]);
1651
1652 // Set transform.
1653 mat4 transform;
1654 if (node->has_matrix) {
1655 transform = mat4_from_array(node->matrix);
1656 } else {
1657 transform = mat4_id();
1658 if (node->has_scale) {
1659 const mat4 scale = mat4_scale(vec3_from_array(node->scale));
1660 transform = mat4_mul(transform, scale);
1661 }
1662 if (node->has_rotation) {
1663 const quat q = quat_from_array(node->rotation);
1664 const mat4 rotate = mat4_from_quat(q);
1665 transform = mat4_mul(transform, rotate);
1666 }
1667 if (node->has_translation) {
1668 const mat4 translate =
1669 mat4_translate(vec3_from_array(node->translation));
1670 transform = mat4_mul(translate, transform);
1671 }
1672 }
1673 gfx_set_node_transform(nodes[n], &transform);
1674 }
1675
1676 // Second pass: wire up the node hierarchy.
1677 for (cgltf_size n = 0; n < data->nodes_count; ++n) {
1678 const cgltf_node* node = &data->nodes[n];
1679
1680 // If this is a top-level node in the glTF scene, set its parent to the
1681 // given root node.
1682 if (!node->parent) {
1683 gfx_set_node_parent(nodes[n], root_node);
1684 } else {
1685 const cgltf_size parent_index = node->parent - data->nodes;
1686 assert(parent_index < data->nodes_count);
1687 SceneNode* parent = nodes[parent_index];
1688 assert(parent);
1689 gfx_set_node_parent(nodes[n], parent);
1690 }
1691 }
1692
1693 // Clean up scene nodes that correspond to joints in the glTF. These are not
1694 // needed anymore.
1695 if (data->skins_count > 0) {
1696 remove_joint_nodes(data, nodes);
1697 }
1698}
1699
1700/// Load all scenes from the glTF file.
1701///
1702/// If the scene is loaded from memory, set filepath = null.
1703///
1704/// This function ignores the many scenes and default scene of the glTF spec
1705/// and instead just loads all scenes into a single Gfx Scene.
1706static Model* load_scene(
1707 cgltf_data* data, Gfx* gfx, const mstring* filepath, ShaderProgram* shader,
1708 const cgltfTangentBuffer* cgltf_tangent_buffers,
1709 cgltf_size num_tangent_buffers) {
1710 // In a GLTF scene, buffers can be shared among meshes, meshes among nodes,
1711 // etc. Each object is referenced by its index in the relevant array. Here we
1712 // do a button-up construction, first allocating our own graphics objects in
1713 // the same quantities and then re-using the GLTF indices to index these
1714 // arrays.
1715 //
1716 // For simplicity, this function also handles all of the cleanup. Arrays are
1717 // allocated up front, and the helper functions construct their elements. If
1718 // an error is encountered, the helper functions can simply return and this
1719 // function cleans up any intermediate objects that had been created up until
1720 // the point of failure.
1721 //
1722 // Loading animation data:
1723 // - Buffers with animation sampler data need to stay on the CPU, not
1724 // uploaded to the GPU. We could try to implement GPU animation at a later
1725 // stage.
1726 assert(data);
1727 assert(gfx);
1728 assert(filepath);
1729 assert((num_tangent_buffers == 0) || (cgltf_tangent_buffers != 0));
1730
1731 bool success = false;
1732
1733 GfxCore* gfxcore = gfx_get_core(gfx);
1734 const size_t primitive_count = get_total_primitives(data);
1735
1736 const mstring directory = mstring_dirname(*filepath);
1737 LOGD("Filepath: %s", mstring_cstr(filepath));
1738 LOGD("Directory: %s", mstring_cstr(&directory));
1739
1740 // A glTF scene does not necessarily have textures. Materials can be given
1741 // as constants, for example.
1742 // gfx textures are owned by asset cache.
1743 Buffer* tangent_buffers[num_tangent_buffers];
1744 Buffer* buffers[data->buffers_count];
1745 LoadTextureCmd load_texture_cmds[data->textures_count];
1746 const Texture* textures[data->textures_count];
1747 Material* materials[data->materials_count];
1748 Geometry* geometries[primitive_count];
1749 Mesh* meshes[primitive_count];
1750 SceneObject* scene_objects[data->meshes_count];
1751 SceneNode* scene_nodes[data->nodes_count];
1752 Anima* anima = 0;
1753 SceneNode* root_node = 0;
1754 Model* model = 0;
1755
1756 if ((num_tangent_buffers > 0) &&
1757 !load_tangent_buffers(
1758 cgltf_tangent_buffers, num_tangent_buffers, gfxcore,
1759 tangent_buffers)) {
1760 goto cleanup;
1761 }
1762
1763 if (!load_buffers(data, gfxcore, buffers)) {
1764 goto cleanup;
1765 }
1766
1767 if (data->textures_count > 0) {
1768 load_textures_lazy(
1769 data, gfxcore, mstring_cstr(&directory), load_texture_cmds);
1770 }
1771
1772 if (!load_materials(data, gfx, load_texture_cmds, textures, materials)) {
1773 goto cleanup;
1774 }
1775
1776 if (!load_meshes(
1777 data, gfxcore, buffers, tangent_buffers, cgltf_tangent_buffers,
1778 num_tangent_buffers, materials, shader, primitive_count, geometries,
1779 meshes, scene_objects)) {
1780 goto cleanup;
1781 }
1782
1783 // Create the scene's root node.
1784 // This is an anima node if the scene has skins; otherwise it is a logical
1785 // node.
1786 if (data->skins_count > 0) {
1787 const cgltf_size base = find_base_joint_index(data);
1788
1789 AnimaDesc anima_desc =
1790 (AnimaDesc){.num_skeletons = data->skins_count,
1791 .num_animations = data->animations_count,
1792 .num_joints = load_skins(data, buffers, base, &anima_desc)};
1793 load_animations(data, base, &anima_desc);
1794
1795 compute_joint_bounding_boxes(
1796 data, anima_desc.num_joints, anima_desc.joints);
1797
1798 anima = gfx_make_anima(&anima_desc);
1799 root_node = gfx_make_anima_node(anima);
1800 } else {
1801 root_node = gfx_make_node();
1802 }
1803 assert(root_node);
1804
1805 // The root node becomes the root of all scene nodes.
1806 load_nodes(data, root_node, scene_objects, anima, scene_nodes);
1807
1808 model = gfx_make_model(root_node);
1809
1810 success = true;
1811
1812cleanup:
1813 // The arrays of resources are no longer needed. The resources themselves are
1814 // destroyed only if this function fails.
1815 if (!success) {
1816 for (cgltf_size i = 0; i < num_tangent_buffers; ++i) {
1817 if (tangent_buffers[i]) {
1818 gfx_destroy_buffer(gfxcore, &tangent_buffers[i]);
1819 }
1820 }
1821 for (cgltf_size i = 0; i < data->buffers_count; ++i) {
1822 if (buffers[i]) {
1823 gfx_destroy_buffer(gfxcore, &buffers[i]);
1824 }
1825 }
1826 for (cgltf_size i = 0; i < data->materials_count; ++i) {
1827 if (materials[i]) {
1828 gfx_destroy_material(&materials[i]);
1829 }
1830 }
1831 for (size_t i = 0; i < primitive_count; ++i) {
1832 if (geometries[i]) {
1833 gfx_destroy_geometry(gfxcore, &geometries[i]);
1834 }
1835 }
1836 for (size_t i = 0; i < primitive_count; ++i) {
1837 if (meshes[i]) {
1838 gfx_destroy_mesh(&meshes[i]);
1839 }
1840 }
1841 for (cgltf_size i = 0; i < data->meshes_count; ++i) {
1842 if (scene_objects[i]) {
1843 gfx_destroy_object(&scene_objects[i]);
1844 }
1845 }
1846 for (cgltf_size i = 0; i < data->nodes_count; ++i) {
1847 if (scene_nodes[i]) {
1848 gfx_destroy_node(&scene_nodes[i]);
1849 }
1850 }
1851 if (root_node) {
1852 gfx_destroy_node(&root_node); // Node owns the anima.
1853 } else if (anima) {
1854 gfx_destroy_anima(&anima);
1855 }
1856 }
1857 return model;
1858}
1859
1860Model* gfx_model_load(Gfx* gfx, const LoadModelCmd* cmd) {
1861 assert(gfx);
1862 assert(cmd);
1863
1864 Model* model = 0;
1865
1866 cgltf_options options = {0};
1867 cgltf_data* data = NULL;
1868 cgltfTangentBuffer* tangent_buffers = 0;
1869
1870 cgltf_result result;
1871 switch (cmd->origin) {
1872 case AssetFromFile:
1873 result = cgltf_parse_file(&options, mstring_cstr(&cmd->filepath), &data);
1874 break;
1875 case AssetFromMemory:
1876 result = cgltf_parse(&options, cmd->data, cmd->size_bytes, &data);
1877 break;
1878 }
1879 if (result != cgltf_result_success) {
1880 goto cleanup;
1881 }
1882
1883 if (cmd->origin == AssetFromFile) {
1884 // Must call cgltf_load_buffers() to load buffer data.
1885 result = cgltf_load_buffers(&options, data, mstring_cstr(&cmd->filepath));
1886 if (result != cgltf_result_success) {
1887 goto cleanup;
1888 }
1889 }
1890
1891 // Compute tangents for normal-mapped models that are missing them.
1892 cgltf_size num_tangent_buffers = 0;
1893 cgltf_compute_tangents(
1894 &options, data, &tangent_buffers, &num_tangent_buffers);
1895
1896 model = load_scene(
1897 data, gfx, &cmd->filepath, cmd->shader, tangent_buffers,
1898 num_tangent_buffers);
1899
1900cleanup:
1901 if (data) {
1902 cgltf_free(data);
1903 }
1904 if (tangent_buffers) {
1905 free(tangent_buffers);
1906 }
1907 return model;
1908}
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..fb423cc
--- /dev/null
+++ b/src/asset/texture.c
@@ -0,0 +1,199 @@
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;
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 int old_components = 0;
69 for (int i = 0; i < 6; ++i) {
70 // Flip +Y and -Y textures vertically.
71 stbi_set_flip_vertically_on_load(((i == 2) || (i == 3)) ? 1 : 0);
72 const char* filepath =
73 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_x + i);
74 stbi_uc* image_pixels =
75 stbi_load(filepath, &width, &height, &components, 0);
76 if (!image_pixels) {
77 log_error("Failed to load texture file: %s", filepath);
78 break;
79 }
80 if ((i > 0) && (components != old_components)) {
81 log_error(
82 "All textures in a cubemap must have the same number of "
83 "components");
84 break;
85 }
86 if ((i != 2) && (i != 3)) {
87 flip_horizontally(image_pixels, width, height, components);
88 }
89 pixels[i] = image_pixels;
90 old_components = components;
91 }
92 break;
93 }
94 }
95 break;
96 case AssetFromMemory:
97 // TODO: Load textures from memory.
98 log_error("Loading textures from memory is not yet implemented");
99 return 0;
100 }
101
102 // Error out if we failed to load a texture.
103 if (!pixels[0] ||
104 (cmd->type == LoadCubemap &&
105 (!pixels[1] || !pixels[2] || !pixels[3] || !pixels[4] || !pixels[5]))) {
106 for (int i = 0; i < 6; ++i) {
107 if (pixels[i]) {
108 stbi_image_free(pixels[i]);
109 }
110 }
111 return 0;
112 }
113
114 TextureDesc desc = (TextureDesc){0};
115 desc.width = width;
116 desc.height = height;
117
118 switch (cmd->type) {
119 case LoadTexture:
120 desc.dimension = Texture2D;
121 break;
122 case LoadCubemap:
123 desc.dimension = TextureCubeMap;
124 break;
125 }
126
127 switch (components) {
128 case 1:
129 switch (cmd->colour_space) {
130 case LinearColourSpace:
131 desc.format = TextureR8;
132 break;
133 case sRGB:
134 // TODO: Gamma single-channel textures are not implemented yet.
135 // The caller should convert the single-channel to RGB and pass it down
136 // as sRGB. This is why the ChronographWatch currently appears red on the
137 // back.
138 log_error("Gamma colour space is not supported for 1-channel textures");
139 // return 0;
140 desc.format = TextureR8;
141 break;
142 default:
143 log_error("Unsupported texture colour space: %d", cmd->colour_space);
144 return 0;
145 }
146 break;
147 case 3:
148 switch (cmd->colour_space) {
149 case LinearColourSpace:
150 desc.format = TextureRGB8;
151 break;
152 case sRGB:
153 desc.format = TextureSRGB8;
154 break;
155 default:
156 log_error("Unsupported texture colour space: %d", cmd->colour_space);
157 return 0;
158 }
159 break;
160 case 4:
161 switch (cmd->colour_space) {
162 case LinearColourSpace:
163 desc.format = TextureRGBA8;
164 break;
165 case sRGB:
166 desc.format = TextureSRGBA8;
167 break;
168 default:
169 log_error("Unsupported texture colour space: %d", cmd->colour_space);
170 return 0;
171 }
172 break;
173 default:
174 log_error("Unsupported number of texture components: %d", components);
175 return 0;
176 }
177
178 desc.filtering = cmd->filtering;
179 desc.mipmaps = cmd->mipmaps;
180
181 switch (cmd->type) {
182 case LoadTexture:
183 desc.data.pixels = pixels[0];
184 break;
185 case LoadCubemap:
186 for (int i = 0; i < 6; ++i) {
187 *(&desc.data.cubemap.pixels_pos_x + i) = pixels[i];
188 }
189 break;
190 }
191
192 Texture* texture = gfx_make_texture(gfxcore, &desc);
193 for (int i = 0; i < 6; ++i) {
194 if (pixels[i]) {
195 stbi_image_free(pixels[i]);
196 }
197 }
198 return texture;
199}
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*);
diff --git a/src/core/buffer.c b/src/core/buffer.c
new file mode 100644
index 0000000..3b7e4bc
--- /dev/null
+++ b/src/core/buffer.c
@@ -0,0 +1,85 @@
1#include "buffer.h"
2
3#include <gfx/core.h>
4#include <gfx_assert.h>
5
6#include <math/vec2.h>
7#include <math/vec3.h>
8#include <math/vec4.h>
9
10static size_t get_buffer_size_bytes(
11 BufferType type, const BufferDataDesc* desc) {
12 return desc->count * gfx_get_buffer_type_size_bytes(type);
13}
14
15static GLenum get_buffer_usage(BufferUsage usage) {
16 switch (usage) {
17 case BufferStatic:
18 return GL_STATIC_DRAW;
19 case BufferDynamic:
20 return GL_DYNAMIC_DRAW;
21 }
22 FAIL("Unhandled buffer usage");
23 return GL_STATIC_DRAW;
24}
25
26size_t gfx_get_buffer_type_size_bytes(BufferType type) {
27 switch (type) {
28 case BufferUntyped:
29 return 1;
30 case Buffer2d:
31 return sizeof(vec2);
32 case Buffer3d:
33 return sizeof(vec3);
34 case Buffer4d:
35 return sizeof(vec4);
36 case BufferFloat:
37 return sizeof(float);
38 case BufferU8:
39 return sizeof(uint8_t);
40 case BufferU16:
41 return sizeof(uint16_t);
42 }
43 FAIL("Unhandled buffer type");
44 return 0;
45}
46
47bool gfx_init_buffer(Buffer* buffer, const BufferDesc* desc) {
48 assert(buffer);
49
50 buffer->type = desc->type;
51 buffer->usage = desc->usage;
52 buffer->size_bytes = get_buffer_size_bytes(desc->type, &desc->data);
53 const GLenum usage = get_buffer_usage(desc->usage);
54
55 glGenBuffers(1, &buffer->vbo);
56 glBindBuffer(GL_ARRAY_BUFFER, buffer->vbo);
57 glBufferData(GL_ARRAY_BUFFER, buffer->size_bytes, desc->data.data, usage);
58 glBindBuffer(GL_ARRAY_BUFFER, 0);
59 ASSERT_GL;
60
61 return true;
62}
63
64void gfx_del_buffer(Buffer* buffer) {
65 assert(buffer);
66 if (buffer->vbo) {
67 glDeleteBuffers(1, &buffer->vbo);
68 buffer->vbo = 0;
69 }
70}
71
72void gfx_update_buffer(Buffer* buffer, const BufferDataDesc* desc) {
73 assert(buffer);
74 assert(desc);
75 // OpenGL allows updating static buffers, but it is not optimal for
76 // performance, so we enforce data in static buffers remain static.
77 assert(buffer->usage == BufferDynamic);
78
79 const size_t update_size_bytes = get_buffer_size_bytes(buffer->type, desc);
80 assert(update_size_bytes <= buffer->size_bytes);
81
82 glBindBuffer(GL_ARRAY_BUFFER, buffer->vbo);
83 glBufferSubData(GL_ARRAY_BUFFER, 0, update_size_bytes, desc->data);
84 glBindBuffer(GL_ARRAY_BUFFER, 0);
85}
diff --git a/src/core/buffer.h b/src/core/buffer.h
new file mode 100644
index 0000000..1df225a
--- /dev/null
+++ b/src/core/buffer.h
@@ -0,0 +1,24 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7#include <stdbool.h>
8#include <stddef.h>
9
10typedef struct Buffer {
11 GLuint vbo;
12 BufferType type;
13 BufferUsage usage;
14 size_t size_bytes;
15} Buffer;
16
17/// Return the buffer type size in bytes.
18size_t gfx_get_buffer_type_size_bytes(BufferType);
19
20/// Create a buffer from raw data.
21bool gfx_init_buffer(Buffer*, const BufferDesc*);
22
23/// Destroy the buffer.
24void gfx_del_buffer(Buffer*);
diff --git a/src/core/constants.h b/src/core/constants.h
new file mode 100644
index 0000000..a6a3b94
--- /dev/null
+++ b/src/core/constants.h
@@ -0,0 +1,9 @@
1#pragma once
2
3// Shaders vertex attribute locations must match the channels here.
4#define GFX_POSITION_CHANNEL 0
5#define GFX_NORMAL_CHANNEL 1
6#define GFX_TANGENT_CHANNEL 2
7#define GFX_TEXCOORDS_CHANNEL 3
8#define GFX_JOINTS_CHANNEL 4
9#define GFX_WEIGHTS_CHANNEL 5
diff --git a/src/core/core.c b/src/core/core.c
new file mode 100644
index 0000000..9c04cc7
--- /dev/null
+++ b/src/core/core.c
@@ -0,0 +1,437 @@
1#include "core_impl.h"
2
3#include "gl_util.h"
4
5// #include <log/log.h>
6#include <fnv1a.h>
7
8#include <assert.h>
9
10void gfx_init_gfxcore(GfxCore* gfxcore) {
11 assert(gfxcore);
12
13 mempool_make(&gfxcore->buffers);
14 mempool_make(&gfxcore->framebuffers);
15 mempool_make(&gfxcore->geometries);
16 mempool_make(&gfxcore->renderbuffers);
17 mempool_make(&gfxcore->shaders);
18 mempool_make(&gfxcore->shader_programs);
19 mempool_make(&gfxcore->textures);
20
21 mempool_make(&gfxcore->shader_cache);
22 mempool_make(&gfxcore->program_cache);
23
24 glEnable(GL_CULL_FACE);
25 glFrontFace(GL_CCW);
26 glCullFace(GL_BACK);
27
28 glEnable(GL_DEPTH_TEST);
29
30 // Filter cubemaps across their faces to avoid seams.
31 // https://www.khronos.org/opengl/wiki/Cubemap_Texture#Seamless_cubemap
32 glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
33}
34
35// Conveniently destroy any objects that have not been destroyed by the
36// application.
37void gfx_del_gfxcore(GfxCore* gfxcore) {
38 assert(gfxcore);
39
40 mempool_foreach(&gfxcore->buffers, buffer, { gfx_del_buffer(buffer); });
41
42 mempool_foreach(&gfxcore->framebuffers, framebuffer, {
43 gfx_del_framebuffer(framebuffer);
44 });
45
46 mempool_foreach(
47 &gfxcore->geometries, geometry, { gfx_del_geometry(geometry); });
48
49 mempool_foreach(&gfxcore->renderbuffers, renderbuffer, {
50 gfx_del_renderbuffer(renderbuffer);
51 });
52
53 mempool_foreach(
54 &gfxcore->shader_programs, prog, { gfx_del_shader_program(prog); });
55
56 mempool_foreach(&gfxcore->shaders, shader, { gfx_del_shader(shader); });
57
58 mempool_foreach(&gfxcore->textures, texture, { gfx_del_texture(texture); });
59}
60
61// -----------------------------------------------------------------------------
62// Render commands.
63// -----------------------------------------------------------------------------
64
65void gfx_start_frame(GfxCore* gfxcore) {
66 assert(gfxcore);
67
68 glViewport(
69 gfxcore->viewport.x, gfxcore->viewport.y, gfxcore->viewport.width,
70 gfxcore->viewport.height);
71 glClearColor(0, 0, 0, 0);
72 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
73
74 ASSERT_GL;
75}
76
77void gfx_end_frame(GfxCore* gfxcore) {
78 assert(gfxcore);
79 ASSERT_GL;
80}
81
82void gfx_set_viewport(GfxCore* gfxcore, int x, int y, int width, int height) {
83 assert(gfxcore);
84 gfxcore->viewport =
85 (Viewport){.x = x, .y = y, .width = width, .height = height};
86}
87
88void gfx_get_viewport(
89 GfxCore* gfxcore, int* x, int* y, int* width, int* height) {
90 assert(gfxcore);
91 assert(x);
92 assert(y);
93 assert(width);
94 assert(height);
95
96 *x = gfxcore->viewport.x;
97 *y = gfxcore->viewport.y;
98 *width = gfxcore->viewport.width;
99 *height = gfxcore->viewport.height;
100}
101
102void gfx_clear(GfxCore* gfxcore, vec4 colour) {
103 assert(gfxcore);
104
105 glClearColor(colour.x, colour.y, colour.z, colour.w);
106 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
107}
108
109void gfx_set_blending(GfxCore* gfxcore, bool enable) {
110 assert(gfxcore);
111 if (enable) {
112 glEnable(GL_BLEND);
113 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
114 } else {
115 glDisable(GL_BLEND);
116 }
117}
118
119void gfx_set_depth_mask(GfxCore* gfxcore, bool enable) {
120 assert(gfxcore);
121 glDepthMask(enable ? GL_TRUE : GL_FALSE);
122}
123
124void gfx_set_culling(GfxCore* gfxcore, bool enable) {
125 assert(gfxcore);
126 if (enable) {
127 glEnable(GL_CULL_FACE);
128 } else {
129 glDisable(GL_CULL_FACE);
130 }
131}
132
133void gfx_set_polygon_offset(GfxCore* gfxcore, float scale, float bias) {
134 assert(gfxcore);
135 if ((scale != 0.0f) || (bias != 0.0f)) {
136 glEnable(GL_POLYGON_OFFSET_FILL);
137 } else {
138 glDisable(GL_POLYGON_OFFSET_FILL);
139 }
140 glPolygonOffset(scale, bias);
141}
142
143void gfx_reset_polygon_offset(GfxCore* gfxcore) {
144 assert(gfxcore);
145 glPolygonOffset(0, 0);
146 glDisable(GL_POLYGON_OFFSET_FILL);
147}
148
149// -----------------------------------------------------------------------------
150// Buffers.
151// -----------------------------------------------------------------------------
152
153Buffer* gfx_make_buffer(GfxCore* gfxcore, const BufferDesc* desc) {
154 assert(gfxcore);
155 assert(desc);
156
157 Buffer* buffer = mempool_alloc(&gfxcore->buffers);
158 if (!gfx_init_buffer(buffer, desc)) {
159 mempool_free(&gfxcore->buffers, &buffer);
160 return 0;
161 }
162 return buffer;
163}
164
165void gfx_destroy_buffer(GfxCore* gfxcore, Buffer** buffer) {
166 assert(gfxcore);
167 assert(buffer);
168 if (*buffer) {
169 gfx_del_buffer(*buffer);
170 mempool_free(&gfxcore->buffers, buffer);
171 }
172}
173
174// -----------------------------------------------------------------------------
175// Geometry.
176// -----------------------------------------------------------------------------
177
178Geometry* gfx_make_geometry(GfxCore* gfxcore, const GeometryDesc* desc) {
179 assert(gfxcore);
180 assert(desc);
181
182 Geometry* geometry = mempool_alloc(&gfxcore->geometries);
183 if (!gfx_init_geometry(geometry, gfxcore, desc)) {
184 mempool_free(&gfxcore->geometries, &geometry);
185 return 0;
186 }
187 return geometry;
188}
189
190void gfx_destroy_geometry(GfxCore* gfxcore, Geometry** geometry) {
191 assert(gfxcore);
192 assert(geometry);
193
194 if (*geometry) {
195 gfx_del_geometry(*geometry);
196 mempool_free(&gfxcore->geometries, geometry);
197 }
198}
199
200// -----------------------------------------------------------------------------
201// Textures.
202// -----------------------------------------------------------------------------
203
204Texture* gfx_make_texture(GfxCore* gfxcore, const TextureDesc* desc) {
205 assert(gfxcore);
206 assert(desc);
207
208 Texture* texture = mempool_alloc(&gfxcore->textures);
209 if (!gfx_init_texture(texture, desc)) {
210 mempool_free(&gfxcore->textures, &texture);
211 return 0;
212 }
213 return texture;
214}
215
216void gfx_destroy_texture(GfxCore* gfxcore, Texture** texture) {
217 assert(gfxcore);
218 assert(texture);
219 assert(*texture);
220
221 if (*texture) {
222 gfx_del_texture(*texture);
223 mempool_free(&gfxcore->textures, texture);
224 }
225}
226
227// -----------------------------------------------------------------------------
228// Renderbuffers.
229// -----------------------------------------------------------------------------
230
231RenderBuffer* gfx_make_renderbuffer(
232 GfxCore* gfxcore, const RenderBufferDesc* desc) {
233 assert(gfxcore);
234 assert(desc);
235
236 RenderBuffer* renderbuffer = mempool_alloc(&gfxcore->renderbuffers);
237 if (!gfx_init_renderbuffer(renderbuffer, desc)) {
238 mempool_free(&gfxcore->renderbuffers, &renderbuffer);
239 }
240 return renderbuffer;
241}
242
243void gfx_destroy_renderbuffer(GfxCore* gfxcore, RenderBuffer** renderbuffer) {
244 assert(gfxcore);
245 assert(renderbuffer);
246 assert(*renderbuffer);
247
248 if (*renderbuffer) {
249 gfx_del_renderbuffer(*renderbuffer);
250 mempool_free(&gfxcore->renderbuffers, renderbuffer);
251 }
252}
253
254// -----------------------------------------------------------------------------
255// Framebuffers.
256// -----------------------------------------------------------------------------
257
258FrameBuffer* gfx_make_framebuffer(
259 GfxCore* gfxcore, const FrameBufferDesc* desc) {
260 assert(gfxcore);
261 assert(desc);
262
263 FrameBuffer* framebuffer = mempool_alloc(&gfxcore->framebuffers);
264 if (!gfx_init_framebuffer(framebuffer, desc)) {
265 mempool_free(&gfxcore->framebuffers, &framebuffer);
266 return 0;
267 }
268 return framebuffer;
269}
270
271void gfx_destroy_framebuffer(GfxCore* gfxcore, FrameBuffer** framebuffer) {
272 assert(gfxcore);
273 assert(framebuffer);
274 assert(*framebuffer);
275
276 if (*framebuffer) {
277 gfx_del_framebuffer(*framebuffer);
278 mempool_free(&gfxcore->framebuffers, framebuffer);
279 }
280}
281
282// -----------------------------------------------------------------------------
283// Shaders.
284// -----------------------------------------------------------------------------
285
286static hash_t hash_shader_desc(const ShaderDesc* desc) {
287 assert(desc);
288 // Defines may affect shader permutations, so we need to hash those as well.
289 hash_t hash = fnv1a32_begin();
290 hash = fnv1a32_update(hash, desc->code, strlen(desc->code));
291 for (size_t i = 0; i < desc->num_defines; ++i) {
292 const ShaderCompilerDefine* define = &desc->defines[i];
293
294 hash = fnv1a32_update(
295 hash, sstring_cstr(&define->name), sstring_length(&define->name));
296 hash = fnv1a32_update(
297 hash, sstring_cstr(&define->value), sstring_length(&define->value));
298 }
299 return hash;
300}
301
302static hash_t hash_program_desc(const ShaderProgramDesc* desc) {
303 assert(desc);
304 return ((hash_t)desc->vertex_shader->id << 16) |
305 (hash_t)desc->fragment_shader->id;
306}
307
308static Shader* find_cached_shader(ShaderCache* cache, hash_t hash) {
309 assert(cache);
310 mempool_foreach(cache, entry, {
311 if (entry->hash == hash) {
312 return entry->shader;
313 }
314 });
315 return 0;
316}
317
318static ShaderProgram* find_cached_program(ProgramCache* cache, hash_t hash) {
319 assert(cache);
320 mempool_foreach(cache, entry, {
321 if (entry->hash == hash) {
322 return entry->program;
323 }
324 });
325 return 0;
326}
327
328static ShaderCacheEntry* find_shader_cache_entry(
329 ShaderCache* cache, const Shader* shader) {
330 assert(cache);
331 assert(shader);
332 mempool_foreach(cache, entry, {
333 if (entry->shader == shader) {
334 return entry;
335 }
336 });
337 return 0;
338}
339
340static ShaderProgramCacheEntry* find_program_cache_entry(
341 ProgramCache* cache, const ShaderProgram* prog) {
342 assert(cache);
343 assert(prog);
344 mempool_foreach(cache, entry, {
345 if (entry->program == prog) {
346 return entry;
347 }
348 });
349 return 0;
350}
351
352Shader* gfx_make_shader(GfxCore* gfxcore, const ShaderDesc* desc) {
353 assert(gfxcore);
354 assert(desc);
355
356 // Check the shader cache first.
357 ShaderCache* cache = &gfxcore->shader_cache;
358 const hash_t hash = hash_shader_desc(desc);
359 Shader* shader = find_cached_shader(cache, hash);
360 if (shader) {
361 // LOGD("Found cached shader with hash [%lx]", hash);
362 return shader;
363 }
364
365 shader = mempool_alloc(&gfxcore->shaders);
366 if (!shader) {
367 return 0;
368 }
369 if (!gfx_compile_shader(shader, desc)) {
370 mempool_free(&gfxcore->shaders, &shader);
371 return 0;
372 }
373 ShaderCacheEntry* entry = mempool_alloc(cache);
374 *entry = (ShaderCacheEntry){.hash = hash, .shader = shader};
375 // LOGD("Added shader with hash [%lx] to cache", hash);
376 return shader;
377}
378
379void gfx_destroy_shader(GfxCore* gfxcore, Shader** shader) {
380 assert(gfxcore);
381 assert(shader);
382
383 if (*shader) {
384 // Remove the shader from the cache.
385 ShaderCache* cache = &gfxcore->shader_cache;
386 ShaderCacheEntry* entry = find_shader_cache_entry(cache, *shader);
387 assert(entry); // Must be there, shaders can't go untracked.
388 mempool_free(cache, &entry);
389
390 gfx_del_shader(*shader);
391 mempool_free(&gfxcore->shaders, shader);
392 }
393}
394
395ShaderProgram* gfx_make_shader_program(
396 GfxCore* gfxcore, const ShaderProgramDesc* desc) {
397 assert(gfxcore);
398 assert(desc);
399
400 // Check the shader program cache first.
401 ProgramCache* cache = &gfxcore->program_cache;
402 const hash_t hash = hash_program_desc(desc);
403 ShaderProgram* prog = find_cached_program(cache, hash);
404 if (prog) {
405 // LOGD("Found cached shader program with hash [%lx]", hash);
406 return prog;
407 }
408
409 prog = mempool_alloc(&gfxcore->shader_programs);
410 if (!gfx_build_shader_program(prog, desc)) {
411 mempool_free(&gfxcore->shader_programs, &prog);
412 return 0;
413 }
414 ShaderProgramCacheEntry* entry = mempool_alloc(cache);
415 *entry = (ShaderProgramCacheEntry){.hash = hash, .program = prog};
416 // LOGD("Added shader program with hash [%lx] to cache", hash);
417 return prog;
418}
419
420void gfx_destroy_shader_program(GfxCore* gfxcore, ShaderProgram** prog) {
421 assert(gfxcore);
422 assert(prog);
423 if (*prog) {
424 // Remove the shader program from the cache.
425 ProgramCache* cache = &gfxcore->program_cache;
426 ShaderProgramCacheEntry* entry = find_program_cache_entry(cache, *prog);
427 // TODO: The following assertion is too restrictive. Clients can end up
428 // re-using the same shader by virtue of the cache. The assertion assumes
429 // that no two "different" clients use the same set of shaders. This can
430 // be relaxed by reference-counting the shaders in the cache.
431 assert(entry); // Must be there, shaders can't go untracked.
432 mempool_free(cache, &entry);
433
434 gfx_del_shader_program(*prog);
435 mempool_free(&gfxcore->shader_programs, prog);
436 }
437}
diff --git a/src/core/core_impl.h b/src/core/core_impl.h
new file mode 100644
index 0000000..320532d
--- /dev/null
+++ b/src/core/core_impl.h
@@ -0,0 +1,70 @@
1#pragma once
2
3#include <gfx/core.h>
4#include <gfx/sizes.h>
5
6#include "buffer.h"
7#include "framebuffer.h"
8#include "geometry.h"
9#include "renderbuffer.h"
10#include "shader.h"
11#include "shader_program.h"
12#include "texture.h"
13
14#include <mempool.h>
15
16#include <stdint.h>
17
18typedef uint32_t hash_t;
19
20// TODO: Make a generic (hash, void*) structure and define functions over it.
21// Then define a macro that defines type-safe macros given the type of the
22// entry.
23typedef struct ShaderCacheEntry {
24 hash_t hash;
25 Shader* shader;
26} ShaderCacheEntry;
27
28typedef struct ShaderProgramCacheEntry {
29 hash_t hash;
30 ShaderProgram* program;
31} ShaderProgramCacheEntry;
32
33DEF_MEMPOOL(buffer_pool, Buffer, GFX_MAX_NUM_BUFFERS)
34DEF_MEMPOOL(framebuffer_pool, FrameBuffer, GFX_MAX_NUM_FRAMEBUFFERS)
35DEF_MEMPOOL(geometry_pool, Geometry, GFX_MAX_NUM_GEOMETRIES)
36DEF_MEMPOOL(renderbuffer_pool, RenderBuffer, GFX_MAX_NUM_RENDERBUFFERS)
37DEF_MEMPOOL(shader_pool, Shader, GFX_MAX_NUM_SHADERS)
38DEF_MEMPOOL(shader_program_pool, ShaderProgram, GFX_MAX_NUM_SHADER_PROGRAMS)
39DEF_MEMPOOL(texture_pool, Texture, GFX_MAX_NUM_TEXTURES)
40
41DEF_MEMPOOL(ShaderCache, ShaderCacheEntry, GFX_MAX_NUM_SHADERS)
42DEF_MEMPOOL(ProgramCache, ShaderProgramCacheEntry, GFX_MAX_NUM_SHADER_PROGRAMS)
43
44typedef struct {
45 int x;
46 int y;
47 int width;
48 int height;
49} Viewport;
50
51typedef struct GfxCore {
52 Viewport viewport;
53 // mempools for render-specific objects: textures, geometry, etc.
54 buffer_pool buffers;
55 framebuffer_pool framebuffers;
56 geometry_pool geometries;
57 renderbuffer_pool renderbuffers;
58 shader_pool shaders;
59 shader_program_pool shader_programs;
60 texture_pool textures;
61 // Caches.
62 ShaderCache shader_cache;
63 ProgramCache program_cache;
64} GfxCore;
65
66/// Create a new render backend.
67void gfx_init_gfxcore(GfxCore*);
68
69/// Destroy the render backend.
70void gfx_del_gfxcore(GfxCore*);
diff --git a/src/core/framebuffer.c b/src/core/framebuffer.c
new file mode 100644
index 0000000..76d9002
--- /dev/null
+++ b/src/core/framebuffer.c
@@ -0,0 +1,151 @@
1#include "framebuffer.h"
2
3#include "renderbuffer.h"
4#include "texture.h"
5
6#include <gfx_assert.h>
7
8#include <error.h>
9
10static void framebuffer_attach_colour(
11 FrameBuffer* framebuffer, const FrameBufferAttachment* attachment) {
12 assert(framebuffer);
13 assert(attachment);
14
15 switch (attachment->type) {
16 case FrameBufferNoAttachment:
17 break;
18 case FrameBufferTexture:
19 glFramebufferTexture2D(
20 GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
21 attachment->texture.texture->id, attachment->texture.mip_level);
22 break;
23 case FrameBufferCubemapTexture:
24 glFramebufferTexture2D(
25 GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
26 to_GL_cubemap_face(attachment->cubemap.face),
27 attachment->cubemap.texture->id, attachment->cubemap.mip_level);
28 break;
29 case FrameBufferRenderBuffer:
30 glFramebufferRenderbuffer(
31 GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
32 attachment->renderbuffer->id);
33 break;
34 }
35
36 ASSERT_GL;
37}
38
39static void framebuffer_attach_depth(
40 FrameBuffer* framebuffer, const FrameBufferAttachment* attachment) {
41 assert(framebuffer);
42 assert(attachment);
43
44 switch (attachment->type) {
45 case FrameBufferNoAttachment:
46 break;
47 case FrameBufferTexture:
48 glFramebufferTexture2D(
49 GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_DEPTH_COMPONENT,
50 attachment->texture.texture->id, attachment->texture.mip_level);
51 break;
52 // TODO: Could distinguish between colour and depth attachment types to make
53 // this a compile-time error.
54 case FrameBufferCubemapTexture:
55 log_error("Cannot use a cubemap texture as a depth framebuffer attachment");
56 break;
57 case FrameBufferRenderBuffer:
58 glFramebufferRenderbuffer(
59 GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
60 attachment->renderbuffer->id);
61 break;
62 }
63
64 ASSERT_GL;
65}
66
67bool gfx_init_framebuffer(
68 FrameBuffer* framebuffer, const FrameBufferDesc* desc) {
69 assert(framebuffer);
70 assert(desc);
71
72 glGenFramebuffers(1, &framebuffer->id);
73 if (!framebuffer->id) {
74 log_error("glGenFramebuffers() failed");
75 return false;
76 }
77
78 // Allow incomplete framebuffers for flexibility.
79 // Attach buffers and check the framebuffer status only if buffers are given
80 // up front.
81 if (desc->colour.type != FrameBufferNoAttachment ||
82 desc->depth.type != FrameBufferNoAttachment) {
83 // TODO: Could use the "named" API to avoid having to bind the framebuffer.
84 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->id);
85 framebuffer_attach_colour(framebuffer, &desc->colour);
86 framebuffer_attach_depth(framebuffer, &desc->depth);
87 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
88 log_error("glCheckFramebufferStatus() failed");
89 gfx_del_framebuffer(framebuffer);
90 return false;
91 }
92 glBindFramebuffer(GL_FRAMEBUFFER, 0);
93 }
94
95 ASSERT_GL;
96 return true;
97}
98
99bool gfx_framebuffer_attach_colour(
100 FrameBuffer* framebuffer, const FrameBufferAttachment* attachment) {
101 assert(framebuffer);
102 assert(attachment);
103
104 // TODO: Could use the "named" API to avoid having to bind the framebuffer.
105 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->id);
106 framebuffer_attach_colour(framebuffer, attachment);
107 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
108 log_error("glCheckFramebufferStatus() failed");
109 return false;
110 }
111 return true;
112}
113
114bool gfx_framebuffer_attach_depth(
115 FrameBuffer* framebuffer, const FrameBufferAttachment* attachment) {
116 assert(framebuffer);
117 assert(attachment);
118
119 // TODO: Could use the "named" API to avoid having to bind the framebuffer.
120 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->id);
121 framebuffer_attach_depth(framebuffer, attachment);
122 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
123 log_error("glCheckFramebufferStatus() failed");
124 return false;
125 }
126 return true;
127}
128
129void gfx_del_framebuffer(FrameBuffer* framebuffer) {
130 assert(framebuffer);
131 if (framebuffer->id) {
132 glDeleteFramebuffers(1, &framebuffer->id);
133 framebuffer->id = 0;
134 }
135}
136
137void gfx_activate_framebuffer(const FrameBuffer* framebuffer) {
138 assert(framebuffer);
139 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->id);
140}
141
142void gfx_deactivate_framebuffer(const FrameBuffer* framebuffer) {
143 assert(framebuffer);
144 glBindFramebuffer(GL_FRAMEBUFFER, 0);
145}
146
147void gfx_framebuffer_set_viewport(
148 FrameBuffer* framebuffer, int x, int y, int width, int height) {
149 assert(framebuffer);
150 glViewport(x, y, width, height);
151}
diff --git a/src/core/framebuffer.h b/src/core/framebuffer.h
new file mode 100644
index 0000000..1a3439c
--- /dev/null
+++ b/src/core/framebuffer.h
@@ -0,0 +1,15 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7typedef struct FrameBuffer {
8 GLuint id;
9} FrameBuffer;
10
11/// Create a new framebuffer.
12bool gfx_init_framebuffer(FrameBuffer*, const FrameBufferDesc*);
13
14/// Destroy the framebuffer.
15void gfx_del_framebuffer(FrameBuffer*);
diff --git a/src/core/geometry.c b/src/core/geometry.c
new file mode 100644
index 0000000..488dc23
--- /dev/null
+++ b/src/core/geometry.c
@@ -0,0 +1,321 @@
1#include "geometry.h"
2
3#include "buffer.h"
4#include "constants.h"
5
6#include <gfx_assert.h>
7
8#include <math/vec2.h>
9#include <math/vec3.h>
10
11/// Determines whether a view is populated.
12///
13/// Note that views are allowed to have no data, in which case a buffer of the
14/// specified size is created.
15#define view_is_populated(BUFFER_VIEW) (BUFFER_VIEW.count > 0)
16
17static GLenum primitive_type_to_gl(PrimitiveType type) {
18 switch (type) {
19 case Triangles:
20 return GL_TRIANGLES;
21 case TriangleFan:
22 return GL_TRIANGLE_FAN;
23 case TriangleStrip:
24 return GL_TRIANGLE_STRIP;
25 }
26 FAIL("primitive_type_to_gl(): missing case");
27 return GL_INVALID_ENUM;
28}
29
30/// Create a typed buffer for the buffer view if the view does not already point
31/// to a buffer.
32void init_view_buffer(
33 GfxCore* gfxcore, BufferView* view, BufferType buffer_type,
34 BufferUsage buffer_usage) {
35 if (!view->buffer) {
36 view->buffer = gfx_make_buffer(
37 gfxcore, &(BufferDesc){.usage = buffer_usage,
38 .type = buffer_type,
39 .data.data = view->data,
40 .data.count = view->count});
41 assert(view->buffer);
42 }
43}
44
45/// Configure the buffer in the VAO.
46static void configure_buffer(
47 GfxCore* gfxcore, const GeometryDesc* desc, BufferView* view,
48 size_t num_components, GLenum component_type, GLboolean normalized,
49 GLuint channel) {
50 assert(gfxcore);
51 assert(desc);
52 assert(view);
53 assert(view->buffer);
54 assert(view->count == desc->num_verts);
55 assert((view->offset_bytes + view->size_bytes) <= view->buffer->size_bytes);
56
57 glBindBuffer(GL_ARRAY_BUFFER, view->buffer->vbo);
58 glEnableVertexAttribArray(channel);
59 if ((component_type == GL_FLOAT) || normalized) {
60 glVertexAttribPointer(
61 channel, num_components, component_type, normalized, view->stride_bytes,
62 (const void*)view->offset_bytes);
63 } else {
64 assert(!normalized);
65 assert(
66 (component_type == GL_BYTE) || (component_type == GL_UNSIGNED_BYTE) ||
67 (component_type == GL_SHORT) || (component_type == GL_UNSIGNED_SHORT) ||
68 (component_type == GL_INT) || component_type == GL_UNSIGNED_INT);
69 glVertexAttribIPointer(
70 channel, num_components, component_type, view->stride_bytes,
71 (const void*)view->offset_bytes);
72 }
73 glBindBuffer(GL_ARRAY_BUFFER, 0);
74}
75
76static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
77 assert(gfxcore);
78 assert(desc);
79
80 if (view_is_populated(desc->positions3d)) {
81 init_view_buffer(
82 gfxcore, (BufferView*)&desc->positions3d, Buffer3d, desc->buffer_usage);
83 if (!desc->positions3d.buffer) {
84 return false;
85 }
86 configure_buffer(
87 gfxcore, desc, (BufferView*)&desc->positions3d, 3, GL_FLOAT, GL_FALSE,
88 GFX_POSITION_CHANNEL);
89 } else if (view_is_populated(desc->positions2d)) {
90 init_view_buffer(
91 gfxcore, (BufferView*)&desc->positions2d, Buffer2d, desc->buffer_usage);
92 if (!desc->positions2d.buffer) {
93 return false;
94 }
95 configure_buffer(
96 gfxcore, desc, (BufferView*)&desc->positions2d, 2, GL_FLOAT, GL_FALSE,
97 GFX_POSITION_CHANNEL);
98 }
99 if (view_is_populated(desc->normals)) {
100 init_view_buffer(
101 gfxcore, (BufferView*)&desc->normals, Buffer3d, desc->buffer_usage);
102 if (!desc->normals.buffer) {
103 return false;
104 }
105 configure_buffer(
106 gfxcore, desc, (BufferView*)&desc->normals, 3, GL_FLOAT, GL_FALSE,
107 GFX_NORMAL_CHANNEL);
108 }
109 if (view_is_populated(desc->tangents)) {
110 init_view_buffer(
111 gfxcore, (BufferView*)&desc->tangents, Buffer4d, desc->buffer_usage);
112 if (!desc->tangents.buffer) {
113 return false;
114 }
115 configure_buffer(
116 gfxcore, desc, (BufferView*)&desc->tangents, 4, GL_FLOAT, GL_FALSE,
117 GFX_TANGENT_CHANNEL);
118 }
119 if (view_is_populated(desc->texcoords)) {
120 init_view_buffer(
121 gfxcore, (BufferView*)&desc->texcoords, Buffer2d, desc->buffer_usage);
122 if (!desc->texcoords.buffer) {
123 return false;
124 }
125 configure_buffer(
126 gfxcore, desc, (BufferView*)&desc->texcoords, 2, GL_FLOAT, GL_FALSE,
127 GFX_TEXCOORDS_CHANNEL);
128 }
129 if (view_is_populated(desc->joints.u8)) {
130 init_view_buffer(
131 gfxcore, (BufferView*)&desc->joints.u8, BufferU8, desc->buffer_usage);
132 if (!desc->joints.u8.buffer) {
133 return false;
134 }
135 configure_buffer(
136 gfxcore, desc, (BufferView*)&desc->joints.u8, 4, GL_UNSIGNED_BYTE,
137 GL_FALSE, GFX_JOINTS_CHANNEL);
138 } else if (view_is_populated(desc->joints.u16)) {
139 init_view_buffer(
140 gfxcore, (BufferView*)&desc->joints.u16, BufferU16, desc->buffer_usage);
141 if (!desc->joints.u16.buffer) {
142 return false;
143 }
144 configure_buffer(
145 gfxcore, desc, (BufferView*)&desc->joints.u16, 4, GL_UNSIGNED_SHORT,
146 GL_FALSE, GFX_JOINTS_CHANNEL);
147 }
148
149 // If weights are given as unsigned integers, then they are normalized
150 // when read by the shader.
151 if (view_is_populated(desc->weights.u8)) {
152 init_view_buffer(
153 gfxcore, (BufferView*)&desc->weights.u8, BufferU8, desc->buffer_usage);
154 if (!desc->weights.u8.buffer) {
155 return false;
156 }
157 configure_buffer(
158 gfxcore, desc, (BufferView*)&desc->weights.u8, 4, GL_UNSIGNED_BYTE,
159 GL_TRUE, GFX_WEIGHTS_CHANNEL);
160 } else if (view_is_populated(desc->weights.u16)) {
161 init_view_buffer(
162 gfxcore, (BufferView*)&desc->weights.u16, BufferU16,
163 desc->buffer_usage);
164 if (!desc->weights.u16.buffer) {
165 return false;
166 }
167 configure_buffer(
168 gfxcore, desc, (BufferView*)&desc->weights.u16, 4, GL_UNSIGNED_SHORT,
169 GL_TRUE, GFX_WEIGHTS_CHANNEL);
170 } else if (view_is_populated(desc->weights.floats)) {
171 init_view_buffer(
172 gfxcore, (BufferView*)&desc->weights.floats, BufferFloat,
173 desc->buffer_usage);
174 if (!desc->weights.floats.buffer) {
175 return false;
176 }
177 configure_buffer(
178 gfxcore, desc, (BufferView*)&desc->weights.floats, 4, GL_FLOAT,
179 GL_FALSE, GFX_WEIGHTS_CHANNEL);
180 }
181
182 return true;
183}
184
185static bool configure_indices(GfxCore* gfxcore, GeometryDesc* desc) {
186 assert(gfxcore);
187 assert(desc);
188
189 if (view_is_populated(desc->indices8)) {
190 assert(desc->num_indices > 0);
191 assert(
192 desc->num_indices <= desc->indices8.size_bytes / sizeof(VertexIndex8));
193 init_view_buffer(
194 gfxcore, (BufferView*)&desc->indices8, BufferU8, desc->buffer_usage);
195 if (!desc->indices8.buffer) {
196 return false;
197 }
198 } else if (view_is_populated(desc->indices16)) {
199 assert(desc->num_indices > 0);
200 assert(
201 desc->num_indices <=
202 desc->indices16.size_bytes / sizeof(VertexIndex16));
203 init_view_buffer(
204 gfxcore, (BufferView*)&desc->indices16, BufferU16, desc->buffer_usage);
205 if (!desc->indices16.buffer) {
206 return false;
207 }
208 }
209
210 return true;
211}
212
213bool gfx_init_geometry(
214 Geometry* geometry, GfxCore* gfxcore, const GeometryDesc* input_desc) {
215 assert(geometry);
216 assert(gfxcore);
217 assert(input_desc);
218 assert(
219 view_is_populated(input_desc->positions3d) ||
220 view_is_populated(input_desc->positions2d));
221 assert(input_desc->num_verts > 0);
222
223 geometry->mode = primitive_type_to_gl(input_desc->type);
224 geometry->desc = *input_desc;
225 geometry->num_verts = input_desc->num_verts;
226 geometry->gfxcore = gfxcore;
227
228 // The geometry's copy of the descriptor is manipulated below. Create a
229 // shorter name for it.
230 GeometryDesc* desc = &geometry->desc;
231
232 glGenVertexArrays(1, &geometry->vao);
233 glBindVertexArray(geometry->vao);
234 if (!configure_vertex_attributes(gfxcore, desc)) {
235 goto cleanup;
236 }
237 if (!configure_indices(gfxcore, desc)) {
238 goto cleanup;
239 }
240 glBindVertexArray(0);
241 ASSERT_GL;
242
243 return true;
244
245cleanup:
246 gfx_del_geometry(geometry);
247 return 0;
248}
249
250void gfx_del_geometry(Geometry* geometry) {
251 assert(geometry);
252 if (geometry->vao) {
253 glDeleteVertexArrays(1, &geometry->vao);
254 geometry->vao = 0;
255 }
256}
257
258void gfx_update_geometry(Geometry* geometry, const GeometryDesc* desc) {
259 assert(geometry);
260 assert(desc);
261 // New geometry size cannot exceed original size.
262 assert(desc->positions3d.size_bytes <= geometry->desc.positions3d.size_bytes);
263 assert(desc->positions2d.size_bytes <= geometry->desc.positions2d.size_bytes);
264 assert(desc->normals.size_bytes <= geometry->desc.normals.size_bytes);
265 assert(desc->tangents.size_bytes <= geometry->desc.tangents.size_bytes);
266 assert(desc->texcoords.size_bytes <= geometry->desc.texcoords.size_bytes);
267 assert(desc->joints.u8.size_bytes <= geometry->desc.joints.u8.size_bytes);
268 assert(desc->joints.u16.size_bytes <= geometry->desc.joints.u16.size_bytes);
269 assert(desc->weights.u8.size_bytes <= geometry->desc.weights.u8.size_bytes);
270 assert(desc->weights.u16.size_bytes <= geometry->desc.weights.u16.size_bytes);
271 assert(
272 desc->weights.floats.size_bytes <=
273 geometry->desc.weights.floats.size_bytes);
274
275 if (desc->positions3d.data) {
276 // The geometry must already have an underlying GPU buffer.
277 assert(geometry->desc.positions3d.buffer);
278 gfx_update_buffer(
279 geometry->desc.positions3d.buffer,
280 &(BufferDataDesc){.vec3s = desc->positions3d.data,
281 .count =
282 desc->positions3d.size_bytes / sizeof(vec3)});
283 }
284 // TODO: more
285 else {
286 FAIL("TODO: gfx_update_geometry() - handle other buffer types");
287 }
288
289 if (desc->num_verts != 0) {
290 geometry->num_verts = desc->num_verts;
291 }
292}
293
294void gfx_render_geometry(const Geometry* geometry) {
295 assert(geometry);
296 assert(geometry->vao);
297
298 const GeometryDesc* desc = &geometry->desc;
299 glBindVertexArray(geometry->vao);
300
301 if (desc->indices8.buffer) {
302 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, desc->indices8.buffer->vbo);
303 glDrawElements(
304 geometry->mode, desc->num_indices, GL_UNSIGNED_BYTE,
305 (const void*)desc->indices8.offset_bytes);
306 } else if (desc->indices16.buffer) {
307 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, desc->indices16.buffer->vbo);
308 glDrawElements(
309 geometry->mode, desc->num_indices, GL_UNSIGNED_SHORT,
310 (const void*)desc->indices16.offset_bytes);
311 } else {
312 glDrawArrays(geometry->mode, 0, geometry->num_verts);
313 }
314
315 glBindVertexArray(0);
316}
317
318aabb3 gfx_get_geometry_aabb(const Geometry* geometry) {
319 assert(geometry);
320 return geometry->desc.aabb;
321}
diff --git a/src/core/geometry.h b/src/core/geometry.h
new file mode 100644
index 0000000..c37a76f
--- /dev/null
+++ b/src/core/geometry.h
@@ -0,0 +1,28 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7#include <stdbool.h>
8
9/// A piece of renderable geometry.
10///
11/// The Geometry does not own its buffers, since buffers are typically shared
12/// to reduce the memory footprint and the number of draw calls. More generally,
13/// the renderer assumes ownership of all rendering resources, which simplifies
14/// their management.
15typedef struct Geometry {
16 GLuint vao;
17 GLenum mode;
18 GeometryDesc desc;
19 size_t num_verts; // May differ from the initial value in the descriptor if
20 // the geometry is updated.
21 GfxCore* gfxcore;
22} Geometry;
23
24/// Create new geometry.
25bool gfx_init_geometry(Geometry*, GfxCore*, const GeometryDesc*);
26
27/// Destroy the geometry.
28void gfx_del_geometry(Geometry*);
diff --git a/src/core/gl_util.h b/src/core/gl_util.h
new file mode 100644
index 0000000..d2d6e22
--- /dev/null
+++ b/src/core/gl_util.h
@@ -0,0 +1,45 @@
1#pragma once
2
3#include <glad/glad.h>
4#include <log/log.h>
5
6#define GFX_GL_CONTEXT_PC 1
7#define GFX_GL_CONTEXT_ES 2
8
9#ifndef GFX_GL_CONTEXT
10#define GFX_GL_CONTEXT GFX_GL_CONTEXT_PC
11#endif // GFX_GL_CONTEXT
12
13/// Log an error if an OpenGL has occurred.
14#ifndef NDEBUG
15#define ASSERT_GL \
16 { \
17 GLenum e = glGetError(); \
18 switch (e) { \
19 case GL_NO_ERROR: \
20 break; \
21 case GL_INVALID_ENUM: \
22 LOGE("GL_INVALID_ENUM"); \
23 break; \
24 case GL_INVALID_VALUE: \
25 LOGE("GL_INVALID_VALUE"); \
26 break; \
27 case GL_INVALID_OPERATION: \
28 LOGE("GL_INVALID_OPERATION"); \
29 break; \
30 case GL_INVALID_FRAMEBUFFER_OPERATION: \
31 LOGE("GL_INVALID_FRAMEBUFFER_OPERATION"); \
32 break; \
33 case GL_OUT_OF_MEMORY: \
34 LOGE("GL_OUT_OF_MEMORY"); \
35 break; \
36 /*case GL_STACK_UNDERFLOW: LOGE("GL_STACK_UNDERFLOW");*/ \
37 /*case GL_STACK_OVERFLOW: LOGE("GL_STACK_OVERFLOW");*/ \
38 default: \
39 LOGE("Unknown OpenGL error"); \
40 break; \
41 } \
42 }
43#else // Not NDEBUG.
44#define ASSERT_GL
45#endif
diff --git a/src/core/renderbuffer.c b/src/core/renderbuffer.c
new file mode 100644
index 0000000..2753f3b
--- /dev/null
+++ b/src/core/renderbuffer.c
@@ -0,0 +1,35 @@
1#include "renderbuffer.h"
2
3#include "texture.h"
4
5#include <error.h>
6
7bool gfx_init_renderbuffer(
8 RenderBuffer* renderbuffer, const RenderBufferDesc* desc) {
9 assert(renderbuffer);
10 assert(desc);
11
12 glGenRenderbuffers(1, &renderbuffer->id);
13 if (!renderbuffer->id) {
14 log_error("glGenRenderbuffers failed");
15 return false;
16 }
17
18 glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer->id);
19 glRenderbufferStorage(
20 GL_RENDERBUFFER, to_GL_internal_format(desc->texture_format), desc->width,
21 desc->height);
22 glBindRenderbuffer(GL_RENDERBUFFER, 0);
23
24 ASSERT_GL;
25 return true;
26}
27
28void gfx_del_renderbuffer(RenderBuffer* renderbuffer) {
29 assert(renderbuffer);
30
31 if (renderbuffer->id) {
32 glDeleteRenderbuffers(1, &renderbuffer->id);
33 renderbuffer->id = 0;
34 }
35}
diff --git a/src/core/renderbuffer.h b/src/core/renderbuffer.h
new file mode 100644
index 0000000..ea11610
--- /dev/null
+++ b/src/core/renderbuffer.h
@@ -0,0 +1,15 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7typedef struct RenderBuffer {
8 GLuint id;
9} RenderBuffer;
10
11/// Create a new renderbuffer.
12bool gfx_init_renderbuffer(RenderBuffer*, const RenderBufferDesc*);
13
14/// Destroy the renderbuffer.
15void gfx_del_renderbuffer(RenderBuffer*);
diff --git a/src/core/shader.c b/src/core/shader.c
new file mode 100644
index 0000000..dded084
--- /dev/null
+++ b/src/core/shader.c
@@ -0,0 +1,92 @@
1#include "shader.h"
2
3#include "gl_util.h"
4#include <gfx_assert.h>
5
6#include <cstring.h>
7#include <log/log.h>
8
9#include <stdlib.h>
10#include <string.h>
11
12static GLenum shader_type_to_gl(ShaderType type) {
13 switch (type) {
14 case VertexShader:
15 return GL_VERTEX_SHADER;
16 case FragmentShader:
17 return GL_FRAGMENT_SHADER;
18 }
19 FAIL("shader_type_to_gl(): missing case");
20 return GL_INVALID_ENUM;
21}
22
23static lstring make_defines_string(const ShaderDesc* desc) {
24 lstring defines = {0};
25 for (size_t i = 0; i < desc->num_defines; ++i) {
26 const ShaderCompilerDefine* define = &desc->defines[i];
27 lstring_append_cstr(&defines, "#define ");
28 lstring_append_cstr(&defines, sstring_cstr(&define->name));
29 lstring_append_cstr(&defines, " ");
30 lstring_append_cstr(&defines, sstring_cstr(&define->value));
31 lstring_append_cstr(&defines, "\n");
32 }
33 return defines;
34}
35
36/// Creates an OpenGL shader.
37/// Returns non-zero on success, 0 on failure.
38static GLuint create_shader(const ShaderDesc* desc) {
39 const GLuint shader = glCreateShader(shader_type_to_gl(desc->type));
40 if (!shader) {
41 return 0;
42 }
43
44#if GFX_GL_CONTEXT == GFX_GL_CONTEXT_ES
45 const char* header = "#version 300 es\n\nprecision highp float;";
46#else
47 const char* header = "#version 400 core\n\n";
48#endif
49
50 lstring defines = make_defines_string(desc);
51
52 const char* source_bits[] = {header, lstring_cstr(&defines), desc->code};
53 const GLint source_lengths[] = {
54 strlen(header), lstring_length(&defines), strlen(desc->code)};
55
56 glShaderSource(shader, 3, source_bits, source_lengths);
57 glCompileShader(shader);
58 GLint result;
59 glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
60 if (result == GL_FALSE) {
61 GLint log_len;
62 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_len);
63 if (log_len > 0) {
64 char* log = calloc(log_len, sizeof(char));
65 glGetShaderInfoLog(shader, log_len, NULL, log);
66 static const char* sep = "----------";
67 LOGE("Failed loading shader: %s\n%s\n%s\n%s", log, sep, desc->code, sep);
68 free(log);
69 } else {
70 LOGE("Failed loading shader:\n%s", desc->code);
71 }
72 glDeleteShader(shader);
73 return 0;
74 }
75 ASSERT_GL;
76 return shader;
77}
78
79bool gfx_compile_shader(Shader* shader, const ShaderDesc* desc) {
80 shader->id = create_shader(desc);
81 return shader->id != 0;
82}
83
84void gfx_del_shader(Shader* shader) {
85 assert(shader);
86
87 if (shader->id) {
88 glDeleteShader(shader->id);
89 shader->id = 0;
90 }
91 ASSERT_GL;
92}
diff --git a/src/core/shader.h b/src/core/shader.h
new file mode 100644
index 0000000..b9f5679
--- /dev/null
+++ b/src/core/shader.h
@@ -0,0 +1,17 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7#include <stdbool.h>
8
9typedef struct Shader {
10 GLuint id;
11} Shader;
12
13/// Compile a new shader.
14bool gfx_compile_shader(Shader*, const ShaderDesc*);
15
16/// Destroy the shader.
17void gfx_del_shader(Shader*);
diff --git a/src/core/shader_program.c b/src/core/shader_program.c
new file mode 100644
index 0000000..3840019
--- /dev/null
+++ b/src/core/shader_program.c
@@ -0,0 +1,355 @@
1#include "shader_program.h"
2
3#include "gl_util.h"
4#include "shader.h"
5#include "texture.h"
6#include <gfx_assert.h>
7
8#include <log/log.h>
9
10#include <stdlib.h>
11#include <string.h>
12
13/// Creates an OpenGL shader program.
14/// Returns non-zero on success, 0 on failure.
15static GLuint create_program(GLuint vertex_shader, GLuint fragment_shader) {
16 const GLuint prog = glCreateProgram();
17 if (prog == 0) {
18 LOGE("Failed creating shader program");
19 return 0;
20 }
21 glAttachShader(prog, vertex_shader);
22 glAttachShader(prog, fragment_shader);
23 glLinkProgram(prog);
24 GLint result;
25 glGetProgramiv(prog, GL_LINK_STATUS, &result);
26 if (result == GL_FALSE) {
27 GLint log_len;
28 glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_len);
29 if (log_len > 0) {
30 char* log = calloc(log_len, sizeof(char));
31 glGetProgramInfoLog(prog, log_len, NULL, log);
32 LOGE("Failed creating shader program: %s", log);
33 free(log);
34 } else {
35 LOGE("Failed creating shader program");
36 }
37 glDeleteProgram(prog);
38 return 0;
39 }
40 ASSERT_GL;
41 return prog;
42}
43
44bool gfx_build_shader_program(
45 ShaderProgram* prog, const ShaderProgramDesc* desc) {
46 assert(prog);
47 assert(desc);
48
49 prog->id = create_program(desc->vertex_shader->id, desc->fragment_shader->id);
50 return prog->id != 0;
51}
52
53void gfx_del_shader_program(ShaderProgram* prog) {
54 assert(prog);
55
56 if (prog->id) {
57 glDeleteProgram(prog->id);
58 prog->id = 0;
59 }
60 ASSERT_GL;
61}
62
63void gfx_activate_shader_program(const ShaderProgram* prog) {
64 assert(prog);
65 glUseProgram(prog->id);
66 ASSERT_GL;
67}
68
69void gfx_deactivate_shader_program(const ShaderProgram* prog) {
70 assert(prog);
71 glUseProgram(0);
72 ASSERT_GL;
73}
74
75static void set_int_uniform(GLuint prog, const char* name, int value) {
76 assert(prog != 0);
77 assert(name);
78
79 const GLint location = glGetUniformLocation(prog, name);
80 if (location >= 0) {
81 glUniform1i(location, value);
82 }
83}
84
85static void set_float_uniform(GLuint prog, const char* name, float value) {
86 assert(prog != 0);
87 assert(name);
88
89 const GLint location = glGetUniformLocation(prog, name);
90 if (location >= 0) {
91 glUniform1f(location, value);
92 }
93}
94
95static void set_mat4_uniform(
96 GLuint prog, const char* name, const mat4* mats, size_t count) {
97 assert(prog != 0);
98 assert(name);
99 assert(mats);
100
101 const GLint location = glGetUniformLocation(prog, name);
102 if (location >= 0) {
103 glUniformMatrix4fv(location, count, GL_FALSE, (const float*)mats);
104 }
105}
106
107static void set_vec3_uniform(GLuint prog, const char* name, vec3 value) {
108 assert(prog != 0);
109 assert(name);
110
111 const GLint location = glGetUniformLocation(prog, name);
112 if (location >= 0) {
113 glUniform3f(location, value.x, value.y, value.z);
114 }
115}
116
117static void set_vec4_uniform(GLuint prog, const char* name, vec4 value) {
118 assert(prog != 0);
119 assert(name);
120
121 const GLint location = glGetUniformLocation(prog, name);
122 if (location >= 0) {
123 glUniform4f(location, value.x, value.y, value.z, value.w);
124 }
125}
126
127static void set_texture_uniform(
128 GLuint prog, const char* name, int texture_unit, const Texture* texture) {
129 assert(prog != 0);
130 assert(name);
131 assert(texture);
132
133 const GLint location = glGetUniformLocation(prog, name);
134 if (location >= 0) {
135 glActiveTexture(GL_TEXTURE0 + texture_unit);
136 glBindTexture(texture->target, texture->id);
137 glUniform1i(location, texture_unit);
138 }
139}
140
141void gfx_apply_uniforms(const ShaderProgram* prog) {
142 assert(prog);
143
144 int next_texture_unit = 0;
145 for (int i = 0; i < prog->num_uniforms; ++i) {
146 const ShaderUniform* uniform = &prog->uniforms[i];
147 switch (uniform->type) {
148 case UniformInt:
149 set_int_uniform(prog->id, uniform->name.str, uniform->value.uniform_int);
150 break;
151 case UniformFloat:
152 set_float_uniform(
153 prog->id, uniform->name.str, uniform->value.uniform_float);
154 break;
155 case UniformMat4:
156 set_mat4_uniform(
157 prog->id, uniform->name.str, &uniform->value.uniform_mat4, 1);
158 break;
159 case UniformVec3:
160 set_vec3_uniform(
161 prog->id, uniform->name.str, uniform->value.uniform_vec3);
162 break;
163 case UniformVec4:
164 set_vec4_uniform(
165 prog->id, uniform->name.str, uniform->value.uniform_vec4);
166 break;
167 case UniformTexture:
168 set_texture_uniform(
169 prog->id, uniform->name.str, next_texture_unit,
170 uniform->value.texture);
171 next_texture_unit++;
172 break;
173 case UniformMat4Array:
174 set_mat4_uniform(
175 prog->id, uniform->name.str, uniform->value.array.values,
176 uniform->value.array.count);
177 break;
178 }
179 }
180}
181
182// Get the ShaderUniform object by name from the shader program if it already
183// exists, or allocate a new one otherwise.
184static ShaderUniform* get_or_allocate_uniform(
185 ShaderProgram* prog, const char* name) {
186 assert(prog);
187 assert(name);
188
189 // First search for the uniform in the list.
190 for (int i = 0; i < prog->num_uniforms; ++i) {
191 ShaderUniform* uniform = &prog->uniforms[i];
192 if (sstring_eq_cstr(uniform->name, name)) {
193 return uniform;
194 }
195 }
196
197 // Create the uniform if it does not exist.
198 if (prog->num_uniforms == GFX_MAX_UNIFORMS_PER_SHADER) {
199 FAIL(
200 "Exceeded the maximum number of uniforms per shader. Please increase "
201 "this value.");
202 return 0;
203 }
204 ShaderUniform* uniform = &prog->uniforms[prog->num_uniforms];
205 prog->num_uniforms++;
206 return uniform;
207}
208
209// The functions below save the value of a uniform in the shader program. If the
210// uniform does not even exist, then there is no need to store the value.
211
212void gfx_set_uniform(ShaderProgram* prog, const ShaderUniform* uniform) {
213 switch (uniform->type) {
214 case UniformInt:
215 gfx_set_int_uniform(prog, uniform->name.str, uniform->value.uniform_int);
216 break;
217 case UniformFloat:
218 gfx_set_float_uniform(
219 prog, uniform->name.str, uniform->value.uniform_float);
220 break;
221 case UniformMat4:
222 gfx_set_mat4_uniform(prog, uniform->name.str, &uniform->value.uniform_mat4);
223 break;
224 case UniformVec3:
225 gfx_set_vec3_uniform(prog, uniform->name.str, uniform->value.uniform_vec3);
226 break;
227 case UniformVec4:
228 gfx_set_vec4_uniform(prog, uniform->name.str, uniform->value.uniform_vec4);
229 break;
230 case UniformTexture:
231 gfx_set_texture_uniform(prog, uniform->name.str, uniform->value.texture);
232 break;
233 case UniformMat4Array:
234 gfx_set_mat4_array_uniform(
235 prog, uniform->name.str, uniform->value.array.values,
236 uniform->value.array.count);
237 break;
238 }
239}
240
241void gfx_set_int_uniform(ShaderProgram* prog, const char* name, int value) {
242 assert(prog);
243 assert(name);
244
245 // No need to store the uniform on our side if it does not exist in the
246 // program.
247 const GLint location = glGetUniformLocation(prog->id, name);
248 if (location < 0) {
249 return;
250 }
251 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
252 assert(uniform);
253 uniform->name = sstring_make(name);
254 uniform->type = UniformInt;
255 uniform->value.uniform_int = value;
256}
257
258void gfx_set_float_uniform(ShaderProgram* prog, const char* name, float value) {
259 assert(prog);
260 assert(name);
261
262 // No need to store the uniform on our side if it does not exist in the
263 // program.
264 const GLint location = glGetUniformLocation(prog->id, name);
265 if (location < 0) {
266 return;
267 }
268 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
269 assert(uniform);
270 uniform->name = sstring_make(name);
271 uniform->type = UniformFloat;
272 uniform->value.uniform_float = value;
273}
274
275void gfx_set_mat4_uniform(
276 ShaderProgram* prog, const char* name, const mat4* mat) {
277 assert(prog);
278 assert(name);
279 assert(mat);
280
281 const GLint location = glGetUniformLocation(prog->id, name);
282 if (location < 0) {
283 return;
284 }
285 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
286 assert(uniform);
287 uniform->name = sstring_make(name);
288 uniform->type = UniformMat4;
289 uniform->value.uniform_mat4 = *mat;
290}
291
292void gfx_set_vec3_uniform(ShaderProgram* prog, const char* name, vec3 value) {
293 assert(prog);
294 assert(name);
295
296 const GLint location = glGetUniformLocation(prog->id, name);
297 if (location < 0) {
298 return;
299 }
300 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
301 assert(uniform);
302 uniform->name = sstring_make(name);
303 uniform->type = UniformVec3;
304 uniform->value.uniform_vec3 = value;
305}
306
307void gfx_set_vec4_uniform(ShaderProgram* prog, const char* name, vec4 value) {
308 assert(prog);
309 assert(name);
310
311 const GLint location = glGetUniformLocation(prog->id, name);
312 if (location < 0) {
313 return;
314 }
315 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
316 assert(uniform);
317 uniform->name = sstring_make(name);
318 uniform->type = UniformVec4;
319 uniform->value.uniform_vec4 = value;
320}
321
322void gfx_set_texture_uniform(
323 ShaderProgram* prog, const char* name, const Texture* texture) {
324 assert(prog);
325 assert(name);
326 assert(texture);
327
328 const GLint location = glGetUniformLocation(prog->id, name);
329 if (location < 0) {
330 return;
331 }
332 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
333 assert(uniform);
334 uniform->name = sstring_make(name);
335 uniform->type = UniformTexture;
336 uniform->value.texture = texture;
337}
338
339void gfx_set_mat4_array_uniform(
340 ShaderProgram* prog, const char* name, const mat4* mats, size_t count) {
341 assert(prog);
342 assert(name);
343 assert(mats);
344
345 const GLint location = glGetUniformLocation(prog->id, name);
346 if (location < 0) {
347 return;
348 }
349 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
350 assert(uniform);
351 uniform->name = sstring_make(name);
352 uniform->type = UniformMat4Array;
353 uniform->value.array.count = count;
354 uniform->value.array.values = mats;
355}
diff --git a/src/core/shader_program.h b/src/core/shader_program.h
new file mode 100644
index 0000000..521118d
--- /dev/null
+++ b/src/core/shader_program.h
@@ -0,0 +1,22 @@
1#pragma once
2
3#include <gfx/core.h>
4#include <gfx/sizes.h>
5
6#include "gl_util.h"
7
8#include <stdbool.h>
9
10typedef struct Texture Texture;
11
12typedef struct ShaderProgram {
13 GLuint id;
14 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_SHADER];
15 int num_uniforms;
16} ShaderProgram;
17
18/// Create a new shader program.
19bool gfx_build_shader_program(ShaderProgram*, const ShaderProgramDesc*);
20
21/// Destroy the shader program.
22void gfx_del_shader_program(ShaderProgram*);
diff --git a/src/core/texture.c b/src/core/texture.c
new file mode 100644
index 0000000..372f9e6
--- /dev/null
+++ b/src/core/texture.c
@@ -0,0 +1,228 @@
1#include "texture.h"
2
3#include <gfx_assert.h>
4
5#include <error.h>
6#include <math/defs.h>
7
8#include <stdbool.h>
9
10bool gfx_init_texture(Texture* texture, const TextureDesc* desc) {
11 assert(texture);
12 assert(desc);
13
14 glGenTextures(1, &texture->id);
15 if (!texture->id) {
16 log_error("glGenTextures() failed");
17 return false;
18 }
19 texture->target = to_GL_dimension(desc->dimension);
20 glBindTexture(texture->target, texture->id);
21
22 // glTexStorageXD
23 const int levels =
24 desc->mipmaps
25 ? max(max(log2(desc->width), log2(desc->height)), log2(desc->depth)) +
26 1
27 : 1;
28 const GLenum internal_format = to_GL_internal_format(desc->format);
29 switch (texture->target) {
30 case GL_TEXTURE_2D:
31 case GL_TEXTURE_CUBE_MAP:
32 glTexStorage2D(
33 texture->target, levels, internal_format, desc->width, desc->height);
34 break;
35 default:
36 FAIL("Unhandled texture dimension");
37 gfx_del_texture(texture);
38 return false;
39 }
40 ASSERT_GL;
41
42 texture->format = to_GL_format(desc->format);
43 texture->type = to_GL_type(desc->format);
44 texture->width = desc->width;
45 texture->height = desc->height;
46 gfx_update_texture(texture, &desc->data);
47
48 // gfx_update_texture() unbinds the texture at the end, so re-bind it here.
49 glBindTexture(texture->target, texture->id);
50
51 // Mipmaps.
52 if (desc->mipmaps) {
53 glGenerateMipmap(texture->target);
54 ASSERT_GL;
55 }
56
57 // Texture filtering.
58 const bool linear = desc->filtering == LinearFiltering;
59 GLenum min = desc->mipmaps ? (linear ? GL_LINEAR_MIPMAP_LINEAR
60 : GL_NEAREST_MIPMAP_NEAREST)
61 : (linear ? GL_LINEAR : GL_NEAREST);
62 GLenum mag = linear ? GL_LINEAR : GL_NEAREST;
63 glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, min);
64 glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, mag);
65 ASSERT_GL;
66
67 // Texture wrapping.
68 GLenum wrap = GL_INVALID_ENUM;
69 switch (desc->wrap) {
70 case Repeat:
71 wrap = GL_REPEAT;
72 break;
73 case ClampToEdge:
74 wrap = GL_CLAMP_TO_EDGE;
75 break;
76 }
77 glTexParameteri(texture->target, GL_TEXTURE_WRAP_R, wrap);
78 glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, wrap);
79 glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, wrap);
80 ASSERT_GL;
81
82 glBindTexture(texture->target, 0);
83 return true;
84}
85
86void gfx_del_texture(Texture* texture) {
87 assert(texture);
88
89 if (texture->id) {
90 glDeleteTextures(1, &texture->id);
91 texture->id = 0;
92 }
93}
94
95void gfx_update_texture(Texture* texture, const TextureDataDesc* desc) {
96 assert(texture);
97 assert(desc);
98
99 glBindTexture(texture->target, texture->id);
100
101 // glTexSubImageXD
102 switch (texture->target) {
103 case GL_TEXTURE_2D:
104 if (desc->pixels) {
105 glTexSubImage2D(
106 GL_TEXTURE_2D, /*level=*/0, /*xoffset=*/0,
107 /*yoffset=*/0, texture->width, texture->height, texture->format,
108 texture->type, desc->pixels);
109 }
110 break;
111 case GL_TEXTURE_CUBE_MAP:
112 for (int i = 0; i < 6; ++i) {
113 const void* pixels = *(&desc->cubemap.pixels_pos_x + i);
114 if (pixels) {
115 glTexSubImage2D(
116 GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, /*level=*/0, /*xoffset=*/0,
117 /*yoffset=*/0, texture->width, texture->height, texture->format,
118 texture->type, pixels);
119 }
120 }
121 break;
122 default:
123 FAIL("Unhandled texture dimension");
124 break;
125 }
126 ASSERT_GL;
127
128 glBindTexture(texture->target, 0);
129}
130
131GLenum to_GL_dimension(TextureDimension dim) {
132 switch (dim) {
133 case Texture2D:
134 return GL_TEXTURE_2D;
135 case TextureCubeMap:
136 return GL_TEXTURE_CUBE_MAP;
137 default:
138 FAIL("Unhandled TextureDimension");
139 return GL_INVALID_ENUM;
140 }
141}
142
143GLenum to_GL_internal_format(TextureFormat format) {
144 switch (format) {
145 case TextureDepth:
146 return GL_DEPTH_COMPONENT;
147 case TextureR8:
148 return GL_R8;
149 case TextureRG16:
150 return GL_RG16;
151 case TextureRG16F:
152 return GL_RG16F;
153 case TextureRGB8:
154 return GL_RGB8;
155 case TextureR11G11B10F:
156 return GL_R11F_G11F_B10F;
157 case TextureRGBA8:
158 return GL_RGBA8;
159 case TextureSRGB8:
160 return GL_SRGB8;
161 case TextureSRGBA8:
162 return GL_SRGB8_ALPHA8;
163 default:
164 FAIL("Unhandled TextureFormat");
165 return GL_INVALID_ENUM;
166 }
167}
168
169GLenum to_GL_format(TextureFormat format) {
170 switch (format) {
171 case TextureDepth:
172 return GL_DEPTH_COMPONENT;
173 case TextureR8:
174 return GL_RED;
175 case TextureRG16:
176 case TextureRG16F:
177 return GL_RG;
178 case TextureRGB8:
179 case TextureR11G11B10F:
180 case TextureSRGB8:
181 return GL_RGB;
182 case TextureRGBA8:
183 case TextureSRGBA8:
184 return GL_RGBA;
185 default:
186 FAIL("Unhandled TextureFormat");
187 return GL_INVALID_ENUM;
188 }
189}
190
191GLenum to_GL_type(TextureFormat format) {
192 switch (format) {
193 case TextureDepth:
194 case TextureRG16F:
195 case TextureR11G11B10F:
196 return GL_FLOAT;
197 case TextureR8:
198 case TextureRG16:
199 case TextureRGB8:
200 case TextureRGBA8:
201 case TextureSRGB8:
202 case TextureSRGBA8:
203 return GL_UNSIGNED_BYTE;
204 default:
205 FAIL("Unhandled TextureFormat");
206 return GL_INVALID_ENUM;
207 }
208}
209
210GLenum to_GL_cubemap_face(CubemapFace face) {
211 switch (face) {
212 case CubemapFacePosX:
213 return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
214 case CubemapFaceNegX:
215 return GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
216 case CubemapFacePosY:
217 return GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
218 case CubemapFaceNegY:
219 return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
220 case CubemapFacePosZ:
221 return GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
222 case CubemapFaceNegZ:
223 return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
224 default:
225 FAIL("Unhandled CubemapFace");
226 return GL_INVALID_ENUM;
227 }
228}
diff --git a/src/core/texture.h b/src/core/texture.h
new file mode 100644
index 0000000..4af41e9
--- /dev/null
+++ b/src/core/texture.h
@@ -0,0 +1,35 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7typedef struct Texture {
8 GLuint id;
9 GLenum target;
10 GLenum format;
11 GLenum type;
12 int width;
13 int height;
14} Texture;
15
16/// Create a new texture.
17bool gfx_init_texture(Texture*, const TextureDesc*);
18
19/// Destroy the texture.
20void gfx_del_texture(Texture*);
21
22/// Converts a TextureDimension into the OpenGL enum equivalent.
23GLenum to_GL_dimension(TextureDimension dim);
24
25/// Converts a texture format into an OpenGL internal format.
26GLenum to_GL_internal_format(TextureFormat format);
27
28/// Converts a texture format into an OpenGL format.
29GLenum to_GL_format(TextureFormat format);
30
31/// Converts a texture format into an OpenGL type.
32GLenum to_GL_type(TextureFormat format);
33
34/// Converts a cubemap face into the OpenGL enum equivalent.
35GLenum to_GL_cubemap_face(CubemapFace face);
diff --git a/src/gfx.c b/src/gfx.c
new file mode 100644
index 0000000..bf713ca
--- /dev/null
+++ b/src/gfx.c
@@ -0,0 +1,83 @@
1#include <gfx/gfx.h>
2
3#include "asset/asset_cache.h"
4#include "core/core_impl.h"
5#include "memory.h"
6#include "render/imm_impl.h"
7#include "render/llr_impl.h"
8#include "render/renderer_impl.h"
9
10#include <assert.h>
11#include <stdlib.h>
12
13typedef struct Gfx {
14 AssetCache asset_cache;
15 GfxCore gfxcore;
16 LLR llr;
17 Imm imm;
18 Renderer renderer;
19} Gfx;
20
21Gfx* gfx_init(void) {
22 Gfx* gfx = calloc(1, sizeof(Gfx));
23 if (!gfx) {
24 return 0;
25 }
26 gfx_init_gfxcore(&gfx->gfxcore);
27 if (!gfx_llr_make(&gfx->llr, &gfx->gfxcore)) {
28 gfx_destroy(&gfx);
29 return 0;
30 }
31 if (!gfx_imm_make(&gfx->imm, &gfx->gfxcore, &gfx->llr)) {
32 // TODO: Add error logs to the initialization failure cases here and inside
33 // the renderers.
34 gfx_destroy(&gfx);
35 return 0;
36 }
37 if (!gfx_renderer_make(&gfx->renderer, &gfx->llr, &gfx->gfxcore)) {
38 gfx_destroy(&gfx);
39 return 0;
40 }
41 gfx_init_asset_cache(&gfx->asset_cache);
42 scene_mem_init();
43 return gfx;
44}
45
46void gfx_destroy(Gfx** gfx) {
47 if (!gfx) {
48 return;
49 }
50 scene_mem_destroy();
51 gfx_destroy_asset_cache(&(*gfx)->asset_cache);
52 gfx_renderer_destroy(&(*gfx)->renderer);
53 gfx_imm_destroy(&(*gfx)->imm);
54 gfx_llr_destroy(&(*gfx)->llr);
55 gfx_del_gfxcore(&(*gfx)->gfxcore);
56 free(*gfx);
57 *gfx = 0;
58}
59
60GfxCore* gfx_get_core(Gfx* gfx) {
61 assert(gfx);
62 return &gfx->gfxcore;
63}
64
65Renderer* gfx_get_renderer(Gfx* gfx) {
66 assert(gfx);
67 return &gfx->renderer;
68}
69
70Imm* gfx_get_imm(Gfx* gfx) {
71 assert(gfx);
72 return &gfx->imm;
73}
74
75LLR* gfx_get_llr(Gfx* gfx) {
76 assert(gfx);
77 return &gfx->llr;
78}
79
80AssetCache* gfx_get_asset_cache(Gfx* gfx) {
81 assert(gfx);
82 return &gfx->asset_cache;
83}
diff --git a/src/gfx_assert.h b/src/gfx_assert.h
new file mode 100644
index 0000000..f4b3aa5
--- /dev/null
+++ b/src/gfx_assert.h
@@ -0,0 +1,5 @@
1#pragma once
2
3#include <log/log.h>
4
5#include <cassert.h> // Include after log to use log's LOGE().
diff --git a/src/memory.c b/src/memory.c
new file mode 100644
index 0000000..754f04d
--- /dev/null
+++ b/src/memory.c
@@ -0,0 +1,178 @@
1#include "memory.h"
2
3#include <gfx/scene.h>
4#include <gfx/sizes.h>
5
6#include "animation_impl.h"
7#include "scene/light_impl.h"
8#include "scene/material_impl.h"
9#include "scene/mesh_impl.h"
10#include "scene/model_impl.h"
11#include "scene/node_impl.h"
12#include "scene/object_impl.h"
13#include "scene/scene_impl.h"
14
15#include <log/log.h>
16#include <mempool.h>
17
18DEF_MEMPOOL(anima_pool, Anima, GFX_MAX_NUM_ANIMAS)
19DEF_MEMPOOL(animation_pool, Animation, GFX_MAX_NUM_ANIMATIONS)
20DEF_MEMPOOL(camera_pool, Camera, GFX_MAX_NUM_CAMERAS)
21DEF_MEMPOOL(light_pool, Light, GFX_MAX_NUM_LIGHTS)
22DEF_MEMPOOL(material_pool, Material, GFX_MAX_NUM_MATERIALS)
23DEF_MEMPOOL(mesh_pool, Mesh, GFX_MAX_NUM_MESHES)
24DEF_MEMPOOL(mesh_link_pool, MeshLink, GFX_MAX_NUM_MESH_LINKS)
25DEF_MEMPOOL(model_pool, Model, GFX_MAX_NUM_MODELS)
26DEF_MEMPOOL(node_pool, SceneNode, GFX_MAX_NUM_NODES)
27DEF_MEMPOOL(object_pool, SceneObject, GFX_MAX_NUM_OBJECTS)
28DEF_MEMPOOL(scene_pool, Scene, GFX_MAX_NUM_SCENES)
29DEF_MEMPOOL(skeleton_pool, Skeleton, GFX_MAX_NUM_SKELETONS)
30
31/// Scene memory.
32///
33/// Holds memory pools for every type of scene object.
34typedef struct SceneMemory {
35 anima_pool animas;
36 animation_pool animations;
37 camera_pool cameras;
38 light_pool lights;
39 material_pool materials;
40 mesh_pool meshs; // Purposeful typo to make the PLURAL() macro work.
41 mesh_link_pool mesh_links;
42 model_pool models;
43 node_pool nodes;
44 object_pool objects;
45 scene_pool scenes;
46 skeleton_pool skeletons;
47} SceneMemory;
48
49static SceneMemory mem;
50
51#define ALLOC_DUMMY(POOL) \
52 { \
53 const void* object = mempool_alloc(POOL); \
54 (void)object; /* Silence warning in release builds. */ \
55 assert(mempool_get_block_index(POOL, object) == 0); \
56 }
57
58#define PLURAL(name) name##s
59#define MEM_FIELD(name) mem.PLURAL(name)
60
61void scene_mem_init(void) {
62 mempool_make(&mem.animas);
63 mempool_make(&mem.animations);
64 mempool_make(&mem.cameras);
65 mempool_make(&mem.lights);
66 mempool_make(&mem.materials);
67 mempool_make(&mem.meshs);
68 mempool_make(&mem.mesh_links);
69 mempool_make(&mem.models);
70 mempool_make(&mem.nodes);
71 mempool_make(&mem.objects);
72 mempool_make(&mem.scenes);
73 mempool_make(&mem.skeletons);
74
75 // Allocate dummy objects at index 0 to guarantee that no objects allocated by
76 // the caller map to index 0. This allows 0 to be used as a sentinel.
77 ALLOC_DUMMY(&mem.animas);
78 ALLOC_DUMMY(&mem.animations);
79 ALLOC_DUMMY(&mem.cameras);
80 ALLOC_DUMMY(&mem.lights);
81 ALLOC_DUMMY(&mem.materials);
82 ALLOC_DUMMY(&mem.meshs);
83 ALLOC_DUMMY(&mem.mesh_links);
84 ALLOC_DUMMY(&mem.models);
85 ALLOC_DUMMY(&mem.nodes);
86 ALLOC_DUMMY(&mem.objects);
87 ALLOC_DUMMY(&mem.scenes);
88 ALLOC_DUMMY(&mem.skeletons);
89}
90
91void scene_mem_destroy(void) {
92 // NOTE: the dummy objects are not constructed, so the destruction code below
93 // always skips index 0. (I don't really like the conditional inside the loop,
94 // but this gets the job done without having to specialize the loop macro.)
95#define DESTROY(NAME) \
96 mempool_foreach(&MEM_FIELD(NAME), obj, { \
97 if (i > 0) { \
98 gfx_destroy_##NAME(&obj); \
99 } \
100 })
101
102 // Print memory diagnostics.
103#define PRINT_POOL(POOL_NAME, POOL) \
104 { \
105 const size_t capacity = mempool_capacity(POOL); \
106 const size_t size = mempool_size(POOL); \
107 const size_t block_size_bytes = mempool_block_size_bytes(POOL); \
108 const size_t size_bytes = size * block_size_bytes; \
109 const size_t capacity_bytes = capacity * block_size_bytes; \
110 LOGI( \
111 "%s pool: %lu/%lu (%lu/%lu bytes)", POOL_NAME, size, capacity, \
112 size_bytes, capacity_bytes); \
113 }
114
115 LOGI("Pool diagnostics:");
116 PRINT_POOL("Animas", &mem.animas);
117 PRINT_POOL("Animations", &mem.animations);
118 PRINT_POOL("Cameras", &mem.cameras);
119 PRINT_POOL("Lights", &mem.lights);
120 PRINT_POOL("Materials", &mem.materials);
121 PRINT_POOL("Meshes", &mem.meshs);
122 PRINT_POOL("Mesh links", &mem.mesh_links);
123 PRINT_POOL("Models", &mem.models);
124 PRINT_POOL("Nodes", &mem.nodes);
125 PRINT_POOL("Objects", &mem.objects);
126 PRINT_POOL("Scenes", &mem.scenes);
127 PRINT_POOL("Skeletons", &mem.skeletons);
128
129 // Models contain scene elements. Destruction is handled by the remainder of
130 // scene destructionb elow.
131 //
132 // First destroy the scenes. This will recursively destroy the scene's nodes
133 // and their objects and avoid a double-free when we then destroy any stray
134 // scene elements.
135 DESTROY(scene);
136 // Then delete stray nodes. This will delete their children nodes and
137 // resource.
138 DESTROY(node);
139 // Destroy remaining scene elements.
140 DESTROY(anima);
141 // Animations are owned by animas and do not have a destructor.
142 DESTROY(camera);
143 DESTROY(light);
144 DESTROY(material);
145 DESTROY(mesh);
146 // Mesh links don't have a destructor.
147 DESTROY(object);
148 // Skeletons are owned by animas and do not have a destructor.
149}
150
151#define DEF_MEMORY(NAME, TYPE) \
152 /* xyz* mem_alloc_xyz(); */ \
153 TYPE* mem_alloc_##NAME(void) { return mempool_alloc(&MEM_FIELD(NAME)); } \
154 /* void mem_free_xyz(xyz**); */ \
155 void mem_free_##NAME(TYPE** obj) { mempool_free(&MEM_FIELD(NAME), obj); } \
156 /* xyz* mem_get_xyz(xyz_idx); */ \
157 TYPE* mem_get_##NAME(NAMED_INDEX(NAME) index) { \
158 assert(index.val != 0); /* 0 is the dummy allocation. */ \
159 return mempool_get_block(&MEM_FIELD(NAME), index.val); \
160 } \
161 /* xyz_idx mem_get_xyz_index(const xyz*); */ \
162 NAMED_INDEX(NAME) mem_get_##NAME##_index(const TYPE* obj) { \
163 return (NAMED_INDEX(NAME)){ \
164 .val = mempool_get_block_index(&MEM_FIELD(NAME), obj)}; \
165 }
166
167DEF_MEMORY(anima, Anima)
168DEF_MEMORY(animation, Animation)
169DEF_MEMORY(camera, Camera)
170DEF_MEMORY(light, Light)
171DEF_MEMORY(material, Material)
172DEF_MEMORY(mesh, Mesh)
173DEF_MEMORY(mesh_link, MeshLink)
174DEF_MEMORY(model, Model)
175DEF_MEMORY(node, SceneNode)
176DEF_MEMORY(object, SceneObject)
177DEF_MEMORY(scene, Scene)
178DEF_MEMORY(skeleton, Skeleton)
diff --git a/src/memory.h b/src/memory.h
new file mode 100644
index 0000000..bfbee66
--- /dev/null
+++ b/src/memory.h
@@ -0,0 +1,41 @@
1/// Memory management of scene objects.
2#pragma once
3
4#include "types.h"
5
6typedef struct Camera Camera;
7
8/// Initialize scene memory.
9///
10/// The scene memory guarantees that every object maps to an index different
11/// than 0. This way, 0 can be used as a special index to denote "no value".
12void scene_mem_init(void);
13
14/// Destroy the scene memory and all allocated objects.
15void scene_mem_destroy(void);
16
17#define NAMED_INDEX(name) name##_idx
18
19#define DECL_MEMORY(name, type) \
20 typedef struct type type; \
21 /* xyz* mem_alloc_xyz() */ \
22 type* mem_alloc_##name(void); \
23 /* mem_free_xyz(xyz**) */ \
24 void mem_free_##name(type**); \
25 /* xyz* mem_get_xyz(xyz_idx); */ \
26 type* mem_get_##name(NAMED_INDEX(name)); \
27 /* xyz_idx mem_get_xyz_index(const xyz*); */ \
28 NAMED_INDEX(name) mem_get_##name##_index(const type*);
29
30DECL_MEMORY(anima, Anima)
31DECL_MEMORY(animation, Animation)
32DECL_MEMORY(camera, Camera)
33DECL_MEMORY(light, Light)
34DECL_MEMORY(material, Material)
35DECL_MEMORY(mesh, Mesh)
36DECL_MEMORY(mesh_link, MeshLink)
37DECL_MEMORY(model, Model)
38DECL_MEMORY(node, SceneNode)
39DECL_MEMORY(object, SceneObject)
40DECL_MEMORY(scene, Scene)
41DECL_MEMORY(skeleton, Skeleton)
diff --git a/src/render/imm.c b/src/render/imm.c
new file mode 100644
index 0000000..7ab8d62
--- /dev/null
+++ b/src/render/imm.c
@@ -0,0 +1,194 @@
1#include "imm_impl.h"
2
3#include <gfx/core.h>
4#include <gfx/render/imm.h>
5#include <gfx/render/llr.h>
6#include <gfx/util/shader.h>
7
8#include <math/aabb3.h>
9
10#include <assert.h>
11#include <string.h> // memcpy
12
13bool gfx_imm_make(Imm* renderer, GfxCore* gfxcore, LLR* llr) {
14 assert(renderer);
15 assert(gfxcore);
16 assert(llr);
17
18 const size_t num_triangle_verts = GFX_IMM_MAX_NUM_TRIANGLES * 3;
19
20 renderer->gfxcore = gfxcore;
21 renderer->llr = llr;
22
23 renderer->triangles = gfx_make_geometry(
24 gfxcore, &(GeometryDesc){
25 .type = Triangles,
26 .buffer_usage = BufferDynamic,
27 .num_verts = num_triangle_verts,
28 .positions3d = (BufferView3d){
29 .size_bytes = num_triangle_verts * sizeof(vec3),
30 .count = num_triangle_verts}
31 });
32 if (!renderer->triangles) {
33 goto cleanup;
34 }
35
36 renderer->shader = gfx_make_immediate_mode_shader(gfxcore);
37 if (!renderer->shader) {
38 goto cleanup;
39 }
40
41 gfx_imm_set_colour(renderer, vec4_make(0.0f, 0.0f, 0.0f, 1.0f));
42
43 return true;
44
45cleanup:
46 gfx_imm_destroy(renderer);
47 return false;
48}
49
50void gfx_imm_destroy(Imm* renderer) {
51 assert(renderer);
52 assert(renderer->gfxcore);
53
54 if (renderer->triangles) {
55 gfx_destroy_geometry(renderer->gfxcore, &renderer->triangles);
56 // TODO: Could also destroy the geometry's buffers here.
57 }
58
59 if (renderer->shader) {
60 gfx_destroy_shader_program(renderer->gfxcore, &renderer->shader);
61 }
62}
63
64void gfx_imm_flush(Imm* renderer) {
65 assert(renderer);
66
67 if (renderer->num_triangle_verts > 0) {
68 gfx_update_geometry(
69 renderer->triangles,
70 &(GeometryDesc){
71 .num_verts = renderer->num_triangle_verts,
72 .positions3d = (BufferView3d){
73 .data = renderer->triangle_verts,
74 .size_bytes = renderer->num_triangle_verts * sizeof(vec3)}
75 });
76
77 gfx_llr_render_geometry(renderer->llr, renderer->triangles);
78
79 renderer->num_triangle_verts = 0;
80 }
81}
82
83void gfx_imm_start(Imm* renderer) {
84 assert(renderer);
85
86 // Shader uniforms are applied lazily.
87 // TODO: In the event that gfx_activate_shader_program() activates uniforms
88 // automatically for convenience, call an overload here that doesn't do so.
89 // gfx_activate_shader_program(renderer->shader);
90 gfx_llr_set_shader(renderer->llr, renderer->shader);
91}
92
93void gfx_imm_end(Imm* renderer) {
94 assert(renderer);
95
96 gfx_imm_flush(renderer);
97 // gfx_deactivate_shader_program(renderer->shader);
98 gfx_llr_set_shader(renderer->llr, 0);
99}
100
101void gfx_imm_draw_triangles(
102 Imm* renderer, const vec3 verts[], size_t num_triangles) {
103 assert(renderer);
104 assert(verts);
105 const size_t new_verts = num_triangles * 3;
106 assert(
107 renderer->num_triangle_verts + new_verts <
108 (GFX_IMM_MAX_NUM_TRIANGLES * 3));
109
110 memcpy(
111 renderer->triangle_verts + renderer->num_triangle_verts, verts,
112 new_verts * sizeof(vec3));
113
114 renderer->num_triangle_verts += new_verts;
115}
116
117void gfx_imm_draw_triangle(Imm* renderer, const vec3 verts[3]) {
118 gfx_imm_draw_triangles(renderer, verts, 1);
119}
120
121void gfx_imm_draw_aabb2(Imm* renderer, aabb2 box) {
122 assert(renderer);
123
124 // clang-format off
125 const vec3 verts[4] = {
126 vec3_make(box.min.x, box.min.y, 0), // 3 ---- 2
127 vec3_make(box.max.x, box.min.y, 0), // | |
128 vec3_make(box.max.x, box.max.y, 0), // | |
129 vec3_make(box.min.x, box.max.y, 0)}; // 0 ---- 1
130 // clang-format on
131
132#define tri(i0, i1, i2) verts[i0], verts[i1], verts[i2]
133 const vec3 tris[6] = {tri(0, 1, 2), tri(0, 2, 3)};
134#undef tri
135
136 gfx_imm_draw_triangles(renderer, tris, 2);
137}
138
139void gfx_imm_draw_aabb3(Imm* renderer, aabb3 box) {
140 assert(renderer);
141
142 // clang-format off
143 const vec3 vertices[8] = {
144 vec3_make(box.min.x, box.min.y, box.max.z), // 7 ----- 6
145 vec3_make(box.max.x, box.min.y, box.max.z), // / /|
146 vec3_make(box.max.x, box.max.y, box.max.z), // 3 ----- 2 |
147 vec3_make(box.min.x, box.max.y, box.max.z), // | | |
148 vec3_make(box.min.x, box.min.y, box.min.z), // | 4 ----- 5
149 vec3_make(box.max.x, box.min.y, box.min.z), // |/ |/
150 vec3_make(box.max.x, box.max.y, box.min.z), // 0 ----- 1
151 vec3_make(box.min.x, box.max.y, box.min.z)};
152 // clang-format on
153
154 gfx_imm_draw_box3(renderer, vertices);
155}
156
157void gfx_imm_draw_box3(Imm* renderer, const vec3 vertices[8]) {
158 assert(renderer);
159 assert(vertices);
160
161 // 7 ----- 6
162 // / /|
163 // 3 ----- 2 |
164 // | | |
165 // | 4 ----- 5
166 // |/ |/
167 // 0 ----- 1
168
169#define tri(i0, i1, i2) vertices[i0], vertices[i1], vertices[i2]
170 const vec3 tris[36] = {
171 // Front.
172 tri(0, 1, 2), tri(0, 2, 3),
173 // Right.
174 tri(1, 5, 6), tri(1, 6, 2),
175 // Back.
176 tri(5, 4, 7), tri(5, 7, 6),
177 // Left.
178 tri(4, 0, 03), tri(4, 3, 7),
179 // Top.
180 tri(3, 2, 6), tri(3, 6, 7),
181 // Bottom.
182 tri(0, 4, 5), tri(0, 5, 1)};
183
184 gfx_imm_draw_triangles(renderer, tris, 12);
185}
186
187void gfx_imm_set_colour(Imm* renderer, vec4 colour) {
188 assert(renderer);
189 assert(renderer->shader);
190
191 gfx_imm_flush(renderer);
192
193 gfx_set_vec4_uniform(renderer->shader, "Colour", colour);
194}
diff --git a/src/render/imm_impl.h b/src/render/imm_impl.h
new file mode 100644
index 0000000..d87b910
--- /dev/null
+++ b/src/render/imm_impl.h
@@ -0,0 +1,43 @@
1#pragma once
2
3#include <gfx/sizes.h>
4
5#include <math/vec3.h>
6
7#include <stdbool.h>
8#include <stddef.h>
9
10typedef struct Geometry Geometry;
11typedef struct GfxCore GfxCore;
12typedef struct IBL IBL;
13typedef struct LLR LLR;
14typedef struct Material Material;
15typedef struct ShaderProgram ShaderProgram;
16typedef struct Texture Texture;
17
18/// Immediate mode renderer.
19///
20/// Currently, the immediate mode renderer can only draw up to a maximum number
21/// of primitives per frame. It does not adjust this number dynamically. Keeps
22/// things simple while the extra complexity is not needed.
23/// TODO: Flush the buffer when it reaches its maximum size to remove this
24/// constraint.
25typedef struct Imm {
26 GfxCore* gfxcore;
27 LLR* llr;
28
29 ShaderProgram* shader; // Immediate-mode shader program for primitives.
30 Geometry* triangles;
31 size_t num_triangle_verts; // Number of triangle verts this frame.
32 // TODO: wireframe rendering.
33 struct {
34 bool wireframe : 1;
35 } flags;
36 vec3 triangle_verts[GFX_IMM_MAX_NUM_TRIANGLES * 3];
37} Imm;
38
39/// Create a new immediate mode renderer.
40bool gfx_imm_make(Imm*, GfxCore*, LLR*);
41
42/// Destroy the immediate mode renderer.
43void gfx_imm_destroy(Imm*);
diff --git a/src/render/llr.c b/src/render/llr.c
new file mode 100644
index 0000000..752b65b
--- /dev/null
+++ b/src/render/llr.c
@@ -0,0 +1,441 @@
1#include "llr_impl.h"
2
3#include "animation_impl.h"
4#include "scene/light_impl.h"
5#include "scene/material_impl.h"
6#include "scene/mesh_impl.h"
7#include "scene/node_impl.h"
8
9#include <gfx/core.h>
10#include <gfx/util/ibl.h>
11
12#include <cassert.h>
13
14static const int IRRADIANCE_MAP_WIDTH = 1024;
15static const int IRRADIANCE_MAP_HEIGHT = 1024;
16static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128;
17static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128;
18static const int BRDF_INTEGRATION_MAP_WIDTH = 512;
19static const int BRDF_INTEGRATION_MAP_HEIGHT = 512;
20
21/// Activate the material.
22///
23/// This configures the shader uniforms that are specific to the material.
24static void material_activate(ShaderProgram* shader, const Material* material) {
25 assert(material);
26 for (int i = 0; i < material->num_uniforms; ++i) {
27 const ShaderUniform* uniform = &material->uniforms[i];
28 gfx_set_uniform(shader, uniform);
29 }
30 if (material->alpha_mode != Opaque) {
31 gfx_set_uniform(
32 shader, &(ShaderUniform){.name = sstring_make("AlphaMode"),
33 .type = UniformInt,
34 .value.uniform_int = material->alpha_mode});
35 }
36 if (material->alpha_mode == Mask) {
37 gfx_set_uniform(
38 shader,
39 &(ShaderUniform){.name = sstring_make("AlphaCutoff"),
40 .type = UniformFloat,
41 .value.uniform_float = material->alpha_cutoff});
42 }
43}
44
45/// Initialize renderer state for IBL.
46static bool init_ibl(LLR* renderer) {
47 assert(renderer);
48 assert(!renderer->ibl);
49 assert(!renderer->brdf_integration_map);
50
51 if (!((renderer->ibl = gfx_make_ibl(renderer->gfxcore)))) {
52 return false;
53 }
54
55 if (!((renderer->brdf_integration_map = gfx_make_brdf_integration_map(
56 renderer->ibl, renderer->gfxcore, BRDF_INTEGRATION_MAP_WIDTH,
57 BRDF_INTEGRATION_MAP_HEIGHT)))) {
58 return false;
59 }
60
61 return true;
62}
63
64/// Compute irradiance and prefiltered environment maps for the light if they
65/// have not been already computed.
66///
67/// This is done lazily here, and not when the light is created, because we
68/// need an IBL instance to do this and it is more convenient for the public
69/// API to create lights without worrying about those details. It also makes the
70/// public API cheaper, since the maps are only computed when they are actually
71/// needed.
72static bool set_up_environment_light(LLR* renderer, EnvironmentLight* light) {
73 assert(renderer);
74 assert(light);
75 assert(renderer->ibl);
76 assert(renderer->brdf_integration_map);
77
78 if (light->irradiance_map) {
79 assert(light->prefiltered_environment_map);
80 return true;
81 }
82
83 // For convenience.
84 GfxCore* gfxcore = renderer->gfxcore;
85
86 Texture* irradiance_map = 0;
87 Texture* prefiltered_environment_map = 0;
88
89 if (!((irradiance_map = gfx_make_irradiance_map(
90 renderer->ibl, gfxcore, light->environment_map,
91 IRRADIANCE_MAP_WIDTH, IRRADIANCE_MAP_HEIGHT)))) {
92 goto cleanup;
93 }
94
95 int max_mip_level = 0;
96 if (!((prefiltered_environment_map = gfx_make_prefiltered_environment_map(
97 renderer->ibl, gfxcore, light->environment_map,
98 PREFILTERED_ENVIRONMENT_MAP_WIDTH,
99 PREFILTERED_ENVIRONMENT_MAP_HEIGHT, &max_mip_level)))) {
100 goto cleanup;
101 }
102
103 light->irradiance_map = irradiance_map;
104 light->prefiltered_environment_map = prefiltered_environment_map;
105 light->max_reflection_lod = max_mip_level;
106
107 return true;
108
109cleanup:
110 if (irradiance_map) {
111 gfx_destroy_texture(gfxcore, &irradiance_map);
112 }
113 if (prefiltered_environment_map) {
114 gfx_destroy_texture(gfxcore, &prefiltered_environment_map);
115 }
116 return false;
117}
118
119static void configure_light(LLR* renderer, Light* light) {
120 assert(renderer);
121 assert(light);
122
123 // For convenience.
124 ShaderProgram* const shader = renderer->shader;
125
126 switch (light->type) {
127 case EnvironmentLightType: {
128 EnvironmentLight* env = &light->environment;
129
130 const bool initialized = set_up_environment_light(renderer, env);
131 ASSERT(initialized);
132 assert(env->environment_map);
133 assert(env->irradiance_map);
134 assert(env->prefiltered_environment_map);
135 assert(renderer->brdf_integration_map);
136
137 gfx_set_texture_uniform(
138 shader, "BRDFIntegrationMap", renderer->brdf_integration_map);
139 gfx_set_texture_uniform(shader, "Sky", env->environment_map);
140 gfx_set_texture_uniform(shader, "IrradianceMap", env->irradiance_map);
141 gfx_set_texture_uniform(
142 shader, "PrefilteredEnvironmentMap", env->prefiltered_environment_map);
143 gfx_set_float_uniform(
144 shader, "MaxReflectionLOD", (float)env->max_reflection_lod);
145
146 break;
147 }
148 default:
149 assert(false); // TODO: Implement other light types.
150 break;
151 }
152}
153
154static void configure_state(LLR* renderer) {
155 assert(renderer);
156
157 // Check if anything changed first so that we don't call gfx_apply_uniforms()
158 // unnecessarily.
159 const bool nothing_changed = (renderer->changed_flags == 0);
160 if (nothing_changed) {
161 return;
162 }
163 // Setting a null shader is also allowed, in which case there is nothing to
164 // configure.
165 if (renderer->shader == 0) {
166 renderer->shader_changed = false;
167 return;
168 }
169
170 // For convenience.
171 ShaderProgram* const shader = renderer->shader;
172 const mat4* const model = &renderer->matrix_stack[renderer->stack_pointer];
173
174 // TODO: Check to see which ones the shader actually uses and avoid
175 // computing the unnecessary matrices.
176
177 if (renderer->matrix_changed || renderer->shader_changed) {
178 renderer->matrix_changed = false;
179
180 gfx_set_mat4_uniform(shader, "Model", model);
181 gfx_set_mat4_uniform(shader, "ModelMatrix", model);
182 }
183
184 // TODO: camera_changed is not set anywhere. Need to think how imm primitive
185 // rendering and imm mesh rendering work together. We could treat imm
186 // primitive calls like setting a new shader.
187 if (renderer->camera_changed || renderer->shader_changed) {
188 renderer->camera_changed = false;
189
190 // Set all supported camera-related uniforms. Shaders can choose which ones
191 // to use.
192 const mat4 modelview = mat4_mul(renderer->view, *model);
193 const mat4 view_proj = mat4_mul(renderer->projection, renderer->view);
194 const mat4 mvp = mat4_mul(renderer->projection, modelview);
195
196 gfx_set_mat4_uniform(shader, "Modelview", &modelview);
197 gfx_set_mat4_uniform(shader, "View", &renderer->view);
198 gfx_set_mat4_uniform(shader, "Projection", &renderer->projection);
199 gfx_set_mat4_uniform(shader, "ViewProjection", &view_proj);
200 gfx_set_mat4_uniform(shader, "MVP", &mvp);
201 gfx_set_vec3_uniform(shader, "CameraPosition", renderer->camera_position);
202 gfx_set_mat4_uniform(shader, "CameraRotation", &renderer->camera_rotation);
203 gfx_set_float_uniform(shader, "Fovy", renderer->fovy);
204 gfx_set_float_uniform(shader, "Aspect", renderer->aspect);
205 }
206
207 if (renderer->lights_changed || renderer->shader_changed) {
208 renderer->lights_changed = false;
209
210 // TODO: Could do better by only setting the lights that have actually
211 // changed.
212 // TODO: Will also need to pass the number of lights to the shader once the
213 // other light types are implemented.
214 for (int i = 0; i < renderer->num_lights; ++i) {
215 configure_light(renderer, renderer->lights[i]);
216 }
217 }
218
219 if (renderer->skeleton_changed || renderer->shader_changed) {
220 renderer->skeleton_changed = false;
221
222 if (renderer->num_joints > 0) {
223 gfx_set_mat4_array_uniform(
224 shader, "JointMatrices", renderer->joint_matrices,
225 renderer->num_joints);
226 }
227 }
228
229 if (renderer->material_changed || renderer->shader_changed) {
230 renderer->material_changed = false;
231
232 // Geometry may be rendered without a material.
233 if (renderer->material) {
234 material_activate(renderer->shader, renderer->material);
235 }
236 }
237
238 if (renderer->shader_changed) {
239 renderer->shader_changed = false;
240 gfx_activate_shader_program(renderer->shader);
241 }
242
243 // TODO: At present, this results in many redundant calls to
244 // glGetUniformLocation() and glUniformXyz(). Look at the trace.
245 //
246 // TODO: Could add to qapitrace functionality to detect redundant calls and
247 // other inefficiencies. Maybe ask in the Github first if there would be
248 // interest in this.
249 //
250 // Must be called after activating the program.
251 gfx_apply_uniforms(renderer->shader);
252}
253
254bool gfx_llr_make(LLR* renderer, GfxCore* gfxcore) {
255 assert(renderer);
256 assert(gfxcore);
257
258 renderer->gfxcore = gfxcore;
259 if (!init_ibl(renderer)) {
260 goto cleanup;
261 }
262 gfx_llr_load_identity(renderer);
263 renderer->view = mat4_id();
264 renderer->projection = mat4_id();
265 renderer->camera_rotation = mat4_id();
266 return true;
267
268cleanup:
269 gfx_llr_destroy(renderer);
270 return false;
271}
272
273void gfx_llr_destroy(LLR* renderer) {
274 assert(renderer);
275 assert(renderer->gfxcore);
276
277 if (renderer->brdf_integration_map) {
278 gfx_destroy_texture(renderer->gfxcore, &renderer->brdf_integration_map);
279 }
280
281 // TODO: Do this once the IBL from the scene renderer is gone.
282 if (renderer->ibl) {
283 // gfx_destroy_ibl(renderer->gfxcore, &renderer->ibl);
284 }
285}
286
287void gfx_llr_set_shader(LLR* renderer, ShaderProgram* shader) {
288 assert(renderer);
289 // null shader is allowed, so do not assert it.
290
291 // It's important to not set shader_changed unnecessarily, since that would
292 // re-trigger the setting of uniforms.
293 if (renderer->shader != shader) {
294 renderer->shader = shader;
295 renderer->shader_changed = true;
296 }
297}
298
299void gfx_llr_push_light(LLR* renderer, Light* light) {
300 assert(renderer);
301 assert(light);
302 assert(renderer->num_lights >= 0);
303 ASSERT(renderer->num_lights < GFX_LLR_MAX_NUM_LIGHTS);
304
305 renderer->lights[renderer->num_lights++] = light;
306 renderer->lights_changed = true;
307}
308
309void gfx_llr_pop_light(LLR* renderer) {
310 assert(renderer);
311 ASSERT(renderer->num_lights > 0);
312
313 renderer->lights[--renderer->num_lights] = 0;
314 renderer->lights_changed = true;
315}
316
317void gfx_llr_set_skeleton(
318 LLR* renderer, const Anima* anima, const Skeleton* skeleton) {
319 assert(renderer);
320 assert(anima);
321 assert(skeleton);
322 assert(skeleton->num_joints <= GFX_MAX_NUM_JOINTS);
323
324 for (size_t i = 0; i < skeleton->num_joints; ++i) {
325 const joint_idx joint_index = skeleton->joints[i];
326 const Joint* joint = &anima->joints[joint_index];
327 renderer->joint_matrices[i] = joint->joint_matrix;
328 }
329 renderer->num_joints = skeleton->num_joints;
330 renderer->skeleton_changed = true;
331}
332
333void gfx_llr_clear_skeleton(LLR* renderer) {
334 assert(renderer);
335
336 renderer->num_joints = 0;
337 renderer->skeleton_changed = true;
338}
339
340void gfx_llr_set_material(LLR* renderer, const Material* material) {
341 assert(renderer);
342 assert(material);
343
344 renderer->material = material;
345 renderer->material_changed = true;
346}
347
348void gfx_llr_set_camera(LLR* renderer, const Camera* camera) {
349 assert(renderer);
350
351 renderer->camera_position = camera->spatial.p;
352 renderer->camera_rotation =
353 mat4_rotation(spatial3_transform(&camera->spatial));
354 renderer->view = spatial3_inverse_transform(&camera->spatial);
355 renderer->projection = camera->projection;
356 // Assuming a perspective matrix.
357 renderer->fovy = (R)atan(1.0 / (mat4_at(camera->projection, 1, 1))) * 2;
358 renderer->camera_changed = true;
359}
360
361void gfx_llr_set_projection_matrix(LLR* renderer, const mat4* projection) {
362 assert(renderer);
363
364 renderer->projection = *projection;
365 renderer->camera_changed = true;
366}
367
368void gfx_llr_set_aspect(LLR* renderer, float aspect) {
369 assert(renderer);
370
371 renderer->aspect = aspect;
372 renderer->camera_changed = true;
373}
374
375void gfx_llr_render_geometry(LLR* renderer, const Geometry* geometry) {
376 assert(renderer);
377 assert(geometry);
378
379 configure_state(renderer);
380 gfx_render_geometry(geometry);
381}
382
383void gfx_llr_render_mesh(LLR* renderer, const Mesh* mesh) {
384 assert(renderer);
385 assert(mesh);
386 assert(mesh->geometry);
387 assert(mesh->material);
388 assert(mesh->shader);
389
390 gfx_llr_set_material(renderer, mesh->material);
391 gfx_llr_set_shader(renderer, mesh->shader);
392 gfx_llr_render_geometry(renderer, mesh->geometry);
393}
394
395void gfx_llr_load_identity(LLR* renderer) {
396 assert(renderer);
397
398 renderer->matrix_stack[0] = mat4_id();
399 renderer->stack_pointer = 0;
400 renderer->matrix_changed = true;
401}
402
403void gfx_llr_push_matrix(LLR* renderer, const mat4* matrix) {
404 assert(renderer);
405 assert(matrix);
406 assert(renderer->stack_pointer >= 0);
407 ASSERT(renderer->stack_pointer < GFX_LLR_MAX_NUM_MATRICES);
408
409 renderer->stack_pointer += 1;
410 renderer->matrix_stack[renderer->stack_pointer] =
411 mat4_mul(*matrix, renderer->matrix_stack[renderer->stack_pointer - 1]);
412 renderer->matrix_changed = true;
413}
414
415void gfx_llr_pop_matrix(LLR* renderer) {
416 assert(renderer);
417 ASSERT(renderer->stack_pointer > 0);
418
419 // For debugging, zero out the matrix stack as matrices are popped out.
420 memset(
421 &renderer->matrix_stack[renderer->stack_pointer], 0,
422 sizeof(renderer->matrix_stack[0]));
423 renderer->stack_pointer -= 1;
424 renderer->matrix_changed = true;
425}
426
427void gfx_llr_translate(LLR* renderer, vec3 offset) {
428 assert(renderer);
429
430 const mat4 mat = mat4_translate(offset);
431 gfx_llr_push_matrix(renderer, &mat);
432}
433
434void gfx_llr_set_model_matrix(LLR* renderer, const mat4* model) {
435 assert(renderer);
436 assert(model);
437
438 renderer->matrix_stack[0] = *model;
439 renderer->stack_pointer = 0;
440 renderer->matrix_changed = true;
441}
diff --git a/src/render/llr_impl.h b/src/render/llr_impl.h
new file mode 100644
index 0000000..9d70843
--- /dev/null
+++ b/src/render/llr_impl.h
@@ -0,0 +1,85 @@
1#pragma once
2
3#include <gfx/render/llr.h>
4#include <gfx/sizes.h>
5
6#include <math/mat4.h>
7#include <math/vec3.h>
8
9#include <stdbool.h>
10#include <stddef.h>
11#include <stdint.h>
12
13typedef struct GfxCore GfxCore;
14typedef struct IBL IBL;
15typedef struct Material Material;
16typedef struct ShaderProgram ShaderProgram;
17typedef struct Texture Texture;
18
19/// Immediate mode renderer.
20///
21/// The renderer caches state changes in memory and only programs the underlying
22/// shader program when a draw call is issued and if anything has changed. This
23/// keeps the number of graphics API calls to a minimum, but requires tracking
24/// state changes. The 'changed' booleans below fulfill this purpose, and
25/// indicate whether a given state has changed since the last draw call.
26///
27/// The renderer must combine state changes accordingly. For example, if only
28/// the lights have changed, then it is sufficient to update light uniforms in
29/// the current shader program. On the other hand, if the shader program has
30/// changed, then the renderer must reconfigure it from scratch and set light
31/// uniforms, camera uniforms, etc.
32///
33/// Note that the shader program API has its own level of caching as well, so
34/// reconfiguration at the level of the renderer does not result in the
35/// worst-case set of graphics API calls.
36typedef struct LLR {
37 GfxCore* gfxcore;
38
39 union {
40 struct {
41 bool shader_changed : 1; // Whether the shader has changed.
42 bool camera_changed : 1; // Whether the camera parameters have changed.
43 bool lights_changed : 1; // Whether the lights have changed.
44 bool skeleton_changed : 1; // Whether the skeleton has changed.
45 bool material_changed : 1; // Whether the material has changed.
46 bool matrix_changed : 1; // Whether the matrix stack has changed.
47 };
48 uint8_t changed_flags;
49 };
50
51 IBL* ibl;
52 Texture* brdf_integration_map;
53
54 ShaderProgram* shader; // Active shader. Not owned.
55
56 const Material* material; // Active material. Not owned.
57
58 vec3 camera_position;
59 mat4 camera_rotation;
60 mat4 view; // Camera view matrix.
61 mat4 projection; // Camera projection matrix.
62 R fovy; // Camera vertical field of view.
63 R aspect; // Aspect ratio.
64
65 // Lights are not const because environment lights store lazily-computed
66 // irradiance maps.
67 Light* lights[GFX_LLR_MAX_NUM_LIGHTS]; // Lights stack.
68 int num_lights; // Number of lights enabled at a given point in time. It
69 // points to one past the top of the stack.
70
71 size_t num_joints;
72 mat4 joint_matrices[GFX_MAX_NUM_JOINTS];
73
74 // The matrix stack contains pre-multiplied matrices.
75 // It is also never empty. The top of the stack is an identity matrix when the
76 // stack is "empty" from the user's perspective.
77 mat4 matrix_stack[GFX_LLR_MAX_NUM_MATRICES];
78 int stack_pointer; // Points to the top of the stack.
79} LLR;
80
81/// Create a new immediate mode renderer.
82bool gfx_llr_make(LLR*, GfxCore*);
83
84/// Destroy the immediate mode renderer.
85void gfx_llr_destroy(LLR*);
diff --git a/src/render/renderer.c b/src/render/renderer.c
new file mode 100644
index 0000000..a9d9bef
--- /dev/null
+++ b/src/render/renderer.c
@@ -0,0 +1,282 @@
1#include "renderer_impl.h"
2
3#include "animation_impl.h"
4#include "llr_impl.h"
5#include "memory.h"
6#include "scene/material_impl.h"
7#include "scene/mesh_impl.h"
8#include "scene/model_impl.h"
9#include "scene/node_impl.h"
10#include "scene/object_impl.h"
11
12#include <gfx/core.h>
13#include <gfx/render/llr.h>
14#include <gfx/util/shader.h>
15
16#include <math/mat4.h>
17
18#include <assert.h>
19
20bool gfx_renderer_make(Renderer* renderer, LLR* llr, GfxCore* gfxcore) {
21 assert(renderer);
22 assert(llr);
23 assert(gfxcore);
24
25 renderer->gfxcore = gfxcore;
26 renderer->llr = llr;
27
28 return true;
29}
30
31void gfx_renderer_destroy(Renderer* renderer) {
32 if (!renderer) {
33 return;
34 }
35 assert(renderer->gfxcore);
36 GfxCore* gfxcore = renderer->gfxcore;
37 if (renderer->shaders.debug) {
38 gfx_destroy_shader_program(gfxcore, &renderer->shaders.debug);
39 }
40 if (renderer->shaders.normals) {
41 gfx_destroy_shader_program(gfxcore, &renderer->shaders.normals);
42 }
43 if (renderer->shaders.normal_mapped_normals) {
44 gfx_destroy_shader_program(
45 gfxcore, &renderer->shaders.normal_mapped_normals);
46 }
47 if (renderer->shaders.tangents) {
48 gfx_destroy_shader_program(gfxcore, &renderer->shaders.tangents);
49 }
50}
51
52static ShaderProgram* load_shader(Renderer* renderer, RenderSceneMode mode) {
53 assert(renderer);
54
55#define LOAD_AND_RETURN(pShader, constructor) \
56 { \
57 if (!pShader) { \
58 pShader = constructor(renderer->gfxcore); \
59 } \
60 assert(pShader); \
61 return pShader; \
62 }
63
64 switch (mode) {
65 case RenderDefault:
66 return 0;
67 case RenderDebug:
68 LOAD_AND_RETURN(renderer->shaders.debug, gfx_make_debug3d_shader);
69 case RenderNormals:
70 LOAD_AND_RETURN(renderer->shaders.normals, gfx_make_view_normals_shader);
71 case RenderNormalMappedNormals:
72 LOAD_AND_RETURN(
73 renderer->shaders.normal_mapped_normals,
74 gfx_make_view_normal_mapped_normals_shader);
75 case RenderTangents:
76 LOAD_AND_RETURN(renderer->shaders.tangents, gfx_make_view_tangents_shader);
77 }
78 assert(false);
79 return 0;
80}
81
82// static void log_matrix(const mat4* m) {
83// for (int row = 0; row < 4; ++row) {
84// LOGI("[ %5.2f, %5.2f, %5.2f, %5.2f ]", m->val[0][row], m->val[1][row],
85// m->val[2][row], m->val[3][row]);
86// }
87// }
88
89typedef struct RenderState {
90 GfxCore* gfxcore;
91 LLR* llr;
92 Renderer* renderer;
93 ShaderProgram* shader; // Null to use scene shaders.
94 const Scene* scene;
95 const Anima* anima;
96 RenderSceneFilter filter;
97} RenderState;
98
99static void draw_children(
100 RenderState* state, const mat4* node_transform, const SceneNode* node);
101
102/// Draw the scene recursively.
103static void draw_recursively(
104 RenderState* state, mat4 parent_transform, const SceneNode* node) {
105 assert(state);
106 const mat4 node_transform = mat4_mul(parent_transform, node->transform);
107
108 // Anima.
109 if (node->type == AnimaNode) {
110 // Save the anima so that we can animate objects.
111 state->anima = gfx_get_node_anima(node);
112
113 draw_children(state, &node_transform, node);
114 }
115 // Activate light.
116 else if (node->type == LightNode) {
117 Light* light = mem_get_light(node->light);
118 assert(light);
119 gfx_llr_push_light(state->llr, light);
120 {
121 draw_children(state, &node_transform, node);
122 }
123 gfx_llr_pop_light(state->llr);
124 }
125 // Model.
126 else if (node->type == ModelNode) {
127 const Model* model = gfx_get_node_model(node);
128 const SceneNode* root = mem_get_node(model->root);
129 draw_recursively(state, parent_transform, root);
130 draw_children(state, &node_transform, node);
131 }
132 // Render object.
133 else if (node->type == ObjectNode) {
134 const SceneObject* object = mem_get_object(node->object);
135 assert(object);
136
137 // TODO: Here we would frustum-cull the object.
138
139 // A model/anima can have many skeletons. We need to animate the given
140 // object using its skeleton, not just any skeleton of the anima.
141 if (object->skeleton.val) {
142 const Skeleton* skeleton = mem_get_skeleton(object->skeleton);
143 gfx_llr_set_skeleton(state->llr, state->anima, skeleton);
144 }
145
146 const mat4 model_matrix = node_transform;
147
148 for (mesh_link_idx mesh_link_index = object->mesh_link;
149 mesh_link_index.val;) {
150 const MeshLink* mesh_link = mem_get_mesh_link(mesh_link_index);
151 mesh_link_index = mesh_link->next;
152
153 const Mesh* mesh = mem_get_mesh(mesh_link->mesh);
154 if (!mesh) {
155 continue;
156 }
157
158 // Filter out by material.
159 const Material* material = mesh->material;
160 if (material) {
161 const AlphaMode mode = material->alpha_mode;
162 switch (state->filter) {
163 case RenderOpaqueAndAlphaMasked:
164 if (mode == Blend) {
165 continue;
166 }
167 break;
168 case RenderTransparent:
169 if (mode != Blend) {
170 continue;
171 }
172 break;
173 }
174 }
175
176 // TODO: Here we would frustum-cull the mesh. The AABB would have to be
177 // transformed by the model matrix. Rotation would make the AABB
178 // relatively large, but still, the culling would be conservative.
179
180 ShaderProgram* shader = state->shader ? state->shader : mesh->shader;
181 gfx_llr_set_shader(state->llr, shader);
182 gfx_llr_set_model_matrix(state->llr, &model_matrix);
183 gfx_llr_render_mesh(state->llr, mesh);
184 }
185
186 if (object->skeleton.val) {
187 gfx_llr_clear_skeleton(state->llr);
188 }
189
190 draw_children(state, &node_transform, node);
191 } else {
192 draw_children(state, &node_transform, node);
193 }
194}
195
196/// Draw the node's children.
197static void draw_children(
198 RenderState* state, const mat4* node_transform, const SceneNode* node) {
199 // Render children recursively.
200 for (node_idx child_index = node->child; child_index.val;) {
201 const SceneNode* child = mem_get_node(child_index);
202 draw_recursively(state, *node_transform, child);
203 child_index = child->next;
204 }
205}
206
207void gfx_render_scene(Renderer* renderer, const RenderSceneParams* params) {
208 assert(renderer);
209 assert(params);
210 assert(params->scene);
211
212 ShaderProgram* const shader = load_shader(renderer, params->mode);
213
214 const Scene* scene = params->scene;
215 const Camera* camera = params->camera;
216 GfxCore* const gfxcore = renderer->gfxcore;
217
218 int x, y, width, height;
219 gfx_get_viewport(gfxcore, &x, &y, &width, &height);
220 const R aspect = (R)width / (R)height;
221
222 RenderState state = {
223 .gfxcore = gfxcore,
224 .llr = renderer->llr,
225 .renderer = renderer,
226 .shader = shader,
227 .scene = scene};
228
229 gfx_llr_set_camera(renderer->llr, camera);
230 gfx_llr_set_aspect(renderer->llr, aspect);
231 // TODO: Render Opaque and Mask alpha-mode materials first, then Blend ones.
232 // TODO: I'm not sure if this belongs to the scene renderer per se, or if it
233 // is something that should be driven from the outside. Specifically, the
234 // caller could pass in a filter that determines what objects to render. The
235 // filter could include alpha mode.
236 // This caller would be some component that understands render passes and
237 // potentially renders the scene multiple times as needed. For example, a
238 // depth-prepass, followed by G-buffer, followed by some post-processing,
239 // etc. Rename this renderer to scene_renderer?
240 // TODO: When rendering transparent geometry, we need to turn off depth
241 // writes.
242 // Opaque.
243 state.filter = RenderOpaqueAndAlphaMasked;
244 draw_recursively(&state, mat4_id(), gfx_get_scene_root(scene));
245 // Transparent.
246 state.filter = RenderTransparent;
247 draw_recursively(&state, mat4_id(), gfx_get_scene_root(scene));
248}
249
250static void update_rec(SceneNode* node, const Camera* camera, R t) {
251 assert(node);
252 assert(camera);
253
254 const NodeType node_type = gfx_get_node_type(node);
255
256 // TODO: Models do not need to be animated if they are not visible to the
257 // camera.
258 if (node_type == AnimaNode) {
259 Anima* anima = gfx_get_node_anima_mut(node);
260 gfx_update_animation(anima, (R)t);
261 } else if (node_type == ModelNode) {
262 Model* model = gfx_get_node_model_mut(node);
263 SceneNode* root = gfx_get_model_root_mut(model);
264 update_rec(root, camera, t);
265 }
266
267 // Children.
268 SceneNode* child = gfx_get_node_child_mut(node);
269 while (child) {
270 update_rec(child, camera, t);
271 child = gfx_get_node_sibling_mut(child);
272 }
273}
274
275// TODO: Move this outside the renderer.
276void gfx_update(Scene* scene, const Camera* camera, R t) {
277 assert(scene);
278 assert(camera);
279
280 SceneNode* node = gfx_get_scene_root_mut(scene);
281 update_rec(node, camera, t);
282}
diff --git a/src/render/renderer_impl.h b/src/render/renderer_impl.h
new file mode 100644
index 0000000..160ff52
--- /dev/null
+++ b/src/render/renderer_impl.h
@@ -0,0 +1,25 @@
1#pragma once
2
3#include <gfx/render/renderer.h>
4
5#include <stdbool.h>
6
7typedef struct LLR LLR;
8typedef struct ShaderProgram ShaderProgram;
9
10typedef struct Renderer {
11 GfxCore* gfxcore;
12 LLR* llr;
13 struct {
14 ShaderProgram* debug;
15 ShaderProgram* normals;
16 ShaderProgram* normal_mapped_normals;
17 ShaderProgram* tangents;
18 } shaders;
19} Renderer;
20
21/// Create a new renderer.
22bool gfx_renderer_make(Renderer*, LLR*, GfxCore*);
23
24/// Destroy the renderer.
25void gfx_renderer_destroy(Renderer*);
diff --git a/src/scene/camera.c b/src/scene/camera.c
new file mode 100644
index 0000000..fcfc496
--- /dev/null
+++ b/src/scene/camera.c
@@ -0,0 +1,18 @@
1#include <gfx/scene.h>
2
3#include "memory.h"
4
5#include <assert.h>
6#include <math/camera.h>
7
8Camera* gfx_make_camera(void) {
9 Camera* camera = mem_alloc_camera();
10 return camera;
11}
12
13void gfx_destroy_camera(Camera** camera) {
14 assert(camera);
15 if (*camera) {
16 mem_free_camera(camera);
17 }
18}
diff --git a/src/scene/light.c b/src/scene/light.c
new file mode 100644
index 0000000..4233330
--- /dev/null
+++ b/src/scene/light.c
@@ -0,0 +1,39 @@
1#include "light_impl.h"
2
3#include "memory.h"
4
5#include <cassert.h>
6#include <error.h>
7
8static void make_environment_light(
9 Light* light, const EnvironmentLightDesc* desc) {
10 assert(light);
11 assert(desc);
12 light->type = EnvironmentLightType;
13 light->environment.environment_map = desc->environment_map;
14}
15
16Light* gfx_make_light(const LightDesc* desc) {
17 assert(desc);
18
19 Light* light = mem_alloc_light();
20
21 switch (desc->type) {
22 case EnvironmentLightType:
23 make_environment_light(light, &desc->light.environment);
24 break;
25 default:
26 log_error("Unhandled light type");
27 gfx_destroy_light(&light);
28 return 0;
29 }
30
31 return light;
32}
33
34void gfx_destroy_light(Light** light) {
35 assert(light);
36 if (*light) {
37 mem_free_light(light);
38 }
39}
diff --git a/src/scene/light_impl.h b/src/scene/light_impl.h
new file mode 100644
index 0000000..3191a50
--- /dev/null
+++ b/src/scene/light_impl.h
@@ -0,0 +1,20 @@
1#pragma once
2
3#include <gfx/scene.h>
4
5/// An environment light.
6typedef struct EnvironmentLight {
7 const Texture* environment_map;
8 const Texture* irradiance_map; // Renderer implementation.
9 const Texture* prefiltered_environment_map; // Renderer implementation.
10 int max_reflection_lod; // Mandatory when prefiltered_environment_map is
11 // given.
12} EnvironmentLight;
13
14/// A scene light.
15typedef struct Light {
16 LightType type;
17 union {
18 EnvironmentLight environment;
19 };
20} Light;
diff --git a/src/scene/material.c b/src/scene/material.c
new file mode 100644
index 0000000..9fe6c1b
--- /dev/null
+++ b/src/scene/material.c
@@ -0,0 +1,24 @@
1#include "material_impl.h"
2
3#include "memory.h"
4
5static void material_make(Material* material, const MaterialDesc* desc) {
6 assert(material);
7 assert(desc);
8 assert(desc->num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
9 material->alpha_mode = desc->alpha_mode;
10 material->alpha_cutoff = desc->alpha_cutoff;
11 material->num_uniforms = (int8_t)desc->num_uniforms;
12 for (int i = 0; i < desc->num_uniforms; ++i) {
13 material->uniforms[i] = desc->uniforms[i];
14 }
15}
16
17Material* gfx_make_material(const MaterialDesc* desc) {
18 assert(desc);
19 Material* material = mem_alloc_material();
20 material_make(material, desc);
21 return material;
22}
23
24void gfx_destroy_material(Material** material) { mem_free_material(material); }
diff --git a/src/scene/material_impl.h b/src/scene/material_impl.h
new file mode 100644
index 0000000..488ffc7
--- /dev/null
+++ b/src/scene/material_impl.h
@@ -0,0 +1,13 @@
1#pragma once
2
3#include <gfx/scene.h>
4#include <gfx/sizes.h>
5
6typedef struct ShaderProgram ShaderProgram;
7
8typedef struct Material {
9 AlphaMode alpha_mode;
10 float alpha_cutoff;
11 int8_t num_uniforms;
12 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL];
13} Material;
diff --git a/src/scene/mesh.c b/src/scene/mesh.c
new file mode 100644
index 0000000..770c3fb
--- /dev/null
+++ b/src/scene/mesh.c
@@ -0,0 +1,26 @@
1#include "mesh_impl.h"
2
3#include <gfx/scene.h>
4
5#include "memory.h"
6
7#include <cassert.h>
8
9static void mesh_make(Mesh* mesh, const MeshDesc* desc) {
10 assert(mesh);
11 assert(desc);
12 assert(desc->geometry);
13 assert(desc->material);
14 assert(desc->shader);
15 mesh->geometry = desc->geometry;
16 mesh->material = desc->material;
17 mesh->shader = desc->shader;
18}
19
20Mesh* gfx_make_mesh(const MeshDesc* desc) {
21 Mesh* mesh = mem_alloc_mesh();
22 mesh_make(mesh, desc);
23 return mesh;
24}
25
26void gfx_destroy_mesh(Mesh** mesh) { mem_free_mesh(mesh); }
diff --git a/src/scene/mesh_impl.h b/src/scene/mesh_impl.h
new file mode 100644
index 0000000..c7e2211
--- /dev/null
+++ b/src/scene/mesh_impl.h
@@ -0,0 +1,11 @@
1#pragma once
2
3typedef struct Geometry Geometry;
4typedef struct Material Material;
5typedef struct ShaderProgram ShaderProgram;
6
7typedef struct Mesh {
8 const Geometry* geometry;
9 const Material* material;
10 ShaderProgram* shader; // TODO: Move this back to Material?
11} Mesh;
diff --git a/src/scene/model.c b/src/scene/model.c
new file mode 100644
index 0000000..1bd0112
--- /dev/null
+++ b/src/scene/model.c
@@ -0,0 +1,45 @@
1#include "model_impl.h"
2
3#include <gfx/scene.h>
4
5#include "memory.h"
6
7#include <assert.h>
8
9Model* gfx_make_model(const SceneNode* root) {
10 assert(root);
11
12 Model* model = mem_alloc_model();
13 model->root = mem_get_node_index(root);
14 return model;
15}
16
17void gfx_del_model(Model** model) {
18 assert(model);
19
20 if (*model) {
21 SceneNode* root = mem_get_node((*model)->root);
22 gfx_destroy_node(&root);
23 *model = 0;
24 }
25}
26
27Anima* gfx_get_model_anima(Model* model) {
28 assert(model);
29
30 SceneNode* root = mem_get_node(model->root);
31 if (gfx_get_node_type(root) == AnimaNode) {
32 return gfx_get_node_anima_mut(root);
33 } else {
34 return 0;
35 }
36}
37
38const SceneNode* gfx_get_model_root(const Model* model) {
39 assert(model);
40 return mem_get_node(model->root);
41}
42
43SceneNode* gfx_get_model_root_mut(Model* model) {
44 return (SceneNode*)gfx_get_model_root(model);
45}
diff --git a/src/scene/model_impl.h b/src/scene/model_impl.h
new file mode 100644
index 0000000..72cd0ab
--- /dev/null
+++ b/src/scene/model_impl.h
@@ -0,0 +1,16 @@
1#pragma once
2
3#include <gfx/scene.h>
4
5#include "memory.h"
6
7/// Model.
8typedef struct Model {
9 node_idx root;
10} Model;
11
12/// Create a new model.
13Model* gfx_make_model(const SceneNode* root);
14
15/// Destroy the model.
16void gfx_del_model(Model**);
diff --git a/src/scene/node.c b/src/scene/node.c
new file mode 100644
index 0000000..0004d27
--- /dev/null
+++ b/src/scene/node.c
@@ -0,0 +1,352 @@
1#include "node_impl.h"
2
3#include "animation_impl.h"
4#include "memory.h"
5#include "object_impl.h"
6#include "render/llr_impl.h"
7#include "scene_graph.h"
8
9#include "gfx_assert.h"
10
11#include <gfx/scene.h>
12
13#include <cstring.h>
14#include <log/log.h>
15
16static void scene_node_make(SceneNode* node) {
17 assert(node);
18 node->type = LogicalNode;
19 node->transform = mat4_id();
20}
21
22SceneNode* gfx_make_node(void) {
23 SceneNode* node = mem_alloc_node();
24 scene_node_make(node);
25 return node;
26}
27
28SceneNode* gfx_make_anima_node(Anima* anima) {
29 assert(anima);
30 SceneNode* node = gfx_make_node();
31 node->type = AnimaNode;
32 node->anima = mem_get_anima_index(anima);
33 return node;
34}
35
36SceneNode* gfx_make_camera_node(Camera* camera) {
37 assert(camera);
38 SceneNode* node = gfx_make_node();
39 node->type = CameraNode;
40 node->camera = mem_get_camera_index(camera);
41 return node;
42}
43
44SceneNode* gfx_make_light_node(Light* light) {
45 assert(light);
46 SceneNode* node = gfx_make_node();
47 node->type = LightNode;
48 node->light = mem_get_light_index(light);
49 return node;
50}
51
52SceneNode* gfx_make_model_node(Model* model) {
53 assert(model);
54 SceneNode* node = gfx_make_node();
55 node->type = ModelNode;
56 node->model = mem_get_model_index(model);
57 return node;
58}
59
60SceneNode* gfx_make_object_node(SceneObject* object) {
61 assert(object);
62 SceneNode* node = gfx_make_node();
63 node->type = ObjectNode;
64 node->object = mem_get_object_index(object);
65 return node;
66}
67
68/// Frees the node's resource.
69static void free_node_resource(SceneNode* node) {
70 assert(node);
71
72 switch (node->type) {
73 case AnimaNode: {
74 Anima* anima = mem_get_anima(node->anima);
75 gfx_destroy_anima(&anima);
76 return;
77 }
78 case CameraNode: {
79 Camera* camera = mem_get_camera(node->camera);
80 gfx_destroy_camera(&camera);
81 return;
82 }
83 case LightNode: {
84 Light* light = mem_get_light(node->light);
85 gfx_destroy_light(&light);
86 return;
87 }
88 case ModelNode: {
89 return; // Model data is owned by the asset cache.
90 }
91 case ObjectNode: {
92 SceneObject* object = mem_get_object(node->object);
93 gfx_destroy_object(&object);
94 return;
95 }
96 case LogicalNode: {
97 return; // Logical nodes have no resource.
98 }
99 }
100 FAIL("unhandled node type");
101}
102
103static void destroy_node_rec(SceneNode* node) {
104 assert(node);
105
106 // First child.
107 if (node->child.val) {
108 destroy_node_rec(mem_get_node(node->child));
109 }
110
111 // Right sibling.
112 if (node->next.val) {
113 destroy_node_rec(mem_get_node(node->next));
114 }
115
116 free_node_resource(node);
117 mem_free_node(&node);
118}
119
120void gfx_destroy_node(SceneNode** node) {
121 assert(node);
122 if (*node) {
123 // Since the node and the whole hierarchy under it gets destroyed, there is
124 // no need to individually detach every node from its hierarchy. We can
125 // simply detach the given node and then destroy it and its sub-hierarchy.
126 TREE_REMOVE(*node);
127 destroy_node_rec(*node);
128 *node = 0;
129 }
130}
131
132// TODO: Think more about ownership of nodes and resources. Should this function
133// even exist?
134void gfx_del_node(node_idx index) {
135 assert(index.val);
136 SceneNode* node = mem_get_node(index);
137 assert(node);
138 // TODO: Should destroy children recursively?
139 TREE_REMOVE(node);
140 mem_free_node(&node);
141}
142
143NodeType gfx_get_node_type(const SceneNode* node) {
144 assert(node);
145 return node->type;
146}
147
148#define NODE_GET(node, field, expected_type) \
149 { \
150 assert(node); \
151 assert(node->type == expected_type); \
152 return mem_get_##field(node->field); \
153 }
154
155const Anima* gfx_get_node_anima(const SceneNode* node) {
156 NODE_GET(node, anima, AnimaNode);
157}
158
159Anima* gfx_get_node_anima_mut(SceneNode* node) {
160 NODE_GET(node, anima, AnimaNode);
161}
162
163const Camera* gfx_get_node_camera(const SceneNode* node) {
164 NODE_GET(node, camera, CameraNode);
165}
166
167Camera* gfx_get_node_camera_mut(SceneNode* node) {
168 NODE_GET(node, camera, CameraNode);
169}
170
171const Light* gfx_get_node_light(const SceneNode* node) {
172 NODE_GET(node, light, LightNode);
173}
174
175Light* gfx_get_node_light_mut(SceneNode* node) {
176 NODE_GET(node, light, LightNode);
177}
178
179const Model* gfx_get_node_model(const SceneNode* node) {
180 NODE_GET(node, model, ModelNode);
181}
182
183Model* gfx_get_node_model_mut(SceneNode* node) {
184 NODE_GET(node, model, ModelNode);
185}
186
187const SceneObject* gfx_get_node_object(const SceneNode* node) {
188 NODE_GET(node, object, ObjectNode);
189}
190
191SceneObject* gfx_get_node_object_mut(SceneNode* node) {
192 NODE_GET(node, object, ObjectNode);
193}
194
195const SceneNode* gfx_get_node_parent(const SceneNode* node) {
196 assert(node);
197 return mem_get_node(node->parent);
198}
199
200SceneNode* gfx_get_node_parent_mut(SceneNode* node) {
201 assert(node);
202 return mem_get_node(node->parent);
203}
204
205const SceneNode* gfx_get_node_child(const SceneNode* node) {
206 assert(node);
207 if (node->child.val) {
208 return mem_get_node(node->child);
209 } else {
210 return 0;
211 }
212}
213
214SceneNode* gfx_get_node_child_mut(SceneNode* node) {
215 return (SceneNode*)gfx_get_node_child(node);
216}
217
218const SceneNode* gfx_get_node_sibling(const SceneNode* node) {
219 assert(node);
220 if (node->next.val) {
221 return mem_get_node(node->next);
222 } else {
223 return 0;
224 }
225}
226
227SceneNode* gfx_get_node_sibling_mut(SceneNode* node) {
228 return (SceneNode*)gfx_get_node_sibling(node);
229}
230
231mat4 gfx_get_node_transform(const SceneNode* node) {
232 assert(node);
233 return node->transform;
234}
235
236mat4 gfx_get_node_global_transform(const SceneNode* node) {
237 assert(node);
238 mat4 transform = node->transform;
239 node_idx parent_index = node->parent;
240 while (parent_index.val != 0) {
241 const SceneNode* parent = mem_get_node(parent_index);
242 transform = mat4_mul(parent->transform, transform);
243 parent_index = parent->parent;
244 }
245 return transform;
246}
247
248void gfx_set_node_parent(SceneNode* child, SceneNode* parent_node) {
249 assert(child);
250 // Parent can be null.
251 SET_PARENT(child, parent_node);
252}
253
254void gfx_set_node_transform(SceneNode* node, const mat4* transform) {
255 assert(node);
256 assert(transform);
257 node->transform = *transform;
258}
259
260void gfx_set_node_position(SceneNode* node, const vec3* position) {
261 assert(node);
262 assert(position);
263 mat4_set_v3(&node->transform, *position);
264}
265
266void gfx_set_node_rotation(SceneNode* node, const quat* rotation) {
267 assert(node);
268 assert(rotation);
269 mat4_set_3x3(&node->transform, mat4_from_quat(*rotation));
270}
271
272void gfx_set_node_rotation_mat(SceneNode* node, const mat4* rotation) {
273 assert(node);
274 assert(rotation);
275 mat4_set_3x3(&node->transform, *rotation);
276}
277
278static const char* get_node_type_str(NodeType type) {
279 switch (type) {
280 case LogicalNode:
281 return "LogicalNode";
282 case AnimaNode:
283 return "AnimaNode";
284 case CameraNode:
285 return "CameraNode";
286 case LightNode:
287 return "LightNode";
288 case ModelNode:
289 return "ModelNode";
290 case ObjectNode:
291 return "ObjectNode";
292 }
293 FAIL("Unhandled node type");
294 return "";
295}
296
297static void log_node_hierarchy_rec(const SceneNode* node, const sstring* pad) {
298 assert(node);
299 assert(pad);
300
301 LOGI(
302 "%s%s (%u)", sstring_cstr(pad), get_node_type_str(node->type),
303 mem_get_node_index(node).val);
304
305 // Log the children.
306 if (node->child.val) {
307 const sstring new_pad = sstring_concat_cstr(*pad, " ");
308 log_node_hierarchy_rec(mem_get_node(node->child), &new_pad);
309 }
310
311 // Then log the siblings.
312 if (node->next.val) {
313 log_node_hierarchy_rec(mem_get_node(node->next), pad);
314 }
315}
316
317void gfx_log_node_hierarchy(const SceneNode* node) {
318 const sstring pad = sstring_make("");
319 log_node_hierarchy_rec(node, &pad);
320}
321
322static SceneNode* clone_scene_rec(const SceneNode* node) {
323 assert(node);
324
325 SceneNode* copy = mem_alloc_node();
326 *copy = *node; // Shallow clone of the node's resource.
327
328 if (node->child.val) {
329 SceneNode* child = mem_get_node(node->child);
330 SceneNode* child_copy = clone_scene_rec(child);
331 copy->child = mem_get_node_index(child_copy);
332 child_copy->parent = mem_get_node_index(copy);
333 }
334
335 if (node->next.val) {
336 SceneNode* next = mem_get_node(node->next);
337 SceneNode* next_copy = clone_scene_rec(next);
338 copy->next = mem_get_node_index(next_copy);
339 next_copy->prev = mem_get_node_index(copy);
340 }
341
342 return copy;
343}
344
345SceneNode* gfx_clone_scene_shallow(const SceneNode* node) {
346 assert(node);
347 // Must be a root node; not allowed to have siblings.
348 assert(!node->prev.val);
349 assert(!node->next.val);
350
351 return clone_scene_rec(node);
352}
diff --git a/src/scene/node_impl.h b/src/scene/node_impl.h
new file mode 100644
index 0000000..9e65588
--- /dev/null
+++ b/src/scene/node_impl.h
@@ -0,0 +1,40 @@
1#pragma once
2
3#include <gfx/scene.h>
4
5#include "../types.h"
6
7#include <math/camera.h>
8#include <math/mat4.h>
9
10/// Scene node.
11///
12/// The SceneNode owns its cameras, objects, lights and child nodes. These
13/// together form a strict tree hierarchy and not a more general DAG.
14typedef struct SceneNode {
15 NodeType type;
16 union {
17 anima_idx anima;
18 camera_idx camera;
19 light_idx light;
20 model_idx model;
21 object_idx object;
22 };
23 mat4 transform; // Transformation for this node and its children.
24 node_idx parent; // Parent SceneNode.
25 node_idx child; // First child SceneNode.
26 node_idx next; // Next sibling SceneNode.
27 node_idx prev; // Previous sibling SceneNode.
28} SceneNode;
29
30/// Recursively destroy a node given its index but without destroying the node
31/// resources.
32///
33/// The node and its children are removed from the scene graph.
34///
35/// This function is for the library's internal use only.
36void gfx_del_node(node_idx);
37
38/// Return a shallow clone of the scene rooted at the given node.
39/// The given node must have no siblings (must be a root node).
40SceneNode* gfx_clone_scene_shallow(const SceneNode*);
diff --git a/src/scene/object.c b/src/scene/object.c
new file mode 100644
index 0000000..ac86b39
--- /dev/null
+++ b/src/scene/object.c
@@ -0,0 +1,80 @@
1#include "object_impl.h"
2
3#include <gfx/core.h>
4
5#include "memory.h"
6#include "render/llr_impl.h"
7#include "scene/mesh_impl.h"
8#include "scene/node_impl.h"
9
10#include <assert.h>
11
12static aabb3 calc_object_aabb(const SceneObject* object) {
13 assert(object);
14
15 bool first = true;
16 aabb3 box;
17
18 mesh_link_idx ml = object->mesh_link;
19 while (ml.val) {
20 const MeshLink* mesh_link = mem_get_mesh_link(ml);
21 const mesh_idx mi = mesh_link->mesh;
22 if (mi.val) {
23 const Mesh* mesh = mem_get_mesh(mi);
24 const aabb3 mesh_box = gfx_get_geometry_aabb(mesh->geometry);
25 if (first) {
26 box = mesh_box;
27 first = false;
28 } else {
29 box = aabb3_sum(box, mesh_box);
30 }
31 }
32 ml = mesh_link->next;
33 }
34
35 return box;
36}
37
38static void add_object_mesh(SceneObject* object, Mesh* mesh) {
39 assert(object);
40 assert(mesh);
41
42 MeshLink* link = mem_alloc_mesh_link();
43 link->mesh = mem_get_mesh_index(mesh);
44 link->next = object->mesh_link;
45 object->mesh_link = mem_get_mesh_link_index(link);
46}
47
48SceneObject* gfx_make_object(const ObjectDesc* desc) {
49 assert(desc);
50
51 SceneObject* object = mem_alloc_object();
52 for (size_t i = 0; i < desc->num_meshes; ++i) {
53 add_object_mesh(object, desc->meshes[i]);
54 }
55 object->box = calc_object_aabb(object);
56 return object;
57}
58
59void gfx_destroy_object(SceneObject** object) {
60 assert(object);
61 if (*object) {
62 mem_free_object(object);
63 }
64}
65
66void gfx_set_object_skeleton(SceneObject* object, const Skeleton* skeleton) {
67 assert(object);
68 assert(skeleton);
69 object->skeleton = mem_get_skeleton_index(skeleton);
70}
71
72const Skeleton* gfx_get_object_skeleton(const SceneObject* object) {
73 assert(object);
74 return (object->skeleton.val == 0) ? 0 : mem_get_skeleton(object->skeleton);
75}
76
77aabb3 gfx_get_object_aabb(const SceneObject* object) {
78 assert(object);
79 return object->box;
80}
diff --git a/src/scene/object_impl.h b/src/scene/object_impl.h
new file mode 100644
index 0000000..345d615
--- /dev/null
+++ b/src/scene/object_impl.h
@@ -0,0 +1,23 @@
1#pragma once
2
3#include <gfx/scene.h>
4
5#include "../types.h"
6
7typedef struct MeshLink {
8 mesh_idx mesh;
9 mesh_link_idx next; // Next MeshLink in the list.
10} MeshLink;
11
12/// Scene object.
13///
14/// A SceneObject does not own its Meshes, and they are instead shared for
15/// re-use. The SceneObject consequently embeds a list of MeshLinks as opposed
16/// to a list of Meshes. The MeshLinks define a list of Meshes, which can be
17/// different for each SceneObject. Each SceneObject may then have a unique list
18/// of Meshes, and the Meshes are re-used.
19typedef struct SceneObject {
20 mesh_link_idx mesh_link; /// First MeshLink in the list.
21 skeleton_idx skeleton; /// 0 for static objects.
22 aabb3 box;
23} SceneObject;
diff --git a/src/scene/scene.c b/src/scene/scene.c
new file mode 100644
index 0000000..52ddb58
--- /dev/null
+++ b/src/scene/scene.c
@@ -0,0 +1,31 @@
1#include "scene_impl.h"
2
3#include "memory.h"
4#include "node_impl.h"
5
6#include <assert.h>
7
8Scene* gfx_make_scene(void) {
9 Scene* scene = mem_alloc_scene();
10 scene->root = mem_get_node_index(gfx_make_node());
11 return scene;
12}
13
14void gfx_destroy_scene(Scene** scene) {
15 assert(scene);
16 if (*scene) {
17 SceneNode* node = mem_get_node((*scene)->root);
18 gfx_destroy_node(&node);
19 mem_free_scene(scene);
20 }
21}
22
23const SceneNode* gfx_get_scene_root(const Scene* scene) {
24 assert(scene);
25 return mem_get_node(scene->root);
26}
27
28SceneNode* gfx_get_scene_root_mut(Scene* scene) {
29 assert(scene);
30 return (SceneNode*)gfx_get_scene_root(scene);
31}
diff --git a/src/scene/scene_graph.h b/src/scene/scene_graph.h
new file mode 100644
index 0000000..36c3a98
--- /dev/null
+++ b/src/scene/scene_graph.h
@@ -0,0 +1,144 @@
1/// Functions for list manipulation.
2#pragma once
3
4#include "memory.h"
5
6// NOTE: SceneMemory guarantees that index 0 can be regarded as an invalid
7// index.
8
9#define MEM_GET(INDEX) \
10 _Generic( \
11 (INDEX), \
12 camera_idx: mem_get_camera, \
13 material_idx: mem_get_material, \
14 mesh_idx: mem_get_mesh, \
15 mesh_link_idx: mem_get_mesh_link, \
16 node_idx: mem_get_node, \
17 object_idx: mem_get_object, \
18 scene_idx: mem_get_scene)(INDEX)
19
20#define MEM_GET_INDEX(ITEM) \
21 _Generic( \
22 (ITEM), \
23 Camera *: mem_get_camera_index, \
24 Material *: mem_get_material_index, \
25 Mesh *: mem_get_mesh_index, \
26 MeshLink *: mem_get_mesh_link_index, \
27 SceneNode *: mem_get_node_index, \
28 SceneObject *: mem_get_object_index, \
29 Scene *: mem_get_scene_index)(ITEM)
30
31/// Assert the list node invariant.
32///
33/// - A node does not point to itself.
34#if NDEBUG
35#define ASSERT_LIST_NODE_INVARIANT(ITEM)
36#else
37#define ASSERT_LIST_NODE_INVARIANT(ITEM) \
38 { \
39 const gfx_idx item_idx = MEM_GET_INDEX(ITEM).val; \
40 assert((ITEM)->prev.val != item_idx); \
41 assert((ITEM)->next.val != item_idx); \
42 }
43#endif
44
45/// Assert the tree node invariant.
46///
47/// - A node does not point to itself.
48/// - The node's left and right siblings cannot be equal, unless both are 0.
49/// - The node's left/right sibling cannot be its child, unless both are 0.
50/// - The node's parent cannot be the node's child or sibling, unless it's 0.
51/// - If the node has a parent and the node is the leftmost sibling, then the
52/// parent's child is the node.
53#define ASSERT_TREE_NODE_INVARIANT(ITEM) \
54 { \
55 const gfx_idx item_idx = MEM_GET_INDEX(ITEM).val; \
56 assert((ITEM)->prev.val != item_idx); \
57 assert((ITEM)->next.val != item_idx); \
58 if ((ITEM)->prev.val) { \
59 assert((ITEM)->prev.val != (ITEM)->next.val); \
60 } \
61 if ((ITEM)->child.val) { \
62 assert((ITEM)->child.val != (ITEM)->prev.val); \
63 assert((ITEM)->child.val != (ITEM)->next.val); \
64 } \
65 assert((ITEM)->parent.val != item_idx); \
66 if ((ITEM)->parent.val && !(ITEM)->prev.val) { \
67 assert((ITEM)->parent.val != (ITEM)->prev.val); \
68 assert((ITEM)->parent.val != (ITEM)->next.val); \
69 const __typeof__(ITEM) item_parent = MEM_GET((ITEM)->parent); \
70 assert(item_parent->child.val == item_idx); \
71 } \
72 }
73
74/// Prepend an item to a list.
75/// Modify HEAD_INDEX to equal the index of the new head.
76#define LIST_PREPEND(HEAD_INDEX, ITEM) \
77 (ITEM)->next = HEAD_INDEX; \
78 if (HEAD_INDEX.val) { \
79 __typeof__(ITEM) old_head = MEM_GET(HEAD_INDEX); \
80 old_head->prev = MEM_GET_INDEX(ITEM); \
81 } \
82 HEAD_INDEX = MEM_GET_INDEX(ITEM); \
83 ASSERT_LIST_NODE_INVARIANT(ITEM);
84
85/// Disconnect an item from its siblings.
86#define LIST_REMOVE(ITEM) \
87 if ((ITEM)->prev.val) { \
88 __typeof__(ITEM) prev_sibling = MEM_GET((ITEM)->prev); \
89 prev_sibling->next = (ITEM)->next; \
90 } \
91 if ((ITEM)->next.val) { \
92 __typeof__(ITEM) next_sibling = MEM_GET((ITEM)->next); \
93 next_sibling->prev = (ITEM)->prev; \
94 } \
95 (ITEM)->prev.val = 0; \
96 (ITEM)->next.val = 0; \
97 ASSERT_LIST_NODE_INVARIANT(ITEM);
98
99/// Set the child's parent.
100///
101/// The hierarchy is a strict tree hierarchy and a parent node points to its
102/// first/leftmost child only. To add a new child, the new child becomes the
103/// leftmost node in the list of siblings, the one that the parent then points
104/// to.
105///
106/// The child is also completely disconnected from its previous hierarchy. This
107/// is because siblings in a hierarchy must all point to the same parent.
108#define SET_PARENT(CHILD, PARENT) \
109 assert(CHILD); \
110 assert(CHILD != PARENT); \
111 ASSERT_TREE_NODE_INVARIANT(CHILD); \
112 ASSERT_TREE_NODE_INVARIANT(PARENT); \
113 TREE_REMOVE(CHILD); /* Disconnect CHILD from its previous hierarchy. */ \
114 if (PARENT) { \
115 LIST_PREPEND((PARENT)->child, CHILD); \
116 (CHILD)->parent = MEM_GET_INDEX(PARENT); \
117 } else { \
118 (CHILD)->parent.val = 0; \
119 } \
120 ASSERT_TREE_NODE_INVARIANT(CHILD); \
121 if (PARENT) { \
122 ASSERT_TREE_NODE_INVARIANT(PARENT); \
123 }
124
125/// Remove an item from its hierarchy.
126///
127/// The item is disconnected from its parents and siblings. The hierarchy rooted
128/// under the item remains intact.
129#define TREE_REMOVE(ITEM) \
130 assert(ITEM); \
131 if ((ITEM)->parent.val) { \
132 /* The parent points only to its first/leftmost child. If this item is */ \
133 /* the leftmost sibling, then we need to rewire the parent to point to */ \
134 /* the next sibling to keep the parent connected to its children. */ \
135 __typeof__(ITEM) parent = MEM_GET((ITEM)->parent); \
136 const __typeof__(ITEM) parent_child = MEM_GET(parent->child); \
137 if (parent_child == ITEM) { \
138 assert((ITEM)->prev.val == 0); \
139 parent->child = (ITEM)->next; \
140 } \
141 } \
142 (ITEM)->parent.val = 0; \
143 LIST_REMOVE(ITEM); /* Disconnect ITEM from its siblings. */ \
144 ASSERT_TREE_NODE_INVARIANT(ITEM);
diff --git a/src/scene/scene_impl.h b/src/scene/scene_impl.h
new file mode 100644
index 0000000..ad2e892
--- /dev/null
+++ b/src/scene/scene_impl.h
@@ -0,0 +1,9 @@
1#pragma once
2
3#include <gfx/scene.h>
4
5#include "../types.h"
6
7typedef struct Scene {
8 node_idx root;
9} Scene;
diff --git a/src/types.h b/src/types.h
new file mode 100644
index 0000000..d0ffc41
--- /dev/null
+++ b/src/types.h
@@ -0,0 +1,24 @@
1/// Strongly-typed indices for every kind of scene node resource.
2#pragma once
3
4#include <stdint.h>
5
6typedef uint16_t gfx_idx;
7
8#define DEF_STRONG_INDEX(TYPE_NAME, IDX_TYPE) \
9 typedef struct TYPE_NAME##_idx { \
10 IDX_TYPE val; \
11 } TYPE_NAME##_idx;
12
13DEF_STRONG_INDEX(anima, gfx_idx)
14DEF_STRONG_INDEX(animation, gfx_idx)
15DEF_STRONG_INDEX(camera, gfx_idx)
16DEF_STRONG_INDEX(light, gfx_idx)
17DEF_STRONG_INDEX(material, gfx_idx)
18DEF_STRONG_INDEX(mesh, gfx_idx)
19DEF_STRONG_INDEX(mesh_link, gfx_idx)
20DEF_STRONG_INDEX(model, gfx_idx)
21DEF_STRONG_INDEX(node, gfx_idx)
22DEF_STRONG_INDEX(object, gfx_idx)
23DEF_STRONG_INDEX(scene, gfx_idx)
24DEF_STRONG_INDEX(skeleton, gfx_idx)
diff --git a/src/util/geometry.c b/src/util/geometry.c
new file mode 100644
index 0000000..2ea0c82
--- /dev/null
+++ b/src/util/geometry.c
@@ -0,0 +1,45 @@
1#include <gfx/util/geometry.h>
2
3#include <math/vec2.h>
4
5static void make_quad_11_positions(vec2 positions[4]) {
6 positions[0] = vec2_make(-1, +1);
7 positions[1] = vec2_make(-1, -1);
8 positions[2] = vec2_make(+1, +1);
9 positions[3] = vec2_make(+1, -1);
10}
11
12static void make_quad_01_positions(vec2 positions[4]) {
13 positions[0] = vec2_make(0, 0);
14 positions[1] = vec2_make(1, 0);
15 positions[2] = vec2_make(1, 1);
16 positions[3] = vec2_make(0, 1);
17}
18
19static GeometryDesc make_quad_desc(vec2 positions[4]) {
20 return (GeometryDesc){
21 .positions2d = (BufferView2d){.data = positions,
22 .size_bytes = 4 * sizeof(vec2),
23 .count = 4},
24 .num_verts = 4,
25 .type = TriangleStrip
26 };
27}
28
29Geometry* gfx_make_quad_11(GfxCore* gfxcore) {
30 assert(gfxcore);
31
32 vec2 positions[4];
33 make_quad_11_positions(positions);
34 const GeometryDesc geometry_desc = make_quad_desc(positions);
35 return gfx_make_geometry(gfxcore, &geometry_desc);
36}
37
38Geometry* gfx_make_quad_01(GfxCore* gfxcore) {
39 assert(gfxcore);
40
41 vec2 positions[4];
42 make_quad_01_positions(positions);
43 const GeometryDesc geometry_desc = make_quad_desc(positions);
44 return gfx_make_geometry(gfxcore, &geometry_desc);
45}
diff --git a/src/util/ibl.c b/src/util/ibl.c
new file mode 100644
index 0000000..5a79990
--- /dev/null
+++ b/src/util/ibl.c
@@ -0,0 +1,328 @@
1#include <gfx/util/ibl.h>
2
3#include <gfx/core.h>
4#include <gfx/util/geometry.h>
5#include <gfx/util/shader.h>
6#include <math/mat4.h>
7
8#include <assert.h>
9#include <stdlib.h>
10
11typedef struct IBL {
12 Geometry* quad;
13 ShaderProgram* brdf_integration_map_shader;
14 ShaderProgram* irradiance_map_shader;
15 ShaderProgram* prefiltered_environment_map_shader;
16 Texture* brdf_integration_map;
17 FrameBuffer* framebuffer;
18 mat4 rotations[6];
19} IBL;
20
21static const CubemapFace faces[6] = {
22 CubemapFacePosX, // Right.
23 CubemapFaceNegX, // Left.
24 CubemapFacePosY, // Up.
25 CubemapFaceNegY, // Down.
26 CubemapFacePosZ, // Back.
27 CubemapFaceNegZ, // Front.
28};
29
30static const float flips[6] = {
31 -1.0f, // Right.
32 -1.0f, // Left.
33 +1.0f, // Up.
34 +1.0f, // Down.
35 -1.0f, // Back.
36 -1.0f, // Front.
37};
38
39IBL* gfx_make_ibl(GfxCore* gfxcore) {
40 assert(gfxcore);
41
42 IBL* ibl = calloc(1, sizeof(IBL));
43 if (!ibl) {
44 return 0;
45 }
46
47 if (!(ibl->quad = gfx_make_quad_11(gfxcore))) {
48 goto cleanup;
49 }
50
51 // We only need the BRDF integration once since we are caching the map, but
52 // compiling the shader up front may lead to fewer surprises. Not that the
53 // shader is fully compiled up front anyway, since the driver will typically
54 // defer full compilation to the first draw call.
55 if (!(ibl->brdf_integration_map_shader =
56 gfx_make_brdf_integration_map_shader(gfxcore))) {
57 goto cleanup;
58 }
59
60 if (!(ibl->irradiance_map_shader = gfx_make_irradiance_map_shader(gfxcore))) {
61 goto cleanup;
62 }
63
64 if (!(ibl->prefiltered_environment_map_shader =
65 gfx_make_prefiltered_environment_map_shader(gfxcore))) {
66 goto cleanup;
67 }
68
69 // Create an empty framebuffer for now. Will attach the colour buffer later
70 // as we render the faces of the cube.
71 if (!(ibl->framebuffer = gfx_make_framebuffer(
72 gfxcore,
73 &(FrameBufferDesc){
74 .colour =
75 (FrameBufferAttachment){.type = FrameBufferNoAttachment},
76 .depth = (FrameBufferAttachment){
77 .type = FrameBufferNoAttachment}}))) {
78 goto cleanup;
79 }
80
81 // TODO: Debug the camera rotations. Irradiance debug output should appear
82 // just like the input cubemap.
83
84 // Right.
85 ibl->rotations[0] = mat4_lookat(
86 /*position=*/vec3_make(0, 0, 0),
87 /*target=*/vec3_make(1, 0, 0),
88 /*up=*/vec3_make(0, 1, 0));
89 // Left.
90 ibl->rotations[1] = mat4_lookat(
91 /*position=*/vec3_make(0, 0, 0),
92 /*target=*/vec3_make(-1, 0, 0),
93 /*up=*/vec3_make(0, 1, 0));
94 // Up.
95 ibl->rotations[2] = mat4_lookat(
96 /*position=*/vec3_make(0, 0, 0),
97 /*target=*/vec3_make(0, 1, 0),
98 /*up=*/vec3_make(0, 0, 1));
99 // Down.
100 ibl->rotations[3] = mat4_lookat(
101 /*position=*/vec3_make(0, 0, 0),
102 /*target=*/vec3_make(0, -1, 0),
103 /*up=*/vec3_make(0, 0, -1));
104 // Back.
105 ibl->rotations[4] = mat4_lookat(
106 /*position=*/vec3_make(0, 0, 0),
107 /*target=*/vec3_make(0, 0, 1),
108 /*up=*/vec3_make(0, 1, 0));
109 // Front.
110 ibl->rotations[5] = mat4_lookat(
111 /*position=*/vec3_make(0, 0, 0),
112 /*target=*/vec3_make(0, 0, -1),
113 /*up=*/vec3_make(0, 1, 0));
114
115 return ibl;
116
117cleanup:
118 gfx_destroy_ibl(gfxcore, &ibl);
119 return 0;
120}
121
122void gfx_destroy_ibl(GfxCore* gfxcore, IBL** ibl) {
123 if (!ibl) {
124 return;
125 }
126 if ((*ibl)->quad) {
127 gfx_destroy_geometry(gfxcore, &(*ibl)->quad);
128 }
129 if ((*ibl)->brdf_integration_map_shader) {
130 gfx_destroy_shader_program(gfxcore, &(*ibl)->brdf_integration_map_shader);
131 }
132 if ((*ibl)->irradiance_map_shader) {
133 gfx_destroy_shader_program(gfxcore, &(*ibl)->irradiance_map_shader);
134 }
135 if ((*ibl)->prefiltered_environment_map_shader) {
136 gfx_destroy_shader_program(
137 gfxcore, &(*ibl)->prefiltered_environment_map_shader);
138 }
139 if ((*ibl)->brdf_integration_map) {
140 gfx_destroy_texture(gfxcore, &(*ibl)->brdf_integration_map);
141 }
142 if ((*ibl)->framebuffer) {
143 gfx_destroy_framebuffer(gfxcore, &(*ibl)->framebuffer);
144 }
145 free(*ibl);
146 *ibl = 0;
147}
148
149Texture* gfx_make_brdf_integration_map(
150 IBL* ibl, GfxCore* gfxcore, int width, int height) {
151 assert(ibl);
152 assert(gfxcore);
153
154 if (ibl->brdf_integration_map) {
155 return ibl->brdf_integration_map;
156 }
157
158 bool success = false;
159
160 if (!(ibl->brdf_integration_map = gfx_make_texture(
161 gfxcore, &(TextureDesc){
162 .width = width,
163 .height = height,
164 .depth = 1,
165 .dimension = Texture2D,
166 .format = TextureRG16F,
167 .filtering = LinearFiltering,
168 .wrap = ClampToEdge,
169 .mipmaps = false}))) {
170 goto cleanup;
171 }
172
173 gfx_activate_framebuffer(ibl->framebuffer);
174 gfx_framebuffer_set_viewport(ibl->framebuffer, 0, 0, width, height);
175 gfx_activate_shader_program(ibl->brdf_integration_map_shader);
176 if (!gfx_framebuffer_attach_colour(
177 ibl->framebuffer, &(FrameBufferAttachment){
178 .type = FrameBufferTexture,
179 .texture.texture = ibl->brdf_integration_map,
180 .texture.mip_level = 0})) {
181 goto cleanup;
182 }
183 gfx_render_geometry(ibl->quad);
184
185 success = true;
186
187cleanup:
188 gfx_deactivate_shader_program(ibl->brdf_integration_map_shader);
189 gfx_deactivate_framebuffer(ibl->framebuffer);
190 if (!success && ibl->brdf_integration_map) {
191 gfx_destroy_texture(gfxcore, &ibl->brdf_integration_map);
192 return 0;
193 } else {
194 return ibl->brdf_integration_map;
195 }
196}
197
198Texture* gfx_make_irradiance_map(
199 IBL* ibl, GfxCore* gfxcore, const Texture* environment_map, int width,
200 int height) {
201 assert(ibl);
202 assert(gfxcore);
203 assert(environment_map);
204
205 bool success = false;
206
207 Texture* irradiance_map = 0;
208
209 // TODO: Could define colour-renderable texture formats separately to make
210 // framebuffer creation less error-prone. Or, at the very least, validate the
211 // choice at runtime.
212 //
213 // Make sure to use a float colour format to avoid [0,1] clamping when the
214 // irradiance values are computed!
215 if (!(irradiance_map = gfx_make_texture(
216 gfxcore, &(TextureDesc){
217 .width = width,
218 .height = height,
219 .depth = 1,
220 .dimension = TextureCubeMap,
221 .format = TextureR11G11B10F,
222 .filtering = LinearFiltering,
223 .mipmaps = false}))) {
224 goto cleanup;
225 }
226
227 gfx_activate_framebuffer(ibl->framebuffer);
228 gfx_framebuffer_set_viewport(ibl->framebuffer, 0, 0, width, height);
229 gfx_activate_shader_program(ibl->irradiance_map_shader);
230 gfx_set_texture_uniform(ibl->irradiance_map_shader, "Sky", environment_map);
231 for (int i = 0; i < 6; ++i) {
232 if (!gfx_framebuffer_attach_colour(
233 ibl->framebuffer, &(FrameBufferAttachment){
234 .type = FrameBufferCubemapTexture,
235 .cubemap.face = faces[i],
236 .cubemap.texture = irradiance_map})) {
237 goto cleanup;
238 }
239 gfx_set_float_uniform(ibl->irradiance_map_shader, "Flip", flips[i]);
240 gfx_set_mat4_uniform(
241 ibl->irradiance_map_shader, "CameraRotation", &ibl->rotations[i]);
242 gfx_apply_uniforms(ibl->irradiance_map_shader);
243 gfx_render_geometry(ibl->quad);
244 }
245
246 success = true;
247
248cleanup:
249 gfx_deactivate_shader_program(ibl->irradiance_map_shader);
250 gfx_deactivate_framebuffer(ibl->framebuffer);
251 if (!success && irradiance_map) {
252 gfx_destroy_texture(gfxcore, &irradiance_map);
253 return 0;
254 } else {
255 return irradiance_map;
256 }
257}
258
259Texture* gfx_make_prefiltered_environment_map(
260 IBL* ibl, GfxCore* gfxcore, const Texture* environment_map, int width,
261 int height, int* max_mip_level) {
262 assert(ibl);
263 assert(gfxcore);
264 assert(environment_map);
265 assert(max_mip_level);
266
267 bool success = false;
268
269 Texture* prefiltered_env_map = 0;
270
271 if (!(prefiltered_env_map = gfx_make_texture(
272 gfxcore, &(TextureDesc){
273 .width = width,
274 .height = height,
275 .depth = 1,
276 .dimension = TextureCubeMap,
277 .format = TextureR11G11B10F,
278 .filtering = LinearFiltering,
279 .mipmaps = true}))) {
280 goto cleanup;
281 }
282
283 gfx_activate_framebuffer(ibl->framebuffer);
284 gfx_activate_shader_program(ibl->prefiltered_environment_map_shader);
285 gfx_set_texture_uniform(
286 ibl->prefiltered_environment_map_shader, "Sky", environment_map);
287 const int max_mip = (int)(rlog2(min(width, height)));
288 for (int mip = 0; mip <= max_mip; ++mip) {
289 const int mip_width = width >> mip;
290 const int mip_height = height >> mip;
291 const float roughness = (float)mip / (float)(max_mip);
292 gfx_framebuffer_set_viewport(ibl->framebuffer, 0, 0, mip_width, mip_height);
293 gfx_set_float_uniform(
294 ibl->prefiltered_environment_map_shader, "Roughness", roughness);
295
296 for (int i = 0; i < 6; ++i) {
297 if (!gfx_framebuffer_attach_colour(
298 ibl->framebuffer, &(FrameBufferAttachment){
299 .type = FrameBufferCubemapTexture,
300 .cubemap.face = faces[i],
301 .cubemap.mip_level = mip,
302 .cubemap.texture = prefiltered_env_map})) {
303 goto cleanup;
304 }
305 gfx_set_float_uniform(
306 ibl->prefiltered_environment_map_shader, "Flip", flips[i]);
307 gfx_set_mat4_uniform(
308 ibl->prefiltered_environment_map_shader, "CameraRotation",
309 &ibl->rotations[i]);
310 gfx_apply_uniforms(ibl->prefiltered_environment_map_shader);
311 gfx_render_geometry(ibl->quad);
312 }
313 }
314
315 *max_mip_level = max_mip;
316
317 success = true;
318
319cleanup:
320 gfx_deactivate_shader_program(ibl->prefiltered_environment_map_shader);
321 gfx_deactivate_framebuffer(ibl->framebuffer);
322 if (!success && prefiltered_env_map) {
323 gfx_destroy_texture(gfxcore, &prefiltered_env_map);
324 return 0;
325 } else {
326 return prefiltered_env_map;
327 }
328}
diff --git a/src/util/shader.c b/src/util/shader.c
new file mode 100644
index 0000000..f5c22cc
--- /dev/null
+++ b/src/util/shader.c
@@ -0,0 +1,136 @@
1#include <gfx/util/shader.h>
2
3#include <gfx/core.h>
4#include <shaders/brdf_integration_map.frag.h>
5#include <shaders/cook_torrance.frag.h>
6#include <shaders/cook_torrance.vert.h>
7#include <shaders/cubemap_filtering.vert.h>
8#include <shaders/debug3d.frag.h>
9#include <shaders/debug3d.vert.h>
10#include <shaders/immediate_mode.frag.h>
11#include <shaders/immediate_mode.vert.h>
12#include <shaders/irradiance_map.frag.h>
13#include <shaders/prefiltered_environment_map.frag.h>
14#include <shaders/quad.vert.h>
15#include <shaders/skyquad.frag.h>
16#include <shaders/skyquad.vert.h>
17#include <shaders/view_normal_mapped_normals.frag.h>
18#include <shaders/view_normal_mapped_normals.vert.h>
19#include <shaders/view_normals.frag.h>
20#include <shaders/view_normals.vert.h>
21#include <shaders/view_tangents.frag.h>
22#include <shaders/view_tangents.vert.h>
23#include <shaders/view_texture.frag.h>
24#include <shaders/view_texture.vert.h>
25
26#include <assert.h>
27#include <string.h>
28
29static ShaderProgram* make_shader_program(
30 GfxCore* gfxcore, const char* vert_source, const char* frag_source,
31 const ShaderCompilerDefine* defines, size_t num_defines) {
32 assert(gfxcore);
33 assert(vert_source);
34 assert(frag_source);
35
36 Shader* vert = 0;
37 Shader* frag = 0;
38
39 ShaderDesc vertex_shader_desc = {
40 .code = vert_source, .type = VertexShader, .num_defines = num_defines};
41 ShaderDesc fragment_shader_desc = {
42 .code = frag_source, .type = FragmentShader, .num_defines = num_defines};
43 if (num_defines > 0) {
44 memcpy(
45 vertex_shader_desc.defines, defines,
46 num_defines * sizeof(ShaderCompilerDefine));
47 memcpy(
48 fragment_shader_desc.defines, defines,
49 num_defines * sizeof(ShaderCompilerDefine));
50 }
51 vert = gfx_make_shader(gfxcore, &vertex_shader_desc);
52 if (!vert) {
53 goto cleanup;
54 }
55 frag = gfx_make_shader(gfxcore, &fragment_shader_desc);
56 if (!frag) {
57 goto cleanup;
58 }
59
60 ShaderProgramDesc shader_program_desc = {
61 .vertex_shader = vert, .fragment_shader = frag};
62 ShaderProgram* prog = gfx_make_shader_program(gfxcore, &shader_program_desc);
63 if (!prog) {
64 goto cleanup;
65 }
66 return prog;
67
68cleanup:
69 if (vert) {
70 gfx_destroy_shader(gfxcore, &vert);
71 }
72 if (frag) {
73 gfx_destroy_shader(gfxcore, &frag);
74 }
75 return 0;
76}
77
78ShaderProgram* gfx_make_brdf_integration_map_shader(GfxCore* gfxcore) {
79 return make_shader_program(
80 gfxcore, quad_vert, brdf_integration_map_frag, 0, 0);
81}
82
83ShaderProgram* gfx_make_cook_torrance_shader(GfxCore* gfxcore) {
84 return make_shader_program(
85 gfxcore, cook_torrance_vert, cook_torrance_frag, 0, 0);
86}
87
88ShaderProgram* gfx_make_cook_torrance_shader_perm(
89 GfxCore* gfxcore, const ShaderCompilerDefine* defines, size_t num_defines) {
90 return make_shader_program(
91 gfxcore, cook_torrance_vert, cook_torrance_frag, defines, num_defines);
92}
93
94ShaderProgram* gfx_make_immediate_mode_shader(GfxCore* gfxcore) {
95 return make_shader_program(
96 gfxcore, immediate_mode_vert, immediate_mode_frag, 0, 0);
97}
98
99ShaderProgram* gfx_make_irradiance_map_shader(GfxCore* gfxcore) {
100 return make_shader_program(
101 gfxcore, cubemap_filtering_vert, irradiance_map_frag, 0, 0);
102}
103
104ShaderProgram* gfx_make_prefiltered_environment_map_shader(GfxCore* gfxcore) {
105 return make_shader_program(
106 gfxcore, cubemap_filtering_vert, prefiltered_environment_map_frag, 0, 0);
107}
108
109ShaderProgram* gfx_make_debug3d_shader(GfxCore* gfxcore) {
110 return make_shader_program(gfxcore, debug3d_vert, debug3d_frag, 0, 0);
111}
112
113ShaderProgram* gfx_make_skyquad_shader(GfxCore* gfxcore) {
114 return make_shader_program(gfxcore, skyquad_vert, skyquad_frag, 0, 0);
115}
116
117ShaderProgram* gfx_make_view_normal_mapped_normals_shader(GfxCore* gfxcore) {
118 return make_shader_program(
119 gfxcore, view_normal_mapped_normals_vert, view_normal_mapped_normals_frag,
120 0, 0);
121}
122
123ShaderProgram* gfx_make_view_normals_shader(GfxCore* gfxcore) {
124 return make_shader_program(
125 gfxcore, view_normals_vert, view_normals_frag, 0, 0);
126}
127
128ShaderProgram* gfx_make_view_tangents_shader(GfxCore* gfxcore) {
129 return make_shader_program(
130 gfxcore, view_tangents_vert, view_tangents_frag, 0, 0);
131}
132
133ShaderProgram* gfx_make_view_texture_shader(GfxCore* gfxcore) {
134 return make_shader_program(
135 gfxcore, view_texture_vert, view_texture_frag, 0, 0);
136}
diff --git a/src/util/skyquad.c b/src/util/skyquad.c
new file mode 100644
index 0000000..f4561f2
--- /dev/null
+++ b/src/util/skyquad.c
@@ -0,0 +1,153 @@
1#include <gfx/util/skyquad.h>
2
3#include <gfx/core.h>
4#include <gfx/render/llr.h>
5#include <gfx/scene.h>
6#include <gfx/util/geometry.h>
7#include <gfx/util/shader.h>
8
9#include <assert.h>
10
11SceneObject* gfx_make_skyquad(GfxCore* gfxcore, const Texture* texture) {
12 assert(gfxcore);
13 assert(texture);
14
15 ShaderProgram* shader = 0;
16 Geometry* geometry = 0;
17 Material* material = 0;
18 Mesh* mesh = 0;
19 SceneObject* object = 0;
20
21 shader = gfx_make_skyquad_shader(gfxcore);
22 if (!shader) {
23 goto cleanup;
24 }
25
26 geometry = gfx_make_quad_11(gfxcore);
27 if (!geometry) {
28 goto cleanup;
29 }
30
31 MaterialDesc material_desc = (MaterialDesc){0};
32 material_desc.uniforms[0] = (ShaderUniform){.type = UniformTexture,
33 .value.texture = texture,
34 .name = sstring_make("Skyquad")};
35 material_desc.num_uniforms = 1;
36 material = gfx_make_material(&material_desc);
37 if (!material) {
38 goto cleanup;
39 }
40
41 MeshDesc mesh_desc = (MeshDesc){0};
42 mesh_desc.geometry = geometry;
43 mesh_desc.material = material;
44 mesh_desc.shader = shader;
45 mesh = gfx_make_mesh(&mesh_desc);
46 if (!mesh) {
47 goto cleanup;
48 }
49
50 object = gfx_make_object(&(ObjectDesc){.num_meshes = 1, .meshes = {mesh}});
51 if (!object) {
52 goto cleanup;
53 }
54
55 return object;
56
57cleanup:
58 if (shader) {
59 gfx_destroy_shader_program(gfxcore, &shader);
60 }
61 if (geometry) {
62 gfx_destroy_geometry(gfxcore, &geometry);
63 }
64 if (material) {
65 gfx_destroy_material(&material);
66 }
67 if (mesh) {
68 gfx_destroy_mesh(&mesh);
69 }
70 if (object) {
71 gfx_destroy_object(&object);
72 }
73 return false;
74}
75
76/// Create an environment light node.
77static SceneNode* make_environment_light(
78 SceneNode* root, const Texture* environment_map) {
79 assert(root);
80 assert(environment_map);
81
82 Light* light = 0;
83 SceneNode* light_node = 0;
84
85 light = gfx_make_light(&(LightDesc){
86 .type = EnvironmentLightType,
87 .light = {(EnvironmentLightDesc){.environment_map = environment_map}}});
88 if (!light) {
89 goto cleanup;
90 }
91
92 light_node = gfx_make_light_node(light);
93 if (!light_node) {
94 goto cleanup;
95 }
96 gfx_set_node_parent(light_node, root);
97
98 return light_node;
99
100cleanup:
101 if (light) {
102 gfx_destroy_light(&light);
103 }
104 if (light_node) {
105 gfx_destroy_node(&light_node);
106 }
107 return 0;
108}
109
110SceneNode* gfx_setup_skyquad(
111 GfxCore* gfxcore, SceneNode* root, const Texture* environment_map) {
112 assert(gfxcore);
113 assert(root);
114 assert(environment_map);
115
116 SceneObject* skyquad_object = 0;
117 SceneNode* object_node = 0;
118 SceneNode* light_node = 0;
119
120 // Create the skyquad object.
121 skyquad_object = gfx_make_skyquad(gfxcore, environment_map);
122 if (!skyquad_object) {
123 goto cleanup;
124 }
125
126 // Create an object node to render the skyquad in the background.
127 object_node = gfx_make_object_node(skyquad_object);
128 if (!object_node) {
129 goto cleanup;
130 }
131 gfx_set_node_parent(object_node, root);
132
133 // Create an environment light node under which to root objects affected by
134 // the skyquad.
135 light_node = make_environment_light(root, environment_map);
136 if (!light_node) {
137 goto cleanup;
138 }
139
140 return light_node;
141
142cleanup:
143 if (skyquad_object) {
144 gfx_destroy_object(&skyquad_object);
145 }
146 if (object_node) {
147 gfx_destroy_node(&object_node);
148 }
149 if (light_node) {
150 gfx_destroy_node(&light_node);
151 }
152 return 0;
153}