diff options
author | 3gg <3gg@shellblade.net> | 2025-06-27 10:18:39 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2025-06-27 10:18:39 -0700 |
commit | bd57f345ed9dbed1d81683e48199626de2ea9044 (patch) | |
tree | 4221f2f2a7ad2244d2e93052bd68187ec91b8ea9 /src | |
parent | 9a82ce0083437a4f9f58108b2c23b957d2249ad8 (diff) |
Diffstat (limited to 'src')
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 | |||
19 | static 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 | |||
32 | static 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 | |||
64 | static 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 | |||
76 | static 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 | |||
116 | static 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 | |||
126 | static 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 | |||
140 | static 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 | |||
152 | static 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 | |||
180 | void 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 | |||
190 | void gfx_destroy_asset_cache(AssetCache* cache) { | ||
191 | assert(cache); | ||
192 | mempool_del(&cache->assets); | ||
193 | } | ||
194 | |||
195 | Model* 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 | |||
225 | const 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 | |||
8 | typedef struct Model Model; | ||
9 | typedef struct Texture Texture; | ||
10 | |||
11 | typedef uint64_t Hash; | ||
12 | |||
13 | typedef enum AssetType { | ||
14 | ModelAsset, | ||
15 | TextureAsset, | ||
16 | } AssetType; | ||
17 | |||
18 | typedef struct Asset { | ||
19 | AssetType type; | ||
20 | Hash hash; | ||
21 | union { | ||
22 | Model* model; | ||
23 | const Texture* texture; | ||
24 | }; | ||
25 | } Asset; | ||
26 | |||
27 | DEF_MEMPOOL(asset_pool, Asset, GFX_MAX_NUM_ASSETS) | ||
28 | |||
29 | typedef struct AssetCache { | ||
30 | asset_pool assets; | ||
31 | } AssetCache; | ||
32 | |||
33 | /// Create a new asset cache. | ||
34 | void gfx_init_asset_cache(AssetCache*); | ||
35 | |||
36 | /// Destroy the asset cache. | ||
37 | void 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 | |||
148 | typedef 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. | ||
158 | typedef 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. | ||
179 | static 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. | ||
210 | static 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. | ||
229 | static 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. | ||
247 | static 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. | ||
268 | static 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. | ||
288 | static 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. | ||
303 | static 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. | ||
326 | int 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. | ||
352 | static 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 | ||
395 | static 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 | |||
437 | typedef 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 | |||
446 | typedef 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 | |||
456 | bool 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 | |||
500 | AccessorIter 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. | ||
554 | static 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. | ||
571 | static 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. | ||
595 | static 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. | ||
632 | static 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. | ||
700 | static 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. | ||
753 | static 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. | ||
853 | static 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). | ||
885 | aabb3 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. | ||
909 | static 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. | ||
1221 | static 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. | ||
1304 | cgltf_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. | ||
1327 | static 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. | ||
1423 | static 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. | ||
1497 | static 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. | ||
1611 | static 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. | ||
1674 | static 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 | |||
1818 | cleanup: | ||
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 | |||
1920 | Model* 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 | |||
1960 | cleanup: | ||
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 | |||
6 | typedef struct Gfx Gfx; | ||
7 | typedef struct Model Model; | ||
8 | |||
9 | /// Load a model. | ||
10 | /// | ||
11 | /// Currently only supports the GLTF format. | ||
12 | Model* 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 | |||
12 | static 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. | ||
46 | Texture* 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. | ||
7 | Texture* 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 | |||
10 | static 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 | |||
15 | static 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 | |||
26 | size_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 | |||
47 | bool 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 | |||
64 | void gfx_del_buffer(Buffer* buffer) { | ||
65 | assert(buffer); | ||
66 | if (buffer->vbo) { | ||
67 | glDeleteBuffers(1, &buffer->vbo); | ||
68 | buffer->vbo = 0; | ||
69 | } | ||
70 | } | ||
71 | |||
72 | void 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 | |||
12 | typedef 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. | ||
20 | size_t gfx_get_buffer_type_size_bytes(BufferType); | ||
21 | |||
22 | /// Create a buffer from raw data. | ||
23 | bool gfx_init_buffer(Buffer*, const BufferDesc*); | ||
24 | |||
25 | /// Destroy the buffer. | ||
26 | void 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 | |||
9 | void 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. | ||
36 | void 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 | |||
64 | void 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 | |||
76 | void gfx_end_frame(GfxCore* gfxcore) { | ||
77 | assert(gfxcore); | ||
78 | ASSERT_GL; | ||
79 | } | ||
80 | |||
81 | void 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 | |||
87 | void 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 | |||
101 | void 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 | |||
108 | void 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 | |||
118 | void gfx_set_depth_mask(GfxCore* gfxcore, bool enable) { | ||
119 | assert(gfxcore); | ||
120 | glDepthMask(enable ? GL_TRUE : GL_FALSE); | ||
121 | } | ||
122 | |||
123 | void 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 | |||
132 | void 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 | |||
142 | void 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 | |||
152 | Buffer* 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 | |||
164 | void 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 | |||
177 | Geometry* 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 | |||
189 | void 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 | |||
203 | Texture* 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 | |||
215 | void 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 | |||
230 | RenderBuffer* 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 | |||
242 | void 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 | |||
257 | FrameBuffer* 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 | |||
270 | void 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 | |||
285 | static 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 | |||
298 | static 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 | |||
304 | static 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 | |||
314 | static 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 | |||
324 | static 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 | |||
336 | static 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 | |||
348 | Shader* 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 | |||
375 | void 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 | |||
391 | ShaderProgram* 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 | |||
416 | void 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. | ||
21 | typedef struct ShaderCacheEntry { | ||
22 | uint64_t hash; | ||
23 | Shader* shader; | ||
24 | } ShaderCacheEntry; | ||
25 | |||
26 | typedef struct ShaderProgramCacheEntry { | ||
27 | uint64_t hash; | ||
28 | ShaderProgram* program; | ||
29 | } ShaderProgramCacheEntry; | ||
30 | |||
31 | DEF_MEMPOOL(buffer_pool, Buffer, GFX_MAX_NUM_BUFFERS) | ||
32 | DEF_MEMPOOL(framebuffer_pool, FrameBuffer, GFX_MAX_NUM_FRAMEBUFFERS) | ||
33 | DEF_MEMPOOL(geometry_pool, Geometry, GFX_MAX_NUM_GEOMETRIES) | ||
34 | DEF_MEMPOOL(renderbuffer_pool, RenderBuffer, GFX_MAX_NUM_RENDERBUFFERS) | ||
35 | DEF_MEMPOOL(shader_pool, Shader, GFX_MAX_NUM_SHADERS) | ||
36 | DEF_MEMPOOL(shader_program_pool, ShaderProgram, GFX_MAX_NUM_SHADER_PROGRAMS) | ||
37 | DEF_MEMPOOL(texture_pool, Texture, GFX_MAX_NUM_TEXTURES) | ||
38 | |||
39 | DEF_MEMPOOL(ShaderCache, ShaderCacheEntry, GFX_MAX_NUM_SHADERS) | ||
40 | DEF_MEMPOOL(ProgramCache, ShaderProgramCacheEntry, GFX_MAX_NUM_SHADER_PROGRAMS) | ||
41 | |||
42 | typedef struct { | ||
43 | int x; | ||
44 | int y; | ||
45 | int width; | ||
46 | int height; | ||
47 | } Viewport; | ||
48 | |||
49 | typedef 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. | ||
65 | void gfx_init_gfxcore(GfxCore*); | ||
66 | |||
67 | /// Destroy the render backend. | ||
68 | void 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 | |||
10 | static 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 | |||
39 | static 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 | |||
67 | bool 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 | |||
99 | bool 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 | |||
114 | bool 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 | |||
129 | void gfx_del_framebuffer(FrameBuffer* framebuffer) { | ||
130 | assert(framebuffer); | ||
131 | if (framebuffer->id) { | ||
132 | glDeleteFramebuffers(1, &framebuffer->id); | ||
133 | framebuffer->id = 0; | ||
134 | } | ||
135 | } | ||
136 | |||
137 | void gfx_activate_framebuffer(const FrameBuffer* framebuffer) { | ||
138 | assert(framebuffer); | ||
139 | glBindFramebuffer(GL_FRAMEBUFFER, framebuffer->id); | ||
140 | } | ||
141 | |||
142 | void gfx_deactivate_framebuffer(const FrameBuffer* framebuffer) { | ||
143 | assert(framebuffer); | ||
144 | glBindFramebuffer(GL_FRAMEBUFFER, 0); | ||
145 | } | ||
146 | |||
147 | void 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 | |||
7 | typedef struct FrameBuffer { | ||
8 | GLuint id; | ||
9 | } FrameBuffer; | ||
10 | |||
11 | /// Create a new framebuffer. | ||
12 | bool gfx_init_framebuffer(FrameBuffer*, const FrameBufferDesc*); | ||
13 | |||
14 | /// Destroy the framebuffer. | ||
15 | void 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 | |||
17 | static 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. | ||
32 | void 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. | ||
49 | static 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 | |||
81 | static 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 | |||
190 | static 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 | |||
218 | bool 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 | |||
250 | cleanup: | ||
251 | gfx_del_geometry(geometry); | ||
252 | return 0; | ||
253 | } | ||
254 | |||
255 | void gfx_del_geometry(Geometry* geometry) { | ||
256 | assert(geometry); | ||
257 | if (geometry->vao) { | ||
258 | glDeleteVertexArrays(1, &geometry->vao); | ||
259 | geometry->vao = 0; | ||
260 | } | ||
261 | } | ||
262 | |||
263 | void 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 | |||
299 | void 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 | |||
323 | aabb3 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. | ||
15 | typedef 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. | ||
25 | bool gfx_init_geometry(Geometry*, GfxCore*, const GeometryDesc*); | ||
26 | |||
27 | /// Destroy the geometry. | ||
28 | void 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 | |||
7 | bool 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 | |||
28 | void 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 | |||
7 | typedef struct RenderBuffer { | ||
8 | GLuint id; | ||
9 | } RenderBuffer; | ||
10 | |||
11 | /// Create a new renderbuffer. | ||
12 | bool gfx_init_renderbuffer(RenderBuffer*, const RenderBufferDesc*); | ||
13 | |||
14 | /// Destroy the renderbuffer. | ||
15 | void 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 | |||
12 | static 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 | |||
23 | static 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. | ||
38 | static 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 | |||
79 | bool gfx_compile_shader(Shader* shader, const ShaderDesc* desc) { | ||
80 | shader->id = create_shader(desc); | ||
81 | return shader->id != 0; | ||
82 | } | ||
83 | |||
84 | void 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 | |||
9 | typedef struct Shader { | ||
10 | GLuint id; | ||
11 | } Shader; | ||
12 | |||
13 | /// Compile a new shader. | ||
14 | bool gfx_compile_shader(Shader*, const ShaderDesc*); | ||
15 | |||
16 | /// Destroy the shader. | ||
17 | void 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. | ||
15 | static 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 | |||
44 | bool 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 | |||
53 | void 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 | |||
63 | void gfx_activate_shader_program(const ShaderProgram* prog) { | ||
64 | assert(prog); | ||
65 | glUseProgram(prog->id); | ||
66 | ASSERT_GL; | ||
67 | } | ||
68 | |||
69 | void gfx_deactivate_shader_program(const ShaderProgram* prog) { | ||
70 | assert(prog); | ||
71 | glUseProgram(0); | ||
72 | ASSERT_GL; | ||
73 | } | ||
74 | |||
75 | static 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 | |||
89 | static 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 | |||
101 | static 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 | |||
111 | static 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 | |||
121 | static 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 | |||
131 | void 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. | ||
167 | static 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 | |||
194 | void 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 | |||
211 | void 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 | |||
228 | void 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 | |||
243 | void 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 | |||
258 | void 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 | |||
275 | void 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 | |||
12 | typedef struct Texture Texture; | ||
13 | |||
14 | typedef 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. | ||
21 | bool gfx_build_shader_program(ShaderProgram*, const ShaderProgramDesc*); | ||
22 | |||
23 | /// Destroy the shader program. | ||
24 | void 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 | |||
10 | bool 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 | |||
82 | void 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 | |||
91 | void 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 | |||
126 | GLenum 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 | |||
138 | GLenum 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 | |||
162 | GLenum 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 | |||
182 | GLenum 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 | |||
200 | GLenum 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 | |||
7 | typedef 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. | ||
17 | bool gfx_init_texture(Texture*, const TextureDesc*); | ||
18 | |||
19 | /// Destroy the texture. | ||
20 | void gfx_del_texture(Texture*); | ||
21 | |||
22 | /// Converts a TextureDimension into the OpenGL enum equivalent. | ||
23 | GLenum to_GL_dimension(TextureDimension dim); | ||
24 | |||
25 | /// Converts a texture format into an OpenGL internal format. | ||
26 | GLenum to_GL_internal_format(TextureFormat format); | ||
27 | |||
28 | /// Converts a texture format into an OpenGL format. | ||
29 | GLenum to_GL_format(TextureFormat format); | ||
30 | |||
31 | /// Converts a texture format into an OpenGL type. | ||
32 | GLenum to_GL_type(TextureFormat format); | ||
33 | |||
34 | /// Converts a cubemap face into the OpenGL enum equivalent. | ||
35 | GLenum 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 | |||
14 | typedef struct Gfx { | ||
15 | AssetCache asset_cache; | ||
16 | GfxCore gfxcore; | ||
17 | Renderer renderer; | ||
18 | ImmRenderer imm_renderer; | ||
19 | } Gfx; | ||
20 | |||
21 | Gfx* 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 | |||
42 | void 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 | |||
55 | GfxCore* gfx_get_core(Gfx* gfx) { | ||
56 | assert(gfx); | ||
57 | return &gfx->gfxcore; | ||
58 | } | ||
59 | |||
60 | Renderer* gfx_get_renderer(Gfx* gfx) { | ||
61 | assert(gfx); | ||
62 | return &gfx->renderer; | ||
63 | } | ||
64 | |||
65 | ImmRenderer* gfx_get_imm_renderer(Gfx* gfx) { | ||
66 | assert(gfx); | ||
67 | return &gfx->imm_renderer; | ||
68 | } | ||
69 | |||
70 | AssetCache* 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 | |||
11 | bool 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 | |||
42 | cleanup: | ||
43 | imm_renderer_destroy(renderer); | ||
44 | return false; | ||
45 | } | ||
46 | |||
47 | void 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 | |||
61 | void 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 | |||
81 | void 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 | |||
90 | void gfx_imm_end(ImmRenderer* renderer) { | ||
91 | assert(renderer); | ||
92 | imm_renderer_flush(renderer); | ||
93 | gfx_deactivate_shader_program(renderer->shader); | ||
94 | } | ||
95 | |||
96 | void 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 | |||
111 | void gfx_imm_draw_triangle(ImmRenderer* renderer, const vec3 verts[3]) { | ||
112 | gfx_imm_draw_triangles(renderer, verts, 1); | ||
113 | } | ||
114 | |||
115 | void 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 | |||
133 | void 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 | |||
151 | void 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. | ||
181 | static 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 | |||
189 | void 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 | |||
196 | void 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 | |||
209 | void 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 | |||
223 | void 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 | |||
229 | void 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 | |||
238 | void 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 | |||
247 | void 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 | |||
255 | void 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 | |||
12 | typedef struct Geometry Geometry; | ||
13 | typedef 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. | ||
20 | typedef 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. | ||
38 | bool imm_renderer_make(ImmRenderer*, GfxCore*); | ||
39 | |||
40 | /// Destroy the immediate mode renderer. | ||
41 | void imm_renderer_destroy(ImmRenderer*); | ||
42 | |||
43 | /// Flush draw commands. | ||
44 | void 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". | ||
25 | static const int IRRADIANCE_MAP_WIDTH = 1024; | ||
26 | static const int IRRADIANCE_MAP_HEIGHT = 1024; | ||
27 | static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128; | ||
28 | static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128; | ||
29 | static const int BRDF_INTEGRATION_MAP_WIDTH = 512; | ||
30 | static const int BRDF_INTEGRATION_MAP_HEIGHT = 512; | ||
31 | |||
32 | bool renderer_make(Renderer* renderer, GfxCore* gfxcore) { | ||
33 | assert(renderer); | ||
34 | assert(gfxcore); | ||
35 | |||
36 | renderer->gfxcore = gfxcore; | ||
37 | |||
38 | return true; | ||
39 | } | ||
40 | |||
41 | void 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. | ||
66 | static 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 | |||
83 | static 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. | ||
122 | static 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 | |||
159 | cleanup: | ||
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 | |||
169 | typedef 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. | ||
187 | static 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. | ||
205 | static 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 | |||
320 | void 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 | |||
365 | static 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 | |||
390 | void 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 | |||
7 | typedef struct IBL IBL; | ||
8 | typedef struct ShaderProgram ShaderProgram; | ||
9 | typedef struct Texture Texture; | ||
10 | |||
11 | typedef 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. | ||
24 | bool renderer_make(Renderer*, GfxCore*); | ||
25 | |||
26 | /// Destroy the renderer. | ||
27 | void 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 | |||
10 | static const R PLAYBACK_UNINITIALIZED = -1; | ||
11 | |||
12 | static 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 | |||
19 | static Joint* get_anima_root_joint(Anima* anima) { | ||
20 | assert(anima); | ||
21 | return &anima->joints[get_anima_root_joint_index(anima)]; | ||
22 | } | ||
23 | |||
24 | static 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 | |||
32 | static Joint* get_anima_joint_mut(Anima* anima, joint_idx index) { | ||
33 | return (Joint*)get_anima_joint(anima, index); | ||
34 | } | ||
35 | |||
36 | static 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 | |||
43 | static 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 | |||
65 | static 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 | |||
81 | static 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 | |||
93 | static 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 | |||
133 | Anima* 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 | |||
191 | void 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 | |||
215 | static 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 | |||
233 | bool 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 | |||
251 | static void gfx_set_joint_position(Joint* joint, vec3 position) { | ||
252 | assert(joint); | ||
253 | mat4_set_v3(&joint->transform, position); | ||
254 | } | ||
255 | |||
256 | static void gfx_set_joint_rotation(Joint* joint, quat rotation) { | ||
257 | assert(joint); | ||
258 | mat4_set_3x3(&joint->transform, mat4_from_quat(rotation)); | ||
259 | } | ||
260 | |||
261 | static 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 | |||
275 | static 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 | |||
281 | static 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 | |||
310 | static 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 | |||
339 | static 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 | |||
376 | static 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 | |||
410 | void 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 | |||
463 | const 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 | |||
477 | size_t gfx_get_skeleton_num_joints(const Skeleton* skeleton) { | ||
478 | assert(skeleton); | ||
479 | return skeleton->num_joints; | ||
480 | } | ||
481 | |||
482 | bool 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 | |||
492 | Box 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 | |||
17 | typedef 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. | ||
26 | typedef 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. | ||
36 | typedef 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. | ||
43 | typedef 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. | ||
52 | typedef 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. | ||
61 | typedef 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. | ||
72 | typedef 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). | ||
91 | typedef 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 | |||
8 | SceneCamera* 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 | |||
18 | void 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 | |||
28 | void gfx_set_camera_camera(SceneCamera* scene_camera, Camera* camera) { | ||
29 | assert(scene_camera); | ||
30 | assert(camera); | ||
31 | scene_camera->camera = *camera; | ||
32 | } | ||
33 | |||
34 | Camera* 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 | |||
9 | typedef 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 | |||
8 | static 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 | |||
16 | Light* 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 | |||
34 | void 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 | |||
7 | typedef struct Texture Texture; | ||
8 | |||
9 | /// An environment light. | ||
10 | typedef 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. | ||
19 | typedef 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 | |||
7 | static 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 | |||
17 | Material* 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 | |||
24 | void gfx_destroy_material(Material** material) { mem_free_material(material); } | ||
25 | |||
26 | static 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 | |||
51 | void 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 | |||
5 | typedef struct ShaderProgram ShaderProgram; | ||
6 | |||
7 | typedef 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. | ||
16 | void 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 | |||
7 | static 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 | |||
18 | Mesh* gfx_make_mesh(const MeshDesc* desc) { | ||
19 | Mesh* mesh = mem_alloc_mesh(); | ||
20 | mesh_make(mesh, desc); | ||
21 | return mesh; | ||
22 | } | ||
23 | |||
24 | void 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 | |||
5 | typedef 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 | |||
9 | Model* 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 | |||
17 | void 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 | |||
27 | Anima* 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 | |||
38 | const SceneNode* gfx_get_model_root(const Model* model) { | ||
39 | assert(model); | ||
40 | return mem_get_node(model->root); | ||
41 | } | ||
42 | |||
43 | SceneNode* 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. | ||
8 | typedef struct Model { | ||
9 | node_idx root; | ||
10 | node_idx parent; // Parent SceneNode. | ||
11 | } Model; | ||
12 | |||
13 | /// Create a new model. | ||
14 | Model* gfx_make_model(const SceneNode* root); | ||
15 | |||
16 | /// Destroy the model. | ||
17 | void 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 | |||
16 | static void scene_node_make(SceneNode* node) { | ||
17 | assert(node); | ||
18 | node->type = LogicalNode; | ||
19 | node->transform = mat4_id(); | ||
20 | } | ||
21 | |||
22 | SceneNode* gfx_make_node() { | ||
23 | SceneNode* node = mem_alloc_node(); | ||
24 | scene_node_make(node); | ||
25 | return node; | ||
26 | } | ||
27 | |||
28 | SceneNode* 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 | |||
37 | SceneNode* 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 | |||
46 | SceneNode* 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 | |||
55 | SceneNode* 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 | |||
64 | SceneNode* 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. | ||
74 | static 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 | |||
113 | void 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 | |||
122 | void 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. | ||
133 | void 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 | |||
142 | void 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 | |||
151 | void 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 | |||
160 | static 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 | |||
177 | void 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? | ||
191 | void 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 | |||
200 | NodeType 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 | |||
212 | const Anima* gfx_get_node_anima(const SceneNode* node) { | ||
213 | NODE_GET(node, anima, AnimaNode); | ||
214 | } | ||
215 | |||
216 | Anima* gfx_get_node_anima_mut(SceneNode* node) { | ||
217 | NODE_GET(node, anima, AnimaNode); | ||
218 | } | ||
219 | |||
220 | const SceneCamera* gfx_get_node_camera(const SceneNode* node) { | ||
221 | NODE_GET(node, camera, CameraNode); | ||
222 | } | ||
223 | |||
224 | SceneCamera* gfx_get_node_camera_mut(SceneNode* node) { | ||
225 | NODE_GET(node, camera, CameraNode); | ||
226 | } | ||
227 | |||
228 | const Light* gfx_get_node_light(const SceneNode* node) { | ||
229 | NODE_GET(node, light, LightNode); | ||
230 | } | ||
231 | |||
232 | Light* gfx_get_node_light_mut(SceneNode* node) { | ||
233 | NODE_GET(node, light, LightNode); | ||
234 | } | ||
235 | |||
236 | const Model* gfx_get_node_model(const SceneNode* node) { | ||
237 | NODE_GET(node, model, ModelNode); | ||
238 | } | ||
239 | |||
240 | Model* gfx_get_node_model_mut(SceneNode* node) { | ||
241 | NODE_GET(node, model, ModelNode); | ||
242 | } | ||
243 | |||
244 | const SceneObject* gfx_get_node_object(const SceneNode* node) { | ||
245 | NODE_GET(node, object, ObjectNode); | ||
246 | } | ||
247 | |||
248 | SceneObject* gfx_get_node_object_mut(SceneNode* node) { | ||
249 | NODE_GET(node, object, ObjectNode); | ||
250 | } | ||
251 | |||
252 | const SceneNode* gfx_get_node_parent(const SceneNode* node) { | ||
253 | assert(node); | ||
254 | return mem_get_node(node->parent); | ||
255 | } | ||
256 | |||
257 | SceneNode* gfx_get_node_parent_mut(SceneNode* node) { | ||
258 | assert(node); | ||
259 | return mem_get_node(node->parent); | ||
260 | } | ||
261 | |||
262 | const 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 | |||
271 | SceneNode* gfx_get_node_child_mut(SceneNode* node) { | ||
272 | return (SceneNode*)gfx_get_node_child(node); | ||
273 | } | ||
274 | |||
275 | const 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 | |||
284 | SceneNode* gfx_get_node_sibling_mut(SceneNode* node) { | ||
285 | return (SceneNode*)gfx_get_node_sibling(node); | ||
286 | } | ||
287 | |||
288 | mat4 gfx_get_node_transform(const SceneNode* node) { | ||
289 | assert(node); | ||
290 | return node->transform; | ||
291 | } | ||
292 | |||
293 | mat4 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 | |||
305 | void 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 | |||
311 | void gfx_set_node_transform(SceneNode* node, const mat4* transform) { | ||
312 | assert(node); | ||
313 | assert(transform); | ||
314 | node->transform = *transform; | ||
315 | } | ||
316 | |||
317 | void gfx_set_node_position(SceneNode* node, const vec3* position) { | ||
318 | assert(node); | ||
319 | assert(position); | ||
320 | mat4_set_v3(&node->transform, *position); | ||
321 | } | ||
322 | |||
323 | void 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 | |||
329 | void 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 | |||
335 | static 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 | |||
354 | static 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 | |||
374 | void gfx_log_node_hierarchy(const SceneNode* node) { | ||
375 | const sstring pad = sstring_make(""); | ||
376 | log_node_hierarchy_rec(node, &pad); | ||
377 | } | ||
378 | |||
379 | static 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 | |||
402 | SceneNode* 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. | ||
14 | typedef 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. | ||
36 | void 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). | ||
40 | SceneNode* 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 | |||
11 | static 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 | |||
37 | static 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 | |||
47 | SceneObject* 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 | |||
58 | void 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 | |||
69 | void 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 | |||
75 | const 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 | |||
80 | aabb3 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 | |||
9 | typedef 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. | ||
21 | typedef 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 | |||
8 | Scene* gfx_make_scene(void) { | ||
9 | Scene* scene = mem_alloc_scene(); | ||
10 | scene->root = gfx_make_node(); | ||
11 | return scene; | ||
12 | } | ||
13 | |||
14 | void 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 | |||
22 | SceneNode* 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 | |||
7 | typedef struct SceneNode SceneNode; | ||
8 | |||
9 | typedef 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 | |||
17 | DEF_MEMPOOL(anima_pool, Anima, GFX_MAX_NUM_ANIMAS) | ||
18 | DEF_MEMPOOL(animation_pool, Animation, GFX_MAX_NUM_ANIMATIONS) | ||
19 | DEF_MEMPOOL(camera_pool, SceneCamera, GFX_MAX_NUM_CAMERAS) | ||
20 | DEF_MEMPOOL(light_pool, Light, GFX_MAX_NUM_LIGHTS) | ||
21 | DEF_MEMPOOL(material_pool, Material, GFX_MAX_NUM_MATERIALS) | ||
22 | DEF_MEMPOOL(mesh_pool, Mesh, GFX_MAX_NUM_MESHES) | ||
23 | DEF_MEMPOOL(mesh_link_pool, MeshLink, GFX_MAX_NUM_MESH_LINKS) | ||
24 | DEF_MEMPOOL(model_pool, Model, GFX_MAX_NUM_MODELS) | ||
25 | DEF_MEMPOOL(node_pool, SceneNode, GFX_MAX_NUM_NODES) | ||
26 | DEF_MEMPOOL(object_pool, SceneObject, GFX_MAX_NUM_OBJECTS) | ||
27 | DEF_MEMPOOL(scene_pool, Scene, GFX_MAX_NUM_SCENES) | ||
28 | DEF_MEMPOOL(skeleton_pool, Skeleton, GFX_MAX_NUM_SKELETONS) | ||
29 | |||
30 | /// Scene memory. | ||
31 | /// | ||
32 | /// Holds memory pools for every type of scene object. | ||
33 | typedef 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 | |||
48 | static 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 | |||
59 | void 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 | |||
89 | void 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 | |||
138 | DEF_MEMORY(anima, Anima) | ||
139 | DEF_MEMORY(animation, Animation) | ||
140 | DEF_MEMORY(camera, SceneCamera) | ||
141 | DEF_MEMORY(light, Light) | ||
142 | DEF_MEMORY(material, Material) | ||
143 | DEF_MEMORY(mesh, Mesh) | ||
144 | DEF_MEMORY(mesh_link, MeshLink) | ||
145 | DEF_MEMORY(model, Model) | ||
146 | DEF_MEMORY(node, SceneNode) | ||
147 | DEF_MEMORY(object, SceneObject) | ||
148 | DEF_MEMORY(scene, Scene) | ||
149 | DEF_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". | ||
10 | void scene_mem_init(); | ||
11 | |||
12 | /// Destroy the scene memory and all allocated objects. | ||
13 | void 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 | |||
28 | DECL_MEMORY(anima, Anima) | ||
29 | DECL_MEMORY(animation, Animation) | ||
30 | DECL_MEMORY(camera, SceneCamera) | ||
31 | DECL_MEMORY(light, Light) | ||
32 | DECL_MEMORY(material, Material) | ||
33 | DECL_MEMORY(mesh, Mesh) | ||
34 | DECL_MEMORY(mesh_link, MeshLink) | ||
35 | DECL_MEMORY(model, Model) | ||
36 | DECL_MEMORY(node, SceneNode) | ||
37 | DECL_MEMORY(object, SceneObject) | ||
38 | DECL_MEMORY(scene, Scene) | ||
39 | DECL_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 | |||
6 | typedef 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 | |||
13 | DEF_STRONG_INDEX(anima, gfx_idx) | ||
14 | DEF_STRONG_INDEX(animation, gfx_idx) | ||
15 | DEF_STRONG_INDEX(camera, gfx_idx) | ||
16 | DEF_STRONG_INDEX(light, gfx_idx) | ||
17 | DEF_STRONG_INDEX(material, gfx_idx) | ||
18 | DEF_STRONG_INDEX(mesh, gfx_idx) | ||
19 | DEF_STRONG_INDEX(mesh_link, gfx_idx) | ||
20 | DEF_STRONG_INDEX(model, gfx_idx) | ||
21 | DEF_STRONG_INDEX(node, gfx_idx) | ||
22 | DEF_STRONG_INDEX(object, gfx_idx) | ||
23 | DEF_STRONG_INDEX(scene, gfx_idx) | ||
24 | DEF_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 | |||
5 | static 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 | |||
12 | static 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 | |||
19 | static 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 | |||
28 | Geometry* 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 | |||
37 | Geometry* 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 | |||
11 | typedef 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 | |||
21 | static const CubemapFace faces[6] = { | ||
22 | CubemapFacePosX, // Right. | ||
23 | CubemapFaceNegX, // Left. | ||
24 | CubemapFacePosY, // Up. | ||
25 | CubemapFaceNegY, // Down. | ||
26 | CubemapFacePosZ, // Back. | ||
27 | CubemapFaceNegZ, // Front. | ||
28 | }; | ||
29 | |||
30 | static 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 | |||
39 | IBL* 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 | |||
117 | cleanup: | ||
118 | gfx_destroy_ibl(gfxcore, &ibl); | ||
119 | return 0; | ||
120 | } | ||
121 | |||
122 | void 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 | |||
149 | Texture* 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 | |||
187 | cleanup: | ||
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 | |||
198 | Texture* 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 | |||
248 | cleanup: | ||
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 | |||
259 | Texture* 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 | |||
319 | cleanup: | ||
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 | |||
29 | static 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 | |||
68 | cleanup: | ||
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 | |||
78 | ShaderProgram* 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 | |||
83 | ShaderProgram* gfx_make_cook_torrance_shader(GfxCore* gfxcore) { | ||
84 | return make_shader_program( | ||
85 | gfxcore, cook_torrance_vert, cook_torrance_frag, 0, 0); | ||
86 | } | ||
87 | |||
88 | ShaderProgram* 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 | |||
94 | ShaderProgram* gfx_make_immediate_mode_shader(GfxCore* gfxcore) { | ||
95 | return make_shader_program( | ||
96 | gfxcore, immediate_mode_vert, immediate_mode_frag, 0, 0); | ||
97 | } | ||
98 | |||
99 | ShaderProgram* gfx_make_irradiance_map_shader(GfxCore* gfxcore) { | ||
100 | return make_shader_program( | ||
101 | gfxcore, cubemap_filtering_vert, irradiance_map_frag, 0, 0); | ||
102 | } | ||
103 | |||
104 | ShaderProgram* 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 | |||
109 | ShaderProgram* gfx_make_debug3d_shader(GfxCore* gfxcore) { | ||
110 | return make_shader_program(gfxcore, debug3d_vert, debug3d_frag, 0, 0); | ||
111 | } | ||
112 | |||
113 | ShaderProgram* gfx_make_skyquad_shader(GfxCore* gfxcore) { | ||
114 | return make_shader_program(gfxcore, skyquad_vert, skyquad_frag, 0, 0); | ||
115 | } | ||
116 | |||
117 | ShaderProgram* 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 | |||
123 | ShaderProgram* gfx_make_view_normals_shader(GfxCore* gfxcore) { | ||
124 | return make_shader_program( | ||
125 | gfxcore, view_normals_vert, view_normals_frag, 0, 0); | ||
126 | } | ||
127 | |||
128 | ShaderProgram* gfx_make_view_tangents_shader(GfxCore* gfxcore) { | ||
129 | return make_shader_program( | ||
130 | gfxcore, view_tangents_vert, view_tangents_frag, 0, 0); | ||
131 | } | ||
132 | |||
133 | ShaderProgram* 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 | |||
18 | SceneObject* 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 | |||
65 | cleanup: | ||
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. | ||
85 | static 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 | |||
108 | cleanup: | ||
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 | |||
118 | SceneNode* 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 | |||
150 | cleanup: | ||
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 | } | ||