aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-06-27 10:18:39 -0700
committer3gg <3gg@shellblade.net>2025-06-27 10:18:39 -0700
commitbd57f345ed9dbed1d81683e48199626de2ea9044 (patch)
tree4221f2f2a7ad2244d2e93052bd68187ec91b8ea9 /src
parent9a82ce0083437a4f9f58108b2c23b957d2249ad8 (diff)
Restructure projectHEADmain
Diffstat (limited to 'src')
-rw-r--r--src/asset/asset_cache.c252
-rw-r--r--src/asset/asset_cache.h37
-rw-r--r--src/asset/model.c1968
-rw-r--r--src/asset/model.h12
-rw-r--r--src/asset/texture.c177
-rw-r--r--src/asset/texture.h7
-rw-r--r--src/core/buffer.c85
-rw-r--r--src/core/buffer.h26
-rw-r--r--src/core/constants.h9
-rw-r--r--src/core/core.c429
-rw-r--r--src/core/core_impl.h68
-rw-r--r--src/core/framebuffer.c151
-rw-r--r--src/core/framebuffer.h15
-rw-r--r--src/core/geometry.c326
-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.c291
-rw-r--r--src/core/shader_program.h24
-rw-r--r--src/core/texture.c218
-rw-r--r--src/core/texture.h35
-rw-r--r--src/gfx.c73
-rw-r--r--src/gfx_assert.h5
-rw-r--r--src/renderer/imm_renderer.c260
-rw-r--r--src/renderer/imm_renderer_impl.h44
-rw-r--r--src/renderer/renderer.c396
-rw-r--r--src/renderer/renderer_impl.h27
-rw-r--r--src/scene/animation.c524
-rw-r--r--src/scene/animation_impl.h98
-rw-r--r--src/scene/camera.c37
-rw-r--r--src/scene/camera_impl.h12
-rw-r--r--src/scene/light.c42
-rw-r--r--src/scene/light_impl.h25
-rw-r--r--src/scene/material.c57
-rw-r--r--src/scene/material_impl.h16
-rw-r--r--src/scene/mesh.c24
-rw-r--r--src/scene/mesh_impl.h12
-rw-r--r--src/scene/model.c45
-rw-r--r--src/scene/model_impl.h17
-rw-r--r--src/scene/node.c409
-rw-r--r--src/scene/node_impl.h40
-rw-r--r--src/scene/object.c83
-rw-r--r--src/scene/object_impl.h26
-rw-r--r--src/scene/scene.c25
-rw-r--r--src/scene/scene_graph.h138
-rw-r--r--src/scene/scene_impl.h13
-rw-r--r--src/scene/scene_memory.c149
-rw-r--r--src/scene/scene_memory.h39
-rw-r--r--src/scene/types.h24
-rw-r--r--src/util/geometry.c44
-rw-r--r--src/util/ibl.c328
-rw-r--r--src/util/shader.c136
-rw-r--r--src/util/skyquad.c161
56 files changed, 7691 insertions, 0 deletions
diff --git a/src/asset/asset_cache.c b/src/asset/asset_cache.c
new file mode 100644
index 0000000..16c4d5c
--- /dev/null
+++ b/src/asset/asset_cache.c
@@ -0,0 +1,252 @@
1#include "asset_cache.h"
2
3#include "model.h"
4#include "scene/animation_impl.h"
5#include "scene/model_impl.h"
6#include "scene/node_impl.h"
7#include "scene/scene_memory.h"
8#include "texture.h"
9
10#include <gfx/asset.h>
11#include <gfx/gfx.h>
12#include <gfx/scene/node.h>
13#include <gfx_assert.h>
14
15#include <cstring.h>
16#include <error.h>
17#include <log/log.h>
18
19static void log_model_load_failure(const LoadModelCmd* cmd) {
20 assert(cmd);
21
22 switch (cmd->origin) {
23 case AssetFromFile:
24 log_error("Failed to load model: %s", mstring_cstr(&cmd->filepath));
25 break;
26 case AssetFromMemory:
27 log_error("Failed to load model: %p", cmd->data);
28 break;
29 }
30}
31
32static void log_texture_load_failure(const LoadTextureCmd* cmd) {
33 assert(cmd);
34
35 switch (cmd->origin) {
36 case AssetFromFile:
37 switch (cmd->type) {
38 case LoadTexture:
39 log_error(
40 "Failed to load texture: %s",
41 mstring_cstr(&cmd->data.texture.filepath));
42 break;
43 case LoadCubemap:
44 log_error(
45 "Failed to load cubemap texture: %s",
46 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_x));
47 break;
48 }
49 break;
50 case AssetFromMemory:
51 switch (cmd->type) {
52 case LoadTexture:
53 log_error("Failed to load texture: %p", cmd->data.texture.data);
54 break;
55 case LoadCubemap:
56 log_error(
57 "Failed to load texture: %p", cmd->data.cubemap.buffers.data_pos_x);
58 break;
59 }
60 break;
61 }
62}
63
64static Hash calc_model_hash(const LoadModelCmd* cmd) {
65 assert(cmd);
66 switch (cmd->origin) {
67 case AssetFromFile:
68 return cstring_hash(mstring_cstr(&cmd->filepath));
69 case AssetFromMemory:
70 return (Hash)cmd->data;
71 }
72 FAIL("Unhandled model asset origin");
73 return 0;
74}
75
76static Hash calc_texture_hash(const LoadTextureCmd* cmd) {
77 assert(cmd);
78 switch (cmd->origin) {
79 case AssetFromFile:
80 switch (cmd->type) {
81 case LoadTexture:
82 return cstring_hash(mstring_cstr(&cmd->data.texture.filepath));
83 case LoadCubemap:
84 return cstring_hash(
85 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_x)) ^
86 cstring_hash(
87 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_neg_x)) ^
88 cstring_hash(
89 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_y)) ^
90 cstring_hash(
91 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_neg_y)) ^
92 cstring_hash(
93 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_z)) ^
94 cstring_hash(
95 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_neg_z));
96 }
97 break;
98 case AssetFromMemory:
99 switch (cmd->type) {
100 case LoadTexture:
101 return (Hash)cmd->data.texture.data;
102 case LoadCubemap:
103 return (Hash)cmd->data.cubemap.buffers.data_pos_x ^
104 (Hash)cmd->data.cubemap.buffers.data_neg_x ^
105 (Hash)cmd->data.cubemap.buffers.data_pos_y ^
106 (Hash)cmd->data.cubemap.buffers.data_neg_y ^
107 (Hash)cmd->data.cubemap.buffers.data_pos_z ^
108 (Hash)cmd->data.cubemap.buffers.data_neg_z;
109 }
110 break;
111 }
112 FAIL("Unhandled texture asset origin");
113 return 0;
114}
115
116static Asset* lookup_cache(AssetCache* cache, Hash hash) {
117 assert(cache);
118 mempool_foreach(&cache->assets, asset, {
119 if (asset->hash == hash) {
120 return asset;
121 }
122 });
123 return 0;
124}
125
126static void log_model_cache_hit(const LoadModelCmd* cmd, Hash hash) {
127 assert(cmd);
128 switch (cmd->origin) {
129 case AssetFromFile:
130 LOGD(
131 "Found asset [%s] in cache with hash [%lu]",
132 mstring_cstr(&cmd->filepath), hash);
133 break;
134 case AssetFromMemory:
135 LOGD("Found asset [%p] in cache with hash [%lu]", cmd->data, hash);
136 break;
137 }
138}
139
140static void log_model_loaded(const LoadModelCmd* cmd) {
141 assert(cmd);
142 switch (cmd->origin) {
143 case AssetFromFile:
144 LOGD("Loaded asset from file: [%s]", mstring_cstr(&cmd->filepath));
145 break;
146 case AssetFromMemory:
147 LOGD("Loaded asset from memory: [%p]", cmd->data);
148 break;
149 }
150}
151
152static Model* clone_model(const Model* model) {
153 assert(model);
154
155 // Only the Anima needs to be cloned since everything else in the model is
156 // static.
157 //
158 // The Anima can be partially shallow-cloned. Skeletons and animations are
159 // static and can be shared with the original Anima. Other members are
160 // deep-cloned. Skeletons in particular point back to their Anima, so need to
161 // be deep-cloned.
162 const SceneNode* root = mem_get_node(model->root);
163 if (gfx_get_node_type(root) == AnimaNode) {
164 const Anima* anima = gfx_get_node_anima(root);
165 Anima* anima_copy = mem_alloc_anima();
166 *anima_copy = *anima; // Shallow copy.
167
168 SceneNode* root_copy = gfx_clone_scene_shallow(root);
169 root_copy->anima = mem_get_anima_index(anima_copy);
170 anima_copy->parent = mem_get_node_index(root_copy);
171
172 Model* copy = mem_alloc_model();
173 copy->root = mem_get_node_index(root_copy);
174 return copy;
175 } else {
176 return (Model*)model; // Static model, can't be mutated.
177 }
178}
179
180void gfx_init_asset_cache(AssetCache* cache) {
181 assert(cache);
182 mempool_make(&cache->assets);
183
184 // Allocate a dummy asset at index 0 to guarantee that no assets allocated by
185 // the caller map to index 0.
186 const Asset* dummy = mempool_alloc(&cache->assets);
187 assert(mempool_get_block_index(&cache->assets, dummy) == 0);
188}
189
190void gfx_destroy_asset_cache(AssetCache* cache) {
191 assert(cache);
192 mempool_del(&cache->assets);
193}
194
195Model* gfx_load_model(Gfx* gfx, const LoadModelCmd* cmd) {
196 assert(gfx);
197
198 AssetCache* cache = gfx_get_asset_cache(gfx);
199
200 // First search for the asset in the cache.
201 const uint64_t hash = calc_model_hash(cmd);
202 Asset* asset = lookup_cache(cache, hash);
203 if (asset) {
204 log_model_cache_hit(cmd, hash);
205 return clone_model(asset->model);
206 }
207
208 // Asset not found in the cache.
209 // Load it, insert it into the cache, and return it.
210 Model* model = gfx_model_load(gfx, cmd);
211 if (model) {
212 *(Asset*)mempool_alloc(&cache->assets) = (Asset){
213 .type = ModelAsset,
214 .hash = hash,
215 .model = model,
216 };
217 log_model_loaded(cmd);
218 return clone_model(model);
219 } else {
220 log_model_load_failure(cmd);
221 return 0;
222 }
223}
224
225const Texture* gfx_load_texture(Gfx* gfx, const LoadTextureCmd* cmd) {
226 assert(gfx);
227 assert(cmd);
228
229 AssetCache* cache = gfx_get_asset_cache(gfx);
230
231 // First search for the asset in the cache.
232 const uint64_t hash = calc_texture_hash(cmd);
233 Asset* asset = lookup_cache(cache, hash);
234 if (asset) {
235 return asset->texture;
236 }
237
238 // Asset not found in the cache.
239 // Load it, insert it into the cache, and return it.
240 GfxCore* gfxcore = gfx_get_core(gfx);
241 const Texture* texture = gfx_texture_load(gfxcore, cmd);
242 if (texture) {
243 *(Asset*)mempool_alloc(&cache->assets) = (Asset){
244 .type = TextureAsset,
245 .hash = hash,
246 .texture = texture,
247 };
248 } else {
249 log_texture_load_failure(cmd);
250 }
251 return texture;
252}
diff --git a/src/asset/asset_cache.h b/src/asset/asset_cache.h
new file mode 100644
index 0000000..b2a35ed
--- /dev/null
+++ b/src/asset/asset_cache.h
@@ -0,0 +1,37 @@
1#pragma once
2
3#include <gfx/sizes.h>
4
5#include <cstring.h>
6#include <mempool.h>
7
8typedef struct Model Model;
9typedef struct Texture Texture;
10
11typedef uint64_t Hash;
12
13typedef enum AssetType {
14 ModelAsset,
15 TextureAsset,
16} AssetType;
17
18typedef struct Asset {
19 AssetType type;
20 Hash hash;
21 union {
22 Model* model;
23 const Texture* texture;
24 };
25} Asset;
26
27DEF_MEMPOOL(asset_pool, Asset, GFX_MAX_NUM_ASSETS)
28
29typedef struct AssetCache {
30 asset_pool assets;
31} AssetCache;
32
33/// Create a new asset cache.
34void gfx_init_asset_cache(AssetCache*);
35
36/// Destroy the asset cache.
37void gfx_destroy_asset_cache(AssetCache*);
diff --git a/src/asset/model.c b/src/asset/model.c
new file mode 100644
index 0000000..25f2780
--- /dev/null
+++ b/src/asset/model.c
@@ -0,0 +1,1968 @@
1/// Loads scenes from memory and files.
2///
3/// Only the GLTF scene format is current supported.
4///
5/// ----------------------------------------------------------------------------
6/// glTF File Format Documentation
7/// ----------------------------------------------------------------------------
8///
9/// cgltf:
10/// https://github.com/jkuhlmann/cgltf
11///
12/// gltf overview:
13/// https://raw.githubusercontent.com/KhronosGroup/glTF/master/specification/2.0/figures/gltfOverview-2.0.0b.png
14///
15/// gltf spec:
16/// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md
17///
18/// Sample models:
19/// https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0
20/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Sponza/glTF/Sponza.gltf
21/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AlphaBlendModeTest/glTF/AlphaBlendModeTest.gltf
22/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/Buggy/glTF/Buggy.gltf
23/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/AntiqueCamera/glTF/AntiqueCamera.gltf
24/// https://github.com/KhronosGroup/glTF-Sample-Models/blob/master/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf
25///
26/// ----------------------------------------------------------------------------
27/// Implementation Notes
28/// ----------------------------------------------------------------------------
29///
30/// # glTF and the gfx library
31///
32/// glTF has concepts that are similar to those in the gfx library, but there
33/// isn't an exact 1-1 mapping. Concepts map as follows:
34///
35/// glTF gfx
36/// ---- ---
37/// buffer Buffer
38/// accessor + buffer view BufferView
39/// mesh primitive (geom + mat) Mesh (also geom + mat)
40/// mesh SceneObject
41/// node SceneNode
42///
43/// glTF buffers map 1-1 with gfx Buffers. glTF scenes make heavy re-use of
44/// buffers across views/accessors/meshes, so it is important to make that same
45/// re-use in the gfx library to use the data effectively and without
46/// duplication. The Sponza scene, for example, has all of its data in one giant
47/// buffer.
48///
49/// glTF accessors and buffer views are combined and mapped to gfx BufferViews.
50/// The glTF buffer view's offset/length/stride are combined with the accessor's
51/// offset, and together with the remaining information of both data structures
52/// baked into a BufferView. Internally, this information is fed into
53/// glVertexAttribPointer() calls, wrapped in a VAO (index view/accessor
54/// information is fed into glDrawElements()). This baking should not hurt
55/// re-use, at least in the OpenGL world.
56///
57/// A glTF mesh primitive contains a piece of geometry and a material. This maps
58/// directly to a gfx Mesh.
59///
60/// A glTF mesh is a list of mesh primitives. This maps nicely to a gfx
61/// SceneObject, with the only inconvenience that terminology gets a little
62/// confusing.
63///
64/// Finally, glTF nodes map directly to gfx SceneNodes. Both enforce a strict
65/// tree hierarchy; DAGs are not supported.
66///
67/// # Materials
68///
69/// glTF uses the metallic-roughness material model. However, an extension
70/// allows a scene to use the specular-glossiness model as well and cgltf
71/// supports it:
72///
73/// https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness/
74///
75/// From the docs, the specular-glossiness model can represent more materials
76/// than the metallic-roughness model, but it is also more computationally
77/// expensive. Furthermore, a material in glTF can specify parameters for both
78/// models, leaving it up to the implementation to decide which one to use.
79/// In our case, we use the specular-glosiness model if parameters for it are
80/// provided, otherwise we use the metallic-roughness model.
81
82#include "asset/model.h"
83
84#include "asset/texture.h"
85#include "gfx/core.h"
86#include "gfx/gfx.h"
87#include "gfx/scene/animation.h"
88#include "gfx/scene/camera.h"
89#include "gfx/scene/material.h"
90#include "gfx/scene/mesh.h"
91#include "gfx/scene/node.h"
92#include "gfx/scene/object.h"
93#include "gfx/scene/scene.h"
94#include "gfx/sizes.h"
95#include "gfx/util/shader.h"
96
97#include "gfx_assert.h"
98#include "scene/model_impl.h"
99
100#include "cstring.h"
101#include "error.h"
102#include "log/log.h"
103#include "math/camera.h"
104#include "math/defs.h"
105#include "math/mat4.h"
106#include "math/quat.h"
107#include "math/vec2.h"
108#include "math/vec3.h"
109
110#include "cgltf_tangents.h"
111#define CGLTF_IMPLEMENTATION
112#include "cgltf.h"
113
114#include <stdbool.h>
115#include <stdlib.h>
116
117// Taken from the GL header file.
118#define GL_NEAREST 0x2600
119#define GL_LINEAR 0x2601
120#define GL_NEAREST_MIPMAP_NEAREST 0x2700
121#define GL_LINEAR_MIPMAP_NEAREST 0x2701
122#define GL_NEAREST_MIPMAP_LINEAR 0x2702
123#define GL_LINEAR_MIPMAP_LINEAR 0x2703
124
125// Uniforms names. Must match the names in shaders.
126#define UNIFORM_BASE_COLOR_FACTOR "BaseColorFactor"
127#define UNIFORM_METALLIC_FACTOR "MetallicFactor"
128#define UNIFORM_ROUGHNESS_FACTOR "RoughnessFactor"
129#define UNIFORM_EMISSIVE_FACTOR "EmissiveFactor"
130#define UNIFORM_BASE_COLOR_TEXTURE "BaseColorTexture"
131#define UNIFORM_METALLIC_ROUGHNESS_TEXTURE "MetallicRoughnessTexture"
132#define UNIFORM_EMISSIVE_TEXTURE "EmissiveTexture"
133#define UNIFORM_AMBIENT_OCCLUSION_TEXTURE "AmbientOcclusionTexture"
134#define UNIFORM_NORMAL_MAP "NormalMap"
135
136// Shader compiler defines. Must match the names in shaders.
137#define DEFINE_HAS_TEXCOORDS "HAS_TEXCOORDS"
138#define DEFINE_HAS_NORMALS "HAS_NORMALS"
139#define DEFINE_HAS_TANGENTS "HAS_TANGENTS"
140#define DEFINE_HAS_ALBEDO_MAP "HAS_ALBEDO_MAP"
141#define DEFINE_HAS_METALLIC_ROUGHNESS_MAP "HAS_METALLIC_ROUGHNESS_MAP"
142#define DEFINE_HAS_NORMAL_MAP "HAS_NORMAL_MAP"
143#define DEFINE_HAS_OCCLUSION_MAP "HAS_OCCLUSION_MAP"
144#define DEFINE_HAS_EMISSIVE_MAP "HAS_EMISSIVE_MAP"
145#define DEFINE_HAS_JOINTS "HAS_JOINTS"
146#define DEFINE_MAX_JOINTS "MAX_JOINTS"
147
148typedef enum TextureType {
149 BaseColorTexture,
150 MetallicRoughnessTexture,
151 EmissiveTexture,
152 AmbientOcclusionTexture,
153 NormalMap,
154} TextureType;
155
156/// Describes the properties of a mesh.
157/// This is used to create shader permutations.
158typedef struct MeshPermutation {
159 union {
160 struct {
161 // Vertex attributes.
162 bool has_texcoords : 1;
163 bool has_normals : 1;
164 bool has_tangents : 1;
165 bool has_joints : 1;
166 bool has_weights : 1;
167 // Textures.
168 bool has_albedo_map : 1;
169 bool has_metallic_roughness_map : 1;
170 bool has_normal_map : 1;
171 bool has_occlusion_map : 1;
172 bool has_emissive_map : 1;
173 };
174 int32_t all;
175 };
176} MeshPermutation;
177
178/// Build shader compiler defines from a mesh permutation.
179static size_t make_defines(
180 MeshPermutation perm, ShaderCompilerDefine* defines) {
181 static const char* str_true = "1";
182 size_t next = 0;
183
184#define check(field, define) \
185 if (perm.field) { \
186 defines[next].name = sstring_make(define); \
187 defines[next].value = sstring_make(str_true); \
188 next++; \
189 }
190 check(has_texcoords, DEFINE_HAS_TEXCOORDS);
191 check(has_normals, DEFINE_HAS_NORMALS);
192 check(has_tangents, DEFINE_HAS_TANGENTS);
193 check(has_joints, DEFINE_HAS_JOINTS);
194 check(has_albedo_map, DEFINE_HAS_ALBEDO_MAP);
195 check(has_metallic_roughness_map, DEFINE_HAS_METALLIC_ROUGHNESS_MAP);
196 check(has_normal_map, DEFINE_HAS_NORMAL_MAP);
197 check(has_occlusion_map, DEFINE_HAS_OCCLUSION_MAP);
198 check(has_emissive_map, DEFINE_HAS_EMISSIVE_MAP);
199
200 if (perm.has_joints) {
201 defines[next].name = sstring_make(DEFINE_MAX_JOINTS);
202 defines[next].value = sstring_itoa(GFX_MAX_NUM_JOINTS);
203 next++;
204 }
205
206 return next;
207}
208
209/// Compile a shader permutation.
210static ShaderProgram* make_shader_permutation(
211 GfxCore* gfxcore, MeshPermutation perm) {
212 LOGD(
213 "Compiling Cook-Torrance shader permutation: texcoords: %d, normals: "
214 "%d, tangents: %d, joints: %d, weights: %d, albedo map: %d, "
215 "metallic-roughness map: "
216 "%d, normal "
217 "map: %d, AO map: %d, emissive map: %d",
218 perm.has_texcoords, perm.has_normals, perm.has_tangents, perm.has_joints,
219 perm.has_weights, perm.has_albedo_map, perm.has_metallic_roughness_map,
220 perm.has_normal_map, perm.has_occlusion_map, perm.has_emissive_map);
221
222 ShaderCompilerDefine defines[GFX_MAX_SHADER_COMPILER_DEFINES];
223 const size_t num_defines = make_defines(perm, defines);
224 return gfx_make_cook_torrance_shader_perm(gfxcore, defines, num_defines);
225}
226
227/// Map a texture type to the name of the shader uniform used to access the
228/// texture.
229static const char* get_texture_uniform_name(TextureType type) {
230 switch (type) {
231 case BaseColorTexture:
232 return UNIFORM_BASE_COLOR_TEXTURE;
233 case MetallicRoughnessTexture:
234 return UNIFORM_METALLIC_ROUGHNESS_TEXTURE;
235 case EmissiveTexture:
236 return UNIFORM_EMISSIVE_TEXTURE;
237 case AmbientOcclusionTexture:
238 return UNIFORM_AMBIENT_OCCLUSION_TEXTURE;
239 case NormalMap:
240 return UNIFORM_NORMAL_MAP;
241 }
242 assert(false);
243 return 0;
244}
245
246/// Map a glTF primitive type to a gfx primitive type.
247static PrimitiveType from_gltf_primitive_type(cgltf_primitive_type type) {
248 switch (type) {
249 case cgltf_primitive_type_triangles:
250 return Triangles;
251 case cgltf_primitive_type_triangle_fan:
252 return TriangleFan;
253 case cgltf_primitive_type_triangle_strip:
254 return TriangleStrip;
255 // Not yet implemented.
256 case cgltf_primitive_type_lines:
257 case cgltf_primitive_type_line_loop:
258 case cgltf_primitive_type_line_strip:
259 case cgltf_primitive_type_points:
260 break;
261 }
262 LOGE("Unsupported primitive type: %d", type);
263 assert(false);
264 return 0;
265}
266
267/// Map a glTF animation path type to its Gfx equivalent.
268static ChannelType from_gltf_animation_path_type(
269 cgltf_animation_path_type type) {
270 switch (type) {
271 case cgltf_animation_path_type_translation:
272 return TranslationChannel;
273 case cgltf_animation_path_type_rotation:
274 return RotationChannel;
275 case cgltf_animation_path_type_scale:
276 return ScaleChannel;
277 case cgltf_animation_path_type_weights:
278 return WeightsChannel;
279 case cgltf_animation_path_type_invalid:
280 assert(false);
281 break;
282 }
283 assert(false);
284 return 0;
285}
286
287/// Map a glTF interpolation to its Gfx equivalent.
288static AnimationInterpolation from_gltf_interpolation_type(
289 cgltf_interpolation_type type) {
290 switch (type) {
291 case cgltf_interpolation_type_linear:
292 return LinearInterpolation;
293 case cgltf_interpolation_type_step:
294 return StepInterpolation;
295 case cgltf_interpolation_type_cubic_spline:
296 return CubicSplineInterpolation;
297 }
298 assert(false);
299 return 0;
300}
301
302/// Return the component's size in bytes.
303static cgltf_size get_component_size(cgltf_component_type type) {
304 switch (type) {
305 case cgltf_component_type_r_8:
306 return 1;
307 case cgltf_component_type_r_8u:
308 return 1;
309 case cgltf_component_type_r_16:
310 return 2;
311 case cgltf_component_type_r_16u:
312 return 2;
313 case cgltf_component_type_r_32u:
314 return 4;
315 case cgltf_component_type_r_32f:
316 return 4;
317 case cgltf_component_type_invalid:
318 assert(false);
319 break;
320 }
321 assert(false);
322 return 0;
323}
324
325/// Return the number dimensionality of the given data type.
326int get_num_dimensions(cgltf_type type) {
327 switch (type) {
328 case cgltf_type_scalar:
329 return 1;
330 case cgltf_type_vec2:
331 return 2;
332 case cgltf_type_vec3:
333 return 3;
334 case cgltf_type_vec4:
335 return 4;
336 case cgltf_type_mat2:
337 return 4; // 2x2
338 case cgltf_type_mat3:
339 return 9; // 3x3
340 case cgltf_type_mat4:
341 return 16; // 4x4
342 case cgltf_type_invalid:
343 FAIL();
344 break;
345 }
346 FAIL();
347 return 0;
348}
349
350/// Read an int64 from the given data pointer and accessor.
351/// The largest integer in glTF is u32, so we can fit all integers in an int64.
352static int64_t read_int(const void* component, const cgltf_accessor* accessor) {
353 assert(component);
354 assert(accessor);
355
356 switch (accessor->component_type) {
357 case cgltf_component_type_r_8: {
358 const int8_t c = *((int8_t*)component);
359 return c;
360 }
361 case cgltf_component_type_r_8u: {
362 const uint8_t c = *((uint8_t*)component);
363 return c;
364 }
365 case cgltf_component_type_r_16: {
366 const int16_t c = *((int16_t*)component);
367 return c;
368 }
369 case cgltf_component_type_r_16u: {
370 const uint16_t c = *((uint16_t*)component);
371 return c;
372 }
373 case cgltf_component_type_r_32u: {
374 const uint32_t c = *((uint32_t*)component);
375 return c;
376 }
377 case cgltf_component_type_r_32f: {
378 const float c = *((float*)component);
379 return (int64_t)c;
380 }
381 case cgltf_component_type_invalid:
382 FAIL();
383 break;
384 }
385 FAIL();
386 return 0;
387}
388
389/// Read a float from the given data pointer and accessor.
390///
391/// This function uses the normalization equations from the spec. See the
392/// animation section:
393///
394/// https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#animations
395static float read_float(const void* component, const cgltf_accessor* accessor) {
396 assert(component);
397 assert(accessor);
398
399 switch (accessor->component_type) {
400 case cgltf_component_type_r_8: {
401 // assert(accessor->normalized);
402 const int8_t c = *((int8_t*)component);
403 return max((float)c / 127.0, -1.0);
404 }
405 case cgltf_component_type_r_8u: {
406 // assert(accessor->normalized);
407 const uint8_t c = *((uint8_t*)component);
408 return (float)c / 255.0;
409 }
410 case cgltf_component_type_r_16: {
411 // assert(accessor->normalized);
412 const int16_t c = *((int16_t*)component);
413 return max((float)c / 32767.0, -1.0);
414 }
415 case cgltf_component_type_r_16u: {
416 // assert(accessor->normalized);
417 const uint16_t c = *((uint16_t*)component);
418 return (float)c / 65535.0;
419 }
420 case cgltf_component_type_r_32u: {
421 // assert(accessor->normalized);
422 const uint32_t c = *((uint32_t*)component);
423 return (float)c / 4294967295.0;
424 }
425 case cgltf_component_type_r_32f: {
426 const float c = *((float*)component);
427 return c;
428 }
429 case cgltf_component_type_invalid:
430 FAIL();
431 break;
432 }
433 FAIL();
434 return 0;
435}
436
437typedef struct AccessorIter {
438 const cgltf_accessor* accessor;
439 const uint8_t* next_element;
440 cgltf_size comp_size; // Component size in bytes.
441 cgltf_size stride; // ELement stride in bytes.
442 cgltf_size index; // Index of the next element.
443 bool is_matrix;
444} AccessorIter;
445
446typedef struct AccessorData {
447 union {
448 struct {
449 float x, y, z, w; // Possibly normalized.
450 int64_t xi, yi, zi, wi; // Always unnormalized.
451 };
452 const float* floats;
453 };
454} AccessorData;
455
456bool accessor_iter_next(AccessorIter* iter, AccessorData* data) {
457 assert(iter);
458 assert(data);
459
460 if (iter->index < iter->accessor->count) {
461 const int dimensions = get_num_dimensions(iter->accessor->type);
462 const uint8_t* component = iter->next_element;
463
464 // So that the caller can access the element's components as an array.
465 data->floats = (const float*)component;
466
467 if (!iter->is_matrix) { // Scalar or vector.
468 // x
469 data->x = read_float(component, iter->accessor);
470 data->xi = read_int(component, iter->accessor);
471 component += iter->comp_size;
472 // y
473 if (dimensions > 1) {
474 data->y = read_float(component, iter->accessor);
475 data->yi = read_int(component, iter->accessor);
476 component += iter->comp_size;
477 }
478 // z
479 if (dimensions > 2) {
480 data->z = read_float(component, iter->accessor);
481 data->zi = read_int(component, iter->accessor);
482 component += iter->comp_size;
483 }
484 // w
485 if (dimensions > 3) {
486 data->w = read_float(component, iter->accessor);
487 data->wi = read_int(component, iter->accessor);
488 component += iter->comp_size;
489 }
490 }
491
492 iter->next_element += iter->stride;
493 iter->index++;
494 return true;
495 }
496
497 return false;
498}
499
500AccessorIter make_accessor_iter(const cgltf_accessor* accessor) {
501 assert(accessor);
502
503 const bool is_matrix = (accessor->type == cgltf_type_mat2) ||
504 (accessor->type == cgltf_type_mat3) ||
505 (accessor->type == cgltf_type_mat4);
506
507 const int dimensions = get_num_dimensions(accessor->type);
508 assert(
509 ((dimensions == 1) && (accessor->type == cgltf_type_scalar)) ||
510 ((dimensions == 2) && (accessor->type == cgltf_type_vec2)) ||
511 ((dimensions == 3) && (accessor->type == cgltf_type_vec3)) ||
512 ((dimensions == 4) && (accessor->type == cgltf_type_vec4)) ||
513 ((dimensions == 4) && (accessor->type == cgltf_type_mat2)) ||
514 ((dimensions == 9) && (accessor->type == cgltf_type_mat3)) ||
515 ((dimensions == 16) && (accessor->type == cgltf_type_mat4)));
516
517 const cgltf_buffer_view* view = accessor->buffer_view;
518 const cgltf_buffer* buffer = view->buffer;
519 const cgltf_size offset = accessor->offset + view->offset;
520 const uint8_t* bytes = (const uint8_t*)buffer->data + offset;
521 // Component size in bytes.
522 const cgltf_size comp_size = get_component_size(accessor->component_type);
523 // Element size in bytes.
524 const cgltf_size elem_size = dimensions * comp_size;
525 // Stride in bytes. If the view stride is 0, then the elements are tightly
526 // packed.
527 const cgltf_size stride = view->stride != 0 ? view->stride : elem_size;
528
529 // There isn't an accessor stride in the spec, but cgltf still specifies one.
530 assert(accessor->stride == elem_size);
531
532 // Accessor data must fit inside the view.
533 assert(accessor->offset + (accessor->count * accessor->stride) <= view->size);
534
535 // Accessor data must fit inside the buffer.
536 assert(
537 (offset + (accessor->count * elem_size) +
538 ((accessor->count - 1) * view->stride)) <= buffer->size);
539
540 return (AccessorIter){
541 .accessor = accessor,
542 .next_element = bytes,
543 .comp_size = comp_size,
544 .stride = stride,
545 .index = 0,
546 .is_matrix = is_matrix,
547 };
548}
549
550/// Return the total number of primitives in the scene. Each mesh may contain
551/// multiple primitives.
552///
553/// Note that this function scans all of the scenes in the glTF data.
554static size_t get_total_primitives(const cgltf_data* data) {
555 size_t total = 0;
556 for (cgltf_size i = 0; i < data->meshes_count; ++i) {
557 total += data->meshes[i].primitives_count;
558 }
559 return total;
560}
561
562/// Load all buffers from the glTF scene.
563///
564/// If buffer data is loaded from memory, set filepath = null.
565///
566/// Return an array of Buffers such that the index of each glTF buffer in the
567/// original array matches the same Buffer in the resulting array.
568///
569/// TODO: There is no need to load the inverse bind matrices buffer into the
570/// GPU. Might need to lazily load buffers.
571static bool load_buffers(
572 const cgltf_data* data, GfxCore* gfxcore, Buffer** buffers) {
573 assert(data);
574 assert(gfxcore);
575 assert(buffers);
576
577 for (cgltf_size i = 0; i < data->buffers_count; ++i) {
578 const cgltf_buffer* buffer = &data->buffers[i];
579 assert(buffer->data);
580 buffers[i] = gfx_make_buffer(
581 gfxcore, &(BufferDesc){
582 .usage = BufferStatic,
583 .type = BufferUntyped,
584 .data.data = buffer->data,
585 .data.count = buffer->size});
586 if (!buffers[i]) {
587 return false;
588 }
589 }
590
591 return true;
592}
593
594/// Load tangent buffers.
595static bool load_tangent_buffers(
596 const cgltfTangentBuffer* cgltf_tangent_buffers,
597 cgltf_size num_tangent_buffers, GfxCore* gfxcore,
598 Buffer** tangent_buffers) {
599 assert(cgltf_tangent_buffers);
600 assert(gfxcore);
601 assert(tangent_buffers);
602
603 for (cgltf_size i = 0; i < num_tangent_buffers; ++i) {
604 const cgltfTangentBuffer* buffer = &cgltf_tangent_buffers[i];
605 assert(buffer->data);
606 tangent_buffers[i] = gfx_make_buffer(
607 gfxcore, &(BufferDesc){
608 .usage = BufferStatic,
609 .type = BufferUntyped,
610 .data.data = buffer->data,
611 .data.count = buffer->size_bytes});
612 if (!tangent_buffers[i]) {
613 return false;
614 }
615 }
616
617 return true;
618}
619
620/// Lazily load all textures from the glTF scene.
621///
622/// Colour textures like albedo are in sRGB colour space. Non-colour textures
623/// like normal maps are in linear space (e.g. DamagedHelmet sample). Since we
624/// don't know how the texture is going to be used at this point, we can't tell
625/// what colour space it should be loaded in (ideally this would be part of the
626/// image file format, but not all formats specify colour space.) Therefore, we
627/// load the textures lazily and don't actually commit them to GPU memory until
628/// we know their colour space when loading glTF materials.
629///
630/// Return an array of LoadTextureCmds such that the index of each cmd matches
631/// the index of each glTF texture in the scene.
632static void load_textures_lazy(
633 const cgltf_data* data, GfxCore* gfxcore, const char* directory,
634 LoadTextureCmd* load_texture_cmds) {
635 assert(data);
636 assert(gfxcore);
637 assert(load_texture_cmds);
638
639 for (cgltf_size i = 0; i < data->textures_count; ++i) {
640 const cgltf_texture* texture = &data->textures[i];
641 const cgltf_image* image = texture->image;
642 const cgltf_sampler* sampler = texture->sampler;
643
644 // glTF models might not specify a sampler. In such case, the client can
645 // pick its own defaults.
646 // https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#samplers
647 bool mipmaps = true;
648 TextureFiltering filtering = LinearFiltering;
649 TextureWrapping wrap = Repeat;
650
651 if (sampler) {
652 // The gfx library does not distinguish between sampling the texture and
653 // combining the mipmap levels.
654 const cgltf_int filter =
655 sampler->min_filter == 0 ? sampler->mag_filter : sampler->min_filter;
656
657 switch (filter) {
658 case GL_NEAREST_MIPMAP_NEAREST:
659 mipmaps = true;
660 filtering = NearestFiltering;
661 break;
662 case GL_NEAREST_MIPMAP_LINEAR:
663 case GL_LINEAR_MIPMAP_NEAREST:
664 case GL_LINEAR_MIPMAP_LINEAR:
665 mipmaps = true;
666 filtering = LinearFiltering;
667 break;
668 case GL_NEAREST:
669 filtering = NearestFiltering;
670 break;
671 case GL_LINEAR:
672 filtering = LinearFiltering;
673 break;
674 default:
675 break;
676 }
677 }
678
679 // Currently only supporting loading textures from files.
680 assert(image->uri);
681 assert(directory);
682 mstring fullpath =
683 mstring_concat_path(mstring_make(directory), mstring_make(image->uri));
684
685 load_texture_cmds[i] = (LoadTextureCmd){
686 .origin = AssetFromFile,
687 .type = LoadTexture,
688 .colour_space = sRGB,
689 .filtering = filtering,
690 .wrap = wrap,
691 .mipmaps = mipmaps,
692 .data.texture.filepath = fullpath};
693 }
694}
695
696/// Load a texture uniform.
697///
698/// This determines a texture's colour space based on its intended use, loads
699/// the texture, and then defines the sampler shader uniform.
700static bool load_texture_and_uniform(
701 const cgltf_data* data, Gfx* gfx, const cgltf_texture_view* texture_view,
702 TextureType texture_type, const Texture** textures,
703 LoadTextureCmd* load_texture_cmds, int* next_uniform, MaterialDesc* desc) {
704 assert(data);
705 assert(gfx);
706 assert(texture_view);
707 assert(textures);
708 assert(next_uniform);
709 assert(desc);
710 assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
711
712 const size_t texture_index = texture_view->texture - data->textures;
713 assert(texture_index < data->textures_count);
714
715 // Here we are assuming that if a texture is re-used, it is re-used with the
716 // same texture view. This should be fine because, e.g., a normal map would
717 // not be used as albedo and vice versa.
718 if (!textures[texture_index]) {
719 LoadTextureCmd* cmd = &load_texture_cmds[texture_index];
720 // TODO: Check for colour textures and default to LinearColourSpace instead.
721 if (texture_type == NormalMap) {
722 cmd->colour_space = LinearColourSpace;
723 }
724
725 LOGD(
726 "Load texture: %s (mipmaps: %d, filtering: %d)",
727 mstring_cstr(&cmd->data.texture.filepath), cmd->mipmaps,
728 cmd->filtering);
729
730 textures[texture_index] = gfx_load_texture(gfx, cmd);
731 if (!textures[texture_index]) {
732 log_error(
733 "Failed to load texture: %s",
734 mstring_cstr(&cmd->data.texture.filepath));
735 return false;
736 }
737 }
738
739 assert(*next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
740 desc->uniforms[(*next_uniform)++] = (ShaderUniform){
741 .name = sstring_make(get_texture_uniform_name(texture_type)),
742 .type = UniformTexture,
743 .value.texture = textures[texture_index]};
744
745 return true;
746}
747
748/// Load all materials from the glTF scene.
749///
750/// Return an array of Materials such that the index of each descriptor matches
751/// the index of each glTF material in the scene. Also return the number of
752/// materials and the textures used by them.
753static bool load_materials(
754 const cgltf_data* data, Gfx* gfx, LoadTextureCmd* load_texture_cmds,
755 const Texture** textures, Material** materials) {
756 assert(data);
757 assert(gfx);
758 assert(materials);
759 if (data->textures_count > 0) {
760 assert(load_texture_cmds);
761 assert(textures);
762 }
763
764 for (cgltf_size i = 0; i < data->materials_count; ++i) {
765 const cgltf_material* mat = &data->materials[i];
766
767 int next_uniform = 0;
768 MaterialDesc desc = {0};
769
770 // TODO: specular/glossiness and other material parameters.
771 if (mat->has_pbr_metallic_roughness) {
772 const cgltf_pbr_metallic_roughness* pbr = &mat->pbr_metallic_roughness;
773
774 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
775 desc.uniforms[next_uniform++] = (ShaderUniform){
776 .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR),
777 .type = UniformVec4,
778 .value.vec4 = vec4_from_array(pbr->base_color_factor)};
779
780 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
781 desc.uniforms[next_uniform++] = (ShaderUniform){
782 .name = sstring_make(UNIFORM_METALLIC_FACTOR),
783 .type = UniformFloat,
784 .value.scalar = pbr->metallic_factor};
785
786 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
787 desc.uniforms[next_uniform++] = (ShaderUniform){
788 .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR),
789 .type = UniformFloat,
790 .value.scalar = pbr->roughness_factor};
791
792 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
793 desc.uniforms[next_uniform++] = (ShaderUniform){
794 .name = sstring_make(UNIFORM_EMISSIVE_FACTOR),
795 .type = UniformVec3,
796 .value.vec3 = vec3_from_array(mat->emissive_factor)};
797
798 if (pbr->base_color_texture.texture) {
799 if (!load_texture_and_uniform(
800 data, gfx, &pbr->base_color_texture, BaseColorTexture, textures,
801 load_texture_cmds, &next_uniform, &desc)) {
802 return false;
803 }
804 }
805
806 if (pbr->metallic_roughness_texture.texture) {
807 if (!load_texture_and_uniform(
808 data, gfx, &pbr->metallic_roughness_texture,
809 MetallicRoughnessTexture, textures, load_texture_cmds,
810 &next_uniform, &desc)) {
811 return false;
812 }
813 }
814 }
815
816 if (mat->emissive_texture.texture) {
817 if (!load_texture_and_uniform(
818 data, gfx, &mat->emissive_texture, EmissiveTexture, textures,
819 load_texture_cmds, &next_uniform, &desc)) {
820 return false;
821 }
822 }
823
824 if (mat->occlusion_texture.texture) {
825 if (!load_texture_and_uniform(
826 data, gfx, &mat->occlusion_texture, AmbientOcclusionTexture,
827 textures, load_texture_cmds, &next_uniform, &desc)) {
828 return false;
829 }
830 }
831
832 if (mat->normal_texture.texture) {
833 if (!load_texture_and_uniform(
834 data, gfx, &mat->normal_texture, NormalMap, textures,
835 load_texture_cmds, &next_uniform, &desc)) {
836 return false;
837 }
838 }
839
840 assert(next_uniform < GFX_MAX_UNIFORMS_PER_MATERIAL);
841 desc.num_uniforms = next_uniform;
842
843 materials[i] = gfx_make_material(&desc);
844 if (!materials[i]) {
845 return false;
846 }
847 }
848
849 return true;
850}
851
852/// Create a default material for meshes that do not have a material.
853static Material* make_default_material() {
854 MaterialDesc desc = (MaterialDesc){0};
855
856 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
857 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){
858 .name = sstring_make(UNIFORM_BASE_COLOR_FACTOR),
859 .type = UniformVec4,
860 .value.vec4 = vec4_make(1, 1, 1, 1)};
861
862 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
863 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){
864 .name = sstring_make(UNIFORM_METALLIC_FACTOR),
865 .type = UniformFloat,
866 .value.scalar = 0};
867
868 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
869 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){
870 .name = sstring_make(UNIFORM_ROUGHNESS_FACTOR),
871 .type = UniformFloat,
872 .value.scalar = 1};
873
874 assert(desc.num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
875 desc.uniforms[desc.num_uniforms++] = (ShaderUniform){
876 .name = sstring_make(UNIFORM_EMISSIVE_FACTOR),
877 .type = UniformVec3,
878 .value.vec3 = vec3_make(0, 0, 0)};
879
880 return gfx_make_material(&desc);
881}
882
883/// Compute the bounding box of the vertices pointed to by the accessor.
884/// 'dim' is the dimension of the vertices (2D or 3D).
885aabb3 compute_aabb(const cgltf_accessor* accessor) {
886 aabb3 box = {0};
887 if (accessor->has_min && accessor->has_max) {
888 box = aabb3_make(
889 vec3_from_array(accessor->min), vec3_from_array(accessor->max));
890 } else {
891 AccessorIter iter = make_accessor_iter(accessor);
892 AccessorData vertex = {0};
893 cgltf_size i = 0;
894
895 while (accessor_iter_next(&iter, &vertex)) {
896 const vec3 p = vec3_make(vertex.x, vertex.y, vertex.z);
897 if (i == 0) {
898 box = aabb3_make(p, p);
899 } else {
900 box = aabb3_add(box, p);
901 }
902 ++i;
903 }
904 }
905 return box;
906}
907
908/// Load all meshes from the glTF scene.
909static bool load_meshes(
910 const cgltf_data* data, GfxCore* gfxcore, Buffer** buffers,
911 Buffer** tangent_buffers, const cgltfTangentBuffer* cgltf_tangent_buffers,
912 cgltf_size num_tangent_buffers, Material** materials,
913 ShaderProgram* const shader, size_t primitive_count, Geometry** geometries,
914 Mesh** meshes, SceneObject** scene_objects) {
915 // Walk through the mesh primitives to create Meshes. A GLTF mesh primitive
916 // has a material (Mesh) and vertex data (Geometry). A GLTF mesh maps to
917 // a SceneObject.
918 //
919 // glTF gfx
920 // ---- ---
921 // Mesh SceneObject
922 // Mesh primitive Mesh / Geometry
923 // Accessor + buffer view BufferView
924 // Buffer Buffer
925 assert(data);
926 assert(gfxcore);
927 assert(buffers);
928 assert(materials);
929 assert(geometries);
930 assert(meshes);
931 assert(scene_objects);
932 if (num_tangent_buffers > 0) {
933 assert(tangent_buffers);
934 assert(cgltf_tangent_buffers);
935 }
936
937 // Points to the next available Mesh and also the next available Geometry.
938 // There is one (Mesh, Geometry) pair per glTF mesh primitive.
939 size_t next_mesh = 0;
940
941 for (cgltf_size m = 0; m < data->meshes_count; ++m) {
942 const cgltf_mesh* mesh = &data->meshes[m];
943
944 ObjectDesc object_desc = {0};
945
946 for (cgltf_size p = 0; p < mesh->primitives_count; ++p) {
947 assert(next_mesh < primitive_count);
948 const cgltf_primitive* prim = &mesh->primitives[p];
949 const cgltf_material* mat = prim->material;
950
951 MeshPermutation perm = {0};
952 if (mat) {
953 perm.has_normal_map = mat->normal_texture.texture != 0;
954 perm.has_occlusion_map = mat->occlusion_texture.texture != 0;
955 perm.has_emissive_map = mat->emissive_texture.texture != 0;
956
957 if (mat->has_pbr_metallic_roughness) {
958 const cgltf_pbr_metallic_roughness* pbr =
959 &mat->pbr_metallic_roughness;
960 perm.has_albedo_map = pbr->base_color_texture.texture != 0;
961 perm.has_metallic_roughness_map =
962 pbr->metallic_roughness_texture.texture != 0;
963 } else {
964 // TODO: specular/glossiness and other material parameters.
965 }
966 }
967
968 GeometryDesc geometry_desc = {
969 .type = from_gltf_primitive_type(prim->type),
970 .buffer_usage = BufferStatic};
971
972 // Vertex indices.
973 if (prim->indices) {
974 const cgltf_accessor* accessor = prim->indices;
975 const cgltf_buffer_view* view = prim->indices->buffer_view;
976 const cgltf_size buffer_index = view->buffer - data->buffers;
977
978 assert(buffer_index < data->buffers_count);
979 Buffer* buffer = buffers[buffer_index];
980
981 const cgltf_size component_size =
982 get_component_size(accessor->component_type);
983 switch (component_size) {
984 case 1: {
985 BufferViewIdx8* indices = &geometry_desc.indices8;
986 // TODO: discards const qualifier.
987 indices->buffer = buffer;
988 indices->offset_bytes = accessor->offset + view->offset;
989 indices->size_bytes = view->size;
990 indices->stride_bytes = view->stride;
991 geometry_desc.num_indices = prim->indices->count;
992 break;
993 }
994 case 2: {
995 BufferViewIdx16* indices = &geometry_desc.indices16;
996 indices->buffer = buffer;
997 indices->offset_bytes = accessor->offset + view->offset;
998 indices->size_bytes = view->size;
999 indices->stride_bytes = view->stride;
1000 geometry_desc.num_indices = prim->indices->count;
1001 break;
1002 }
1003 default:
1004 // TODO: Handle 32-bit indices.
1005 assert(false);
1006 break;
1007 }
1008 }
1009
1010 // Vertex attributes.
1011 for (cgltf_size a = 0; a < prim->attributes_count; ++a) {
1012 const cgltf_attribute* attrib = &prim->attributes[a];
1013 const cgltf_accessor* accessor = attrib->data;
1014 const cgltf_buffer_view* view = accessor->buffer_view;
1015 const cgltf_size offset = accessor->offset + view->offset;
1016 const cgltf_size buffer_index = view->buffer - data->buffers;
1017
1018 assert(buffer_index < data->buffers_count);
1019 Buffer* buffer = buffers[buffer_index];
1020
1021 BufferView2d* buffer_view_2d = 0;
1022 BufferView3d* buffer_view_3d = 0;
1023 BufferView4d* buffer_view_4d = 0;
1024 BufferViewFloat* buffer_view_float = 0;
1025 BufferViewU8* buffer_view_u8 = 0;
1026 BufferViewU16* buffer_view_u16 = 0;
1027
1028 switch (attrib->type) {
1029 case cgltf_attribute_type_position: {
1030 switch (accessor->type) {
1031 case cgltf_type_vec2:
1032 assert(geometry_desc.positions3d.buffer == 0);
1033 buffer_view_2d = &geometry_desc.positions2d;
1034 geometry_desc.aabb = compute_aabb(accessor);
1035 break;
1036 case cgltf_type_vec3:
1037 assert(geometry_desc.positions2d.buffer == 0);
1038 buffer_view_3d = &geometry_desc.positions3d;
1039 geometry_desc.aabb = compute_aabb(accessor);
1040 break;
1041 default:
1042 FAIL(
1043 "Unhandled accessor type %d in vertex positions",
1044 accessor->type);
1045 assert(false);
1046 return false;
1047 }
1048 // It is assumed that meshes have positions, so there is nothing to
1049 // do for the mesh permutation in this case.
1050 break;
1051 }
1052 case cgltf_attribute_type_normal:
1053 buffer_view_3d = &geometry_desc.normals;
1054 perm.has_normals = true;
1055 break;
1056 case cgltf_attribute_type_tangent:
1057 buffer_view_4d = &geometry_desc.tangents;
1058 perm.has_tangents = true;
1059 break;
1060 case cgltf_attribute_type_texcoord:
1061 buffer_view_2d = &geometry_desc.texcoords;
1062 perm.has_texcoords = true;
1063 break;
1064 case cgltf_attribute_type_color:
1065 // TODO: Add support for color.
1066 break;
1067 case cgltf_attribute_type_joints:
1068 // Joints can be either u8 or u16.
1069 switch (accessor->component_type) {
1070 case cgltf_component_type_r_8u:
1071 buffer_view_u8 = &geometry_desc.joints.u8;
1072 break;
1073 case cgltf_component_type_r_16u:
1074 buffer_view_u16 = &geometry_desc.joints.u16;
1075 break;
1076 default:
1077 assert(false);
1078 return false;
1079 }
1080 perm.has_joints = true;
1081 break;
1082 case cgltf_attribute_type_weights:
1083 // Weights can be either u8, u16, or float.
1084 switch (accessor->component_type) {
1085 case cgltf_component_type_r_8u:
1086 buffer_view_u8 = &geometry_desc.weights.u8;
1087 break;
1088 case cgltf_component_type_r_16u:
1089 buffer_view_u16 = &geometry_desc.weights.u16;
1090 break;
1091 case cgltf_component_type_r_32f:
1092 buffer_view_float = &geometry_desc.weights.floats;
1093 break;
1094 default:
1095 assert(false);
1096 return false;
1097 }
1098 perm.has_weights = true;
1099 break;
1100 case cgltf_attribute_type_invalid:
1101 assert(false);
1102 break;
1103 }
1104
1105#define CONFIGURE_BUFFER(buf) \
1106 if (buf) { \
1107 buf->buffer = buffer; \
1108 buf->offset_bytes = offset; \
1109 buf->size_bytes = view->size; \
1110 buf->stride_bytes = view->stride; \
1111 }
1112 CONFIGURE_BUFFER(buffer_view_2d);
1113 CONFIGURE_BUFFER(buffer_view_3d);
1114 CONFIGURE_BUFFER(buffer_view_4d);
1115 CONFIGURE_BUFFER(buffer_view_u8);
1116 CONFIGURE_BUFFER(buffer_view_u16);
1117 CONFIGURE_BUFFER(buffer_view_float);
1118 } // Vertex attributes.
1119
1120 assert(
1121 (perm.has_joints && perm.has_weights) ||
1122 (!perm.has_joints && !perm.has_weights));
1123
1124 // If the mesh primitive has no tangents, see if they were computed
1125 // separately.
1126 if (!geometry_desc.tangents.buffer) {
1127 for (cgltf_size t = 0; t < num_tangent_buffers; ++t) {
1128 const cgltfTangentBuffer* cgltf_buffer = &cgltf_tangent_buffers[t];
1129
1130 if (cgltf_buffer->primitive == prim) {
1131 BufferView4d* view = &geometry_desc.tangents;
1132 view->buffer = tangent_buffers[t];
1133 view->offset_bytes = 0;
1134 view->size_bytes = cgltf_buffer->size_bytes;
1135 view->stride_bytes = 0; // Tightly packed.
1136 break;
1137 }
1138 }
1139 }
1140
1141 // Set the number of vertices in the geometry. Since a geometry can have
1142 // either 2d or 3d positions but not both, here we can perform addition
1143 // to compute the total number of vertices.
1144 geometry_desc.num_verts =
1145 (geometry_desc.positions2d.size_bytes / sizeof(vec2)) +
1146 (geometry_desc.positions3d.size_bytes / sizeof(vec3));
1147
1148#define CHECK_COUNT(buffer_view, type, num_components) \
1149 if (geometry_desc.buffer_view.buffer) { \
1150 assert( \
1151 (geometry_desc.buffer_view.size_bytes / \
1152 (num_components * sizeof(type))) == geometry_desc.num_verts); \
1153 }
1154
1155 // Check that the number of vertices is consistent across all vertex
1156 // attributes.
1157 CHECK_COUNT(normals, vec3, 1);
1158 CHECK_COUNT(tangents, vec4, 1);
1159 CHECK_COUNT(texcoords, vec2, 1);
1160 CHECK_COUNT(joints.u8, uint8_t, 4);
1161 CHECK_COUNT(joints.u16, uint16_t, 4);
1162 CHECK_COUNT(weights.u8, uint8_t, 4);
1163 CHECK_COUNT(weights.u16, uint16_t, 4);
1164 CHECK_COUNT(weights.floats, float, 4);
1165
1166 Material* material = 0;
1167 if (mat) {
1168 const cgltf_size material_index = mat - data->materials;
1169 assert(material_index < data->materials_count);
1170 material = materials[material_index];
1171 } else {
1172 // Create a default material for meshes that do not specify one.
1173 material = make_default_material();
1174 }
1175 assert(material);
1176
1177 geometries[next_mesh] = gfx_make_geometry(gfxcore, &geometry_desc);
1178 if (!geometries[next_mesh]) {
1179 return false;
1180 }
1181
1182 // If the user specifies a custom shader, use that instead. Otherwise
1183 // compile a shader based on the mesh's permutation.
1184 //
1185 // Note that Gfx takes care of caching shaders and shader programs.
1186 //
1187 // Caching materials could be useful, but, provided they can share
1188 // shaders, the renderer can check later whether uniforms have the same
1189 // values. Also, changing uniforms is much faster than swapping shaders,
1190 // so shader caching is the most important thing here.
1191 ShaderProgram* mesh_shader =
1192 shader ? shader : make_shader_permutation(gfxcore, perm);
1193 assert(mesh_shader);
1194
1195 meshes[next_mesh] = gfx_make_mesh(&(MeshDesc){
1196 .geometry = geometries[next_mesh],
1197 .material = material,
1198 .shader = mesh_shader});
1199
1200 if (!meshes[next_mesh]) {
1201 return false;
1202 }
1203
1204 assert(object_desc.num_meshes < GFX_MAX_NUM_MESHES);
1205 object_desc.meshes[object_desc.num_meshes] = meshes[next_mesh];
1206 object_desc.num_meshes++;
1207
1208 ++next_mesh;
1209 } // glTF mesh primitive / gfx Mesh.
1210
1211 scene_objects[m] = gfx_make_object(&object_desc);
1212 if (!scene_objects[m]) {
1213 return false;
1214 }
1215 } // glTF mesh / gfx SceneObject.
1216
1217 return true;
1218}
1219
1220/// Compute bounding boxes for the joints in the model.
1221static void compute_joint_bounding_boxes(
1222 const cgltf_data* data, size_t num_joints, JointDesc* joint_descs) {
1223 assert(data);
1224 assert(joint_descs);
1225 assert(num_joints <= GFX_MAX_NUM_JOINTS);
1226
1227 // Initialize bounding boxes so that we can compute unions below.
1228 for (size_t i = 0; i < num_joints; ++i) {
1229 joint_descs[i].box = aabb3_make_empty();
1230 }
1231
1232 // Iterate over the meshes -> primitives -> vertices -> joint indices, and add
1233 // the vertex to the joint's bounding box.
1234 for (cgltf_size n = 0; n < data->nodes_count; ++n) {
1235 const cgltf_node* node = &data->nodes[n];
1236
1237 if (node->skin) {
1238 if (node->mesh) {
1239 const cgltf_mesh* mesh = node->mesh;
1240
1241 for (cgltf_size pr = 0; pr < mesh->primitives_count; ++pr) {
1242 const cgltf_primitive* prim = &mesh->primitives[pr];
1243
1244 // Find the indices of the positions and joints arrays in the
1245 // primitive's attributes.
1246 int positions_index = -1;
1247 int joints_index = -1;
1248 for (int a = 0; a < (int)prim->attributes_count; ++a) {
1249 const cgltf_attribute* attrib = &prim->attributes[a];
1250
1251 if (attrib->type == cgltf_attribute_type_position) {
1252 positions_index = a;
1253 } else if (attrib->type == cgltf_attribute_type_joints) {
1254 joints_index = a;
1255 }
1256 }
1257
1258 if ((positions_index != -1) && (joints_index != -1)) {
1259 const cgltf_accessor* positions =
1260 prim->attributes[positions_index].data;
1261 const cgltf_accessor* joints = prim->attributes[joints_index].data;
1262
1263 assert(positions->count == joints->count);
1264
1265 AccessorIter positions_iter = make_accessor_iter(positions);
1266 AccessorIter joints_iter = make_accessor_iter(joints);
1267 AccessorData position = {0}, joint = {0};
1268
1269 while (accessor_iter_next(&positions_iter, &position)) {
1270 const bool advance = accessor_iter_next(&joints_iter, &joint);
1271 assert(advance); // Counts should match.
1272
1273 const vec3 p = vec3_make(position.x, position.y, position.z);
1274 const int64_t j[4] = {joint.xi, joint.yi, joint.wi, joint.zi};
1275
1276 for (int i = 0; i < 4; ++i) {
1277 const size_t joint_index = j[i];
1278 assert((size_t)joint_index < num_joints);
1279
1280 joint_descs[joint_index].box =
1281 aabb3_add(joint_descs[joint_index].box, p);
1282 }
1283 }
1284 }
1285 }
1286 }
1287 }
1288 }
1289}
1290
1291/// Find the joint node with the smallest index across all skeletons.
1292///
1293/// The channels in glTF may target arbitrary nodes in the scene (those nodes
1294/// are the joints). However, we want to map the "base joint" (the joint/node
1295/// with the smallest index) to 0 in the AnimaDesc's joint array. We can do this
1296/// by subtracting the "base node index" from every joint index or channel
1297/// target.
1298///
1299/// There is an assumption in the animation library that joints are contiguous
1300/// anyway, so this "base joint index" works provided the joint nodes are also
1301/// contiguous in the glTF. The glTF does not guarantee this, but I think it's
1302/// a reasonable assumption that exporters write glTF files in such a way, and
1303/// Blender does appear to do so.
1304cgltf_size find_base_joint_index(const cgltf_data* data) {
1305 assert(data);
1306
1307 cgltf_size base_joint_index = (cgltf_size)-1;
1308
1309 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1310 const cgltf_skin* skin = &data->skins[s];
1311 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1312 // Joint is an index/pointer into the nodes array.
1313 const cgltf_size node_index = skin->joints[j] - data->nodes;
1314 assert(node_index < data->nodes_count);
1315 // Min.
1316 if (node_index < base_joint_index) {
1317 base_joint_index = node_index;
1318 }
1319 }
1320 }
1321
1322 return base_joint_index;
1323}
1324
1325/// Load all skins (Gfx skeletons) from the glTF scene.
1326/// Return the total number of joints.
1327static size_t load_skins(
1328 const cgltf_data* data, Buffer* const* buffers, cgltf_size base_joint_index,
1329 AnimaDesc* anima_desc) {
1330 assert(data);
1331 assert(buffers);
1332 assert(anima_desc);
1333 assert(base_joint_index < data->nodes_count);
1334
1335 // Determines whether the ith joint in the node hierarchy is a joint node.
1336 // This is then used to determine whether a joint is a root of the joint
1337 // hierarchy.
1338 bool is_joint_node[GFX_MAX_NUM_JOINTS] = {false};
1339
1340 size_t num_joints = 0;
1341
1342 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1343 const cgltf_skin* skin = &data->skins[s];
1344 const cgltf_accessor* matrices_accessor = skin->inverse_bind_matrices;
1345 assert(matrices_accessor->count == skin->joints_count);
1346
1347 num_joints += skin->joints_count;
1348 assert(num_joints < GFX_MAX_NUM_JOINTS);
1349
1350 SkeletonDesc* skeleton_desc = &anima_desc->skeletons[s];
1351 *skeleton_desc = (SkeletonDesc){.num_joints = skin->joints_count};
1352
1353 // for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1354 AccessorIter iter = make_accessor_iter(matrices_accessor);
1355 AccessorData matrix = {0};
1356 for (cgltf_size i = 0; accessor_iter_next(&iter, &matrix); ++i) {
1357 const mat4 inv_bind_matrix = mat4_from_array(matrix.floats);
1358
1359 // Joint is an index/pointer into the nodes array.
1360 const cgltf_size node_index = skin->joints[i] - data->nodes;
1361 assert(node_index < data->nodes_count);
1362
1363 const cgltf_size parent_node_index =
1364 skin->joints[i]->parent - data->nodes;
1365 assert(parent_node_index < data->nodes_count);
1366
1367 // Subtract the base index to pack the joints as tightly as possible in
1368 // the AnimaDesc.
1369 assert(node_index >= base_joint_index);
1370 const cgltf_size joint_index = node_index - base_joint_index;
1371
1372 assert(parent_node_index >= base_joint_index);
1373 const cgltf_size parent_index = parent_node_index - base_joint_index;
1374
1375 skeleton_desc->joints[i] = joint_index;
1376
1377 JointDesc* joint_desc = &anima_desc->joints[joint_index];
1378 joint_desc->parent = parent_index;
1379 joint_desc->inv_bind_matrix = inv_bind_matrix;
1380
1381 is_joint_node[joint_index] = true;
1382 };
1383
1384 // glTF may specify a "skeleton", which is the root of the skin's
1385 // (skeleton's) node hierarchy.
1386 // if (skin->skeleton) {
1387 // // cgltf_size root_index = skin->skeleton - data->nodes;
1388 // // assert(root_index <= data->nodes_count);
1389 // // root_node = nodes[root_index];
1390 // assert(false);
1391 //}
1392 }
1393
1394 // Animation library assumes that joints are contiguous.
1395 for (size_t i = 0; i < num_joints; ++i) {
1396 assert(is_joint_node[i]);
1397 }
1398
1399 // Insert the root joint.
1400 // This is the root of all skeletons. It is, specifically, the root of all
1401 // joints that do not have a parent; skins (skeletons) in glTF are not
1402 // guaranteed to have a common parent, but are generally a set of disjoint
1403 // trees.
1404 const size_t root_index = num_joints;
1405 assert(root_index < GFX_MAX_NUM_JOINTS);
1406 anima_desc->joints[root_index] = (JointDesc){.parent = INDEX_NONE};
1407 num_joints++;
1408
1409 // Make root joints point to the root joint at index N.
1410 // The root joints are the ones that have a non-joint node in the glTF as a
1411 // parent.
1412 for (size_t i = 0; i < root_index; ++i) {
1413 JointDesc* joint = &anima_desc->joints[i];
1414 if ((joint->parent >= root_index) || !is_joint_node[joint->parent]) {
1415 joint->parent = root_index;
1416 }
1417 }
1418
1419 return num_joints;
1420}
1421
1422/// Load all animations from the glTF scene.
1423static void load_animations(
1424 const cgltf_data* data, cgltf_size base_joint_index,
1425 AnimaDesc* anima_desc) {
1426 assert(data);
1427 assert(anima_desc);
1428 assert(base_joint_index < data->nodes_count);
1429 assert(data->animations_count <= GFX_MAX_NUM_ANIMATIONS);
1430
1431 for (cgltf_size a = 0; a < data->animations_count; ++a) {
1432 const cgltf_animation* animation = &data->animations[a];
1433 AnimationDesc* animation_desc = &anima_desc->animations[a];
1434
1435 *animation_desc = (AnimationDesc){
1436 .name = sstring_make(animation->name),
1437 .num_channels = animation->channels_count};
1438
1439 assert(animation->channels_count <= GFX_MAX_NUM_CHANNELS);
1440 for (cgltf_size c = 0; c < animation->channels_count; ++c) {
1441 const cgltf_animation_channel* channel = &animation->channels[c];
1442 ChannelDesc* channel_desc = &animation_desc->channels[c];
1443 const cgltf_animation_sampler* sampler = channel->sampler;
1444
1445 const size_t target_index = channel->target_node - data->nodes;
1446 assert(target_index < data->nodes_count);
1447
1448 assert(target_index >= base_joint_index);
1449 const size_t tight_target_index = target_index - base_joint_index;
1450 assert(tight_target_index < anima_desc->num_joints);
1451
1452 *channel_desc = (ChannelDesc){
1453 .target = tight_target_index,
1454 .type = from_gltf_animation_path_type(channel->target_path),
1455 .interpolation = from_gltf_interpolation_type(sampler->interpolation),
1456 .num_keyframes = 0};
1457
1458 // Read time inputs.
1459 AccessorIter iter = make_accessor_iter(sampler->input);
1460 AccessorData input = {0};
1461 for (cgltf_size i = 0; accessor_iter_next(&iter, &input); ++i) {
1462 channel_desc->keyframes[i].time = input.x;
1463 channel_desc->num_keyframes++;
1464 }
1465
1466 // Read transform outputs.
1467 AccessorData output = {0};
1468 switch (channel->target_path) {
1469 case cgltf_animation_path_type_translation: {
1470 iter = make_accessor_iter(sampler->output);
1471 for (cgltf_size i = 0; accessor_iter_next(&iter, &output); ++i) {
1472 channel_desc->keyframes[i].translation =
1473 vec3_make(output.x, output.y, output.z);
1474 }
1475 break;
1476 }
1477 case cgltf_animation_path_type_rotation: {
1478 iter = make_accessor_iter(sampler->output);
1479 for (cgltf_size i = 0; accessor_iter_next(&iter, &output); ++i) {
1480 channel_desc->keyframes[i].rotation =
1481 qmake(output.x, output.y, output.z, output.w);
1482 }
1483 break;
1484 }
1485 default:
1486 // TODO: Handle other channel transformations.
1487 break;
1488 }
1489 }
1490 }
1491}
1492
1493/// Load all nodes from the glTF scene.
1494///
1495/// This function ignores the many scenes and default scene of the glTF spec
1496/// and instead just loads all nodes into a single gfx Scene.
1497static void load_nodes(
1498 const cgltf_data* data, SceneNode* root_node, SceneObject** objects,
1499 SceneCamera** cameras, const Anima* anima, SceneNode** nodes) {
1500 // Note that with glTF 2.0, nodes do not form a DAG / scene graph but a
1501 // disjount union of strict trees:
1502 //
1503 // "For Version 2.0 conformance, the glTF node hierarchy is not a directed
1504 // acyclic graph (DAG) or scene graph, but a disjoint union of strict trees.
1505 // That is, no node may be a direct descendant of more than one node. This
1506 // restriction is meant to simplify implementation and facilitate
1507 // conformance."
1508 //
1509 // This matches the gfx library implementation, where every node can have at
1510 // most one parent.
1511 assert(data);
1512 assert(root_node);
1513 assert(objects);
1514 assert(cameras);
1515 assert(nodes);
1516
1517 cgltf_size next_camera = 0;
1518
1519 for (cgltf_size n = 0; n < data->nodes_count; ++n) {
1520 const cgltf_node* node = &data->nodes[n];
1521
1522 // Add SceneObject, SceneCamera or Lights.
1523 // TODO: Handle lights once they are implemented in the gfx library.
1524 if (node->mesh) {
1525 const cgltf_size mesh_index = node->mesh - data->meshes;
1526 assert(mesh_index < data->meshes_count);
1527 SceneObject* object = objects[mesh_index];
1528 gfx_construct_object_node(nodes[n], object);
1529
1530 if (node->skin) {
1531 assert(anima);
1532
1533 const cgltf_size skin_index = node->skin - data->skins;
1534 assert(skin_index < data->skins_count);
1535 const Skeleton* skeleton = gfx_get_anima_skeleton(anima, skin_index);
1536 gfx_set_object_skeleton(object, skeleton);
1537 }
1538 } else if (node->camera) {
1539 assert(next_camera < data->cameras_count);
1540
1541 Camera camera;
1542 const cgltf_camera* cam = node->camera;
1543
1544 // TODO: We could define a function load_cameras() the same way we load
1545 // every mesh and then remove this ad-hoc loading of cameras here, as well
1546 // as remove 'next_camera'.
1547 switch (cam->type) {
1548 case cgltf_camera_type_orthographic:
1549 camera = camera_orthographic(
1550 0, cam->data.orthographic.xmag, 0, cam->data.orthographic.ymag,
1551 cam->data.orthographic.znear, cam->data.orthographic.zfar);
1552 break;
1553 case cgltf_camera_type_perspective:
1554 camera = camera_perspective(
1555 cam->data.perspective.yfov, cam->data.perspective.aspect_ratio,
1556 cam->data.perspective.znear, cam->data.perspective.zfar);
1557 break;
1558 case cgltf_camera_type_invalid:
1559 break;
1560 }
1561
1562 gfx_set_camera_camera(cameras[next_camera], &camera);
1563 gfx_construct_camera_node(nodes[n], cameras[next_camera]);
1564 ++next_camera;
1565 } else {
1566 // TODO: implementation for missing node types.
1567 // These nodes currently default to logical nodes.
1568 }
1569 assert(nodes[n]);
1570
1571 // Set transform.
1572 mat4 transform;
1573 if (node->has_matrix) {
1574 transform = mat4_from_array(node->matrix);
1575 } else {
1576 transform = mat4_id();
1577 if (node->has_scale) {
1578 const mat4 scale = mat4_scale(vec3_from_array(node->scale));
1579 transform = mat4_mul(transform, scale);
1580 }
1581 if (node->has_rotation) {
1582 const quat q = quat_from_array(node->rotation);
1583 const mat4 rotate = mat4_from_quat(q);
1584 transform = mat4_mul(transform, rotate);
1585 }
1586 if (node->has_translation) {
1587 const mat4 translate =
1588 mat4_translate(vec3_from_array(node->translation));
1589 transform = mat4_mul(translate, transform);
1590 }
1591 }
1592 gfx_set_node_transform(nodes[n], &transform);
1593
1594 // If this is a top-level node in the glTF scene, set its parent to the
1595 // given root node.
1596 if (!node->parent) {
1597 gfx_set_node_parent(nodes[n], root_node);
1598 } else {
1599 const cgltf_size parent_index = node->parent - data->nodes;
1600 assert(parent_index < data->nodes_count);
1601 SceneNode* parent = nodes[parent_index];
1602 assert(parent);
1603 gfx_set_node_parent(nodes[n], parent);
1604 }
1605 } // SceneNode.
1606}
1607
1608/// Remove joint nodes from the Gfx Scene.
1609///
1610/// Joint nodes are not needed because joints are packed into the Anima.
1611static void remove_joint_nodes(
1612 const cgltf_data* data, SceneNode** scene_nodes) {
1613 assert(data);
1614 assert(scene_nodes);
1615
1616 // This works assuming the joint nodes are contiguous. Contiguity is checked
1617 // when loading skins. See load_skins().
1618 size_t min_joint_index = (size_t)-1;
1619 size_t max_joint_index = 0;
1620
1621 // First get the minimum and maximum indices of all joint nodes.
1622 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1623 const cgltf_skin* skin = &data->skins[s];
1624
1625 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1626 // Joint is an index/pointer into the nodes array.
1627 const cgltf_size joint_index = skin->joints[j] - data->nodes;
1628 assert(joint_index < data->nodes_count);
1629
1630 if (joint_index < min_joint_index) {
1631 min_joint_index = joint_index;
1632 }
1633 if (joint_index > max_joint_index) {
1634 max_joint_index = joint_index;
1635 }
1636 }
1637 }
1638
1639 assert(min_joint_index < data->nodes_count);
1640 assert(max_joint_index < data->nodes_count);
1641
1642 // Now walk over the joint nodes. If a joint's parent is itself not a joint
1643 // node, then that joint is a root of a joint hierarchy (skins in glTF may
1644 // have multiple roots). In such case, delete the root joint recursively.
1645 for (cgltf_size s = 0; s < data->skins_count; ++s) {
1646 const cgltf_skin* skin = &data->skins[s];
1647
1648 for (cgltf_size j = 0; j < skin->joints_count; ++j) {
1649 // Joint is an index/pointer into the nodes array.
1650 const cgltf_size joint_index = skin->joints[j] - data->nodes;
1651 assert(joint_index < data->nodes_count);
1652
1653 const cgltf_node* joint = &data->nodes[joint_index];
1654
1655 // Parent node index.
1656 const cgltf_size parent_index = joint->parent - data->nodes;
1657 assert(parent_index < data->nodes_count);
1658
1659 // If the parent is not a joint node, recursively delete this joint node.
1660 if ((parent_index < min_joint_index) ||
1661 (parent_index > max_joint_index)) {
1662 gfx_destroy_node(&scene_nodes[joint_index]);
1663 }
1664 }
1665 }
1666}
1667
1668/// Load all scenes from the glTF file.
1669///
1670/// If the scene is loaded from memory, set filepath = null.
1671///
1672/// This function ignores the many scenes and default scene of the glTF spec
1673/// and instead just loads all scenes into a single Gfx Scene.
1674static Model* load_scene(
1675 cgltf_data* data, Gfx* gfx, const mstring* filepath, ShaderProgram* shader,
1676 const cgltfTangentBuffer* cgltf_tangent_buffers,
1677 cgltf_size num_tangent_buffers) {
1678 // In a GLTF scene, buffers can be shared among meshes, meshes among nodes,
1679 // etc. Each object is referenced by its index in the relevant array. Here we
1680 // do a button-up construction, first allocating our own graphics objects in
1681 // the same quantities and then re-using the GLTF indices to index these
1682 // arrays.
1683 //
1684 // For simplicity, this function also handles all of the cleanup. Arrays are
1685 // allocated up front, and the helper functions construct their elements. If
1686 // an error is encountered, the helper functions can simply return and this
1687 // function cleans up any intermediate objects that had been created up until
1688 // the point of failure.
1689 //
1690 // Loading animation data:
1691 // - Buffers with animation sampler data need to stay on the CPU, not
1692 // uploaded to the GPU. We could try to implement GPU animation at a later
1693 // stage.
1694 assert(data);
1695 assert(gfx);
1696 assert(filepath);
1697 assert((num_tangent_buffers == 0) || (cgltf_tangent_buffers != 0));
1698
1699 bool success = false;
1700
1701 GfxCore* gfxcore = gfx_get_core(gfx);
1702 const size_t primitive_count = get_total_primitives(data);
1703
1704 const mstring directory = mstring_dirname(*filepath);
1705 LOGD("Filepath: %s", mstring_cstr(filepath));
1706 LOGD("Directory: %s", mstring_cstr(&directory));
1707
1708 Buffer** tangent_buffers = 0;
1709 Buffer** buffers = 0;
1710 LoadTextureCmd* load_texture_cmds = 0;
1711 const Texture** textures = 0; // Textures are owned by asset cache.
1712 Material** materials = 0;
1713 Geometry** geometries = 0;
1714 Mesh** meshes = 0;
1715 AnimaDesc* anima_desc = 0;
1716 SceneObject** scene_objects = 0;
1717 SceneCamera** scene_cameras = 0;
1718 SceneNode** scene_nodes = 0;
1719 Anima* anima = 0;
1720 SceneNode* root_node = 0;
1721 Model* model = 0;
1722
1723 tangent_buffers = calloc(num_tangent_buffers, sizeof(Buffer*));
1724 buffers = calloc(data->buffers_count, sizeof(Buffer*));
1725 textures = calloc(data->textures_count, sizeof(Texture*));
1726 materials = calloc(data->materials_count, sizeof(Material*));
1727 geometries = calloc(primitive_count, sizeof(Geometry*));
1728 meshes = calloc(primitive_count, sizeof(Mesh*));
1729 scene_objects = calloc(data->meshes_count, sizeof(SceneObject*));
1730 scene_cameras = calloc(data->cameras_count, sizeof(SceneCamera**));
1731 scene_nodes = calloc(data->nodes_count, sizeof(SceneNode**));
1732 // A glTF scene does not necessarily have textures. Materials can be given
1733 // as constants, for example.
1734 if (data->textures_count > 0) {
1735 load_texture_cmds = calloc(data->textures_count, sizeof(LoadTextureCmd));
1736 }
1737
1738 if (!buffers || !tangent_buffers ||
1739 ((data->textures_count > 0) && !load_texture_cmds) || !textures ||
1740 !materials || !geometries || !meshes || !scene_objects ||
1741 !scene_cameras || !scene_nodes) {
1742 goto cleanup;
1743 }
1744
1745 if ((num_tangent_buffers > 0) &&
1746 !load_tangent_buffers(
1747 cgltf_tangent_buffers, num_tangent_buffers, gfxcore,
1748 tangent_buffers)) {
1749 goto cleanup;
1750 }
1751
1752 if (!load_buffers(data, gfxcore, buffers)) {
1753 goto cleanup;
1754 }
1755
1756 if (data->textures_count > 0) {
1757 load_textures_lazy(
1758 data, gfxcore, mstring_cstr(&directory), load_texture_cmds);
1759 }
1760
1761 if (!load_materials(data, gfx, load_texture_cmds, textures, materials)) {
1762 goto cleanup;
1763 }
1764
1765 if (!load_meshes(
1766 data, gfxcore, buffers, tangent_buffers, cgltf_tangent_buffers,
1767 num_tangent_buffers, materials, shader, primitive_count, geometries,
1768 meshes, scene_objects)) {
1769 goto cleanup;
1770 }
1771
1772 // Skins refer to nodes, and nodes may refer to skins. To break this circular
1773 // dependency, glTF defines skins in terms of node indices. We could do the
1774 // same if Gfx allowed allocating nodes contiguously in memory. For now,
1775 // create the nodes up front and use the indices of the array to map to the
1776 // node_idx.
1777 for (cgltf_size i = 0; i < data->nodes_count; ++i) {
1778 scene_nodes[i] = gfx_make_node();
1779 }
1780
1781 // Create the scene's root node.
1782 // This is an anima node if the scene has skins; otherwise it is a logical
1783 // node.
1784 root_node = gfx_make_node();
1785 if (data->skins_count > 0) {
1786 anima_desc = calloc(1, sizeof(AnimaDesc));
1787 if (!anima_desc) {
1788 goto cleanup;
1789 }
1790
1791 const cgltf_size base = find_base_joint_index(data);
1792
1793 anima_desc->num_skeletons = data->skins_count;
1794 anima_desc->num_animations = data->animations_count;
1795 anima_desc->num_joints = load_skins(data, buffers, base, anima_desc);
1796 load_animations(data, base, anima_desc);
1797
1798 compute_joint_bounding_boxes(
1799 data, anima_desc->num_joints, anima_desc->joints);
1800
1801 anima = gfx_make_anima(anima_desc);
1802 gfx_construct_anima_node(root_node, anima);
1803 }
1804
1805 // The root node becomes the root of all scene nodes.
1806 load_nodes(data, root_node, scene_objects, scene_cameras, anima, scene_nodes);
1807
1808 // Clean up scene nodes that correspond to joints in the glTF. These are
1809 // not needed anymore.
1810 if (data->skins_count > 0) {
1811 remove_joint_nodes(data, scene_nodes);
1812 }
1813
1814 model = gfx_make_model(root_node);
1815
1816 success = true;
1817
1818cleanup:
1819 // The arrays of resources are no longer needed. The resources themselves are
1820 // destroyed only if this function fails.
1821 if (tangent_buffers) {
1822 if (!success) {
1823 for (cgltf_size i = 0; i < num_tangent_buffers; ++i) {
1824 if (tangent_buffers[i]) {
1825 gfx_destroy_buffer(gfxcore, &tangent_buffers[i]);
1826 }
1827 }
1828 }
1829 free(tangent_buffers);
1830 }
1831 if (buffers) {
1832 if (!success) {
1833 for (cgltf_size i = 0; i < data->buffers_count; ++i) {
1834 if (buffers[i]) {
1835 gfx_destroy_buffer(gfxcore, &buffers[i]);
1836 }
1837 }
1838 }
1839 free(buffers);
1840 }
1841 if (load_texture_cmds) {
1842 free(load_texture_cmds);
1843 }
1844 if (textures) {
1845 free(textures);
1846 }
1847 if (materials) {
1848 if (!success) {
1849 for (cgltf_size i = 0; i < data->materials_count; ++i) {
1850 if (materials[i]) {
1851 gfx_destroy_material(&materials[i]);
1852 }
1853 }
1854 }
1855 free(materials);
1856 }
1857 if (geometries) {
1858 if (!success) {
1859 for (size_t i = 0; i < primitive_count; ++i) {
1860 if (geometries[i]) {
1861 gfx_destroy_geometry(gfxcore, &geometries[i]);
1862 }
1863 }
1864 }
1865 free(geometries);
1866 }
1867 if (meshes) {
1868 if (!success) {
1869 for (size_t i = 0; i < primitive_count; ++i) {
1870 if (meshes[i]) {
1871 gfx_destroy_mesh(&meshes[i]);
1872 }
1873 }
1874 }
1875 free(meshes);
1876 }
1877 if (anima_desc) {
1878 free(anima_desc);
1879 }
1880 if (scene_objects) {
1881 if (!success) {
1882 for (cgltf_size i = 0; i < data->meshes_count; ++i) {
1883 if (scene_objects[i]) {
1884 gfx_destroy_object(&scene_objects[i]);
1885 }
1886 }
1887 }
1888 free(scene_objects);
1889 }
1890 if (scene_cameras) {
1891 if (!success) {
1892 for (cgltf_size i = 0; i < data->cameras_count; ++i) {
1893 if (scene_cameras[i]) {
1894 gfx_destroy_camera(&scene_cameras[i]);
1895 }
1896 }
1897 }
1898 free(scene_cameras);
1899 }
1900 if (scene_nodes) {
1901 if (!success) {
1902 for (cgltf_size i = 0; i < data->nodes_count; ++i) {
1903 if (scene_nodes[i]) {
1904 gfx_destroy_node(&scene_nodes[i]);
1905 }
1906 }
1907 }
1908 free(scene_nodes);
1909 }
1910 if (!success) {
1911 if (root_node) {
1912 gfx_destroy_node(&root_node); // Node owns the anima.
1913 } else if (anima) {
1914 gfx_destroy_anima(&anima);
1915 }
1916 }
1917 return model;
1918}
1919
1920Model* gfx_model_load(Gfx* gfx, const LoadModelCmd* cmd) {
1921 assert(gfx);
1922 assert(cmd);
1923
1924 Model* model = 0;
1925
1926 cgltf_options options = {0};
1927 cgltf_data* data = NULL;
1928 cgltfTangentBuffer* tangent_buffers = 0;
1929
1930 cgltf_result result;
1931 switch (cmd->origin) {
1932 case AssetFromFile:
1933 result = cgltf_parse_file(&options, mstring_cstr(&cmd->filepath), &data);
1934 break;
1935 case AssetFromMemory:
1936 result = cgltf_parse(&options, cmd->data, cmd->size_bytes, &data);
1937 break;
1938 }
1939 if (result != cgltf_result_success) {
1940 goto cleanup;
1941 }
1942
1943 if (cmd->origin == AssetFromFile) {
1944 // Must call cgltf_load_buffers() to load buffer data.
1945 result = cgltf_load_buffers(&options, data, mstring_cstr(&cmd->filepath));
1946 if (result != cgltf_result_success) {
1947 goto cleanup;
1948 }
1949 }
1950
1951 // Compute tangents for normal-mapped models that are missing them.
1952 cgltf_size num_tangent_buffers = 0;
1953 cgltf_compute_tangents(
1954 &options, data, &tangent_buffers, &num_tangent_buffers);
1955
1956 model = load_scene(
1957 data, gfx, &cmd->filepath, cmd->shader, tangent_buffers,
1958 num_tangent_buffers);
1959
1960cleanup:
1961 if (data) {
1962 cgltf_free(data);
1963 }
1964 if (tangent_buffers) {
1965 free(tangent_buffers);
1966 }
1967 return model;
1968}
diff --git a/src/asset/model.h b/src/asset/model.h
new file mode 100644
index 0000000..d6399b1
--- /dev/null
+++ b/src/asset/model.h
@@ -0,0 +1,12 @@
1/// Load scene files.
2#pragma once
3
4#include <gfx/asset.h>
5
6typedef struct Gfx Gfx;
7typedef struct Model Model;
8
9/// Load a model.
10///
11/// Currently only supports the GLTF format.
12Model* gfx_model_load(Gfx*, const LoadModelCmd*);
diff --git a/src/asset/texture.c b/src/asset/texture.c
new file mode 100644
index 0000000..c790394
--- /dev/null
+++ b/src/asset/texture.c
@@ -0,0 +1,177 @@
1#include "texture.h"
2
3#include "gfx/core.h"
4
5#include "error.h"
6
7#define STB_IMAGE_IMPLEMENTATION
8#include "stb_image.h"
9
10#include <assert.h>
11
12static void flip_horizontally(
13 unsigned char* pixels, int width, int height, int components) {
14 assert(pixels);
15
16 for (int y = 0; y < height; ++y) {
17 for (int x = 0; x < width / 2; ++x) {
18 unsigned char* p1 = &pixels[(y * width + x) * components];
19 unsigned char* p2 = &pixels[(y * width + (width - x - 1)) * components];
20
21 for (int c = 0; c < components; ++c) {
22 unsigned char tmp = *p1;
23 *p1 = *p2;
24 *p2 = tmp;
25 p1++;
26 p2++;
27 }
28 }
29 }
30}
31
32// Note that the cubemap coordinate system uses the one in RenderMan:
33//
34// https://www.khronos.org/opengl/wiki/Cubemap_Texture
35//
36// This is what happens:
37//
38// - Cubemaps follow a left-handed coordinate system. Say, +X is right, +Y is
39// up, and +Z is forward.
40// - The texture coordinate system follow's DirectX's, so +V goes down, not up
41// like it does in OpenGL.
42//
43// For this reason, we do X and Y flips when doing cubemap textures so that we
44// can sample cubemaps as if they were given in the usual OpenGL coordinate
45// system.
46Texture* gfx_texture_load(GfxCore* gfxcore, const LoadTextureCmd* cmd) {
47 assert(gfxcore);
48 assert(cmd);
49 assert(cmd->origin == AssetFromFile || cmd->origin == AssetFromMemory);
50 assert(cmd->type == LoadTexture || cmd->type == LoadCubemap);
51
52 int width, height, components, old_components;
53 unsigned char* pixels[6] = {0};
54
55 switch (cmd->origin) {
56 case AssetFromFile:
57 switch (cmd->type) {
58 case LoadTexture: {
59 const char* filepath = mstring_cstr(&cmd->data.texture.filepath);
60 stbi_set_flip_vertically_on_load(0);
61 pixels[0] = stbi_load(filepath, &width, &height, &components, 0);
62 if (!pixels[0]) {
63 log_error("Failed to load texture file: %s", filepath);
64 }
65 break;
66 }
67 case LoadCubemap:
68 for (int i = 0; i < 6; ++i) {
69 // Flip +Y and -Y textures vertically.
70 stbi_set_flip_vertically_on_load(((i == 2) || (i == 3)) ? 1 : 0);
71 const char* filepath =
72 mstring_cstr(&cmd->data.cubemap.filepaths.filepath_pos_x + i);
73 stbi_uc* image_pixels =
74 stbi_load(filepath, &width, &height, &components, 0);
75 if (!image_pixels) {
76 log_error("Failed to load texture file: %s", filepath);
77 break;
78 }
79 if (i > 0 && components != old_components) {
80 log_error("All textures in a cubemap must have the same number of "
81 "components");
82 break;
83 }
84 if ((i != 2) && (i != 3)) {
85 flip_horizontally(image_pixels, width, height, components);
86 }
87 pixels[i] = image_pixels;
88 old_components = components;
89 }
90 break;
91 }
92 break;
93 case AssetFromMemory:
94 // TODO: Load textures from memory.
95 log_error("Loading textures from memory is not yet implemented");
96 return 0;
97 }
98
99 // Error out if we failed to load a texture.
100 if (!pixels[0] ||
101 (cmd->type == LoadCubemap &&
102 (!pixels[1] || !pixels[2] || !pixels[3] || !pixels[4] || !pixels[5]))) {
103 for (int i = 0; i < 6; ++i) {
104 if (pixels[i]) {
105 stbi_image_free(pixels[i]);
106 }
107 }
108 return 0;
109 }
110
111 TextureDesc desc = (TextureDesc){0};
112 desc.width = width;
113 desc.height = height;
114
115 switch (cmd->type) {
116 case LoadTexture:
117 desc.dimension = Texture2D;
118 break;
119 case LoadCubemap:
120 desc.dimension = TextureCubeMap;
121 break;
122 }
123
124 switch (components) {
125 case 3:
126 switch (cmd->colour_space) {
127 case LinearColourSpace:
128 desc.format = TextureRGB8;
129 break;
130 case sRGB:
131 desc.format = TextureSRGB8;
132 break;
133 default:
134 log_error("Unsupported texture colour space: %d", cmd->colour_space);
135 return 0;
136 }
137 break;
138 case 4:
139 switch (cmd->colour_space) {
140 case LinearColourSpace:
141 desc.format = TextureRGBA8;
142 break;
143 case sRGB:
144 desc.format = TextureSRGBA8;
145 break;
146 default:
147 log_error("Unsupported texture colour space: %d", cmd->colour_space);
148 return 0;
149 }
150 break;
151 default:
152 log_error("Unsupported number of texture components: %d", components);
153 return 0;
154 }
155
156 desc.filtering = cmd->filtering;
157 desc.mipmaps = cmd->mipmaps;
158
159 switch (cmd->type) {
160 case LoadTexture:
161 desc.data.pixels = pixels[0];
162 break;
163 case LoadCubemap:
164 for (int i = 0; i < 6; ++i) {
165 *(&desc.data.cubemap.pixels_pos_x + i) = pixels[i];
166 }
167 break;
168 }
169
170 Texture* texture = gfx_make_texture(gfxcore, &desc);
171 for (int i = 0; i < 6; ++i) {
172 if (pixels[i]) {
173 stbi_image_free(pixels[i]);
174 }
175 }
176 return texture;
177}
diff --git a/src/asset/texture.h b/src/asset/texture.h
new file mode 100644
index 0000000..0d38bd9
--- /dev/null
+++ b/src/asset/texture.h
@@ -0,0 +1,7 @@
1/// Load textures from images.
2#pragma once
3
4#include <gfx/asset.h>
5
6/// Load a texture.
7Texture* gfx_texture_load(GfxCore*, const LoadTextureCmd*);
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..b9080f0
--- /dev/null
+++ b/src/core/buffer.h
@@ -0,0 +1,26 @@
1#pragma once
2
3#include <gfx/core.h>
4
5#include "gl_util.h"
6
7#include <math/fwd.h>
8
9#include <stdbool.h>
10#include <stddef.h>
11
12typedef struct Buffer {
13 GLuint vbo;
14 BufferType type;
15 BufferUsage usage;
16 size_t size_bytes;
17} Buffer;
18
19/// Return the buffer type size in bytes.
20size_t gfx_get_buffer_type_size_bytes(BufferType);
21
22/// Create a buffer from raw data.
23bool gfx_init_buffer(Buffer*, const BufferDesc*);
24
25/// Destroy the buffer.
26void 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..90038c6
--- /dev/null
+++ b/src/core/core.c
@@ -0,0 +1,429 @@
1#include "core_impl.h"
2
3#include "gl_util.h"
4
5// #include <log/log.h>
6
7#include <assert.h>
8
9void gfx_init_gfxcore(GfxCore* gfxcore) {
10 assert(gfxcore);
11
12 mempool_make(&gfxcore->buffers);
13 mempool_make(&gfxcore->framebuffers);
14 mempool_make(&gfxcore->geometries);
15 mempool_make(&gfxcore->renderbuffers);
16 mempool_make(&gfxcore->shaders);
17 mempool_make(&gfxcore->shader_programs);
18 mempool_make(&gfxcore->textures);
19
20 mempool_make(&gfxcore->shader_cache);
21 mempool_make(&gfxcore->program_cache);
22
23 glEnable(GL_CULL_FACE);
24 glFrontFace(GL_CCW);
25 glCullFace(GL_BACK);
26
27 glEnable(GL_DEPTH_TEST);
28
29 // Filter cubemaps across their faces to avoid seams.
30 // https://www.khronos.org/opengl/wiki/Cubemap_Texture#Seamless_cubemap
31 glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
32}
33
34// Conveniently destroy any objects that have not been destroyed by the
35// application.
36void gfx_del_gfxcore(GfxCore* gfxcore) {
37 assert(gfxcore);
38
39 mempool_foreach(&gfxcore->buffers, buffer, { gfx_del_buffer(buffer); });
40
41 mempool_foreach(&gfxcore->framebuffers, framebuffer, {
42 gfx_del_framebuffer(framebuffer);
43 });
44
45 mempool_foreach(
46 &gfxcore->geometries, geometry, { gfx_del_geometry(geometry); });
47
48 mempool_foreach(&gfxcore->renderbuffers, renderbuffer, {
49 gfx_del_renderbuffer(renderbuffer);
50 });
51
52 mempool_foreach(
53 &gfxcore->shader_programs, prog, { gfx_del_shader_program(prog); });
54
55 mempool_foreach(&gfxcore->shaders, shader, { gfx_del_shader(shader); });
56
57 mempool_foreach(&gfxcore->textures, texture, { gfx_del_texture(texture); });
58}
59
60// -----------------------------------------------------------------------------
61// Render commands.
62// -----------------------------------------------------------------------------
63
64void gfx_start_frame(GfxCore* gfxcore) {
65 assert(gfxcore);
66
67 glViewport(
68 gfxcore->viewport.x, gfxcore->viewport.y, gfxcore->viewport.width,
69 gfxcore->viewport.height);
70 glClearColor(0, 0, 0, 0);
71 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
72
73 ASSERT_GL;
74}
75
76void gfx_end_frame(GfxCore* gfxcore) {
77 assert(gfxcore);
78 ASSERT_GL;
79}
80
81void gfx_set_viewport(GfxCore* gfxcore, int x, int y, int width, int height) {
82 assert(gfxcore);
83 gfxcore->viewport =
84 (Viewport){.x = x, .y = y, .width = width, .height = height};
85}
86
87void gfx_get_viewport(
88 GfxCore* gfxcore, int* x, int* y, int* width, int* height) {
89 assert(gfxcore);
90 assert(x);
91 assert(y);
92 assert(width);
93 assert(height);
94
95 *x = gfxcore->viewport.x;
96 *y = gfxcore->viewport.y;
97 *width = gfxcore->viewport.width;
98 *height = gfxcore->viewport.height;
99}
100
101void gfx_clear(GfxCore* gfxcore, vec4 colour) {
102 assert(gfxcore);
103
104 glClearColor(colour.x, colour.y, colour.z, colour.w);
105 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
106}
107
108void gfx_set_blending(GfxCore* gfxcore, bool enable) {
109 assert(gfxcore);
110 if (enable) {
111 glEnable(GL_BLEND);
112 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
113 } else {
114 glDisable(GL_BLEND);
115 }
116}
117
118void gfx_set_depth_mask(GfxCore* gfxcore, bool enable) {
119 assert(gfxcore);
120 glDepthMask(enable ? GL_TRUE : GL_FALSE);
121}
122
123void gfx_set_culling(GfxCore* gfxcore, bool enable) {
124 assert(gfxcore);
125 if (enable) {
126 glEnable(GL_CULL_FACE);
127 } else {
128 glDisable(GL_CULL_FACE);
129 }
130}
131
132void gfx_set_polygon_offset(GfxCore* gfxcore, float scale, float bias) {
133 assert(gfxcore);
134 if ((scale != 0.0f) || (bias != 0.0f)) {
135 glEnable(GL_POLYGON_OFFSET_FILL);
136 } else {
137 glDisable(GL_POLYGON_OFFSET_FILL);
138 }
139 glPolygonOffset(scale, bias);
140}
141
142void gfx_reset_polygon_offset(GfxCore* gfxcore) {
143 assert(gfxcore);
144 glPolygonOffset(0, 0);
145 glDisable(GL_POLYGON_OFFSET_FILL);
146}
147
148// -----------------------------------------------------------------------------
149// Buffers.
150// -----------------------------------------------------------------------------
151
152Buffer* gfx_make_buffer(GfxCore* gfxcore, const BufferDesc* desc) {
153 assert(gfxcore);
154 assert(desc);
155
156 Buffer* buffer = mempool_alloc(&gfxcore->buffers);
157 if (!gfx_init_buffer(buffer, desc)) {
158 mempool_free(&gfxcore->buffers, &buffer);
159 return 0;
160 }
161 return buffer;
162}
163
164void gfx_destroy_buffer(GfxCore* gfxcore, Buffer** buffer) {
165 assert(gfxcore);
166 assert(buffer);
167 if (*buffer) {
168 gfx_del_buffer(*buffer);
169 mempool_free(&gfxcore->buffers, buffer);
170 }
171}
172
173// -----------------------------------------------------------------------------
174// Geometry.
175// -----------------------------------------------------------------------------
176
177Geometry* gfx_make_geometry(GfxCore* gfxcore, const GeometryDesc* desc) {
178 assert(gfxcore);
179 assert(desc);
180
181 Geometry* geometry = mempool_alloc(&gfxcore->geometries);
182 if (!gfx_init_geometry(geometry, gfxcore, desc)) {
183 mempool_free(&gfxcore->geometries, &geometry);
184 return 0;
185 }
186 return geometry;
187}
188
189void gfx_destroy_geometry(GfxCore* gfxcore, Geometry** geometry) {
190 assert(gfxcore);
191 assert(geometry);
192
193 if (*geometry) {
194 gfx_del_geometry(*geometry);
195 mempool_free(&gfxcore->geometries, geometry);
196 }
197}
198
199// -----------------------------------------------------------------------------
200// Textures.
201// -----------------------------------------------------------------------------
202
203Texture* gfx_make_texture(GfxCore* gfxcore, const TextureDesc* desc) {
204 assert(gfxcore);
205 assert(desc);
206
207 Texture* texture = mempool_alloc(&gfxcore->textures);
208 if (!gfx_init_texture(texture, desc)) {
209 mempool_free(&gfxcore->textures, &texture);
210 return 0;
211 }
212 return texture;
213}
214
215void gfx_destroy_texture(GfxCore* gfxcore, Texture** texture) {
216 assert(gfxcore);
217 assert(texture);
218 assert(*texture);
219
220 if (*texture) {
221 gfx_del_texture(*texture);
222 mempool_free(&gfxcore->textures, texture);
223 }
224}
225
226// -----------------------------------------------------------------------------
227// Renderbuffers.
228// -----------------------------------------------------------------------------
229
230RenderBuffer* gfx_make_renderbuffer(
231 GfxCore* gfxcore, const RenderBufferDesc* desc) {
232 assert(gfxcore);
233 assert(desc);
234
235 RenderBuffer* renderbuffer = mempool_alloc(&gfxcore->renderbuffers);
236 if (!gfx_init_renderbuffer(renderbuffer, desc)) {
237 mempool_free(&gfxcore->renderbuffers, &renderbuffer);
238 }
239 return renderbuffer;
240}
241
242void gfx_destroy_renderbuffer(GfxCore* gfxcore, RenderBuffer** renderbuffer) {
243 assert(gfxcore);
244 assert(renderbuffer);
245 assert(*renderbuffer);
246
247 if (*renderbuffer) {
248 gfx_del_renderbuffer(*renderbuffer);
249 mempool_free(&gfxcore->renderbuffers, renderbuffer);
250 }
251}
252
253// -----------------------------------------------------------------------------
254// Framebuffers.
255// -----------------------------------------------------------------------------
256
257FrameBuffer* gfx_make_framebuffer(
258 GfxCore* gfxcore, const FrameBufferDesc* desc) {
259 assert(gfxcore);
260 assert(desc);
261
262 FrameBuffer* framebuffer = mempool_alloc(&gfxcore->framebuffers);
263 if (!gfx_init_framebuffer(framebuffer, desc)) {
264 mempool_free(&gfxcore->framebuffers, &framebuffer);
265 return 0;
266 }
267 return framebuffer;
268}
269
270void gfx_destroy_framebuffer(GfxCore* gfxcore, FrameBuffer** framebuffer) {
271 assert(gfxcore);
272 assert(framebuffer);
273 assert(*framebuffer);
274
275 if (*framebuffer) {
276 gfx_del_framebuffer(*framebuffer);
277 mempool_free(&gfxcore->framebuffers, framebuffer);
278 }
279}
280
281// -----------------------------------------------------------------------------
282// Shaders.
283// -----------------------------------------------------------------------------
284
285static uint64_t hash_shader_desc(const ShaderDesc* desc) {
286 assert(desc);
287 // Note that defines may affect shader permutations, so we need to hash those
288 // as well.
289 uint64_t hash = 0;
290 for (size_t i = 0; i < desc->num_defines; ++i) {
291 const ShaderCompilerDefine* define = &desc->defines[i];
292 hash = (((hash << 13) + sstring_hash(define->name)) << 7) +
293 sstring_hash(define->value);
294 }
295 return (hash << 17) + cstring_hash(desc->code);
296}
297
298static uint64_t hash_program_desc(const ShaderProgramDesc* desc) {
299 assert(desc);
300 return ((uint64_t)desc->vertex_shader->id << 32) |
301 (uint64_t)desc->fragment_shader->id;
302}
303
304static Shader* find_cached_shader(ShaderCache* cache, uint64_t hash) {
305 assert(cache);
306 mempool_foreach(cache, entry, {
307 if (entry->hash == hash) {
308 return entry->shader;
309 }
310 });
311 return 0;
312}
313
314static ShaderProgram* find_cached_program(ProgramCache* cache, uint64_t hash) {
315 assert(cache);
316 mempool_foreach(cache, entry, {
317 if (entry->hash == hash) {
318 return entry->program;
319 }
320 });
321 return 0;
322}
323
324static ShaderCacheEntry* find_shader_cache_entry(
325 ShaderCache* cache, const Shader* shader) {
326 assert(cache);
327 assert(shader);
328 mempool_foreach(cache, entry, {
329 if (entry->shader == shader) {
330 return entry;
331 }
332 });
333 return 0;
334}
335
336static ShaderProgramCacheEntry* find_program_cache_entry(
337 ProgramCache* cache, const ShaderProgram* prog) {
338 assert(cache);
339 assert(prog);
340 mempool_foreach(cache, entry, {
341 if (entry->program == prog) {
342 return entry;
343 }
344 });
345 return 0;
346}
347
348Shader* gfx_make_shader(GfxCore* gfxcore, const ShaderDesc* desc) {
349 assert(gfxcore);
350 assert(desc);
351
352 // Check the shader cache first.
353 ShaderCache* cache = &gfxcore->shader_cache;
354 const uint64_t hash = hash_shader_desc(desc);
355 Shader* shader = find_cached_shader(cache, hash);
356 if (shader) {
357 // LOGD("Found cached shader with hash [%lx]", hash);
358 return shader;
359 }
360
361 shader = mempool_alloc(&gfxcore->shaders);
362 if (!shader) {
363 return 0;
364 }
365 if (!gfx_compile_shader(shader, desc)) {
366 mempool_free(&gfxcore->shaders, &shader);
367 return 0;
368 }
369 ShaderCacheEntry* entry = mempool_alloc(cache);
370 *entry = (ShaderCacheEntry){.hash = hash, .shader = shader};
371 // LOGD("Added shader with hash [%lx] to cache", hash);
372 return shader;
373}
374
375void gfx_destroy_shader(GfxCore* gfxcore, Shader** shader) {
376 assert(gfxcore);
377 assert(shader);
378
379 if (*shader) {
380 // Remove the shader from the cache.
381 ShaderCache* cache = &gfxcore->shader_cache;
382 ShaderCacheEntry* entry = find_shader_cache_entry(cache, *shader);
383 assert(entry); // Must be there, shaders can't go untracked.
384 mempool_free(cache, &entry);
385
386 gfx_del_shader(*shader);
387 mempool_free(&gfxcore->shaders, shader);
388 }
389}
390
391ShaderProgram* gfx_make_shader_program(
392 GfxCore* gfxcore, const ShaderProgramDesc* desc) {
393 assert(gfxcore);
394 assert(desc);
395
396 // Check the shader program cache first.
397 ProgramCache* cache = &gfxcore->program_cache;
398 const uint64_t hash = hash_program_desc(desc);
399 ShaderProgram* prog = find_cached_program(cache, hash);
400 if (prog) {
401 // LOGD("Found cached shader program with hash [%lx]", hash);
402 return prog;
403 }
404
405 prog = mempool_alloc(&gfxcore->shader_programs);
406 if (!gfx_build_shader_program(prog, desc)) {
407 mempool_free(&gfxcore->shader_programs, &prog);
408 return 0;
409 }
410 ShaderProgramCacheEntry* entry = mempool_alloc(cache);
411 *entry = (ShaderProgramCacheEntry){.hash = hash, .program = prog};
412 // LOGD("Added shader program with hash [%lx] to cache", hash);
413 return prog;
414}
415
416void gfx_destroy_shader_program(GfxCore* gfxcore, ShaderProgram** prog) {
417 assert(gfxcore);
418 assert(prog);
419 if (*prog) {
420 // Remove the shader program from the cache.
421 ProgramCache* cache = &gfxcore->program_cache;
422 ShaderProgramCacheEntry* entry = find_program_cache_entry(cache, *prog);
423 assert(entry); // Must be there, shaders can't go untracked.
424 mempool_free(cache, &entry);
425
426 gfx_del_shader_program(*prog);
427 mempool_free(&gfxcore->shader_programs, prog);
428 }
429}
diff --git a/src/core/core_impl.h b/src/core/core_impl.h
new file mode 100644
index 0000000..eefdfbe
--- /dev/null
+++ b/src/core/core_impl.h
@@ -0,0 +1,68 @@
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
18// TODO: Make a generic (hash, void*) structure and define functions over it.
19// Then define a macro that defines type-safe macros given the type of the
20// entry.
21typedef struct ShaderCacheEntry {
22 uint64_t hash;
23 Shader* shader;
24} ShaderCacheEntry;
25
26typedef struct ShaderProgramCacheEntry {
27 uint64_t hash;
28 ShaderProgram* program;
29} ShaderProgramCacheEntry;
30
31DEF_MEMPOOL(buffer_pool, Buffer, GFX_MAX_NUM_BUFFERS)
32DEF_MEMPOOL(framebuffer_pool, FrameBuffer, GFX_MAX_NUM_FRAMEBUFFERS)
33DEF_MEMPOOL(geometry_pool, Geometry, GFX_MAX_NUM_GEOMETRIES)
34DEF_MEMPOOL(renderbuffer_pool, RenderBuffer, GFX_MAX_NUM_RENDERBUFFERS)
35DEF_MEMPOOL(shader_pool, Shader, GFX_MAX_NUM_SHADERS)
36DEF_MEMPOOL(shader_program_pool, ShaderProgram, GFX_MAX_NUM_SHADER_PROGRAMS)
37DEF_MEMPOOL(texture_pool, Texture, GFX_MAX_NUM_TEXTURES)
38
39DEF_MEMPOOL(ShaderCache, ShaderCacheEntry, GFX_MAX_NUM_SHADERS)
40DEF_MEMPOOL(ProgramCache, ShaderProgramCacheEntry, GFX_MAX_NUM_SHADER_PROGRAMS)
41
42typedef struct {
43 int x;
44 int y;
45 int width;
46 int height;
47} Viewport;
48
49typedef struct GfxCore {
50 Viewport viewport;
51 // mempools for render-specific objects: textures, geometry, etc.
52 buffer_pool buffers;
53 framebuffer_pool framebuffers;
54 geometry_pool geometries;
55 renderbuffer_pool renderbuffers;
56 shader_pool shaders;
57 shader_program_pool shader_programs;
58 texture_pool textures;
59 // Caches.
60 ShaderCache shader_cache;
61 ProgramCache program_cache;
62} GfxCore;
63
64/// Create a new render backend.
65void gfx_init_gfxcore(GfxCore*);
66
67/// Destroy the render backend.
68void 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..cfc749f
--- /dev/null
+++ b/src/core/geometry.c
@@ -0,0 +1,326 @@
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.size_bytes > 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,
38 &(BufferDesc){
39 .usage = buffer_usage,
40 .type = buffer_type,
41 .data.data = view->data,
42 .data.count = view->size_bytes /
43 gfx_get_buffer_type_size_bytes(buffer_type)});
44 }
45 assert(view->size_bytes <= view->buffer->size_bytes);
46}
47
48/// Configure the buffer in teh VAO.
49static void configure_buffer(
50 GfxCore* gfxcore, const GeometryDesc* desc, BufferView* view,
51 size_t num_components, size_t component_size_bytes, GLenum component_type,
52 GLboolean normalized, GLuint channel) {
53 assert(gfxcore);
54 assert(desc);
55 assert(view);
56 assert(view->buffer);
57 assert(
58 desc->num_verts <=
59 view->size_bytes / (num_components * component_size_bytes));
60 assert(view->size_bytes <= view->buffer->size_bytes);
61
62 glBindBuffer(GL_ARRAY_BUFFER, view->buffer->vbo);
63 glEnableVertexAttribArray(channel);
64 if ((component_type == GL_FLOAT) || normalized) {
65 glVertexAttribPointer(
66 channel, num_components, component_type, normalized, view->stride_bytes,
67 (const void*)view->offset_bytes);
68 } else {
69 assert(!normalized);
70 assert(
71 (component_type == GL_BYTE) || (component_type == GL_UNSIGNED_BYTE) ||
72 (component_type == GL_SHORT) || (component_type == GL_UNSIGNED_SHORT) ||
73 (component_type == GL_INT) || component_type == GL_UNSIGNED_INT);
74 glVertexAttribIPointer(
75 channel, num_components, component_type, view->stride_bytes,
76 (const void*)view->offset_bytes);
77 }
78 glBindBuffer(GL_ARRAY_BUFFER, 0);
79}
80
81static bool configure_vertex_attributes(GfxCore* gfxcore, GeometryDesc* desc) {
82 assert(gfxcore);
83 assert(desc);
84
85 if (view_is_populated(desc->positions3d)) {
86 init_view_buffer(
87 gfxcore, (BufferView*)&desc->positions3d, Buffer3d, desc->buffer_usage);
88 if (!desc->positions3d.buffer) {
89 return false;
90 }
91 configure_buffer(
92 gfxcore, desc, (BufferView*)&desc->positions3d, 3, sizeof(float),
93 GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL);
94 } else if (view_is_populated(desc->positions2d)) {
95 init_view_buffer(
96 gfxcore, (BufferView*)&desc->positions2d, Buffer2d, desc->buffer_usage);
97 if (!desc->positions2d.buffer) {
98 return false;
99 }
100 configure_buffer(
101 gfxcore, desc, (BufferView*)&desc->positions2d, 2, sizeof(float),
102 GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL);
103 }
104 if (view_is_populated(desc->normals)) {
105 init_view_buffer(
106 gfxcore, (BufferView*)&desc->normals, Buffer3d, desc->buffer_usage);
107 if (!desc->normals.buffer) {
108 return false;
109 }
110 configure_buffer(
111 gfxcore, desc, (BufferView*)&desc->normals, 3, sizeof(float), GL_FLOAT,
112 GL_FALSE, GFX_NORMAL_CHANNEL);
113 }
114 if (view_is_populated(desc->tangents)) {
115 init_view_buffer(
116 gfxcore, (BufferView*)&desc->tangents, Buffer4d, desc->buffer_usage);
117 if (!desc->tangents.buffer) {
118 return false;
119 }
120 configure_buffer(
121 gfxcore, desc, (BufferView*)&desc->tangents, 4, sizeof(float), GL_FLOAT,
122 GL_FALSE, GFX_TANGENT_CHANNEL);
123 }
124 if (view_is_populated(desc->texcoords)) {
125 init_view_buffer(
126 gfxcore, (BufferView*)&desc->texcoords, Buffer2d, desc->buffer_usage);
127 if (!desc->texcoords.buffer) {
128 return false;
129 }
130 configure_buffer(
131 gfxcore, desc, (BufferView*)&desc->texcoords, 2, sizeof(float),
132 GL_FLOAT, GL_FALSE, GFX_TEXCOORDS_CHANNEL);
133 }
134 if (view_is_populated(desc->joints.u8)) {
135 init_view_buffer(
136 gfxcore, (BufferView*)&desc->joints.u8, BufferU8, desc->buffer_usage);
137 if (!desc->joints.u8.buffer) {
138 return false;
139 }
140 configure_buffer(
141 gfxcore, desc, (BufferView*)&desc->joints.u8, 4, sizeof(uint8_t),
142 GL_UNSIGNED_BYTE, GL_FALSE, GFX_JOINTS_CHANNEL);
143 } else if (view_is_populated(desc->joints.u16)) {
144 init_view_buffer(
145 gfxcore, (BufferView*)&desc->joints.u16, BufferU16, desc->buffer_usage);
146 if (!desc->joints.u16.buffer) {
147 return false;
148 }
149 configure_buffer(
150 gfxcore, desc, (BufferView*)&desc->joints.u16, 4, sizeof(uint16_t),
151 GL_UNSIGNED_SHORT, GL_FALSE, GFX_JOINTS_CHANNEL);
152 }
153
154 // If weights are given as unsigned integers, then they are normalized
155 // when read by the shader.
156 if (view_is_populated(desc->weights.u8)) {
157 init_view_buffer(
158 gfxcore, (BufferView*)&desc->weights.u8, BufferU8, desc->buffer_usage);
159 if (!desc->weights.u8.buffer) {
160 return false;
161 }
162 configure_buffer(
163 gfxcore, desc, (BufferView*)&desc->weights.u8, 4, sizeof(uint8_t),
164 GL_UNSIGNED_BYTE, GL_TRUE, GFX_WEIGHTS_CHANNEL);
165 } else if (view_is_populated(desc->weights.u16)) {
166 init_view_buffer(
167 gfxcore, (BufferView*)&desc->weights.u16, BufferU16,
168 desc->buffer_usage);
169 if (!desc->weights.u16.buffer) {
170 return false;
171 }
172 configure_buffer(
173 gfxcore, desc, (BufferView*)&desc->weights.u16, 4, sizeof(uint16_t),
174 GL_UNSIGNED_SHORT, GL_TRUE, GFX_WEIGHTS_CHANNEL);
175 } else if (view_is_populated(desc->weights.floats)) {
176 init_view_buffer(
177 gfxcore, (BufferView*)&desc->weights.floats, BufferFloat,
178 desc->buffer_usage);
179 if (!desc->weights.floats.buffer) {
180 return false;
181 }
182 configure_buffer(
183 gfxcore, desc, (BufferView*)&desc->weights.floats, 4, sizeof(float),
184 GL_FLOAT, GL_FALSE, GFX_WEIGHTS_CHANNEL);
185 }
186
187 return true;
188}
189
190static bool configure_indices(GfxCore* gfxcore, GeometryDesc* desc) {
191 assert(gfxcore);
192 assert(desc);
193
194 if (view_is_populated(desc->indices8)) {
195 assert(desc->num_indices > 0);
196 assert(
197 desc->num_indices <= desc->indices8.size_bytes / sizeof(VertexIndex8));
198 init_view_buffer(
199 gfxcore, (BufferView*)&desc->indices8, BufferU8, desc->buffer_usage);
200 if (!desc->indices8.buffer) {
201 return false;
202 }
203 } else if (view_is_populated(desc->indices16)) {
204 assert(desc->num_indices > 0);
205 assert(
206 desc->num_indices <=
207 desc->indices16.size_bytes / sizeof(VertexIndex16));
208 init_view_buffer(
209 gfxcore, (BufferView*)&desc->indices16, BufferU16, desc->buffer_usage);
210 if (!desc->indices16.buffer) {
211 return false;
212 }
213 }
214
215 return true;
216}
217
218bool gfx_init_geometry(
219 Geometry* geometry, GfxCore* gfxcore, const GeometryDesc* input_desc) {
220 assert(geometry);
221 assert(gfxcore);
222 assert(input_desc);
223 assert(
224 view_is_populated(input_desc->positions3d) ||
225 view_is_populated(input_desc->positions2d));
226 assert(input_desc->num_verts > 0);
227
228 geometry->mode = primitive_type_to_gl(input_desc->type);
229 geometry->desc = *input_desc;
230 geometry->num_verts = input_desc->num_verts;
231 geometry->gfxcore = gfxcore;
232
233 // The geometry's copy of the descriptor is manipulated below. Create a
234 // shorter name for it.
235 GeometryDesc* desc = &geometry->desc;
236
237 glGenVertexArrays(1, &geometry->vao);
238 glBindVertexArray(geometry->vao);
239 if (!configure_vertex_attributes(gfxcore, desc)) {
240 goto cleanup;
241 }
242 if (!configure_indices(gfxcore, desc)) {
243 goto cleanup;
244 }
245 glBindVertexArray(0);
246 ASSERT_GL;
247
248 return true;
249
250cleanup:
251 gfx_del_geometry(geometry);
252 return 0;
253}
254
255void gfx_del_geometry(Geometry* geometry) {
256 assert(geometry);
257 if (geometry->vao) {
258 glDeleteVertexArrays(1, &geometry->vao);
259 geometry->vao = 0;
260 }
261}
262
263void gfx_update_geometry(Geometry* geometry, const GeometryDesc* desc) {
264 assert(geometry);
265 assert(desc);
266 // New geometry size cannot exceed original size.
267 assert(desc->positions3d.size_bytes <= geometry->desc.positions3d.size_bytes);
268 assert(desc->positions2d.size_bytes <= geometry->desc.positions2d.size_bytes);
269 assert(desc->normals.size_bytes <= geometry->desc.normals.size_bytes);
270 assert(desc->tangents.size_bytes <= geometry->desc.tangents.size_bytes);
271 assert(desc->texcoords.size_bytes <= geometry->desc.texcoords.size_bytes);
272 assert(desc->joints.u8.size_bytes <= geometry->desc.joints.u8.size_bytes);
273 assert(desc->joints.u16.size_bytes <= geometry->desc.joints.u16.size_bytes);
274 assert(desc->weights.u8.size_bytes <= geometry->desc.weights.u8.size_bytes);
275 assert(desc->weights.u16.size_bytes <= geometry->desc.weights.u16.size_bytes);
276 assert(
277 desc->weights.floats.size_bytes <=
278 geometry->desc.weights.floats.size_bytes);
279
280 if (desc->positions3d.data) {
281 // The geometry must already have an underlying GPU buffer.
282 assert(geometry->desc.positions3d.buffer);
283 gfx_update_buffer(
284 geometry->desc.positions3d.buffer,
285 &(BufferDataDesc){
286 .vec3s = desc->positions3d.data,
287 .count = desc->positions3d.size_bytes / sizeof(vec3)});
288 }
289 // TODO: more
290 else {
291 FAIL("TODO: gfx_update_geometry() - handle other buffer types");
292 }
293
294 if (desc->num_verts != 0) {
295 geometry->num_verts = desc->num_verts;
296 }
297}
298
299void gfx_render_geometry(const Geometry* geometry) {
300 assert(geometry);
301 assert(geometry->vao);
302
303 const GeometryDesc* desc = &geometry->desc;
304 glBindVertexArray(geometry->vao);
305
306 if (desc->indices8.buffer) {
307 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, desc->indices8.buffer->vbo);
308 glDrawElements(
309 geometry->mode, desc->num_indices, GL_UNSIGNED_BYTE,
310 (const void*)desc->indices8.offset_bytes);
311 } else if (desc->indices16.buffer) {
312 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, desc->indices16.buffer->vbo);
313 glDrawElements(
314 geometry->mode, desc->num_indices, GL_UNSIGNED_SHORT,
315 (const void*)desc->indices16.offset_bytes);
316 } else {
317 glDrawArrays(geometry->mode, 0, geometry->num_verts);
318 }
319
320 glBindVertexArray(0);
321}
322
323aabb3 gfx_get_geometry_aabb(const Geometry* geometry) {
324 assert(geometry);
325 return geometry->desc.aabb;
326}
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..3cbe48d
--- /dev/null
+++ b/src/core/shader_program.c
@@ -0,0 +1,291 @@
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_texture_uniform(
76 GLuint prog, const char* name, int texture_unit, const Texture* texture) {
77 assert(prog != 0);
78 assert(name);
79 assert(texture);
80
81 const GLint location = glGetUniformLocation(prog, name);
82 if (location >= 0) {
83 glActiveTexture(GL_TEXTURE0 + texture_unit);
84 glBindTexture(texture->target, texture->id);
85 glUniform1i(location, texture_unit);
86 }
87}
88
89static void set_mat4_uniform(
90 GLuint prog, const char* name, const mat4* mats, size_t count) {
91 assert(prog != 0);
92 assert(name);
93 assert(mats);
94
95 const GLint location = glGetUniformLocation(prog, name);
96 if (location >= 0) {
97 glUniformMatrix4fv(location, count, GL_FALSE, (const float*)mats);
98 }
99}
100
101static void set_vec3_uniform(GLuint prog, const char* name, vec3 value) {
102 assert(prog != 0);
103 assert(name);
104
105 const GLint location = glGetUniformLocation(prog, name);
106 if (location >= 0) {
107 glUniform3f(location, value.x, value.y, value.z);
108 }
109}
110
111static void set_vec4_uniform(GLuint prog, const char* name, vec4 value) {
112 assert(prog != 0);
113 assert(name);
114
115 const GLint location = glGetUniformLocation(prog, name);
116 if (location >= 0) {
117 glUniform4f(location, value.x, value.y, value.z, value.w);
118 }
119}
120
121static void set_float_uniform(GLuint prog, const char* name, float value) {
122 assert(prog != 0);
123 assert(name);
124
125 const GLint location = glGetUniformLocation(prog, name);
126 if (location >= 0) {
127 glUniform1f(location, value);
128 }
129}
130
131void gfx_apply_uniforms(const ShaderProgram* prog) {
132 assert(prog);
133
134 int next_texture_unit = 0;
135 for (int i = 0; i < prog->num_uniforms; ++i) {
136 const ShaderUniform* uniform = &prog->uniforms[i];
137 switch (uniform->type) {
138 case UniformTexture:
139 set_texture_uniform(
140 prog->id, uniform->name.str, next_texture_unit,
141 uniform->value.texture);
142 next_texture_unit++;
143 break;
144 case UniformMat4:
145 set_mat4_uniform(prog->id, uniform->name.str, &uniform->value.mat4, 1);
146 break;
147 case UniformVec3:
148 set_vec3_uniform(prog->id, uniform->name.str, uniform->value.vec3);
149 break;
150 case UniformVec4:
151 set_vec4_uniform(prog->id, uniform->name.str, uniform->value.vec4);
152 break;
153 case UniformFloat:
154 set_float_uniform(prog->id, uniform->name.str, uniform->value.scalar);
155 break;
156 case UniformMat4Array:
157 set_mat4_uniform(
158 prog->id, uniform->name.str, uniform->value.array.values,
159 uniform->value.array.count);
160 break;
161 }
162 }
163}
164
165// Get the ShaderUniform object by name from the shader program if it already
166// exists, or allocate a new one otherwise.
167static ShaderUniform* get_or_allocate_uniform(
168 ShaderProgram* prog, const char* name) {
169 assert(prog);
170 assert(name);
171
172 // First search for the uniform in the list.
173 for (int i = 0; i < prog->num_uniforms; ++i) {
174 ShaderUniform* uniform = &prog->uniforms[i];
175 if (sstring_eq_cstr(uniform->name, name)) {
176 return uniform;
177 }
178 }
179
180 // Create the uniform if it does not exist.
181 if (prog->num_uniforms == GFX_MAX_UNIFORMS_PER_SHADER) {
182 FAIL("Exceeded the maximum number of uniforms per shader. Please increase "
183 "this value.");
184 return 0;
185 }
186 ShaderUniform* uniform = &prog->uniforms[prog->num_uniforms];
187 prog->num_uniforms++;
188 return uniform;
189}
190
191// The functions below save the value of a uniform in the shader program. If the
192// uniform does not even exist, then there is no need to store the value.
193
194void gfx_set_texture_uniform(
195 ShaderProgram* prog, const char* name, const Texture* texture) {
196 assert(prog);
197 assert(name);
198 assert(texture);
199
200 const GLint location = glGetUniformLocation(prog->id, name);
201 if (location < 0) {
202 return;
203 }
204 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
205 assert(uniform);
206 uniform->name = sstring_make(name);
207 uniform->type = UniformTexture;
208 uniform->value.texture = texture;
209}
210
211void gfx_set_mat4_uniform(
212 ShaderProgram* prog, const char* name, const mat4* mat) {
213 assert(prog);
214 assert(name);
215 assert(mat);
216
217 const GLint location = glGetUniformLocation(prog->id, name);
218 if (location < 0) {
219 return;
220 }
221 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
222 assert(uniform);
223 uniform->name = sstring_make(name);
224 uniform->type = UniformMat4;
225 uniform->value.mat4 = *mat;
226}
227
228void gfx_set_vec3_uniform(ShaderProgram* prog, const char* name, vec3 value) {
229 assert(prog);
230 assert(name);
231
232 const GLint location = glGetUniformLocation(prog->id, name);
233 if (location < 0) {
234 return;
235 }
236 ShaderUniform* uniform = get_or_allocate_uniform(prog, name);
237 assert(uniform);
238 uniform->name = sstring_make(name);
239 uniform->type = UniformVec3;
240 uniform->value.vec3 = value;
241}
242
243void gfx_set_vec4_uniform(ShaderProgram* prog, const char* name, vec4 value) {
244 assert(prog);
245 assert(name);
246
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 = UniformVec4;
255 uniform->value.vec4 = 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.scalar = value;
273}
274
275void gfx_set_mat4_array_uniform(
276 ShaderProgram* prog, const char* name, const mat4* mats, size_t count) {
277 assert(prog);
278 assert(name);
279 assert(mats);
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 = UniformMat4Array;
289 uniform->value.array.count = count;
290 uniform->value.array.values = mats;
291}
diff --git a/src/core/shader_program.h b/src/core/shader_program.h
new file mode 100644
index 0000000..1443663
--- /dev/null
+++ b/src/core/shader_program.h
@@ -0,0 +1,24 @@
1#pragma once
2
3#include <gfx/core.h>
4#include <gfx/sizes.h>
5
6#include "gl_util.h"
7
8#include <math/fwd.h>
9
10#include <stdbool.h>
11
12typedef struct Texture Texture;
13
14typedef struct ShaderProgram {
15 GLuint id;
16 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_SHADER];
17 int num_uniforms;
18} ShaderProgram;
19
20/// Create a new shader program.
21bool gfx_build_shader_program(ShaderProgram*, const ShaderProgramDesc*);
22
23/// Destroy the shader program.
24void gfx_del_shader_program(ShaderProgram*);
diff --git a/src/core/texture.c b/src/core/texture.c
new file mode 100644
index 0000000..89f7ec0
--- /dev/null
+++ b/src/core/texture.c
@@ -0,0 +1,218 @@
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
41 texture->format = to_GL_format(desc->format);
42 texture->type = to_GL_type(desc->format);
43 texture->width = desc->width;
44 texture->height = desc->height;
45 gfx_update_texture(texture, &desc->data);
46
47 // gfx_update_texture() unbinds the texture at the end, so re-bind it here.
48 glBindTexture(texture->target, texture->id);
49
50 // Mipmaps.
51 if (desc->mipmaps) {
52 glGenerateMipmap(texture->target);
53 }
54
55 // Texture filtering.
56 const bool linear = desc->filtering == LinearFiltering;
57 GLenum min = desc->mipmaps ? (linear ? GL_LINEAR_MIPMAP_LINEAR
58 : GL_NEAREST_MIPMAP_NEAREST)
59 : (linear ? GL_LINEAR : GL_NEAREST);
60 GLenum mag = linear ? GL_LINEAR : GL_NEAREST;
61 glTexParameteri(texture->target, GL_TEXTURE_MIN_FILTER, min);
62 glTexParameteri(texture->target, GL_TEXTURE_MAG_FILTER, mag);
63
64 // Texture wrapping.
65 GLenum wrap = GL_INVALID_ENUM;
66 switch (desc->wrap) {
67 case Repeat:
68 wrap = GL_REPEAT;
69 break;
70 case ClampToEdge:
71 wrap = GL_CLAMP_TO_EDGE;
72 break;
73 }
74 glTexParameteri(texture->target, GL_TEXTURE_WRAP_R, wrap);
75 glTexParameteri(texture->target, GL_TEXTURE_WRAP_S, wrap);
76 glTexParameteri(texture->target, GL_TEXTURE_WRAP_T, wrap);
77
78 glBindTexture(texture->target, 0);
79 return true;
80}
81
82void gfx_del_texture(Texture* texture) {
83 assert(texture);
84
85 if (texture->id) {
86 glDeleteTextures(1, &texture->id);
87 texture->id = 0;
88 }
89}
90
91void gfx_update_texture(Texture* texture, const TextureDataDesc* desc) {
92 assert(texture);
93 assert(desc);
94
95 glBindTexture(texture->target, texture->id);
96
97 // glTexSubImageXD
98 switch (texture->target) {
99 case GL_TEXTURE_2D:
100 if (desc->pixels) {
101 glTexSubImage2D(
102 GL_TEXTURE_2D, /*level=*/0, /*xoffset=*/0,
103 /*yoffset=*/0, texture->width, texture->height, texture->format,
104 texture->type, desc->pixels);
105 }
106 break;
107 case GL_TEXTURE_CUBE_MAP:
108 for (int i = 0; i < 6; ++i) {
109 const void* pixels = *(&desc->cubemap.pixels_pos_x + i);
110 if (pixels) {
111 glTexSubImage2D(
112 GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, /*level=*/0, /*xoffset=*/0,
113 /*yoffset=*/0, texture->width, texture->height, texture->format,
114 texture->type, pixels);
115 }
116 }
117 break;
118 default:
119 FAIL("Unhandled texture dimension");
120 break;
121 }
122
123 glBindTexture(texture->target, 0);
124}
125
126GLenum to_GL_dimension(TextureDimension dim) {
127 switch (dim) {
128 case Texture2D:
129 return GL_TEXTURE_2D;
130 case TextureCubeMap:
131 return GL_TEXTURE_CUBE_MAP;
132 default:
133 FAIL("Unhandled TextureDimension");
134 return GL_INVALID_ENUM;
135 }
136}
137
138GLenum to_GL_internal_format(TextureFormat format) {
139 switch (format) {
140 case TextureDepth:
141 return GL_DEPTH_COMPONENT;
142 case TextureRG16:
143 return GL_RG16;
144 case TextureRG16F:
145 return GL_RG16F;
146 case TextureRGB8:
147 return GL_RGB8;
148 case TextureR11G11B10F:
149 return GL_R11F_G11F_B10F;
150 case TextureRGBA8:
151 return GL_RGBA8;
152 case TextureSRGB8:
153 return GL_SRGB8;
154 case TextureSRGBA8:
155 return GL_SRGB8_ALPHA8;
156 default:
157 FAIL("Unhandled TextureFormat");
158 return GL_INVALID_ENUM;
159 }
160}
161
162GLenum to_GL_format(TextureFormat format) {
163 switch (format) {
164 case TextureDepth:
165 return GL_DEPTH_COMPONENT;
166 case TextureRG16:
167 case TextureRG16F:
168 return GL_RG;
169 case TextureRGB8:
170 case TextureR11G11B10F:
171 case TextureSRGB8:
172 return GL_RGB;
173 case TextureRGBA8:
174 case TextureSRGBA8:
175 return GL_RGBA;
176 default:
177 FAIL("Unhandled TextureFormat");
178 return GL_INVALID_ENUM;
179 }
180}
181
182GLenum to_GL_type(TextureFormat format) {
183 switch (format) {
184 case TextureDepth:
185 case TextureRG16F:
186 case TextureR11G11B10F:
187 return GL_FLOAT;
188 case TextureRG16:
189 case TextureRGB8:
190 case TextureRGBA8:
191 case TextureSRGB8:
192 case TextureSRGBA8:
193 return GL_UNSIGNED_BYTE;
194 default:
195 FAIL("Unhandled TextureFormat");
196 return GL_INVALID_ENUM;
197 }
198}
199
200GLenum to_GL_cubemap_face(CubemapFace face) {
201 switch (face) {
202 case CubemapFacePosX:
203 return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
204 case CubemapFaceNegX:
205 return GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
206 case CubemapFacePosY:
207 return GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
208 case CubemapFaceNegY:
209 return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
210 case CubemapFacePosZ:
211 return GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
212 case CubemapFaceNegZ:
213 return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
214 default:
215 FAIL("Unhandled CubemapFace");
216 return GL_INVALID_ENUM;
217 }
218}
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..cd2ac90
--- /dev/null
+++ b/src/gfx.c
@@ -0,0 +1,73 @@
1#include <gfx/gfx.h>
2
3#include "asset/asset_cache.h"
4#include "core/core_impl.h"
5#include "renderer/imm_renderer_impl.h"
6#include "renderer/renderer_impl.h"
7#include "scene/scene_memory.h"
8
9#include <log/log.h>
10
11#include <assert.h>
12#include <stdlib.h>
13
14typedef struct Gfx {
15 AssetCache asset_cache;
16 GfxCore gfxcore;
17 Renderer renderer;
18 ImmRenderer imm_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 (!renderer_make(&gfx->renderer, &gfx->gfxcore)) {
28 gfx_destroy(&gfx);
29 return 0;
30 }
31 if (!imm_renderer_make(&gfx->imm_renderer, &gfx->gfxcore)) {
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 gfx_init_asset_cache(&gfx->asset_cache);
38 scene_mem_init();
39 return gfx;
40}
41
42void gfx_destroy(Gfx** gfx) {
43 if (!gfx) {
44 return;
45 }
46 scene_mem_destroy();
47 gfx_destroy_asset_cache(&(*gfx)->asset_cache);
48 renderer_destroy(&(*gfx)->renderer);
49 imm_renderer_destroy(&(*gfx)->imm_renderer);
50 gfx_del_gfxcore(&(*gfx)->gfxcore);
51 free(*gfx);
52 *gfx = 0;
53}
54
55GfxCore* gfx_get_core(Gfx* gfx) {
56 assert(gfx);
57 return &gfx->gfxcore;
58}
59
60Renderer* gfx_get_renderer(Gfx* gfx) {
61 assert(gfx);
62 return &gfx->renderer;
63}
64
65ImmRenderer* gfx_get_imm_renderer(Gfx* gfx) {
66 assert(gfx);
67 return &gfx->imm_renderer;
68}
69
70AssetCache* gfx_get_asset_cache(Gfx* gfx) {
71 assert(gfx);
72 return &gfx->asset_cache;
73}
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/renderer/imm_renderer.c b/src/renderer/imm_renderer.c
new file mode 100644
index 0000000..8cf3a10
--- /dev/null
+++ b/src/renderer/imm_renderer.c
@@ -0,0 +1,260 @@
1#include "imm_renderer_impl.h"
2
3#include <gfx/core.h>
4#include <gfx/util/shader.h>
5
6#include <math/aabb3.h>
7
8#include <assert.h>
9#include <string.h> // memcpy
10
11bool imm_renderer_make(ImmRenderer* renderer, GfxCore* gfxcore) {
12 assert(renderer);
13 assert(gfxcore);
14
15 const size_t num_triangle_verts = IMM_MAX_NUM_TRIANGLES * 3;
16
17 renderer->gfxcore = gfxcore;
18
19 renderer->triangles = gfx_make_geometry(
20 gfxcore, &(GeometryDesc){
21 .type = Triangles,
22 .buffer_usage = BufferDynamic,
23 .num_verts = num_triangle_verts,
24 .positions3d = (BufferView3d){
25 .size_bytes = num_triangle_verts * sizeof(vec3)}});
26 if (!renderer->triangles) {
27 goto cleanup;
28 }
29
30 renderer->shader = gfx_make_immediate_mode_shader(gfxcore);
31 if (!renderer->shader) {
32 goto cleanup;
33 }
34
35 renderer->matrix_stack[0] = mat4_id();
36 renderer->stack_pointer = 0;
37
38 gfx_imm_set_colour(renderer, vec4_make(0.0, 0.0, 0.0, 1.0));
39
40 return true;
41
42cleanup:
43 imm_renderer_destroy(renderer);
44 return false;
45}
46
47void imm_renderer_destroy(ImmRenderer* renderer) {
48 assert(renderer);
49 assert(renderer->gfxcore);
50
51 if (renderer->triangles) {
52 gfx_destroy_geometry(renderer->gfxcore, &renderer->triangles);
53 // TODO: Could also destroy the geometry's buffers here.
54 }
55
56 if (renderer->shader) {
57 gfx_destroy_shader_program(renderer->gfxcore, &renderer->shader);
58 }
59}
60
61void imm_renderer_flush(ImmRenderer* renderer) {
62 assert(renderer);
63
64 if (renderer->num_triangle_verts > 0) {
65 gfx_update_geometry(
66 renderer->triangles,
67 &(GeometryDesc){
68 .num_verts = renderer->num_triangle_verts,
69 .positions3d = (BufferView3d){
70 .data = renderer->triangle_verts,
71 .size_bytes = renderer->num_triangle_verts * sizeof(vec3)}
72 });
73
74 gfx_apply_uniforms(renderer->shader);
75 gfx_render_geometry(renderer->triangles);
76
77 renderer->num_triangle_verts = 0;
78 }
79}
80
81void gfx_imm_start(ImmRenderer* renderer) {
82 assert(renderer);
83 // Shader uniforms are applied lazily.
84 // TODO: In the event that gfx_activate_shader_program() activates uniforms
85 // automatically for convenience, call an overload here that doesn't do so.
86 ShaderProgram* shader = renderer->shader;
87 gfx_activate_shader_program(shader);
88}
89
90void gfx_imm_end(ImmRenderer* renderer) {
91 assert(renderer);
92 imm_renderer_flush(renderer);
93 gfx_deactivate_shader_program(renderer->shader);
94}
95
96void gfx_imm_draw_triangles(
97 ImmRenderer* renderer, const vec3 verts[], size_t num_triangles) {
98 assert(renderer);
99 assert(verts);
100 const size_t new_verts = num_triangles * 3;
101 assert(
102 renderer->num_triangle_verts + new_verts < (IMM_MAX_NUM_TRIANGLES * 3));
103
104 memcpy(
105 renderer->triangle_verts + renderer->num_triangle_verts, verts,
106 new_verts * sizeof(vec3));
107
108 renderer->num_triangle_verts += new_verts;
109}
110
111void gfx_imm_draw_triangle(ImmRenderer* renderer, const vec3 verts[3]) {
112 gfx_imm_draw_triangles(renderer, verts, 1);
113}
114
115void gfx_imm_draw_aabb2(ImmRenderer* renderer, aabb2 box) {
116 assert(renderer);
117
118 // clang-format off
119 const vec3 verts[4] = {
120 vec3_make(box.min.x, box.min.y, 0), // 3 ---- 2
121 vec3_make(box.max.x, box.min.y, 0), // | |
122 vec3_make(box.max.x, box.max.y, 0), // | |
123 vec3_make(box.min.x, box.max.y, 0)}; // 0 ---- 1
124 // clang-format on
125
126#define tri(i0, i1, i2) verts[i0], verts[i1], verts[i2]
127 const vec3 tris[6] = {tri(0, 1, 2), tri(0, 2, 3)};
128#undef tri
129
130 gfx_imm_draw_triangles(renderer, tris, 2);
131}
132
133void gfx_imm_draw_aabb3(ImmRenderer* renderer, aabb3 box) {
134 assert(renderer);
135
136 // clang-format off
137 const vec3 vertices[8] = {
138 vec3_make(box.min.x, box.min.y, box.max.z), // 7 ----- 6
139 vec3_make(box.max.x, box.min.y, box.max.z), // / /|
140 vec3_make(box.max.x, box.max.y, box.max.z), // 3 ----- 2 |
141 vec3_make(box.min.x, box.max.y, box.max.z), // | | |
142 vec3_make(box.min.x, box.min.y, box.min.z), // | 4 ----- 5
143 vec3_make(box.max.x, box.min.y, box.min.z), // |/ |/
144 vec3_make(box.max.x, box.max.y, box.min.z), // 0 ----- 1
145 vec3_make(box.min.x, box.max.y, box.min.z)};
146 // clang-format on
147
148 gfx_imm_draw_box3(renderer, vertices);
149}
150
151void gfx_imm_draw_box3(ImmRenderer* renderer, const vec3 vertices[8]) {
152 assert(renderer);
153 assert(vertices);
154
155 // 7 ----- 6
156 // / /|
157 // 3 ----- 2 |
158 // | | |
159 // | 4 ----- 5
160 // |/ |/
161 // 0 ----- 1
162
163#define tri(i0, i1, i2) vertices[i0], vertices[i1], vertices[i2]
164 const vec3 tris[36] = {// Front.
165 tri(0, 1, 2), tri(0, 2, 3),
166 // Right.
167 tri(1, 5, 6), tri(1, 6, 2),
168 // Back.
169 tri(5, 4, 7), tri(5, 7, 6),
170 // Left.
171 tri(4, 0, 03), tri(4, 3, 7),
172 // Top.
173 tri(3, 2, 6), tri(3, 6, 7),
174 // Bottom.
175 tri(0, 4, 5), tri(0, 5, 1)};
176
177 gfx_imm_draw_triangles(renderer, tris, 12);
178}
179
180// Load the top of the matrix stack into the shader.
181static void update_shader_model_matrix(ImmRenderer* renderer) {
182 assert(renderer);
183 imm_renderer_flush(renderer);
184 gfx_set_mat4_uniform(
185 renderer->shader, "Model",
186 &renderer->matrix_stack[renderer->stack_pointer]);
187}
188
189void gfx_imm_load_identity(ImmRenderer* renderer) {
190 assert(renderer);
191 renderer->matrix_stack[0] = mat4_id();
192 renderer->stack_pointer = 0;
193 update_shader_model_matrix(renderer);
194}
195
196void gfx_imm_push_matrix(ImmRenderer* renderer, const mat4* matrix) {
197 assert(renderer);
198 assert(matrix);
199 assert(renderer->stack_pointer >= 0);
200 assert(renderer->stack_pointer < IMM_MAX_NUM_MATRICES); // TODO: hard assert.
201
202 renderer->matrix_stack[renderer->stack_pointer + 1] =
203 mat4_mul(*matrix, renderer->matrix_stack[renderer->stack_pointer]);
204 renderer->stack_pointer += 1;
205
206 update_shader_model_matrix(renderer);
207}
208
209void gfx_imm_pop_matrix(ImmRenderer* renderer) {
210 assert(renderer);
211 assert(renderer->stack_pointer > 0); // TODO: hard assert.
212
213 // For debugging, zero out the matrix stack as matrices are popped out.
214 memset(
215 &renderer->matrix_stack[renderer->stack_pointer], 0,
216 sizeof(renderer->matrix_stack[0]));
217
218 renderer->stack_pointer -= 1;
219
220 update_shader_model_matrix(renderer);
221}
222
223void gfx_imm_translate(ImmRenderer* renderer, vec3 offset) {
224 assert(renderer);
225 const mat4 mat = mat4_translate(offset);
226 gfx_imm_push_matrix(renderer, &mat);
227}
228
229void gfx_imm_set_camera(ImmRenderer* renderer, const Camera* camera) {
230 assert(renderer);
231 assert(renderer->shader);
232 imm_renderer_flush(renderer);
233 const mat4 view = spatial3_inverse_transform(&camera->spatial);
234 const mat4 view_proj = mat4_mul(camera->projection, view);
235 gfx_imm_set_view_projection_matrix(renderer, &view_proj);
236}
237
238void gfx_imm_set_model_matrix(ImmRenderer* renderer, const mat4* model) {
239 assert(renderer);
240 assert(model);
241 imm_renderer_flush(renderer);
242 renderer->matrix_stack[0] = *model;
243 renderer->stack_pointer = 0;
244 update_shader_model_matrix(renderer);
245}
246
247void gfx_imm_set_view_projection_matrix(
248 ImmRenderer* renderer, const mat4* view_proj) {
249 assert(renderer);
250 assert(renderer->shader);
251 imm_renderer_flush(renderer);
252 gfx_set_mat4_uniform(renderer->shader, "ViewProjection", view_proj);
253}
254
255void gfx_imm_set_colour(ImmRenderer* renderer, vec4 colour) {
256 assert(renderer);
257 assert(renderer->shader);
258 imm_renderer_flush(renderer);
259 gfx_set_vec4_uniform(renderer->shader, "Colour", colour);
260}
diff --git a/src/renderer/imm_renderer_impl.h b/src/renderer/imm_renderer_impl.h
new file mode 100644
index 0000000..5ece354
--- /dev/null
+++ b/src/renderer/imm_renderer_impl.h
@@ -0,0 +1,44 @@
1#pragma once
2
3#include <gfx/renderer.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
12typedef struct Geometry Geometry;
13typedef struct ShaderProgram ShaderProgram;
14
15/// Immediate mode renderer.
16///
17/// Currently, the immediate mode renderer can only draw up to a maximum number
18/// of primitives per frame. It does not adjust this number dynamically. Keeps
19/// things simple while the extra complexity is not needed.
20typedef struct ImmRenderer {
21 GfxCore* gfxcore;
22 ShaderProgram* shader;
23 Geometry* triangles;
24 size_t num_triangle_verts; // Number of triangle verts this frame.
25 // TODO: wireframe rendering.
26 struct {
27 bool wireframe : 1;
28 } flags;
29 vec3 triangle_verts[IMM_MAX_NUM_TRIANGLES * 3];
30 // Matrix stack contains pre-multiplied matrices.
31 // It is also never empty. The top of the stack is an identity matrix when the
32 // stack is "empty" from the user's perspective.
33 mat4 matrix_stack[IMM_MAX_NUM_MATRICES];
34 int stack_pointer;
35} ImmRenderer;
36
37/// Create a new immediate mode renderer.
38bool imm_renderer_make(ImmRenderer*, GfxCore*);
39
40/// Destroy the immediate mode renderer.
41void imm_renderer_destroy(ImmRenderer*);
42
43/// Flush draw commands.
44void imm_renderer_flush(ImmRenderer*);
diff --git a/src/renderer/renderer.c b/src/renderer/renderer.c
new file mode 100644
index 0000000..c2a7dda
--- /dev/null
+++ b/src/renderer/renderer.c
@@ -0,0 +1,396 @@
1#include "renderer_impl.h"
2
3#include "scene/animation_impl.h"
4#include "scene/camera_impl.h"
5#include "scene/light_impl.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#include "scene/scene_impl.h"
12#include "scene/scene_memory.h"
13
14#include <gfx/core.h>
15#include <gfx/util/ibl.h>
16#include <gfx/util/shader.h>
17
18#include <log/log.h>
19#include <math/mat4.h>
20#include <math/spatial3.h>
21
22#include <assert.h>
23
24// TODO: Move to a header like "constants.h".
25static const int IRRADIANCE_MAP_WIDTH = 1024;
26static const int IRRADIANCE_MAP_HEIGHT = 1024;
27static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128;
28static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128;
29static const int BRDF_INTEGRATION_MAP_WIDTH = 512;
30static const int BRDF_INTEGRATION_MAP_HEIGHT = 512;
31
32bool renderer_make(Renderer* renderer, GfxCore* gfxcore) {
33 assert(renderer);
34 assert(gfxcore);
35
36 renderer->gfxcore = gfxcore;
37
38 return true;
39}
40
41void renderer_destroy(Renderer* renderer) {
42 if (!renderer) {
43 return;
44 }
45 assert(renderer->gfxcore);
46 GfxCore* gfxcore = renderer->gfxcore;
47 if (renderer->ibl) {
48 gfx_destroy_ibl(gfxcore, &renderer->ibl);
49 }
50 if (renderer->shaders.debug) {
51 gfx_destroy_shader_program(gfxcore, &renderer->shaders.debug);
52 }
53 if (renderer->shaders.normals) {
54 gfx_destroy_shader_program(gfxcore, &renderer->shaders.normals);
55 }
56 if (renderer->shaders.normal_mapped_normals) {
57 gfx_destroy_shader_program(
58 gfxcore, &renderer->shaders.normal_mapped_normals);
59 }
60 if (renderer->shaders.tangents) {
61 gfx_destroy_shader_program(gfxcore, &renderer->shaders.tangents);
62 }
63}
64
65/// Initialize renderer state for IBL if not already initialized.
66static bool init_ibl(Renderer* renderer) {
67 assert(renderer);
68
69 if (!renderer->ibl && !(renderer->ibl = gfx_make_ibl(renderer->gfxcore))) {
70 return false;
71 }
72
73 if (!renderer->brdf_integration_map &&
74 !(renderer->brdf_integration_map = gfx_make_brdf_integration_map(
75 renderer->ibl, renderer->gfxcore, BRDF_INTEGRATION_MAP_WIDTH,
76 BRDF_INTEGRATION_MAP_HEIGHT))) {
77 return false;
78 }
79
80 return true;
81}
82
83static ShaderProgram* load_shader(Renderer* renderer, RenderSceneMode mode) {
84 assert(renderer);
85
86#define LOAD_AND_RETURN(pShader, constructor) \
87 { \
88 if (!pShader) { \
89 pShader = constructor(renderer->gfxcore); \
90 } \
91 assert(pShader); \
92 return pShader; \
93 }
94
95 switch (mode) {
96 case RenderDefault:
97 return 0;
98 case RenderDebug:
99 LOAD_AND_RETURN(renderer->shaders.debug, gfx_make_debug3d_shader);
100 case RenderNormals:
101 LOAD_AND_RETURN(renderer->shaders.normals, gfx_make_view_normals_shader);
102 case RenderNormalMappedNormals:
103 LOAD_AND_RETURN(
104 renderer->shaders.normal_mapped_normals,
105 gfx_make_view_normal_mapped_normals_shader);
106 case RenderTangents:
107 LOAD_AND_RETURN(renderer->shaders.tangents, gfx_make_view_tangents_shader);
108 }
109 assert(false);
110 return 0;
111}
112
113// static void log_matrix(const mat4* m) {
114// for (int row = 0; row < 4; ++row) {
115// LOGI("[ %5.2f, %5.2f, %5.2f, %5.2f ]", m->val[0][row], m->val[1][row],
116// m->val[2][row], m->val[3][row]);
117// }
118// }
119
120/// Computes irradiance and prefiltered environment maps for the light if they
121/// have not been already computed.
122static bool setup_environment_light(
123 Renderer* renderer, GfxCore* gfxcore, EnvironmentLight* light) {
124 assert(renderer);
125 assert(light);
126
127 if (!init_ibl(renderer)) {
128 return false;
129 }
130
131 if (light->irradiance_map) {
132 assert(light->prefiltered_environment_map);
133 return true;
134 }
135
136 Texture* irradiance_map = 0;
137 Texture* prefiltered_environment_map = 0;
138
139 if (!(irradiance_map = gfx_make_irradiance_map(
140 renderer->ibl, gfxcore, light->environment_map,
141 IRRADIANCE_MAP_WIDTH, IRRADIANCE_MAP_HEIGHT))) {
142 goto cleanup;
143 }
144
145 int max_mip_level = 0;
146 if (!(prefiltered_environment_map = gfx_make_prefiltered_environment_map(
147 renderer->ibl, gfxcore, light->environment_map,
148 PREFILTERED_ENVIRONMENT_MAP_WIDTH,
149 PREFILTERED_ENVIRONMENT_MAP_HEIGHT, &max_mip_level))) {
150 goto cleanup;
151 }
152
153 light->irradiance_map = irradiance_map;
154 light->prefiltered_environment_map = prefiltered_environment_map;
155 light->max_reflection_lod = max_mip_level;
156
157 return true;
158
159cleanup:
160 if (irradiance_map) {
161 gfx_destroy_texture(gfxcore, &irradiance_map);
162 }
163 if (prefiltered_environment_map) {
164 gfx_destroy_texture(gfxcore, &prefiltered_environment_map);
165 }
166 return false;
167}
168
169typedef struct RenderState {
170 GfxCore* gfxcore;
171 Renderer* renderer;
172 ShaderProgram* shader; // Null to use scene shaders.
173 const Scene* scene;
174 const Camera* camera;
175 const mat4* camera_rotation; // From camera to world space, rotation only.
176 const mat4* view_matrix;
177 const mat4* projection;
178 const float fovy;
179 const float aspect;
180 Light* environment_light;
181 const Anima* anima;
182 size_t num_joints;
183 mat4 joint_matrices[GFX_MAX_NUM_JOINTS];
184} RenderState;
185
186/// Load joint matrices into the render state.
187static void load_skeleton(RenderState* state, skeleton_idx skeleton_index) {
188 assert(state);
189 assert(skeleton_index.val != 0);
190
191 const Skeleton* skeleton = mem_get_skeleton(skeleton_index);
192 assert(skeleton);
193 assert(skeleton->num_joints <= GFX_MAX_NUM_JOINTS);
194
195 state->num_joints = skeleton->num_joints;
196
197 for (size_t i = 0; i < skeleton->num_joints; ++i) {
198 const joint_idx joint_index = skeleton->joints[i];
199 const Joint* joint = &state->anima->joints[joint_index];
200 state->joint_matrices[i] = joint->joint_matrix;
201 }
202}
203
204/// Draw the scene recursively.
205static void draw_recursively(
206 RenderState* state, mat4 parent_transform, const SceneNode* node) {
207 assert(state);
208 const mat4 node_transform = mat4_mul(parent_transform, node->transform);
209
210 // Anima.
211 if (node->type == AnimaNode) {
212 state->anima = gfx_get_node_anima(node);
213 }
214 // Activate light.
215 else if (node->type == LightNode) {
216 Light* light = mem_get_light(node->light);
217 assert(light);
218
219 if (light->type == EnvironmentLightType) {
220 bool result = setup_environment_light(
221 state->renderer, state->gfxcore, &light->environment);
222 // TODO: Handle the result in a better way.
223 assert(result);
224 state->environment_light = light;
225 }
226 }
227 // Model.
228 else if (node->type == ModelNode) {
229 const Model* model = gfx_get_node_model(node);
230 const SceneNode* root = mem_get_node(model->root);
231 draw_recursively(state, parent_transform, root);
232 }
233 // Render object.
234 else if (node->type == ObjectNode) {
235 const SceneObject* object = mem_get_object(node->object);
236 assert(object);
237
238 // TODO: Here we would frustum-cull the object.
239
240 // TODO: Avoid computing matrices like Modelview or MVP if the shader does
241 // not use them.
242 const mat4 model_matrix = node_transform;
243 const mat4 modelview = mat4_mul(*state->view_matrix, model_matrix);
244 const mat4 mvp = mat4_mul(*state->projection, modelview);
245
246 if (object->skeleton.val) {
247 load_skeleton(state, object->skeleton);
248 }
249
250 for (mesh_link_idx mesh_link_index = object->mesh_link;
251 mesh_link_index.val;) {
252 const MeshLink* mesh_link = mem_get_mesh_link(mesh_link_index);
253 mesh_link_index = mesh_link->next;
254
255 const Mesh* mesh = mem_get_mesh(mesh_link->mesh);
256 if (!mesh) {
257 continue;
258 }
259 assert(mesh->geometry);
260 assert(mesh->material);
261
262 // TODO: Here we would frustum-cull the mesh. The AABB would have to be
263 // transformed by the model matrix. Rotation would make the AABB
264 // relatively large, but still, the culling would be conservative.
265
266 // Apply common shader uniforms not captured by materials.
267 ShaderProgram* shader = state->shader ? state->shader : mesh->shader;
268 gfx_set_mat4_uniform(shader, "ModelMatrix", &model_matrix);
269 gfx_set_mat4_uniform(shader, "Modelview", &modelview);
270 gfx_set_mat4_uniform(shader, "View", state->view_matrix);
271 gfx_set_mat4_uniform(shader, "Projection", state->projection);
272 gfx_set_mat4_uniform(shader, "MVP", &mvp);
273 gfx_set_mat4_uniform(shader, "CameraRotation", state->camera_rotation);
274 gfx_set_float_uniform(shader, "Fovy", state->fovy);
275 gfx_set_float_uniform(shader, "Aspect", state->aspect);
276 if (state->camera) {
277 gfx_set_vec3_uniform(
278 shader, "CameraPosition", state->camera->spatial.p);
279 }
280 if (state->num_joints > 0) {
281 gfx_set_mat4_array_uniform(
282 shader, "JointMatrices", state->joint_matrices, state->num_joints);
283 }
284 // Apply lights.
285 if (state->environment_light) {
286 const EnvironmentLight* light = &state->environment_light->environment;
287 assert(light->environment_map);
288 assert(light->irradiance_map);
289 assert(light->prefiltered_environment_map);
290 assert(state->renderer->brdf_integration_map);
291 gfx_set_texture_uniform(
292 shader, "BRDFIntegrationMap",
293 state->renderer->brdf_integration_map);
294 gfx_set_texture_uniform(shader, "Sky", light->environment_map);
295 gfx_set_texture_uniform(shader, "IrradianceMap", light->irradiance_map);
296 gfx_set_texture_uniform(
297 shader, "PrefilteredEnvironmentMap",
298 light->prefiltered_environment_map);
299 gfx_set_float_uniform(
300 shader, "MaxReflectionLOD", light->max_reflection_lod);
301 }
302 material_activate(shader, mesh->material);
303 gfx_activate_shader_program(shader);
304 gfx_apply_uniforms(shader);
305 gfx_render_geometry(mesh->geometry);
306 }
307
308 // Reset state for next object.
309 state->num_joints = 0;
310 }
311
312 // Render children recursively.
313 for (node_idx child_index = node->child; child_index.val;) {
314 const SceneNode* child = mem_get_node(child_index);
315 draw_recursively(state, node_transform, child);
316 child_index = child->next;
317 }
318}
319
320void gfx_render_scene(Renderer* renderer, const RenderSceneParams* params) {
321 assert(renderer);
322 assert(params);
323 assert(params->scene);
324
325 ShaderProgram* const shader = load_shader(renderer, params->mode);
326
327 const Scene* scene = params->scene;
328 const SceneCamera* camera = params->camera;
329
330 GfxCore* gfxcore = renderer->gfxcore;
331
332 mat4 projection, camera_rotation, view_matrix;
333 if (camera) {
334 projection = camera->camera.projection;
335 camera_rotation =
336 mat4_rotation(spatial3_transform(&camera->camera.spatial));
337 view_matrix = spatial3_inverse_transform(&camera->camera.spatial);
338 } else {
339 projection = mat4_id();
340 camera_rotation = mat4_id();
341 view_matrix = mat4_id();
342 }
343
344 int x, y, width, height;
345 gfx_get_viewport(gfxcore, &x, &y, &width, &height);
346 const float aspect = (float)width / (float)height;
347
348 RenderState state = {
349 .gfxcore = gfxcore,
350 .renderer = renderer,
351 .shader = shader,
352 .scene = scene,
353 .camera = &camera->camera,
354 .camera_rotation = &camera_rotation,
355 .view_matrix = &view_matrix,
356 .projection = &projection,
357 .environment_light = 0,
358 // Assuming a perspective matrix.
359 .fovy = atan(1.0 / (mat4_at(projection, 1, 1))) * 2,
360 .aspect = aspect};
361
362 draw_recursively(&state, mat4_id(), scene->root);
363}
364
365static void update_rec(SceneNode* node, const SceneCamera* camera, R t) {
366 assert(node);
367 assert(camera);
368
369 const NodeType node_type = gfx_get_node_type(node);
370
371 // TODO: Models do not need to be animated if they are not visible to the
372 // camera.
373 if (node_type == AnimaNode) {
374 Anima* anima = gfx_get_node_anima_mut(node);
375 gfx_update_animation(anima, (R)t);
376 } else if (node_type == ModelNode) {
377 Model* model = gfx_get_node_model_mut(node);
378 SceneNode* root = gfx_get_model_root_mut(model);
379 update_rec(root, camera, t);
380 }
381
382 // Children.
383 SceneNode* child = gfx_get_node_child_mut(node);
384 while (child) {
385 update_rec(child, camera, t);
386 child = gfx_get_node_sibling_mut(child);
387 }
388}
389
390void gfx_update(Scene* scene, const SceneCamera* camera, R t) {
391 assert(scene);
392 assert(camera);
393
394 SceneNode* node = gfx_get_scene_root(scene);
395 update_rec(node, camera, t);
396}
diff --git a/src/renderer/renderer_impl.h b/src/renderer/renderer_impl.h
new file mode 100644
index 0000000..fc14dcb
--- /dev/null
+++ b/src/renderer/renderer_impl.h
@@ -0,0 +1,27 @@
1#pragma once
2
3#include <gfx/renderer.h>
4
5#include <stdbool.h>
6
7typedef struct IBL IBL;
8typedef struct ShaderProgram ShaderProgram;
9typedef struct Texture Texture;
10
11typedef struct Renderer {
12 GfxCore* gfxcore;
13 IBL* ibl;
14 Texture* brdf_integration_map;
15 struct {
16 ShaderProgram* debug;
17 ShaderProgram* normals;
18 ShaderProgram* normal_mapped_normals;
19 ShaderProgram* tangents;
20 } shaders;
21} Renderer;
22
23/// Create a new renderer.
24bool renderer_make(Renderer*, GfxCore*);
25
26/// Destroy the renderer.
27void renderer_destroy(Renderer*);
diff --git a/src/scene/animation.c b/src/scene/animation.c
new file mode 100644
index 0000000..08d02ce
--- /dev/null
+++ b/src/scene/animation.c
@@ -0,0 +1,524 @@
1#include "animation_impl.h"
2
3#include "node_impl.h"
4#include "scene_memory.h"
5
6#include <string.h>
7
8// #include <log/log.h> // Debugging.
9
10static const R PLAYBACK_UNINITIALIZED = -1;
11
12static joint_idx get_anima_root_joint_index(Anima* anima) {
13 assert(anima);
14 assert(anima->num_joints > 0);
15 assert(anima->num_joints < GFX_MAX_NUM_JOINTS);
16 return anima->num_joints - 1;
17}
18
19static Joint* get_anima_root_joint(Anima* anima) {
20 assert(anima);
21 return &anima->joints[get_anima_root_joint_index(anima)];
22}
23
24static const Joint* get_anima_joint(const Anima* anima, joint_idx index) {
25 assert(anima);
26 assert(index < GFX_MAX_NUM_JOINTS);
27 assert(index != INDEX_NONE);
28 assert(index < anima->num_joints);
29 return &anima->joints[index];
30}
31
32static Joint* get_anima_joint_mut(Anima* anima, joint_idx index) {
33 return (Joint*)get_anima_joint(anima, index);
34}
35
36static const Joint* get_skeleton_joint(
37 const Anima* anima, const Skeleton* skeleton, joint_idx index) {
38 assert(anima);
39 assert(skeleton);
40 return get_anima_joint(anima, skeleton->joints[index]);
41}
42
43static void set_joint_parent(
44 Anima* anima, joint_idx joint_index, joint_idx parent_index) {
45 assert(anima);
46 assert(joint_index != INDEX_NONE);
47 assert(joint_index != get_anima_root_joint_index(anima));
48 assert(parent_index != INDEX_NONE);
49
50 Joint* parent = get_anima_joint_mut(anima, parent_index);
51
52 if (parent->child == INDEX_NONE) {
53 parent->child = joint_index;
54 } else {
55 // Find the last child in the chain of children.
56 Joint* child = get_anima_joint_mut(anima, parent->child);
57 while (child->next != INDEX_NONE) {
58 child = get_anima_joint_mut(anima, child->next);
59 }
60 // Wire up this joint as the last child's sibling.
61 child->next = joint_index;
62 }
63}
64
65static void make_joint(Anima* anima, const JointDesc* desc, Joint* joint) {
66 assert(anima);
67 assert(desc);
68 assert(joint);
69
70 // The joint matrix needs to be initialized so that meshes look right even if
71 // no animation is played. Initializing joint matrices to the identity makes
72 // meshes appear in their bind pose.
73 joint->child = INDEX_NONE;
74 joint->next = INDEX_NONE;
75 joint->transform = mat4_id();
76 joint->inv_bind_matrix = desc->inv_bind_matrix;
77 joint->joint_matrix = mat4_id();
78 joint->box = desc->box;
79}
80
81static Skeleton* make_skeleton(const SkeletonDesc* desc) {
82 assert(desc);
83 assert(desc->num_joints <= GFX_MAX_NUM_JOINTS);
84
85 Skeleton* skeleton = mem_alloc_skeleton();
86 skeleton->num_joints = desc->num_joints;
87 memcpy(
88 skeleton->joints, desc->joints,
89 desc->num_joints * sizeof(skeleton->joints[0]));
90 return skeleton;
91}
92
93static Animation* make_animation(const AnimationDesc* desc) {
94 assert(desc);
95 assert(desc->num_channels < GFX_MAX_NUM_CHANNELS);
96
97 Animation* animation = mem_alloc_animation();
98 animation->name = desc->name;
99 animation->duration = 0;
100 animation->num_channels = desc->num_channels;
101 R start_time = 0;
102 R end_time = 0;
103
104 for (size_t c = 0; c < desc->num_channels; ++c) {
105 const ChannelDesc* channel_desc = &desc->channels[c];
106 Channel* channel = &animation->channels[c];
107
108 channel->target = channel_desc->target;
109 channel->type = channel_desc->type;
110 channel->interpolation = channel_desc->interpolation;
111 channel->num_keyframes = channel_desc->num_keyframes;
112 assert(channel_desc->num_keyframes < GFX_MAX_NUM_KEYFRAMES);
113
114 for (size_t k = 0; k < channel_desc->num_keyframes; ++k) {
115 const KeyframeDesc* keyframe_desc = &channel_desc->keyframes[k];
116 Keyframe* keyframe = &channel->keyframes[k];
117
118 keyframe->time = keyframe_desc->time;
119 keyframe->translation = keyframe_desc->translation;
120 keyframe->rotation = keyframe_desc->rotation;
121
122 start_time = keyframe->time < start_time ? keyframe->time : start_time;
123 end_time = keyframe->time > end_time ? keyframe->time : end_time;
124 }
125 }
126
127 // LOGD("Animation start/end: %f / %f", start_time, end_time);
128 animation->duration = end_time - start_time;
129 assert(animation->duration >= 0);
130 return animation;
131}
132
133Anima* gfx_make_anima(const AnimaDesc* desc) {
134 assert(desc);
135 assert(desc->num_joints > 0);
136 assert(desc->num_joints <= GFX_MAX_NUM_JOINTS);
137 // All joints should have a parent except for the root.
138 for (size_t i = 0; i < desc->num_joints - 1; ++i) {
139 const joint_idx parent = desc->joints[i].parent;
140 assert(parent != INDEX_NONE);
141 assert(parent < desc->num_joints);
142 }
143 // The root should have no parent.
144 assert(desc->joints[desc->num_joints - 1].parent == INDEX_NONE);
145
146 Anima* anima = mem_alloc_anima();
147
148 // Wire the skeletons in the same order they are given in the descriptor.
149 Skeleton* last_skeleton = 0;
150 for (size_t i = 0; i < desc->num_skeletons; ++i) {
151 Skeleton* skeleton = make_skeleton(&desc->skeletons[i]);
152 const skeleton_idx skeleton_index = mem_get_skeleton_index(skeleton);
153 if (last_skeleton == 0) {
154 anima->skeleton = skeleton_index;
155 } else {
156 last_skeleton->next = skeleton_index;
157 }
158 last_skeleton = skeleton;
159 }
160
161 // Wire the animations in the same order they are given in the descriptor.
162 Animation* last_animation = 0;
163 for (size_t i = 0; i < desc->num_animations; ++i) {
164 Animation* animation = make_animation(&desc->animations[i]);
165 const animation_idx animation_index = mem_get_animation_index(animation);
166 if (last_animation == 0) {
167 anima->animation = animation_index;
168 } else {
169 last_animation->next = animation_index;
170 }
171 last_animation = animation;
172 }
173
174 // Create joints.
175 anima->num_joints = desc->num_joints;
176 // Initialize all joints.
177 // Child and sibling pointers must be initialized before wiring up the
178 // hierarchy.
179 for (size_t i = 0; i < desc->num_joints; ++i) {
180 Joint* joint = get_anima_joint_mut(anima, i);
181 make_joint(anima, &desc->joints[i], joint);
182 }
183 // Wire up joints to their parents. -1 to skip the root.
184 for (size_t i = 0; i < desc->num_joints - 1; ++i) {
185 set_joint_parent(anima, i, desc->joints[i].parent);
186 }
187
188 return anima;
189}
190
191void gfx_destroy_anima(Anima** anima) {
192 assert(anima);
193
194 if (*anima) {
195 for (skeleton_idx i = (*anima)->skeleton; i.val != 0;) {
196 Skeleton* skeleton = mem_get_skeleton(i);
197 i = skeleton->next;
198 mem_free_skeleton(&skeleton);
199 }
200
201 for (animation_idx i = (*anima)->animation; i.val != 0;) {
202 Animation* animation = mem_get_animation(i);
203 i = animation->next;
204 mem_free_animation(&animation);
205 }
206
207 if ((*anima)->parent.val) {
208 gfx_del_node((*anima)->parent);
209 }
210
211 mem_free_anima(anima);
212 }
213}
214
215static Animation* find_animation(animation_idx index, const char* name) {
216 assert(name);
217
218 while (index.val != 0) {
219 Animation* animation = mem_get_animation(index);
220 if (sstring_eq_cstr(animation->name, name)) {
221 // LOGD(
222 // "Found animation at index %u, %s - %s", index.val,
223 // sstring_cstr(&animation->name), name);
224 // LOGD("Animation has duration %f", animation->duration);
225 return animation;
226 }
227 index = animation->next;
228 }
229
230 return 0;
231}
232
233bool gfx_play_animation(Anima* anima, const AnimationPlaySettings* settings) {
234 assert(anima);
235 assert(settings);
236
237 // TODO: Should we animate at t=0 here to kickstart the animation? Otherwise
238 // the client is forced to call gfx_update_animation() to do this.
239 Animation* animation = find_animation(anima->animation, settings->name);
240 if (!animation) {
241 return false;
242 }
243 // Playback initialized on first call to update().
244 AnimationState* state = &anima->state;
245 state->start_time = PLAYBACK_UNINITIALIZED;
246 state->animation = mem_get_animation_index(animation);
247 state->loop = settings->loop;
248 return true;
249}
250
251static void gfx_set_joint_position(Joint* joint, vec3 position) {
252 assert(joint);
253 mat4_set_v3(&joint->transform, position);
254}
255
256static void gfx_set_joint_rotation(Joint* joint, quat rotation) {
257 assert(joint);
258 mat4_set_3x3(&joint->transform, mat4_from_quat(rotation));
259}
260
261static void find_keyframes(const Channel* channel, R t, int* prev, int* next) {
262 assert(channel);
263 assert(prev);
264 assert(next);
265
266 *prev = -1;
267 *next = 0;
268 while (((*next + 1) < (int)channel->num_keyframes) &&
269 (t >= channel->keyframes[*next + 1].time)) {
270 (*prev)++;
271 (*next)++;
272 }
273}
274
275static R normalize_time(R a, R b, R t) {
276 assert(a <= t);
277 assert(t <= b);
278 return (t - a) / (b - a);
279}
280
281static quat interpolate_rotation(
282 const Channel* channel, int prev, int next, R t) {
283 assert(channel);
284
285 if (next == 0) {
286 // Animation has not started at this point in time yet.
287 return channel->keyframes[next].rotation;
288 } else {
289 switch (channel->interpolation) {
290 case StepInterpolation:
291 return channel->keyframes[prev].rotation;
292 case LinearInterpolation: {
293 const R normalized_t = normalize_time(
294 channel->keyframes[prev].time, channel->keyframes[next].time, t);
295 return qnormalize(qslerp(
296 channel->keyframes[prev].rotation, channel->keyframes[next].rotation,
297 normalized_t));
298 break;
299 }
300 case CubicSplineInterpolation:
301 assert(false); // TODO
302 return qmake(0, 0, 0, 0);
303 default:
304 assert(false);
305 return qmake(0, 0, 0, 0);
306 }
307 }
308}
309
310static vec3 interpolate_translation(
311 const Channel* channel, int prev, int next, R t) {
312 assert(channel);
313
314 if (next == 0) {
315 // Animation has not started at this point in time yet.
316 return channel->keyframes[next].translation;
317 } else {
318 switch (channel->interpolation) {
319 case StepInterpolation:
320 return channel->keyframes[prev].translation;
321 case LinearInterpolation: {
322 const R normalized_t = normalize_time(
323 channel->keyframes[prev].time, channel->keyframes[next].time, t);
324 return vec3_lerp(
325 channel->keyframes[prev].translation,
326 channel->keyframes[next].translation, normalized_t);
327 break;
328 }
329 case CubicSplineInterpolation:
330 assert(false); // TODO
331 return vec3_make(0, 0, 0);
332 default:
333 assert(false);
334 return vec3_make(0, 0, 0);
335 }
336 }
337}
338
339static void animate_channel(Anima* anima, const Channel* channel, R t) {
340 assert(anima);
341 assert(channel);
342 assert(channel->target < anima->num_joints);
343
344 int prev, next;
345 find_keyframes(channel, t, &prev, &next);
346
347 // Note that not all channels extend to the duration of an animation; some
348 // channels may stop animating their targets earlier. Clamp the animation time
349 // to the channel's end keyframe to make the rest of the math (normalize_time)
350 // work.
351 t = t > channel->keyframes[next].time ? channel->keyframes[next].time : t;
352
353 Joint* target = get_anima_joint_mut(anima, channel->target);
354
355 switch (channel->type) {
356 case RotationChannel: {
357 const quat rotation = interpolate_rotation(channel, prev, next, t);
358 gfx_set_joint_rotation(target, rotation);
359 break;
360 }
361 case TranslationChannel: {
362 const vec3 translation = interpolate_translation(channel, prev, next, t);
363 gfx_set_joint_position(target, translation);
364 break;
365 }
366 // Not yet supported.
367 case ScaleChannel:
368 case WeightsChannel:
369 default:
370 // TODO: Add back the assertion or add support for scaling.
371 // assert(false);
372 break;
373 }
374}
375
376static void compute_joint_matrices_rec(
377 Anima* anima, Joint* joint, const mat4* parent_global_joint_transform,
378 const mat4* root_inv_global_transform) {
379 assert(anima);
380 assert(joint);
381 assert(parent_global_joint_transform);
382 assert(root_inv_global_transform);
383
384 const mat4 global_joint_transform =
385 mat4_mul(*parent_global_joint_transform, joint->transform);
386
387 // Compute this joint's matrix.
388 joint->joint_matrix = mat4_mul(
389 *root_inv_global_transform,
390 mat4_mul(global_joint_transform, joint->inv_bind_matrix));
391
392 // Recursively compute the joint matrices for this joint's siblings.
393 if (joint->next != INDEX_NONE) {
394 Joint* sibling = get_anima_joint_mut(anima, joint->next);
395
396 compute_joint_matrices_rec(
397 anima, sibling, parent_global_joint_transform,
398 root_inv_global_transform);
399 }
400
401 // Recursively compute the joint matrices for this joint's children.
402 if (joint->child != INDEX_NONE) {
403 Joint* child = get_anima_joint_mut(anima, joint->child);
404
405 compute_joint_matrices_rec(
406 anima, child, &global_joint_transform, root_inv_global_transform);
407 }
408}
409
410void gfx_update_animation(Anima* anima, R t) {
411 assert(anima);
412
413 AnimationState* state = &anima->state;
414 if (state->animation.val == 0) {
415 return; // No active animation.
416 }
417 const Animation* animation = mem_get_animation(state->animation);
418 assert(animation);
419
420 // On a call to play(), the start time is set to -1 to signal that the
421 // animation playback has not yet been initialized.
422 if (state->start_time == PLAYBACK_UNINITIALIZED) {
423 state->start_time = t;
424 }
425 // Locate the current time point inside the animation's timeline.
426 assert(t >= state->start_time);
427 assert(animation->duration >= 0.0);
428 const R local_time = t - state->start_time;
429 const R animation_time = state->loop
430 ? rmod(local_time, animation->duration)
431 : clamp(local_time, 0.0, animation->duration);
432
433 // LOGD(
434 // "animation_time = %f, animation duration: %f", animation_time,
435 // animation->duration);
436
437 // Play through the animation to transform skeleton nodes.
438 for (size_t i = 0; i < animation->num_channels; ++i) {
439 const Channel* channel = &animation->channels[i];
440 animate_channel(anima, channel, animation_time);
441 }
442
443 // Compute joint matrices after having transformed the skeletons.
444 //
445 // The anima's parent node is the common ancestor of all skeletons, and its
446 // transform maps the skeletons from object space to world space. This is the
447 // transform used as the "global transform" in the joint matrix equations.
448 //
449 // Joint matrix calculation begins by descending from the anima's root joint,
450 // which we have constructed to be the common root of all skeletons.
451 //
452 // This procedure touches every joint exactly once.
453 SceneNode* root_node = mem_get_node(anima->parent);
454 // LOGD("Root: %u, child: %u", anima->parent.val, root->child.val);
455 const mat4 root_global_transform = gfx_get_node_global_transform(root_node);
456 const mat4 root_inv_global_transform = mat4_inverse(root_global_transform);
457
458 Joint* root_joint = get_anima_root_joint(anima);
459 compute_joint_matrices_rec(
460 anima, root_joint, &root_global_transform, &root_inv_global_transform);
461}
462
463const Skeleton* gfx_get_anima_skeleton(const Anima* anima, size_t i) {
464 assert(anima);
465
466 skeleton_idx skeleton_index = anima->skeleton;
467 const Skeleton* skeleton = mem_get_skeleton(skeleton_index);
468
469 for (size_t j = 1; j < i; ++j) {
470 skeleton_index = skeleton->next;
471 mem_get_skeleton(skeleton_index);
472 }
473
474 return skeleton;
475}
476
477size_t gfx_get_skeleton_num_joints(const Skeleton* skeleton) {
478 assert(skeleton);
479 return skeleton->num_joints;
480}
481
482bool gfx_joint_has_box(
483 const Anima* anima, const Skeleton* skeleton, size_t joint_index) {
484 assert(anima);
485 assert(skeleton);
486 assert(joint_index < skeleton->num_joints);
487
488 const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index);
489 return !aabb3_is_empty(joint->box);
490}
491
492Box gfx_get_joint_box(
493 const Anima* anima, const Skeleton* skeleton, size_t joint_index) {
494 assert(anima);
495 assert(skeleton);
496
497 const Joint* joint = get_skeleton_joint(anima, skeleton, joint_index);
498
499 // Transform the box to anima space.
500 // Note that joint matrices do not usually have a translation since joints
501 // mostly just rotate with respect to their parent.
502 const vec3 pmin = joint->box.min;
503 const vec3 pmax = joint->box.max;
504 return (Box){
505 .vertices = {
506 mat4_mul_vec3(
507 joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmax.z), 1),
508 mat4_mul_vec3(
509 joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmax.z), 1),
510 mat4_mul_vec3(
511 joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmax.z), 1),
512 mat4_mul_vec3(
513 joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmax.z), 1),
514 mat4_mul_vec3(
515 joint->joint_matrix, vec3_make(pmin.x, pmin.y, pmin.z), 1),
516 mat4_mul_vec3(
517 joint->joint_matrix, vec3_make(pmax.x, pmin.y, pmin.z), 1),
518 mat4_mul_vec3(
519 joint->joint_matrix, vec3_make(pmax.x, pmax.y, pmin.z), 1),
520 mat4_mul_vec3(
521 joint->joint_matrix, vec3_make(pmin.x, pmax.y, pmin.z), 1),
522 }
523 };
524}
diff --git a/src/scene/animation_impl.h b/src/scene/animation_impl.h
new file mode 100644
index 0000000..4408158
--- /dev/null
+++ b/src/scene/animation_impl.h
@@ -0,0 +1,98 @@
1#pragma once
2
3#include <gfx/scene/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#include <stdint.h>
16
17typedef struct Buffer Buffer;
18
19// Currently ignoring scale in skinning and animation.
20//
21// TODO: Simultaneous animation of disjoint animations.
22
23/// Skeleton joint.
24/// Joints are mutable and store the transform and joint matrices that result
25/// from animation, aside from the inverse bind matrix.
26typedef struct Joint {
27 joint_idx child; /// First child Joint; index into Anima's joints.
28 joint_idx next; /// Next sibling Joint; index into Anima's joints.
29 mat4 transform; /// Local transform relative to parent.
30 mat4 inv_bind_matrix; /// Transforms the mesh into the joint's local space.
31 mat4 joint_matrix; /// inv(global) * global joint transform * inv(bind).
32 aabb3 box; /// Bounding box of vertices affected by joint.
33} Joint;
34
35/// Animation skeleton.
36typedef struct Skeleton {
37 skeleton_idx next;
38 size_t num_joints;
39 joint_idx joints[GFX_MAX_NUM_JOINTS]; /// Indices into Anima's joints array.
40} Skeleton;
41
42/// A keyframe of animation.
43typedef struct Keyframe {
44 R time; /// Start time in [0, end animation time]
45 union {
46 vec3 translation;
47 quat rotation;
48 };
49} Keyframe;
50
51/// Animation channel.
52typedef struct Channel {
53 joint_idx target; /// Index into Anima's joints array.
54 ChannelType type;
55 AnimationInterpolation interpolation;
56 size_t num_keyframes;
57 Keyframe keyframes[GFX_MAX_NUM_KEYFRAMES];
58} Channel;
59
60/// A skeletal animation.
61typedef struct Animation {
62 animation_idx next;
63 sstring name;
64 R duration;
65 size_t num_channels;
66 Channel channels[GFX_MAX_NUM_CHANNELS];
67} Animation;
68
69/// Animation state.
70///
71/// This represents the current state of an animation.
72typedef struct AnimationState {
73 R start_time; // Time when the current animation started playing. -1 means the
74 // animation playback has not yet been initialized.
75 animation_idx animation; // Current animation. 0 = no animation.
76 bool loop;
77} AnimationState;
78
79/// Animation object.
80///
81/// This is the top-level animation object that encapsulates everything
82/// necessary for animation.
83///
84/// For lack of a better name, this is called an Anima. It is short and the
85/// Latin root of animation.
86///
87/// The last joint of the joints array at index 'num_joints - 1' is the root of
88/// all skeletons; specifically, the root of all joints that otherwise would
89/// have no parent (a skeleton need not have its own root and can be a set of
90/// disjoint node hierarchies).
91typedef struct Anima {
92 node_idx parent; /// Parent SceneNode.
93 skeleton_idx skeleton; /// Index of first skeleton.
94 animation_idx animation; /// Index of first animation.
95 AnimationState state; /// Current animation state.
96 size_t num_joints; /// Number of actual joints in the array.
97 Joint joints[GFX_MAX_NUM_JOINTS]; /// Shared by all skeletons.
98} Anima;
diff --git a/src/scene/camera.c b/src/scene/camera.c
new file mode 100644
index 0000000..be7d806
--- /dev/null
+++ b/src/scene/camera.c
@@ -0,0 +1,37 @@
1#include "camera_impl.h"
2
3#include "node_impl.h"
4#include "scene_memory.h"
5
6#include <assert.h>
7
8SceneCamera* gfx_make_camera() {
9 SceneCamera* camera = mem_alloc_camera();
10
11 camera->camera = camera_perspective(
12 /*fovy=*/90.0 * TO_RAD, /*aspect=*/16.0 / 9.0,
13 /*near=*/0.1, /*far=*/1000);
14
15 return camera;
16}
17
18void gfx_destroy_camera(SceneCamera** camera) {
19 assert(camera);
20 if (*camera) {
21 if ((*camera)->parent.val) {
22 gfx_del_node((*camera)->parent);
23 }
24 mem_free_camera(camera);
25 }
26}
27
28void gfx_set_camera_camera(SceneCamera* scene_camera, Camera* camera) {
29 assert(scene_camera);
30 assert(camera);
31 scene_camera->camera = *camera;
32}
33
34Camera* gfx_get_camera_camera(SceneCamera* camera) {
35 assert(camera);
36 return &camera->camera;
37}
diff --git a/src/scene/camera_impl.h b/src/scene/camera_impl.h
new file mode 100644
index 0000000..20c3890
--- /dev/null
+++ b/src/scene/camera_impl.h
@@ -0,0 +1,12 @@
1#pragma once
2
3#include <gfx/scene/camera.h>
4
5#include "types.h"
6
7#include <math/camera.h>
8
9typedef struct SceneCamera {
10 Camera camera;
11 node_idx parent; // Parent SceneNode.
12} SceneCamera;
diff --git a/src/scene/light.c b/src/scene/light.c
new file mode 100644
index 0000000..adbec8d
--- /dev/null
+++ b/src/scene/light.c
@@ -0,0 +1,42 @@
1#include "light_impl.h"
2
3#include "node_impl.h"
4#include "scene_memory.h"
5
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 if ((*light)->parent.val) {
38 gfx_del_node((*light)->parent);
39 }
40 mem_free_light(light);
41 }
42}
diff --git a/src/scene/light_impl.h b/src/scene/light_impl.h
new file mode 100644
index 0000000..1aa0bb4
--- /dev/null
+++ b/src/scene/light_impl.h
@@ -0,0 +1,25 @@
1#pragma once
2
3#include <gfx/scene/light.h>
4
5#include "types.h"
6
7typedef struct Texture Texture;
8
9/// An environment light.
10typedef struct EnvironmentLight {
11 const Texture* environment_map;
12 const Texture* irradiance_map; // Renderer implementation.
13 const Texture* prefiltered_environment_map; // Renderer implementation.
14 int max_reflection_lod; // Mandatory when prefiltered_environment_map is
15 // given.
16} EnvironmentLight;
17
18/// A scene light.
19typedef struct Light {
20 LightType type;
21 union {
22 EnvironmentLight environment;
23 };
24 node_idx parent; // Parent SceneNode.
25} Light;
diff --git a/src/scene/material.c b/src/scene/material.c
new file mode 100644
index 0000000..3248243
--- /dev/null
+++ b/src/scene/material.c
@@ -0,0 +1,57 @@
1#include "material_impl.h"
2
3#include "scene_memory.h"
4
5#include <gfx/core.h>
6
7static void material_make(Material* material, const MaterialDesc* desc) {
8 assert(material);
9 assert(desc);
10 assert(desc->num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
11 material->num_uniforms = 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); }
25
26static void set_uniform(ShaderProgram* prog, const ShaderUniform* uniform) {
27 switch (uniform->type) {
28 case UniformTexture:
29 gfx_set_texture_uniform(prog, uniform->name.str, uniform->value.texture);
30 break;
31 case UniformMat4:
32 gfx_set_mat4_uniform(prog, uniform->name.str, &uniform->value.mat4);
33 break;
34 case UniformVec3:
35 gfx_set_vec3_uniform(prog, uniform->name.str, uniform->value.vec3);
36 break;
37 case UniformVec4:
38 gfx_set_vec4_uniform(prog, uniform->name.str, uniform->value.vec4);
39 break;
40 case UniformFloat:
41 gfx_set_float_uniform(prog, uniform->name.str, uniform->value.scalar);
42 break;
43 case UniformMat4Array:
44 gfx_set_mat4_array_uniform(
45 prog, uniform->name.str, uniform->value.array.values,
46 uniform->value.array.count);
47 break;
48 }
49}
50
51void material_activate(ShaderProgram* shader, const Material* material) {
52 assert(material);
53 for (int i = 0; i < material->num_uniforms; ++i) {
54 const ShaderUniform* uniform = &material->uniforms[i];
55 set_uniform(shader, uniform);
56 }
57}
diff --git a/src/scene/material_impl.h b/src/scene/material_impl.h
new file mode 100644
index 0000000..a6aa95b
--- /dev/null
+++ b/src/scene/material_impl.h
@@ -0,0 +1,16 @@
1#pragma once
2
3#include <gfx/scene/material.h>
4
5typedef struct ShaderProgram ShaderProgram;
6
7typedef struct Material {
8 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL];
9 int num_uniforms;
10} Material;
11
12/// Activate the material.
13///
14/// This activates the material's shader and configures the shader uniforms that
15/// are specific to the material.
16void material_activate(ShaderProgram* shader, const Material* material);
diff --git a/src/scene/mesh.c b/src/scene/mesh.c
new file mode 100644
index 0000000..1a93bed
--- /dev/null
+++ b/src/scene/mesh.c
@@ -0,0 +1,24 @@
1#include "mesh_impl.h"
2
3#include "scene_memory.h"
4
5#include <assert.h>
6
7static void mesh_make(Mesh* mesh, const MeshDesc* desc) {
8 assert(mesh);
9 assert(desc);
10 assert(desc->geometry);
11 assert(desc->material);
12 assert(desc->shader);
13 mesh->geometry = desc->geometry;
14 mesh->material = desc->material;
15 mesh->shader = desc->shader;
16}
17
18Mesh* gfx_make_mesh(const MeshDesc* desc) {
19 Mesh* mesh = mem_alloc_mesh();
20 mesh_make(mesh, desc);
21 return mesh;
22}
23
24void 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..560b77e
--- /dev/null
+++ b/src/scene/mesh_impl.h
@@ -0,0 +1,12 @@
1#pragma once
2
3#include <gfx/scene/mesh.h>
4
5typedef struct Mesh {
6 const Geometry* geometry;
7 const Material* material;
8 ShaderProgram* shader;
9} Mesh;
10
11// TODO: a mesh_render() that takes a transform, applies the material and the
12// transform, and then renders the geometry.
diff --git a/src/scene/model.c b/src/scene/model.c
new file mode 100644
index 0000000..cc41a9a
--- /dev/null
+++ b/src/scene/model.c
@@ -0,0 +1,45 @@
1#include "model_impl.h"
2
3#include <gfx/scene/node.h>
4
5#include "scene_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..a99d32c
--- /dev/null
+++ b/src/scene/model_impl.h
@@ -0,0 +1,17 @@
1#pragma once
2
3#include <gfx/scene/model.h>
4
5#include "scene_memory.h"
6
7/// Model.
8typedef struct Model {
9 node_idx root;
10 node_idx parent; // Parent SceneNode.
11} Model;
12
13/// Create a new model.
14Model* gfx_make_model(const SceneNode* root);
15
16/// Destroy the model.
17void gfx_del_model(Model**);
diff --git a/src/scene/node.c b/src/scene/node.c
new file mode 100644
index 0000000..67ce93c
--- /dev/null
+++ b/src/scene/node.c
@@ -0,0 +1,409 @@
1#include "node_impl.h"
2
3#include "animation_impl.h"
4#include "camera_impl.h"
5#include "light_impl.h"
6#include "model_impl.h"
7#include "object_impl.h"
8#include "scene_graph.h"
9#include "scene_memory.h"
10
11#include "gfx_assert.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() {
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 anima->parent = mem_get_node_index(node);
34 return node;
35}
36
37SceneNode* gfx_make_camera_node(SceneCamera* camera) {
38 assert(camera);
39 SceneNode* node = gfx_make_node();
40 node->type = CameraNode;
41 node->camera = mem_get_camera_index(camera);
42 camera->parent = mem_get_node_index(node);
43 return node;
44}
45
46SceneNode* gfx_make_light_node(Light* light) {
47 assert(light);
48 SceneNode* node = gfx_make_node();
49 node->type = LightNode;
50 node->light = mem_get_light_index(light);
51 light->parent = mem_get_node_index(node);
52 return node;
53}
54
55SceneNode* gfx_make_model_node(Model* model) {
56 assert(model);
57 SceneNode* node = gfx_make_node();
58 node->type = ModelNode;
59 node->model = mem_get_model_index(model);
60 model->parent = mem_get_node_index(node);
61 return node;
62}
63
64SceneNode* gfx_make_object_node(SceneObject* object) {
65 assert(object);
66 SceneNode* node = gfx_make_node();
67 node->type = ObjectNode;
68 node->object = mem_get_object_index(object);
69 object->parent = mem_get_node_index(node);
70 return node;
71}
72
73/// Frees the node's resource.
74static void free_node_resource(SceneNode* node) {
75 assert(node);
76
77 // Set the resource's parent node back to 0 to avoid a recursive call into
78 // gfx_del_node().
79 switch (node->type) {
80 case AnimaNode: {
81 Anima* anima = mem_get_anima(node->anima);
82 anima->parent.val = 0;
83 gfx_destroy_anima(&anima);
84 return;
85 }
86 case CameraNode: {
87 SceneCamera* camera = mem_get_camera(node->camera);
88 camera->parent.val = 0;
89 gfx_destroy_camera(&camera);
90 return;
91 }
92 case LightNode: {
93 Light* light = mem_get_light(node->light);
94 light->parent.val = 0;
95 gfx_destroy_light(&light);
96 return;
97 }
98 case ModelNode: {
99 return; // Model data is owned by the asset cache.
100 }
101 case ObjectNode: {
102 SceneObject* object = mem_get_object(node->object);
103 object->parent.val = 0;
104 gfx_destroy_object(&object);
105 return;
106 }
107 case LogicalNode:
108 return; // Logical nodes have no resource.
109 }
110 FAIL("unhandled node type");
111}
112
113void gfx_construct_anima_node(SceneNode* node, Anima* anima) {
114 assert(node);
115 assert(anima);
116 free_node_resource(node);
117 node->type = AnimaNode;
118 node->anima = mem_get_anima_index(anima);
119 anima->parent = mem_get_node_index(node);
120}
121
122void gfx_construct_camera_node(SceneNode* node, SceneCamera* camera) {
123 assert(node);
124 assert(camera);
125 free_node_resource(node);
126 node->type = CameraNode;
127 node->camera = mem_get_camera_index(camera);
128 camera->parent = mem_get_node_index(node);
129}
130
131// TODO: Add a common helper function between each gfx_make_xyz_node() and
132// gfx_construct_xyz_node() pair.
133void gfx_construct_light_node(SceneNode* node, Light* light) {
134 assert(node);
135 assert(light);
136 free_node_resource(node);
137 node->type = LightNode;
138 node->light = mem_get_light_index(light);
139 light->parent = mem_get_node_index(node);
140}
141
142void gfx_construct_model_node(SceneNode* node, Model* model) {
143 assert(node);
144 assert(model);
145 free_node_resource(node);
146 node->type = ModelNode;
147 node->model = mem_get_model_index(model);
148 model->parent = mem_get_node_index(node);
149}
150
151void gfx_construct_object_node(SceneNode* node, SceneObject* object) {
152 assert(node);
153 assert(object);
154 free_node_resource(node);
155 node->type = ObjectNode;
156 node->object = mem_get_object_index(object);
157 object->parent = mem_get_node_index(node);
158}
159
160static void destroy_node_rec(SceneNode* node) {
161 assert(node);
162
163 // First child.
164 if (node->child.val) {
165 destroy_node_rec(mem_get_node(node->child));
166 }
167
168 // Right sibling.
169 if (node->next.val) {
170 destroy_node_rec(mem_get_node(node->next));
171 }
172
173 free_node_resource(node);
174 mem_free_node(&node);
175}
176
177void gfx_destroy_node(SceneNode** node) {
178 assert(node);
179 if (*node) {
180 // Since the node and the whole hierarchy under it gets destroyed, there is
181 // no need to individually detach every node from its hierarchy. We can
182 // simply detach the given node and then destroy it and its sub-hierarchy.
183 TREE_REMOVE(*node);
184 destroy_node_rec(*node);
185 *node = 0;
186 }
187}
188
189// TODO: Think more about ownership of nodes and resources. Should this function
190// even exist?
191void gfx_del_node(node_idx index) {
192 assert(index.val);
193 SceneNode* node = mem_get_node(index);
194 assert(node);
195 // TODO: Should destroy children recursively?
196 TREE_REMOVE(node);
197 mem_free_node(&node);
198}
199
200NodeType gfx_get_node_type(const SceneNode* node) {
201 assert(node);
202 return node->type;
203}
204
205#define NODE_GET(node, field, expected_type) \
206 { \
207 assert(node); \
208 assert(node->type == expected_type); \
209 return mem_get_##field(node->field); \
210 }
211
212const Anima* gfx_get_node_anima(const SceneNode* node) {
213 NODE_GET(node, anima, AnimaNode);
214}
215
216Anima* gfx_get_node_anima_mut(SceneNode* node) {
217 NODE_GET(node, anima, AnimaNode);
218}
219
220const SceneCamera* gfx_get_node_camera(const SceneNode* node) {
221 NODE_GET(node, camera, CameraNode);
222}
223
224SceneCamera* gfx_get_node_camera_mut(SceneNode* node) {
225 NODE_GET(node, camera, CameraNode);
226}
227
228const Light* gfx_get_node_light(const SceneNode* node) {
229 NODE_GET(node, light, LightNode);
230}
231
232Light* gfx_get_node_light_mut(SceneNode* node) {
233 NODE_GET(node, light, LightNode);
234}
235
236const Model* gfx_get_node_model(const SceneNode* node) {
237 NODE_GET(node, model, ModelNode);
238}
239
240Model* gfx_get_node_model_mut(SceneNode* node) {
241 NODE_GET(node, model, ModelNode);
242}
243
244const SceneObject* gfx_get_node_object(const SceneNode* node) {
245 NODE_GET(node, object, ObjectNode);
246}
247
248SceneObject* gfx_get_node_object_mut(SceneNode* node) {
249 NODE_GET(node, object, ObjectNode);
250}
251
252const SceneNode* gfx_get_node_parent(const SceneNode* node) {
253 assert(node);
254 return mem_get_node(node->parent);
255}
256
257SceneNode* gfx_get_node_parent_mut(SceneNode* node) {
258 assert(node);
259 return mem_get_node(node->parent);
260}
261
262const SceneNode* gfx_get_node_child(const SceneNode* node) {
263 assert(node);
264 if (node->child.val) {
265 return mem_get_node(node->child);
266 } else {
267 return 0;
268 }
269}
270
271SceneNode* gfx_get_node_child_mut(SceneNode* node) {
272 return (SceneNode*)gfx_get_node_child(node);
273}
274
275const SceneNode* gfx_get_node_sibling(const SceneNode* node) {
276 assert(node);
277 if (node->next.val) {
278 return mem_get_node(node->next);
279 } else {
280 return 0;
281 }
282}
283
284SceneNode* gfx_get_node_sibling_mut(SceneNode* node) {
285 return (SceneNode*)gfx_get_node_sibling(node);
286}
287
288mat4 gfx_get_node_transform(const SceneNode* node) {
289 assert(node);
290 return node->transform;
291}
292
293mat4 gfx_get_node_global_transform(const SceneNode* node) {
294 assert(node);
295 mat4 transform = node->transform;
296 node_idx parent_index = node->parent;
297 while (parent_index.val != 0) {
298 const SceneNode* parent = mem_get_node(parent_index);
299 transform = mat4_mul(parent->transform, transform);
300 parent_index = parent->parent;
301 }
302 return transform;
303}
304
305void gfx_set_node_parent(SceneNode* child, SceneNode* parent_node) {
306 assert(child);
307 // Parent can be null.
308 SET_PARENT(child, parent_node);
309}
310
311void gfx_set_node_transform(SceneNode* node, const mat4* transform) {
312 assert(node);
313 assert(transform);
314 node->transform = *transform;
315}
316
317void gfx_set_node_position(SceneNode* node, const vec3* position) {
318 assert(node);
319 assert(position);
320 mat4_set_v3(&node->transform, *position);
321}
322
323void gfx_set_node_rotation(SceneNode* node, const quat* rotation) {
324 assert(node);
325 assert(rotation);
326 mat4_set_3x3(&node->transform, mat4_from_quat(*rotation));
327}
328
329void gfx_set_node_rotation_mat(SceneNode* node, const mat4* rotation) {
330 assert(node);
331 assert(rotation);
332 mat4_set_3x3(&node->transform, *rotation);
333}
334
335static const char* get_node_type_str(NodeType type) {
336 switch (type) {
337 case LogicalNode:
338 return "LogicalNode";
339 case AnimaNode:
340 return "AnimaNode";
341 case CameraNode:
342 return "CameraNode";
343 case LightNode:
344 return "LightNode";
345 case ModelNode:
346 return "ModelNode";
347 case ObjectNode:
348 return "ObjectNode";
349 }
350 FAIL("Unhandled node type");
351 return "";
352}
353
354static void log_node_hierarchy_rec(const SceneNode* node, const sstring* pad) {
355 assert(node);
356 assert(pad);
357
358 LOGI(
359 "%s%s (%u)", sstring_cstr(pad), get_node_type_str(node->type),
360 mem_get_node_index(node).val);
361
362 // Log the children.
363 if (node->child.val) {
364 const sstring new_pad = sstring_concat_cstr(*pad, " ");
365 log_node_hierarchy_rec(mem_get_node(node->child), &new_pad);
366 }
367
368 // Then log the siblings.
369 if (node->next.val) {
370 log_node_hierarchy_rec(mem_get_node(node->next), pad);
371 }
372}
373
374void gfx_log_node_hierarchy(const SceneNode* node) {
375 const sstring pad = sstring_make("");
376 log_node_hierarchy_rec(node, &pad);
377}
378
379static SceneNode* clone_scene_rec(const SceneNode* node) {
380 assert(node);
381
382 SceneNode* copy = mem_alloc_node();
383 *copy = *node; // Shallow clone of the node's resource.
384
385 if (node->child.val) {
386 SceneNode* child = mem_get_node(node->child);
387 SceneNode* child_copy = clone_scene_rec(child);
388 copy->child = mem_get_node_index(child_copy);
389 child_copy->parent = mem_get_node_index(copy);
390 }
391
392 if (node->next.val) {
393 SceneNode* next = mem_get_node(node->next);
394 SceneNode* next_copy = clone_scene_rec(next);
395 copy->next = mem_get_node_index(next_copy);
396 next_copy->prev = mem_get_node_index(copy);
397 }
398
399 return copy;
400}
401
402SceneNode* gfx_clone_scene_shallow(const SceneNode* node) {
403 assert(node);
404 // Must be a root node; not allowed to have siblings.
405 assert(!node->prev.val);
406 assert(!node->next.val);
407
408 return clone_scene_rec(node);
409}
diff --git a/src/scene/node_impl.h b/src/scene/node_impl.h
new file mode 100644
index 0000000..c79f252
--- /dev/null
+++ b/src/scene/node_impl.h
@@ -0,0 +1,40 @@
1#pragma once
2
3#include <gfx/scene/node.h>
4
5#include "types.h"
6
7#include <cstring.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..e8e3ee6
--- /dev/null
+++ b/src/scene/object.c
@@ -0,0 +1,83 @@
1#include "object_impl.h"
2
3#include <gfx/core.h>
4
5#include "mesh_impl.h"
6#include "node_impl.h"
7#include "scene_memory.h"
8
9#include <assert.h>
10
11static aabb3 calc_object_aabb(const SceneObject* object) {
12 assert(object);
13
14 bool first = true;
15 aabb3 box;
16
17 mesh_link_idx ml = object->mesh_link;
18 while (ml.val) {
19 const MeshLink* mesh_link = mem_get_mesh_link(ml);
20 const mesh_idx mi = mesh_link->mesh;
21 if (mi.val) {
22 const Mesh* mesh = mem_get_mesh(mi);
23 const aabb3 mesh_box = gfx_get_geometry_aabb(mesh->geometry);
24 if (first) {
25 box = mesh_box;
26 first = false;
27 } else {
28 box = aabb3_sum(box, mesh_box);
29 }
30 }
31 ml = mesh_link->next;
32 }
33
34 return box;
35}
36
37static void add_object_mesh(SceneObject* object, Mesh* mesh) {
38 assert(object);
39 assert(mesh);
40
41 MeshLink* link = mem_alloc_mesh_link();
42 link->mesh = mem_get_mesh_index(mesh);
43 link->next = object->mesh_link;
44 object->mesh_link = mem_get_mesh_link_index(link);
45}
46
47SceneObject* gfx_make_object(const ObjectDesc* desc) {
48 assert(desc);
49
50 SceneObject* object = mem_alloc_object();
51 for (size_t i = 0; i < desc->num_meshes; ++i) {
52 add_object_mesh(object, desc->meshes[i]);
53 }
54 object->box = calc_object_aabb(object);
55 return object;
56}
57
58void gfx_destroy_object(SceneObject** object) {
59 assert(object);
60
61 if (*object) {
62 if ((*object)->parent.val) {
63 gfx_del_node((*object)->parent);
64 }
65 mem_free_object(object);
66 }
67}
68
69void gfx_set_object_skeleton(SceneObject* object, const Skeleton* skeleton) {
70 assert(object);
71 assert(skeleton);
72 object->skeleton = mem_get_skeleton_index(skeleton);
73}
74
75const Skeleton* gfx_get_object_skeleton(const SceneObject* object) {
76 assert(object);
77 return (object->skeleton.val == 0) ? 0 : mem_get_skeleton(object->skeleton);
78}
79
80aabb3 gfx_get_object_aabb(const SceneObject* object) {
81 assert(object);
82 return object->box;
83}
diff --git a/src/scene/object_impl.h b/src/scene/object_impl.h
new file mode 100644
index 0000000..88f8e31
--- /dev/null
+++ b/src/scene/object_impl.h
@@ -0,0 +1,26 @@
1#pragma once
2
3#include <gfx/scene/object.h>
4
5#include "types.h"
6
7#include <math/mat4.h>
8
9typedef struct MeshLink {
10 mesh_idx mesh;
11 mesh_link_idx next; // Next MeshLink in the list.
12} MeshLink;
13
14/// Scene object.
15///
16/// A SceneObject does not own its Meshes, and they are instead shared for
17/// re-use. The SceneObject consequently embeds a list of MeshLinks as opposed
18/// to a list of Meshes. The MeshLinks define a list of Meshes, which can be
19/// different for each SceneObject. Each SceneObject may then have a unique list
20/// of Meshes, and the Meshes are re-used.
21typedef struct SceneObject {
22 mesh_link_idx mesh_link; /// First MeshLink in the list.
23 skeleton_idx skeleton; /// 0 for static objects.
24 node_idx parent; /// Parent SceneNode.
25 aabb3 box;
26} SceneObject;
diff --git a/src/scene/scene.c b/src/scene/scene.c
new file mode 100644
index 0000000..54452dd
--- /dev/null
+++ b/src/scene/scene.c
@@ -0,0 +1,25 @@
1#include "scene_impl.h"
2
3#include "node_impl.h"
4#include "scene_memory.h"
5
6#include <assert.h>
7
8Scene* gfx_make_scene(void) {
9 Scene* scene = mem_alloc_scene();
10 scene->root = gfx_make_node();
11 return scene;
12}
13
14void gfx_destroy_scene(Scene** scene) {
15 assert(scene);
16 if (*scene) {
17 gfx_destroy_node(&(*scene)->root);
18 mem_free_scene(scene);
19 }
20}
21
22SceneNode* gfx_get_scene_root(Scene* scene) {
23 assert(scene);
24 return scene->root;
25}
diff --git a/src/scene/scene_graph.h b/src/scene/scene_graph.h
new file mode 100644
index 0000000..a26f828
--- /dev/null
+++ b/src/scene/scene_graph.h
@@ -0,0 +1,138 @@
1/// Functions for list manipulation.
2#pragma once
3
4#include "scene_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((INDEX), camera_idx \
11 : mem_get_camera, material_idx \
12 : mem_get_material, mesh_idx \
13 : mem_get_mesh, mesh_link_idx \
14 : mem_get_mesh_link, node_idx \
15 : mem_get_node, object_idx \
16 : mem_get_object, scene_idx \
17 : mem_get_scene)(INDEX)
18
19#define MEM_GET_INDEX(ITEM) \
20 _Generic((ITEM), SceneCamera * \
21 : mem_get_camera_index, Material * \
22 : mem_get_material_index, Mesh * \
23 : mem_get_mesh_index, MeshLink * \
24 : mem_get_mesh_link_index, SceneNode * \
25 : mem_get_node_index, SceneObject * \
26 : mem_get_object_index, Scene * \
27 : mem_get_scene_index)(ITEM)
28
29/// Assert the list node invariant.
30///
31/// - A node does not point to itself.
32#define ASSERT_LIST_NODE_INVARIANT(ITEM) \
33 { \
34 const gfx_idx item_idx = MEM_GET_INDEX(ITEM).val; \
35 assert((ITEM)->prev.val != item_idx); \
36 assert((ITEM)->next.val != item_idx); \
37 }
38
39/// Assert the tree node invariant.
40///
41/// - A node does not point to itself.
42/// - The node's left and right siblings cannot be equal, unless both are 0.
43/// - The node's left/right sibling cannot be its child, unless both are 0.
44/// - The node's parent cannot be the node's child or sibling, unless it's 0.
45/// - If the node has a parent and the node is the leftmost sibling, then the
46/// parent's child is the node.
47#define ASSERT_TREE_NODE_INVARIANT(ITEM) \
48 { \
49 const gfx_idx item_idx = MEM_GET_INDEX(ITEM).val; \
50 assert((ITEM)->prev.val != item_idx); \
51 assert((ITEM)->next.val != item_idx); \
52 if ((ITEM)->prev.val) { \
53 assert((ITEM)->prev.val != (ITEM)->next.val); \
54 } \
55 if ((ITEM)->child.val) { \
56 assert((ITEM)->child.val != (ITEM)->prev.val); \
57 assert((ITEM)->child.val != (ITEM)->next.val); \
58 } \
59 assert((ITEM)->parent.val != item_idx); \
60 if ((ITEM)->parent.val && !(ITEM)->prev.val) { \
61 assert((ITEM)->parent.val != (ITEM)->prev.val); \
62 assert((ITEM)->parent.val != (ITEM)->next.val); \
63 const __typeof__(ITEM) item_parent = MEM_GET((ITEM)->parent); \
64 assert(item_parent->child.val == item_idx); \
65 } \
66 }
67
68/// Prepend an item to a list.
69/// Modify HEAD_INDEX to equal the index of the new head.
70#define LIST_PREPEND(HEAD_INDEX, ITEM) \
71 (ITEM)->next = HEAD_INDEX; \
72 if (HEAD_INDEX.val) { \
73 __typeof__(ITEM) old_head = MEM_GET(HEAD_INDEX); \
74 old_head->prev = MEM_GET_INDEX(ITEM); \
75 } \
76 HEAD_INDEX = MEM_GET_INDEX(ITEM); \
77 ASSERT_LIST_NODE_INVARIANT(ITEM);
78
79/// Disconnect an item from its siblings.
80#define LIST_REMOVE(ITEM) \
81 if ((ITEM)->prev.val) { \
82 __typeof__(ITEM) prev_sibling = MEM_GET((ITEM)->prev); \
83 prev_sibling->next = (ITEM)->next; \
84 } \
85 if ((ITEM)->next.val) { \
86 __typeof__(ITEM) next_sibling = MEM_GET((ITEM)->next); \
87 next_sibling->prev = (ITEM)->prev; \
88 } \
89 (ITEM)->prev.val = 0; \
90 (ITEM)->next.val = 0; \
91 ASSERT_LIST_NODE_INVARIANT(ITEM);
92
93/// Set the child's parent.
94///
95/// The hierarchy is a strict tree hierarchy and a parent node points to its
96/// first/leftmost child only. To add a new child, the new child becomes the
97/// leftmost node in the list of siblings, the one that the parent then points
98/// to.
99///
100/// The child is also completely disconnected from its previous hierarchy. This
101/// is because siblings in a hierarchy must all point to the same parent.
102#define SET_PARENT(CHILD, PARENT) \
103 assert(CHILD); \
104 assert(CHILD != PARENT); \
105 ASSERT_TREE_NODE_INVARIANT(CHILD); \
106 ASSERT_TREE_NODE_INVARIANT(PARENT); \
107 TREE_REMOVE(CHILD); /* Disconnect CHILD from its previous hierarchy. */ \
108 if (PARENT) { \
109 LIST_PREPEND((PARENT)->child, CHILD); \
110 (CHILD)->parent = MEM_GET_INDEX(PARENT); \
111 } else { \
112 (CHILD)->parent.val = 0; \
113 } \
114 ASSERT_TREE_NODE_INVARIANT(CHILD); \
115 if (PARENT) { \
116 ASSERT_TREE_NODE_INVARIANT(PARENT); \
117 }
118
119/// Remove an item from its hierarchy.
120///
121/// The item is disconnected from its parents and siblings. The hierarchy rooted
122/// under the item remains intact.
123#define TREE_REMOVE(ITEM) \
124 assert(ITEM); \
125 if ((ITEM)->parent.val) { \
126 /* The parent points only to its first/leftmost child. If this item is */ \
127 /* the leftmost sibling, then we need to rewire the parent to point to */ \
128 /* the next sibling to keep the parent connected to its children. */ \
129 __typeof__(ITEM) parent = MEM_GET((ITEM)->parent); \
130 const __typeof__(ITEM) parent_child = MEM_GET(parent->child); \
131 if (parent_child == ITEM) { \
132 assert((ITEM)->prev.val == 0); \
133 parent->child = (ITEM)->next; \
134 } \
135 } \
136 (ITEM)->parent.val = 0; \
137 LIST_REMOVE(ITEM); /* Disconnect ITEM from its siblings. */ \
138 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..992f620
--- /dev/null
+++ b/src/scene/scene_impl.h
@@ -0,0 +1,13 @@
1#pragma once
2
3#include <gfx/scene/scene.h>
4
5#include "types.h"
6
7typedef struct SceneNode SceneNode;
8
9typedef struct Scene {
10 SceneNode* root;
11 scene_idx next;
12 scene_idx prev;
13} Scene;
diff --git a/src/scene/scene_memory.c b/src/scene/scene_memory.c
new file mode 100644
index 0000000..85c27e7
--- /dev/null
+++ b/src/scene/scene_memory.c
@@ -0,0 +1,149 @@
1#include "scene_memory.h"
2
3#include <gfx/sizes.h>
4
5#include "animation_impl.h"
6#include "camera_impl.h"
7#include "light_impl.h"
8#include "material_impl.h"
9#include "mesh_impl.h"
10#include "model_impl.h"
11#include "node_impl.h"
12#include "object_impl.h"
13#include "scene_impl.h"
14
15#include <mempool.h>
16
17DEF_MEMPOOL(anima_pool, Anima, GFX_MAX_NUM_ANIMAS)
18DEF_MEMPOOL(animation_pool, Animation, GFX_MAX_NUM_ANIMATIONS)
19DEF_MEMPOOL(camera_pool, SceneCamera, GFX_MAX_NUM_CAMERAS)
20DEF_MEMPOOL(light_pool, Light, GFX_MAX_NUM_LIGHTS)
21DEF_MEMPOOL(material_pool, Material, GFX_MAX_NUM_MATERIALS)
22DEF_MEMPOOL(mesh_pool, Mesh, GFX_MAX_NUM_MESHES)
23DEF_MEMPOOL(mesh_link_pool, MeshLink, GFX_MAX_NUM_MESH_LINKS)
24DEF_MEMPOOL(model_pool, Model, GFX_MAX_NUM_MODELS)
25DEF_MEMPOOL(node_pool, SceneNode, GFX_MAX_NUM_NODES)
26DEF_MEMPOOL(object_pool, SceneObject, GFX_MAX_NUM_OBJECTS)
27DEF_MEMPOOL(scene_pool, Scene, GFX_MAX_NUM_SCENES)
28DEF_MEMPOOL(skeleton_pool, Skeleton, GFX_MAX_NUM_SKELETONS)
29
30/// Scene memory.
31///
32/// Holds memory pools for every type of scene object.
33typedef struct SceneMemory {
34 anima_pool animas;
35 animation_pool animations;
36 camera_pool cameras;
37 light_pool lights;
38 material_pool materials;
39 mesh_pool meshs; // Purposeful typo to make the PLURAL() macro work.
40 mesh_link_pool mesh_links;
41 model_pool models;
42 node_pool nodes;
43 object_pool objects;
44 scene_pool scenes;
45 skeleton_pool skeletons;
46} SceneMemory;
47
48static SceneMemory mem;
49
50#define ALLOC_DUMMY(POOL) \
51 { \
52 const void* object = mempool_alloc(POOL); \
53 assert(mempool_get_block_index(POOL, object) == 0); \
54 }
55
56#define PLURAL(name) name##s
57#define MEM_FIELD(name) mem.PLURAL(name)
58
59void scene_mem_init() {
60 mempool_make(&mem.animas);
61 mempool_make(&mem.animations);
62 mempool_make(&mem.cameras);
63 mempool_make(&mem.lights);
64 mempool_make(&mem.materials);
65 mempool_make(&mem.meshs);
66 mempool_make(&mem.mesh_links);
67 mempool_make(&mem.models);
68 mempool_make(&mem.nodes);
69 mempool_make(&mem.objects);
70 mempool_make(&mem.scenes);
71 mempool_make(&mem.skeletons);
72
73 // Allocate dummy objects at index 0 to guarantee that no objects allocated by
74 // the caller map to index 0.
75 ALLOC_DUMMY(&mem.animas);
76 ALLOC_DUMMY(&mem.animations);
77 ALLOC_DUMMY(&mem.cameras);
78 ALLOC_DUMMY(&mem.lights);
79 ALLOC_DUMMY(&mem.materials);
80 ALLOC_DUMMY(&mem.meshs);
81 ALLOC_DUMMY(&mem.mesh_links);
82 ALLOC_DUMMY(&mem.models);
83 ALLOC_DUMMY(&mem.nodes);
84 ALLOC_DUMMY(&mem.objects);
85 ALLOC_DUMMY(&mem.scenes);
86 ALLOC_DUMMY(&mem.skeletons);
87}
88
89void scene_mem_destroy() {
90 // NOTE: the dummy objects are not constructed, so the destruction code below
91 // always skips index 0. (I don't really like the conditional inside the loop,
92 // but this gets the job done without having to specialize the loop macro.)
93#define DESTROY(name) \
94 mempool_foreach(&MEM_FIELD(name), obj, { \
95 if (i > 0) { \
96 gfx_destroy_##name(&obj); \
97 } \
98 })
99
100 // Models contain scene elements. Destruction is handled by the remainder of
101 // scene destructionb elow.
102 //
103 // First destroy the scenes. This will recursively destroy the scene's nodes
104 // and their objects and avoid a double-free when we then destroy any stray
105 // scene elements.
106 DESTROY(scene);
107 // Then delete stray nodes. This will delete their children nodes and
108 // resource.
109 DESTROY(node);
110 // Destroy remaining scene elements.
111 DESTROY(anima);
112 // Animations are owned by animas and do not have a destructor.
113 DESTROY(camera);
114 DESTROY(light);
115 DESTROY(material);
116 DESTROY(mesh);
117 // Mesh links don't have a destructor.
118 DESTROY(object);
119 // Skeletons are owned by animas and do not have a destructor.
120}
121
122#define DEF_MEMORY(name, type) \
123 /* xyz* mem_alloc_xyz(); */ \
124 type* mem_alloc_##name() { return mempool_alloc(&MEM_FIELD(name)); } \
125 /* void mem_free_xyz(xyz**); */ \
126 void mem_free_##name(type** obj) { mempool_free(&MEM_FIELD(name), obj); } \
127 /* xyz* mem_get_xyz(xyz_idx); */ \
128 type* mem_get_##name(NAMED_INDEX(name) index) { \
129 assert(index.val != 0); /* 0 is the dummy allocation. */ \
130 return mempool_get_block(&MEM_FIELD(name), index.val); \
131 } \
132 /* xyz_idx mem_get_xyz_index(const xyz*); */ \
133 NAMED_INDEX(name) mem_get_##name##_index(const type* obj) { \
134 return (NAMED_INDEX(name)){ \
135 .val = mempool_get_block_index(&MEM_FIELD(name), obj)}; \
136 }
137
138DEF_MEMORY(anima, Anima)
139DEF_MEMORY(animation, Animation)
140DEF_MEMORY(camera, SceneCamera)
141DEF_MEMORY(light, Light)
142DEF_MEMORY(material, Material)
143DEF_MEMORY(mesh, Mesh)
144DEF_MEMORY(mesh_link, MeshLink)
145DEF_MEMORY(model, Model)
146DEF_MEMORY(node, SceneNode)
147DEF_MEMORY(object, SceneObject)
148DEF_MEMORY(scene, Scene)
149DEF_MEMORY(skeleton, Skeleton)
diff --git a/src/scene/scene_memory.h b/src/scene/scene_memory.h
new file mode 100644
index 0000000..d175cba
--- /dev/null
+++ b/src/scene/scene_memory.h
@@ -0,0 +1,39 @@
1/// Memory management of scene objects.
2#pragma once
3
4#include "types.h"
5
6/// Initialize scene memory.
7///
8/// The scene memory guarantees that every object maps to an index different
9/// than 0. This way, 0 can be used as a special index to denote "no value".
10void scene_mem_init();
11
12/// Destroy the scene memory and all allocated objects.
13void scene_mem_destroy();
14
15#define NAMED_INDEX(name) name##_idx
16
17#define DECL_MEMORY(name, type) \
18 typedef struct type type; \
19 /* xyz* mem_alloc_xyz() */ \
20 type* mem_alloc_##name(); \
21 /* mem_free_xyz(xyz**) */ \
22 void mem_free_##name(type**); \
23 /* xyz* mem_get_xyz(xyz_idx); */ \
24 type* mem_get_##name(NAMED_INDEX(name)); \
25 /* xyz_idx mem_get_xyz_index(const xyz*); */ \
26 NAMED_INDEX(name) mem_get_##name##_index(const type*);
27
28DECL_MEMORY(anima, Anima)
29DECL_MEMORY(animation, Animation)
30DECL_MEMORY(camera, SceneCamera)
31DECL_MEMORY(light, Light)
32DECL_MEMORY(material, Material)
33DECL_MEMORY(mesh, Mesh)
34DECL_MEMORY(mesh_link, MeshLink)
35DECL_MEMORY(model, Model)
36DECL_MEMORY(node, SceneNode)
37DECL_MEMORY(object, SceneObject)
38DECL_MEMORY(scene, Scene)
39DECL_MEMORY(skeleton, Skeleton)
diff --git a/src/scene/types.h b/src/scene/types.h
new file mode 100644
index 0000000..d0ffc41
--- /dev/null
+++ b/src/scene/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..afe0109
--- /dev/null
+++ b/src/util/geometry.c
@@ -0,0 +1,44 @@
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 GeometryDesc desc = (GeometryDesc){0};
21 desc.positions2d.data = positions;
22 desc.positions2d.size_bytes = 4 * sizeof(vec2);
23 desc.num_verts = 4;
24 desc.type = TriangleStrip;
25 return desc;
26}
27
28Geometry* gfx_make_quad_11(GfxCore* gfxcore) {
29 assert(gfxcore);
30
31 vec2 positions[4];
32 make_quad_11_positions(positions);
33 const GeometryDesc geometry_desc = make_quad_desc(positions);
34 return gfx_make_geometry(gfxcore, &geometry_desc);
35}
36
37Geometry* gfx_make_quad_01(GfxCore* gfxcore) {
38 assert(gfxcore);
39
40 vec2 positions[4];
41 make_quad_01_positions(positions);
42 const GeometryDesc geometry_desc = make_quad_desc(positions);
43 return gfx_make_geometry(gfxcore, &geometry_desc);
44}
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..08fa044
--- /dev/null
+++ b/src/util/skyquad.c
@@ -0,0 +1,161 @@
1#include <gfx/util/skyquad.h>
2
3#include <gfx/core.h>
4#include <gfx/gfx.h>
5#include <gfx/scene/light.h>
6#include <gfx/scene/material.h>
7#include <gfx/scene/mesh.h>
8#include <gfx/scene/node.h>
9#include <gfx/scene/object.h>
10#include <gfx/scene/scene.h>
11#include <gfx/util/geometry.h>
12#include <gfx/util/shader.h>
13
14#include <math/vec4.h>
15
16#include <assert.h>
17
18SceneObject* gfx_make_skyquad(GfxCore* gfxcore, const Texture* texture) {
19 assert(gfxcore);
20 assert(texture);
21
22 ShaderProgram* shader = 0;
23 Geometry* geometry = 0;
24 Material* material = 0;
25 Mesh* mesh = 0;
26 SceneObject* object = 0;
27
28 shader = gfx_make_skyquad_shader(gfxcore);
29 if (!shader) {
30 goto cleanup;
31 }
32
33 geometry = gfx_make_quad_11(gfxcore);
34 if (!geometry) {
35 goto cleanup;
36 }
37
38 MaterialDesc material_desc = (MaterialDesc){0};
39 material_desc.uniforms[0] = (ShaderUniform){
40 .type = UniformTexture,
41 .value.texture = texture,
42 .name = sstring_make("Skyquad")};
43 material_desc.num_uniforms = 1;
44 material = gfx_make_material(&material_desc);
45 if (!material) {
46 goto cleanup;
47 }
48
49 MeshDesc mesh_desc = (MeshDesc){0};
50 mesh_desc.geometry = geometry;
51 mesh_desc.material = material;
52 mesh_desc.shader = shader;
53 mesh = gfx_make_mesh(&mesh_desc);
54 if (!mesh) {
55 goto cleanup;
56 }
57
58 object = gfx_make_object(&(ObjectDesc){.num_meshes = 1, .meshes = {mesh}});
59 if (!object) {
60 goto cleanup;
61 }
62
63 return object;
64
65cleanup:
66 if (shader) {
67 gfx_destroy_shader_program(gfxcore, &shader);
68 }
69 if (geometry) {
70 gfx_destroy_geometry(gfxcore, &geometry);
71 }
72 if (material) {
73 gfx_destroy_material(&material);
74 }
75 if (mesh) {
76 gfx_destroy_mesh(&mesh);
77 }
78 if (object) {
79 gfx_destroy_object(&object);
80 }
81 return false;
82}
83
84/// Create an environment light node.
85static SceneNode* make_environment_light(
86 SceneNode* root, const Texture* environment_map) {
87 assert(root);
88 assert(environment_map);
89
90 Light* light = 0;
91 SceneNode* light_node = 0;
92
93 light = gfx_make_light(&(LightDesc){
94 .type = EnvironmentLightType,
95 .light = {(EnvironmentLightDesc){.environment_map = environment_map}}});
96 if (!light) {
97 goto cleanup;
98 }
99
100 light_node = gfx_make_light_node(light);
101 if (!light_node) {
102 goto cleanup;
103 }
104 gfx_set_node_parent(light_node, root);
105
106 return light_node;
107
108cleanup:
109 if (light) {
110 gfx_destroy_light(&light);
111 }
112 if (light_node) {
113 gfx_destroy_node(&light_node);
114 }
115 return 0;
116}
117
118SceneNode* gfx_setup_skyquad(
119 GfxCore* gfxcore, SceneNode* root, const Texture* environment_map) {
120 assert(gfxcore);
121 assert(root);
122 assert(environment_map);
123
124 SceneObject* skyquad_object = 0;
125 SceneNode* object_node = 0;
126 SceneNode* light_node = 0;
127
128 // Create the skyquad object.
129 skyquad_object = gfx_make_skyquad(gfxcore, environment_map);
130 if (!skyquad_object) {
131 goto cleanup;
132 }
133
134 // Create an object node to render the skyquad in the background.
135 object_node = gfx_make_object_node(skyquad_object);
136 if (!object_node) {
137 goto cleanup;
138 }
139 gfx_set_node_parent(object_node, root);
140
141 // Create an environment light node under which to root objects affected by
142 // the skyquad.
143 light_node = make_environment_light(root, environment_map);
144 if (!light_node) {
145 goto cleanup;
146 }
147
148 return light_node;
149
150cleanup:
151 if (skyquad_object) {
152 gfx_destroy_object(&skyquad_object);
153 }
154 if (object_node) {
155 gfx_destroy_node(&object_node);
156 }
157 if (light_node) {
158 gfx_destroy_node(&light_node);
159 }
160 return 0;
161}