aboutsummaryrefslogtreecommitdiff
path: root/src/render
diff options
context:
space:
mode:
Diffstat (limited to 'src/render')
-rw-r--r--src/render/imm.c192
-rw-r--r--src/render/imm_impl.h43
-rw-r--r--src/render/llr.c527
-rw-r--r--src/render/llr_impl.h117
-rw-r--r--src/render/renderer.c246
-rw-r--r--src/render/renderer_impl.h25
6 files changed, 1150 insertions, 0 deletions
diff --git a/src/render/imm.c b/src/render/imm.c
new file mode 100644
index 0000000..8a93488
--- /dev/null
+++ b/src/render/imm.c
@@ -0,0 +1,192 @@
1#include "imm_impl.h"
2
3#include <gfx/core.h>
4#include <gfx/render/imm.h>
5#include <gfx/render/llr.h>
6#include <gfx/util/shader.h>
7
8#include <math/aabb3.h>
9
10#include <assert.h>
11#include <string.h> // memcpy
12
13bool gfx_imm_make(Imm* renderer, GfxCore* gfxcore, LLR* llr) {
14 assert(renderer);
15 assert(gfxcore);
16 assert(llr);
17
18 const size_t num_triangle_verts = GFX_IMM_MAX_NUM_TRIANGLES * 3;
19
20 renderer->gfxcore = gfxcore;
21 renderer->llr = llr;
22
23 renderer->triangles = gfx_make_geometry(
24 gfxcore,
25 &(GeometryDesc){.type = Triangles,
26 .buffer_usage = BufferDynamic,
27 .num_verts = num_triangle_verts,
28 .positions3d = (BufferView3d){
29 .size_bytes = num_triangle_verts * sizeof(vec3)}});
30 if (!renderer->triangles) {
31 goto cleanup;
32 }
33
34 renderer->shader = gfx_make_immediate_mode_shader(gfxcore);
35 if (!renderer->shader) {
36 goto cleanup;
37 }
38
39 gfx_imm_set_colour(renderer, vec4_make(0.0f, 0.0f, 0.0f, 1.0f));
40
41 return true;
42
43cleanup:
44 gfx_imm_destroy(renderer);
45 return false;
46}
47
48void gfx_imm_destroy(Imm* renderer) {
49 assert(renderer);
50 assert(renderer->gfxcore);
51
52 if (renderer->triangles) {
53 gfx_destroy_geometry(renderer->gfxcore, &renderer->triangles);
54 // TODO: Could also destroy the geometry's buffers here.
55 }
56
57 if (renderer->shader) {
58 gfx_destroy_shader_program(renderer->gfxcore, &renderer->shader);
59 }
60}
61
62void gfx_imm_flush(Imm* renderer) {
63 assert(renderer);
64
65 if (renderer->num_triangle_verts > 0) {
66 gfx_update_geometry(
67 renderer->triangles,
68 &(GeometryDesc){
69 .num_verts = renderer->num_triangle_verts,
70 .positions3d = (BufferView3d){
71 .data = renderer->triangle_verts,
72 .size_bytes = renderer->num_triangle_verts * sizeof(vec3)}
73 });
74
75 gfx_llr_render_geometry(renderer->llr, renderer->triangles);
76
77 renderer->num_triangle_verts = 0;
78 }
79}
80
81void gfx_imm_start(Imm* renderer) {
82 assert(renderer);
83
84 // Shader uniforms are applied lazily.
85 // TODO: In the event that gfx_activate_shader_program() activates uniforms
86 // automatically for convenience, call an overload here that doesn't do so.
87 // gfx_activate_shader_program(renderer->shader);
88 gfx_llr_set_shader(renderer->llr, renderer->shader);
89}
90
91void gfx_imm_end(Imm* renderer) {
92 assert(renderer);
93
94 gfx_imm_flush(renderer);
95 // gfx_deactivate_shader_program(renderer->shader);
96 gfx_llr_set_shader(renderer->llr, 0);
97}
98
99void gfx_imm_draw_triangles(
100 Imm* renderer, const vec3 verts[], size_t num_triangles) {
101 assert(renderer);
102 assert(verts);
103 const size_t new_verts = num_triangles * 3;
104 assert(
105 renderer->num_triangle_verts + new_verts <
106 (GFX_IMM_MAX_NUM_TRIANGLES * 3));
107
108 memcpy(
109 renderer->triangle_verts + renderer->num_triangle_verts, verts,
110 new_verts * sizeof(vec3));
111
112 renderer->num_triangle_verts += new_verts;
113}
114
115void gfx_imm_draw_triangle(Imm* renderer, const vec3 verts[3]) {
116 gfx_imm_draw_triangles(renderer, verts, 1);
117}
118
119void gfx_imm_draw_aabb2(Imm* renderer, aabb2 box) {
120 assert(renderer);
121
122 // clang-format off
123 const vec3 verts[4] = {
124 vec3_make(box.min.x, box.min.y, 0), // 3 ---- 2
125 vec3_make(box.max.x, box.min.y, 0), // | |
126 vec3_make(box.max.x, box.max.y, 0), // | |
127 vec3_make(box.min.x, box.max.y, 0)}; // 0 ---- 1
128 // clang-format on
129
130#define tri(i0, i1, i2) verts[i0], verts[i1], verts[i2]
131 const vec3 tris[6] = {tri(0, 1, 2), tri(0, 2, 3)};
132#undef tri
133
134 gfx_imm_draw_triangles(renderer, tris, 2);
135}
136
137void gfx_imm_draw_aabb3(Imm* renderer, aabb3 box) {
138 assert(renderer);
139
140 // clang-format off
141 const vec3 vertices[8] = {
142 vec3_make(box.min.x, box.min.y, box.max.z), // 7 ----- 6
143 vec3_make(box.max.x, box.min.y, box.max.z), // / /|
144 vec3_make(box.max.x, box.max.y, box.max.z), // 3 ----- 2 |
145 vec3_make(box.min.x, box.max.y, box.max.z), // | | |
146 vec3_make(box.min.x, box.min.y, box.min.z), // | 4 ----- 5
147 vec3_make(box.max.x, box.min.y, box.min.z), // |/ |/
148 vec3_make(box.max.x, box.max.y, box.min.z), // 0 ----- 1
149 vec3_make(box.min.x, box.max.y, box.min.z)};
150 // clang-format on
151
152 gfx_imm_draw_box3(renderer, vertices);
153}
154
155void gfx_imm_draw_box3(Imm* renderer, const vec3 vertices[8]) {
156 assert(renderer);
157 assert(vertices);
158
159 // 7 ----- 6
160 // / /|
161 // 3 ----- 2 |
162 // | | |
163 // | 4 ----- 5
164 // |/ |/
165 // 0 ----- 1
166
167#define tri(i0, i1, i2) vertices[i0], vertices[i1], vertices[i2]
168 const vec3 tris[36] = {
169 // Front.
170 tri(0, 1, 2), tri(0, 2, 3),
171 // Right.
172 tri(1, 5, 6), tri(1, 6, 2),
173 // Back.
174 tri(5, 4, 7), tri(5, 7, 6),
175 // Left.
176 tri(4, 0, 03), tri(4, 3, 7),
177 // Top.
178 tri(3, 2, 6), tri(3, 6, 7),
179 // Bottom.
180 tri(0, 4, 5), tri(0, 5, 1)};
181
182 gfx_imm_draw_triangles(renderer, tris, 12);
183}
184
185void gfx_imm_set_colour(Imm* renderer, vec4 colour) {
186 assert(renderer);
187 assert(renderer->shader);
188
189 gfx_imm_flush(renderer);
190
191 gfx_set_vec4_uniform(renderer->shader, "Colour", colour);
192}
diff --git a/src/render/imm_impl.h b/src/render/imm_impl.h
new file mode 100644
index 0000000..d87b910
--- /dev/null
+++ b/src/render/imm_impl.h
@@ -0,0 +1,43 @@
1#pragma once
2
3#include <gfx/sizes.h>
4
5#include <math/vec3.h>
6
7#include <stdbool.h>
8#include <stddef.h>
9
10typedef struct Geometry Geometry;
11typedef struct GfxCore GfxCore;
12typedef struct IBL IBL;
13typedef struct LLR LLR;
14typedef struct Material Material;
15typedef struct ShaderProgram ShaderProgram;
16typedef struct Texture Texture;
17
18/// Immediate mode renderer.
19///
20/// Currently, the immediate mode renderer can only draw up to a maximum number
21/// of primitives per frame. It does not adjust this number dynamically. Keeps
22/// things simple while the extra complexity is not needed.
23/// TODO: Flush the buffer when it reaches its maximum size to remove this
24/// constraint.
25typedef struct Imm {
26 GfxCore* gfxcore;
27 LLR* llr;
28
29 ShaderProgram* shader; // Immediate-mode shader program for primitives.
30 Geometry* triangles;
31 size_t num_triangle_verts; // Number of triangle verts this frame.
32 // TODO: wireframe rendering.
33 struct {
34 bool wireframe : 1;
35 } flags;
36 vec3 triangle_verts[GFX_IMM_MAX_NUM_TRIANGLES * 3];
37} Imm;
38
39/// Create a new immediate mode renderer.
40bool gfx_imm_make(Imm*, GfxCore*, LLR*);
41
42/// Destroy the immediate mode renderer.
43void gfx_imm_destroy(Imm*);
diff --git a/src/render/llr.c b/src/render/llr.c
new file mode 100644
index 0000000..a1b37be
--- /dev/null
+++ b/src/render/llr.c
@@ -0,0 +1,527 @@
1#include "llr_impl.h"
2
3#include "memory.h"
4#include "scene/animation_impl.h"
5#include "scene/node_impl.h"
6
7#include <gfx/core.h>
8#include <gfx/util/ibl.h>
9
10#include <cassert.h>
11#include <error.h>
12
13static const int IRRADIANCE_MAP_WIDTH = 1024;
14static const int IRRADIANCE_MAP_HEIGHT = 1024;
15static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128;
16static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128;
17static const int BRDF_INTEGRATION_MAP_WIDTH = 512;
18static const int BRDF_INTEGRATION_MAP_HEIGHT = 512;
19
20static void make_environment_light(
21 Light* light, const EnvironmentLightDesc* desc) {
22 assert(light);
23 assert(desc);
24 light->type = EnvironmentLightType;
25 light->environment.environment_map = desc->environment_map;
26}
27
28Light* gfx_make_light(const LightDesc* desc) {
29 assert(desc);
30
31 Light* light = mem_alloc_light();
32
33 switch (desc->type) {
34 case EnvironmentLightType:
35 make_environment_light(light, &desc->light.environment);
36 break;
37 default:
38 log_error("Unhandled light type");
39 gfx_destroy_light(&light);
40 return 0;
41 }
42
43 return light;
44}
45
46void gfx_destroy_light(Light** light) {
47 assert(light);
48 if (*light) {
49 if ((*light)->parent.val) {
50 gfx_del_node((*light)->parent);
51 }
52 mem_free_light(light);
53 }
54}
55
56static void material_make(Material* material, const MaterialDesc* desc) {
57 assert(material);
58 assert(desc);
59 assert(desc->num_uniforms < GFX_MAX_UNIFORMS_PER_MATERIAL);
60 material->num_uniforms = desc->num_uniforms;
61 for (int i = 0; i < desc->num_uniforms; ++i) {
62 material->uniforms[i] = desc->uniforms[i];
63 }
64}
65
66Material* gfx_make_material(const MaterialDesc* desc) {
67 assert(desc);
68 Material* material = mem_alloc_material();
69 material_make(material, desc);
70 return material;
71}
72
73void gfx_destroy_material(Material** material) { mem_free_material(material); }
74
75static void set_uniform(ShaderProgram* prog, const ShaderUniform* uniform) {
76 switch (uniform->type) {
77 case UniformTexture:
78 gfx_set_texture_uniform(prog, uniform->name.str, uniform->value.texture);
79 break;
80 case UniformMat4:
81 gfx_set_mat4_uniform(prog, uniform->name.str, &uniform->value.mat4);
82 break;
83 case UniformVec3:
84 gfx_set_vec3_uniform(prog, uniform->name.str, uniform->value.vec3);
85 break;
86 case UniformVec4:
87 gfx_set_vec4_uniform(prog, uniform->name.str, uniform->value.vec4);
88 break;
89 case UniformFloat:
90 gfx_set_float_uniform(prog, uniform->name.str, uniform->value.scalar);
91 break;
92 case UniformMat4Array:
93 gfx_set_mat4_array_uniform(
94 prog, uniform->name.str, uniform->value.array.values,
95 uniform->value.array.count);
96 break;
97 }
98}
99
100/// Activate the material.
101///
102/// This configures the shader uniforms that are specific to the material.
103static void gfx_material_activate(
104 ShaderProgram* shader, const Material* material) {
105 assert(material);
106 for (int i = 0; i < material->num_uniforms; ++i) {
107 const ShaderUniform* uniform = &material->uniforms[i];
108 set_uniform(shader, uniform);
109 }
110}
111
112static void mesh_make(Mesh* mesh, const MeshDesc* desc) {
113 assert(mesh);
114 assert(desc);
115 assert(desc->geometry);
116 assert(desc->material);
117 assert(desc->shader);
118 mesh->geometry = desc->geometry;
119 mesh->material = desc->material;
120 mesh->shader = desc->shader;
121}
122
123Mesh* gfx_make_mesh(const MeshDesc* desc) {
124 Mesh* mesh = mem_alloc_mesh();
125 mesh_make(mesh, desc);
126 return mesh;
127}
128
129void gfx_destroy_mesh(Mesh** mesh) { mem_free_mesh(mesh); }
130
131/// Initialize renderer state for IBL.
132static bool init_ibl(LLR* renderer) {
133 assert(renderer);
134 assert(!renderer->ibl);
135 assert(!renderer->brdf_integration_map);
136
137 if (!((renderer->ibl = gfx_make_ibl(renderer->gfxcore)))) {
138 return false;
139 }
140
141 if (!((renderer->brdf_integration_map = gfx_make_brdf_integration_map(
142 renderer->ibl, renderer->gfxcore, BRDF_INTEGRATION_MAP_WIDTH,
143 BRDF_INTEGRATION_MAP_HEIGHT)))) {
144 return false;
145 }
146
147 return true;
148}
149
150/// Compute irradiance and prefiltered environment maps for the light if they
151/// have not been already computed.
152///
153/// This is done lazily here, and not when the light is created, because we
154/// need an IBL instance to do this and it is more convenient for the public
155/// API to create lights without worrying about those details. It also makes the
156/// public API cheaper, since the maps are only computed when they are actually
157/// needed.
158static bool set_up_environment_light(LLR* renderer, EnvironmentLight* light) {
159 assert(renderer);
160 assert(light);
161 assert(renderer->ibl);
162 assert(renderer->brdf_integration_map);
163
164 if (light->irradiance_map) {
165 assert(light->prefiltered_environment_map);
166 return true;
167 }
168
169 // For convenience.
170 GfxCore* gfxcore = renderer->gfxcore;
171
172 Texture* irradiance_map = 0;
173 Texture* prefiltered_environment_map = 0;
174
175 if (!((irradiance_map = gfx_make_irradiance_map(
176 renderer->ibl, gfxcore, light->environment_map,
177 IRRADIANCE_MAP_WIDTH, IRRADIANCE_MAP_HEIGHT)))) {
178 goto cleanup;
179 }
180
181 int max_mip_level = 0;
182 if (!((prefiltered_environment_map = gfx_make_prefiltered_environment_map(
183 renderer->ibl, gfxcore, light->environment_map,
184 PREFILTERED_ENVIRONMENT_MAP_WIDTH,
185 PREFILTERED_ENVIRONMENT_MAP_HEIGHT, &max_mip_level)))) {
186 goto cleanup;
187 }
188
189 light->irradiance_map = irradiance_map;
190 light->prefiltered_environment_map = prefiltered_environment_map;
191 light->max_reflection_lod = max_mip_level;
192
193 return true;
194
195cleanup:
196 if (irradiance_map) {
197 gfx_destroy_texture(gfxcore, &irradiance_map);
198 }
199 if (prefiltered_environment_map) {
200 gfx_destroy_texture(gfxcore, &prefiltered_environment_map);
201 }
202 return false;
203}
204
205static void configure_light(LLR* renderer, Light* light) {
206 assert(renderer);
207 assert(light);
208
209 // For convenience.
210 ShaderProgram* const shader = renderer->shader;
211
212 switch (light->type) {
213 case EnvironmentLightType: {
214 EnvironmentLight* env = &light->environment;
215
216 const bool initialized = set_up_environment_light(renderer, env);
217 ASSERT(initialized);
218 assert(env->environment_map);
219 assert(env->irradiance_map);
220 assert(env->prefiltered_environment_map);
221 assert(renderer->brdf_integration_map);
222
223 gfx_set_texture_uniform(
224 shader, "BRDFIntegrationMap", renderer->brdf_integration_map);
225 gfx_set_texture_uniform(shader, "Sky", env->environment_map);
226 gfx_set_texture_uniform(shader, "IrradianceMap", env->irradiance_map);
227 gfx_set_texture_uniform(
228 shader, "PrefilteredEnvironmentMap", env->prefiltered_environment_map);
229 gfx_set_float_uniform(
230 shader, "MaxReflectionLOD", (float)env->max_reflection_lod);
231
232 break;
233 }
234 default:
235 assert(false); // TODO: Implement other light types.
236 break;
237 }
238}
239
240static void configure_state(LLR* renderer) {
241 assert(renderer);
242
243 // Check if anything changed first so that we don't call gfx_apply_uniforms()
244 // unnecessarily.
245 const bool nothing_changed = (renderer->changed_flags == 0);
246 if (nothing_changed) {
247 return;
248 }
249 // Setting a null shader is also allowed, in which case there is nothing to
250 // configure.
251 if (renderer->shader == 0) {
252 renderer->shader_changed = false;
253 return;
254 }
255
256 // For convenience.
257 ShaderProgram* const shader = renderer->shader;
258 const mat4* const model = &renderer->matrix_stack[renderer->stack_pointer];
259
260 // TODO: Check to see which ones the shader actually uses and avoid
261 // computing the unnecessary matrices.
262
263 if (renderer->matrix_changed || renderer->shader_changed) {
264 renderer->matrix_changed = false;
265
266 gfx_set_mat4_uniform(shader, "Model", model);
267 gfx_set_mat4_uniform(shader, "ModelMatrix", model);
268 }
269
270 // TODO: camera_changed is not set anywhere. Need to think how imm primitive
271 // rendering and imm mesh rendering work together. We could treat imm
272 // primitive calls like setting a new shader.
273 if (renderer->camera_changed || renderer->shader_changed) {
274 renderer->camera_changed = false;
275
276 // Set all supported camera-related uniforms. Shaders can choose which ones
277 // to use.
278 const mat4 modelview = mat4_mul(renderer->view, *model);
279 const mat4 view_proj = mat4_mul(renderer->projection, renderer->view);
280 const mat4 mvp = mat4_mul(renderer->projection, modelview);
281
282 gfx_set_mat4_uniform(shader, "Modelview", &modelview);
283 gfx_set_mat4_uniform(shader, "View", &renderer->view);
284 gfx_set_mat4_uniform(shader, "Projection", &renderer->projection);
285 gfx_set_mat4_uniform(shader, "ViewProjection", &view_proj);
286 gfx_set_mat4_uniform(shader, "MVP", &mvp);
287 gfx_set_vec3_uniform(shader, "CameraPosition", renderer->camera_position);
288 gfx_set_mat4_uniform(shader, "CameraRotation", &renderer->camera_rotation);
289 gfx_set_float_uniform(shader, "Fovy", renderer->fovy);
290 gfx_set_float_uniform(shader, "Aspect", renderer->aspect);
291 }
292
293 if (renderer->lights_changed || renderer->shader_changed) {
294 renderer->lights_changed = false;
295
296 // TODO: Could do better by only setting the lights that have actually
297 // changed.
298 // TODO: Will also need to pass the number of lights to the shader once the
299 // other light types are implemented.
300 for (int i = 0; i < renderer->num_lights; ++i) {
301 configure_light(renderer, renderer->lights[i]);
302 }
303 }
304
305 if (renderer->skeleton_changed || renderer->shader_changed) {
306 renderer->skeleton_changed = false;
307
308 if (renderer->num_joints > 0) {
309 gfx_set_mat4_array_uniform(
310 shader, "JointMatrices", renderer->joint_matrices,
311 renderer->num_joints);
312 }
313 }
314
315 if (renderer->material_changed || renderer->shader_changed) {
316 renderer->material_changed = false;
317
318 // Geometry may be rendered without a material.
319 if (renderer->material) {
320 gfx_material_activate(renderer->shader, renderer->material);
321 }
322 }
323
324 if (renderer->shader_changed) {
325 renderer->shader_changed = false;
326 gfx_activate_shader_program(renderer->shader);
327 }
328
329 // TODO: At present, this results in many redundant calls to
330 // glGetUniformLocation() and glUniformXyz(). Look at the trace.
331 //
332 // TODO: Could add to qapitrace functionality to detect redundant calls and
333 // other inefficiencies. Maybe ask in the Github first if there would be
334 // interest in this.
335 //
336 // Must be called after activating the program.
337 gfx_apply_uniforms(renderer->shader);
338}
339
340bool gfx_llr_make(LLR* renderer, GfxCore* gfxcore) {
341 assert(renderer);
342 assert(gfxcore);
343
344 renderer->gfxcore = gfxcore;
345 if (!init_ibl(renderer)) {
346 goto cleanup;
347 }
348 gfx_llr_load_identity(renderer);
349 renderer->view = mat4_id();
350 renderer->projection = mat4_id();
351 renderer->camera_rotation = mat4_id();
352 return true;
353
354cleanup:
355 gfx_llr_destroy(renderer);
356 return false;
357}
358
359void gfx_llr_destroy(LLR* renderer) {
360 assert(renderer);
361 assert(renderer->gfxcore);
362
363 if (renderer->brdf_integration_map) {
364 gfx_destroy_texture(renderer->gfxcore, &renderer->brdf_integration_map);
365 }
366
367 // TODO: Do this once the IBL from the scene renderer is gone.
368 if (renderer->ibl) {
369 // gfx_destroy_ibl(renderer->gfxcore, &renderer->ibl);
370 }
371}
372
373void gfx_llr_set_shader(LLR* renderer, ShaderProgram* shader) {
374 assert(renderer);
375 // null shader is allowed, so do not assert it.
376
377 // It's important to not set shader_changed unnecessarily, since that would
378 // re-trigger the setting of uniforms.
379 if (renderer->shader != shader) {
380 renderer->shader = shader;
381 renderer->shader_changed = true;
382 }
383}
384
385void gfx_llr_push_light(LLR* renderer, Light* light) {
386 assert(renderer);
387 assert(light);
388 assert(renderer->num_lights >= 0);
389 ASSERT(renderer->num_lights < GFX_LLR_MAX_NUM_LIGHTS);
390
391 renderer->lights[renderer->num_lights++] = light;
392 renderer->lights_changed = true;
393}
394
395void gfx_llr_pop_light(LLR* renderer) {
396 assert(renderer);
397 ASSERT(renderer->num_lights > 0);
398
399 renderer->lights[--renderer->num_lights] = 0;
400 renderer->lights_changed = true;
401}
402
403void gfx_llr_set_skeleton(
404 LLR* renderer, const Anima* anima, const Skeleton* skeleton) {
405 assert(renderer);
406 assert(anima);
407 assert(skeleton);
408 assert(skeleton->num_joints <= GFX_MAX_NUM_JOINTS);
409
410 for (size_t i = 0; i < skeleton->num_joints; ++i) {
411 const joint_idx joint_index = skeleton->joints[i];
412 const Joint* joint = &anima->joints[joint_index];
413 renderer->joint_matrices[i] = joint->joint_matrix;
414 }
415 renderer->num_joints = skeleton->num_joints;
416 renderer->skeleton_changed = true;
417}
418
419void gfx_llr_clear_skeleton(LLR* renderer) {
420 assert(renderer);
421
422 renderer->num_joints = 0;
423 renderer->skeleton_changed = true;
424}
425
426void gfx_llr_set_material(LLR* renderer, const Material* material) {
427 assert(renderer);
428 assert(material);
429
430 renderer->material = material;
431 renderer->material_changed = true;
432}
433
434void gfx_llr_set_camera(LLR* renderer, const Camera* camera) {
435 assert(renderer);
436
437 renderer->camera_position = camera->spatial.p;
438 renderer->camera_rotation =
439 mat4_rotation(spatial3_transform(&camera->spatial));
440 renderer->view = spatial3_inverse_transform(&camera->spatial);
441 renderer->projection = camera->projection;
442 // Assuming a perspective matrix.
443 renderer->fovy = (R)atan(1.0 / (mat4_at(camera->projection, 1, 1))) * 2;
444 renderer->camera_changed = true;
445}
446
447void gfx_llr_set_projection_matrix(LLR* renderer, const mat4* projection) {
448 assert(renderer);
449
450 renderer->projection = *projection;
451 renderer->camera_changed = true;
452}
453
454void gfx_llr_set_aspect(LLR* renderer, float aspect) {
455 assert(renderer);
456
457 renderer->aspect = aspect;
458 renderer->camera_changed = true;
459}
460
461void gfx_llr_render_geometry(LLR* renderer, const Geometry* geometry) {
462 assert(renderer);
463 assert(geometry);
464
465 configure_state(renderer);
466 gfx_render_geometry(geometry);
467}
468
469void gfx_llr_render_mesh(LLR* renderer, const Mesh* mesh) {
470 assert(renderer);
471 assert(mesh);
472 assert(mesh->geometry);
473 assert(mesh->material);
474 assert(mesh->shader);
475
476 gfx_llr_set_material(renderer, mesh->material);
477 gfx_llr_set_shader(renderer, mesh->shader);
478 gfx_llr_render_geometry(renderer, mesh->geometry);
479}
480
481void gfx_llr_load_identity(LLR* renderer) {
482 assert(renderer);
483
484 renderer->matrix_stack[0] = mat4_id();
485 renderer->stack_pointer = 0;
486 renderer->matrix_changed = true;
487}
488
489void gfx_llr_push_matrix(LLR* renderer, const mat4* matrix) {
490 assert(renderer);
491 assert(matrix);
492 assert(renderer->stack_pointer >= 0);
493 ASSERT(renderer->stack_pointer < GFX_LLR_MAX_NUM_MATRICES);
494
495 renderer->stack_pointer += 1;
496 renderer->matrix_stack[renderer->stack_pointer] =
497 mat4_mul(*matrix, renderer->matrix_stack[renderer->stack_pointer - 1]);
498 renderer->matrix_changed = true;
499}
500
501void gfx_llr_pop_matrix(LLR* renderer) {
502 assert(renderer);
503 ASSERT(renderer->stack_pointer > 0);
504
505 // For debugging, zero out the matrix stack as matrices are popped out.
506 memset(
507 &renderer->matrix_stack[renderer->stack_pointer], 0,
508 sizeof(renderer->matrix_stack[0]));
509 renderer->stack_pointer -= 1;
510 renderer->matrix_changed = true;
511}
512
513void gfx_llr_translate(LLR* renderer, vec3 offset) {
514 assert(renderer);
515
516 const mat4 mat = mat4_translate(offset);
517 gfx_llr_push_matrix(renderer, &mat);
518}
519
520void gfx_llr_set_model_matrix(LLR* renderer, const mat4* model) {
521 assert(renderer);
522 assert(model);
523
524 renderer->matrix_stack[0] = *model;
525 renderer->stack_pointer = 0;
526 renderer->matrix_changed = true;
527}
diff --git a/src/render/llr_impl.h b/src/render/llr_impl.h
new file mode 100644
index 0000000..319441c
--- /dev/null
+++ b/src/render/llr_impl.h
@@ -0,0 +1,117 @@
1#pragma once
2
3#include <gfx/render/llr.h>
4#include <gfx/sizes.h>
5
6#include "scene/types.h"
7
8#include <math/mat4.h>
9#include <math/vec3.h>
10
11#include <stdbool.h>
12#include <stddef.h>
13#include <stdint.h>
14
15typedef struct Geometry Geometry;
16typedef struct GfxCore GfxCore;
17typedef struct IBL IBL;
18typedef struct Material Material;
19typedef struct ShaderProgram ShaderProgram;
20typedef struct Texture Texture;
21
22/// An environment light.
23typedef struct EnvironmentLight {
24 const Texture* environment_map;
25 const Texture* irradiance_map; // Renderer implementation.
26 const Texture* prefiltered_environment_map; // Renderer implementation.
27 int max_reflection_lod; // Mandatory when prefiltered_environment_map is
28 // given.
29} EnvironmentLight;
30
31/// A scene light.
32typedef struct Light {
33 LightType type;
34 union {
35 EnvironmentLight environment;
36 };
37 node_idx parent; // Parent SceneNode.
38} Light;
39
40typedef struct Material {
41 ShaderUniform uniforms[GFX_MAX_UNIFORMS_PER_MATERIAL];
42 int num_uniforms;
43} Material;
44
45typedef struct Mesh {
46 const Geometry* geometry;
47 const Material* material;
48 ShaderProgram* shader; // TODO: Move this back to Material?
49} Mesh;
50
51/// Immediate mode renderer.
52///
53/// The renderer caches state changes in memory and only programs the underlying
54/// shader program when a draw call is issued and if anything has changed. This
55/// keeps the number of graphics API calls to a minimum, but requires tracking
56/// state changes. The 'changed' booleans below fulfill this purpose, and
57/// indicate whether a given state has changed since the last draw call.
58///
59/// The renderer must combine state changes accordingly. For example, if only
60/// the lights have changed, then it is sufficient to update light uniforms in
61/// the current shader program. On the other hand, if the shader program has
62/// changed, then the renderer must reconfigure it from scratch and set light
63/// uniforms, camera uniforms, etc.
64///
65/// Note that the shader program API has its own level of caching as well, so
66/// reconfiguration at the level of the renderer does not result in the
67/// worst-case set of graphics API calls.
68typedef struct LLR {
69 GfxCore* gfxcore;
70
71 union {
72 struct {
73 bool shader_changed : 1; // Whether the shader has changed.
74 bool camera_changed : 1; // Whether the camera parameters have changed.
75 bool lights_changed : 1; // Whether the lights have changed.
76 bool skeleton_changed : 1; // Whether the skeleton has changed.
77 bool material_changed : 1; // Whether the material has changed.
78 bool matrix_changed : 1; // Whether the matrix stack has changed.
79 };
80 uint8_t changed_flags;
81 };
82
83 IBL* ibl;
84 Texture* brdf_integration_map;
85
86 ShaderProgram* shader; // Active shader. Not owned.
87
88 const Material* material; // Active material. Not owned.
89
90 vec3 camera_position;
91 mat4 camera_rotation;
92 mat4 view; // Camera view matrix.
93 mat4 projection; // Camera projection matrix.
94 R fovy; // Camera vertical field of view.
95 R aspect; // Aspect ratio.
96
97 // Lights are not const because environment lights store lazily-computed
98 // irradiance maps.
99 Light* lights[GFX_LLR_MAX_NUM_LIGHTS]; // Lights stack.
100 int num_lights; // Number of lights enabled at a given point in time. It
101 // points to one past the top of the stack.
102
103 size_t num_joints;
104 mat4 joint_matrices[GFX_MAX_NUM_JOINTS];
105
106 // The matrix stack contains pre-multiplied matrices.
107 // It is also never empty. The top of the stack is an identity matrix when the
108 // stack is "empty" from the user's perspective.
109 mat4 matrix_stack[GFX_LLR_MAX_NUM_MATRICES];
110 int stack_pointer; // Points to the top of the stack.
111} LLR;
112
113/// Create a new immediate mode renderer.
114bool gfx_llr_make(LLR*, GfxCore*);
115
116/// Destroy the immediate mode renderer.
117void gfx_llr_destroy(LLR*);
diff --git a/src/render/renderer.c b/src/render/renderer.c
new file mode 100644
index 0000000..eca7551
--- /dev/null
+++ b/src/render/renderer.c
@@ -0,0 +1,246 @@
1#include "renderer_impl.h"
2
3#include "llr_impl.h"
4#include "memory.h"
5#include "scene/animation_impl.h"
6#include "scene/camera_impl.h"
7#include "scene/model_impl.h"
8#include "scene/node_impl.h"
9#include "scene/object_impl.h"
10#include "scene/scene_impl.h"
11
12#include <gfx/core.h>
13#include <gfx/render/llr.h>
14#include <gfx/util/shader.h>
15
16#include <math/mat4.h>
17
18#include <assert.h>
19
20bool gfx_renderer_make(Renderer* renderer, LLR* llr, GfxCore* gfxcore) {
21 assert(renderer);
22 assert(llr);
23 assert(gfxcore);
24
25 renderer->gfxcore = gfxcore;
26 renderer->llr = llr;
27
28 return true;
29}
30
31void gfx_renderer_destroy(Renderer* renderer) {
32 if (!renderer) {
33 return;
34 }
35 assert(renderer->gfxcore);
36 GfxCore* gfxcore = renderer->gfxcore;
37 if (renderer->shaders.debug) {
38 gfx_destroy_shader_program(gfxcore, &renderer->shaders.debug);
39 }
40 if (renderer->shaders.normals) {
41 gfx_destroy_shader_program(gfxcore, &renderer->shaders.normals);
42 }
43 if (renderer->shaders.normal_mapped_normals) {
44 gfx_destroy_shader_program(
45 gfxcore, &renderer->shaders.normal_mapped_normals);
46 }
47 if (renderer->shaders.tangents) {
48 gfx_destroy_shader_program(gfxcore, &renderer->shaders.tangents);
49 }
50}
51
52static ShaderProgram* load_shader(Renderer* renderer, RenderSceneMode mode) {
53 assert(renderer);
54
55#define LOAD_AND_RETURN(pShader, constructor) \
56 { \
57 if (!pShader) { \
58 pShader = constructor(renderer->gfxcore); \
59 } \
60 assert(pShader); \
61 return pShader; \
62 }
63
64 switch (mode) {
65 case RenderDefault:
66 return 0;
67 case RenderDebug:
68 LOAD_AND_RETURN(renderer->shaders.debug, gfx_make_debug3d_shader);
69 case RenderNormals:
70 LOAD_AND_RETURN(renderer->shaders.normals, gfx_make_view_normals_shader);
71 case RenderNormalMappedNormals:
72 LOAD_AND_RETURN(
73 renderer->shaders.normal_mapped_normals,
74 gfx_make_view_normal_mapped_normals_shader);
75 case RenderTangents:
76 LOAD_AND_RETURN(renderer->shaders.tangents, gfx_make_view_tangents_shader);
77 }
78 assert(false);
79 return 0;
80}
81
82// static void log_matrix(const mat4* m) {
83// for (int row = 0; row < 4; ++row) {
84// LOGI("[ %5.2f, %5.2f, %5.2f, %5.2f ]", m->val[0][row], m->val[1][row],
85// m->val[2][row], m->val[3][row]);
86// }
87// }
88
89typedef struct RenderState {
90 GfxCore* gfxcore;
91 LLR* llr;
92 Renderer* renderer;
93 ShaderProgram* shader; // Null to use scene shaders.
94 const Scene* scene;
95 const Anima* anima;
96} RenderState;
97
98static void draw_children(
99 RenderState* state, const mat4* node_transform, const SceneNode* node);
100
101/// Draw the scene recursively.
102static void draw_recursively(
103 RenderState* state, mat4 parent_transform, const SceneNode* node) {
104 assert(state);
105 const mat4 node_transform = mat4_mul(parent_transform, node->transform);
106
107 // Anima.
108 if (node->type == AnimaNode) {
109 // Save the anima so that we can animate objects.
110 state->anima = gfx_get_node_anima(node);
111
112 draw_children(state, &node_transform, node);
113 }
114 // Activate light.
115 else if (node->type == LightNode) {
116 Light* light = mem_get_light(node->light);
117 assert(light);
118 gfx_llr_push_light(state->llr, light);
119 {
120 draw_children(state, &node_transform, node);
121 }
122 gfx_llr_pop_light(state->llr);
123 }
124 // Model.
125 else if (node->type == ModelNode) {
126 const Model* model = gfx_get_node_model(node);
127 const SceneNode* root = mem_get_node(model->root);
128 draw_recursively(state, parent_transform, root);
129 draw_children(state, &node_transform, node);
130 }
131 // Render object.
132 else if (node->type == ObjectNode) {
133 const SceneObject* object = mem_get_object(node->object);
134 assert(object);
135
136 // TODO: Here we would frustum-cull the object.
137
138 // A model/anima can have many skeletons. We need to animate the given
139 // object using its skeleton, not just any skeleton of the anima.
140 if (object->skeleton.val) {
141 const Skeleton* skeleton = mem_get_skeleton(object->skeleton);
142 gfx_llr_set_skeleton(state->llr, state->anima, skeleton);
143 }
144
145 const mat4 model_matrix = node_transform;
146
147 for (mesh_link_idx mesh_link_index = object->mesh_link;
148 mesh_link_index.val;) {
149 const MeshLink* mesh_link = mem_get_mesh_link(mesh_link_index);
150 mesh_link_index = mesh_link->next;
151
152 const Mesh* mesh = mem_get_mesh(mesh_link->mesh);
153 if (!mesh) {
154 continue;
155 }
156
157 // TODO: Here we would frustum-cull the mesh. The AABB would have to be
158 // transformed by the model matrix. Rotation would make the AABB
159 // relatively large, but still, the culling would be conservative.
160
161 ShaderProgram* shader = state->shader ? state->shader : mesh->shader;
162 gfx_llr_set_shader(state->llr, shader);
163 gfx_llr_set_model_matrix(state->llr, &model_matrix);
164 gfx_llr_render_mesh(state->llr, mesh);
165 }
166
167 if (object->skeleton.val) {
168 gfx_llr_clear_skeleton(state->llr);
169 }
170
171 draw_children(state, &node_transform, node);
172 } else {
173 draw_children(state, &node_transform, node);
174 }
175}
176
177/// Draw the node's children.
178static void draw_children(
179 RenderState* state, const mat4* node_transform, const SceneNode* node) {
180 // Render children recursively.
181 for (node_idx child_index = node->child; child_index.val;) {
182 const SceneNode* child = mem_get_node(child_index);
183 draw_recursively(state, *node_transform, child);
184 child_index = child->next;
185 }
186}
187
188void gfx_render_scene(Renderer* renderer, const RenderSceneParams* params) {
189 assert(renderer);
190 assert(params);
191 assert(params->scene);
192
193 ShaderProgram* const shader = load_shader(renderer, params->mode);
194
195 const Scene* scene = params->scene;
196 const SceneCamera* camera = params->camera;
197 GfxCore* const gfxcore = renderer->gfxcore;
198
199 int x, y, width, height;
200 gfx_get_viewport(gfxcore, &x, &y, &width, &height);
201 const R aspect = (R)width / (R)height;
202
203 RenderState state = {
204 .gfxcore = gfxcore,
205 .llr = renderer->llr,
206 .renderer = renderer,
207 .shader = shader,
208 .scene = scene};
209
210 gfx_llr_set_camera(renderer->llr, &camera->camera);
211 gfx_llr_set_aspect(renderer->llr, aspect);
212 draw_recursively(&state, mat4_id(), scene->root);
213}
214
215static void update_rec(SceneNode* node, const SceneCamera* camera, R t) {
216 assert(node);
217 assert(camera);
218
219 const NodeType node_type = gfx_get_node_type(node);
220
221 // TODO: Models do not need to be animated if they are not visible to the
222 // camera.
223 if (node_type == AnimaNode) {
224 Anima* anima = gfx_get_node_anima_mut(node);
225 gfx_update_animation(anima, (R)t);
226 } else if (node_type == ModelNode) {
227 Model* model = gfx_get_node_model_mut(node);
228 SceneNode* root = gfx_get_model_root_mut(model);
229 update_rec(root, camera, t);
230 }
231
232 // Children.
233 SceneNode* child = gfx_get_node_child_mut(node);
234 while (child) {
235 update_rec(child, camera, t);
236 child = gfx_get_node_sibling_mut(child);
237 }
238}
239
240void gfx_update(Scene* scene, const SceneCamera* camera, R t) {
241 assert(scene);
242 assert(camera);
243
244 SceneNode* node = gfx_get_scene_root(scene);
245 update_rec(node, camera, t);
246}
diff --git a/src/render/renderer_impl.h b/src/render/renderer_impl.h
new file mode 100644
index 0000000..160ff52
--- /dev/null
+++ b/src/render/renderer_impl.h
@@ -0,0 +1,25 @@
1#pragma once
2
3#include <gfx/render/renderer.h>
4
5#include <stdbool.h>
6
7typedef struct LLR LLR;
8typedef struct ShaderProgram ShaderProgram;
9
10typedef struct Renderer {
11 GfxCore* gfxcore;
12 LLR* llr;
13 struct {
14 ShaderProgram* debug;
15 ShaderProgram* normals;
16 ShaderProgram* normal_mapped_normals;
17 ShaderProgram* tangents;
18 } shaders;
19} Renderer;
20
21/// Create a new renderer.
22bool gfx_renderer_make(Renderer*, LLR*, GfxCore*);
23
24/// Destroy the renderer.
25void gfx_renderer_destroy(Renderer*);