aboutsummaryrefslogtreecommitdiff
path: root/src/llr/llr.c
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2025-07-04 10:27:06 -0700
committer3gg <3gg@shellblade.net>2025-07-04 10:27:06 -0700
commit1ec46bead3cf87971a2329f9ef4ddde5a0c48325 (patch)
tree3f4c404467c3ad9c94265295f4aa1b97a10a9eb3 /src/llr/llr.c
parente386405ac636b7e4a41d5c03eb363e9c120ce919 (diff)
Clarify doc
Diffstat (limited to 'src/llr/llr.c')
-rw-r--r--src/llr/llr.c554
1 files changed, 554 insertions, 0 deletions
diff --git a/src/llr/llr.c b/src/llr/llr.c
new file mode 100644
index 0000000..62a7c30
--- /dev/null
+++ b/src/llr/llr.c
@@ -0,0 +1,554 @@
1#include "imm_renderer_impl.h"
2#include "light_impl.h"
3#include "mesh_impl.h"
4
5#include "scene/animation_impl.h"
6
7#include <gfx/core.h>
8#include <gfx/util/ibl.h>
9#include <gfx/util/shader.h>
10
11#include <math/aabb3.h>
12
13#include <cassert.h>
14#include <string.h> // memcpy
15
16static const int IRRADIANCE_MAP_WIDTH = 1024;
17static const int IRRADIANCE_MAP_HEIGHT = 1024;
18static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128;
19static const int PREFILTERED_ENVIRONMENT_MAP_HEIGHT = 128;
20static const int BRDF_INTEGRATION_MAP_WIDTH = 512;
21static const int BRDF_INTEGRATION_MAP_HEIGHT = 512;
22
23/// Initialize renderer state for IBL.
24static bool init_ibl(ImmRenderer* renderer) {
25 assert(renderer);
26 assert(!renderer->ibl);
27 assert(!renderer->brdf_integration_map);
28
29 if (!((renderer->ibl = gfx_make_ibl(renderer->gfxcore)))) {
30 return false;
31 }
32
33 if (!((renderer->brdf_integration_map = gfx_make_brdf_integration_map(
34 renderer->ibl, renderer->gfxcore, BRDF_INTEGRATION_MAP_WIDTH,
35 BRDF_INTEGRATION_MAP_HEIGHT)))) {
36 return false;
37 }
38
39 return true;
40}
41
42// TODO: Why is this done lazily here? Do it when the environment light is
43// created.
44//
45/// Compute irradiance and prefiltered environment maps for the light if they
46/// have not been already computed.
47static bool set_up_environment_light(
48 ImmRenderer* renderer, EnvironmentLight* light) {
49 assert(renderer);
50 assert(light);
51 assert(renderer->ibl);
52 assert(renderer->brdf_integration_map);
53
54 if (light->irradiance_map) {
55 assert(light->prefiltered_environment_map);
56 return true;
57 }
58
59 // For convenience.
60 GfxCore* gfxcore = renderer->gfxcore;
61
62 Texture* irradiance_map = 0;
63 Texture* prefiltered_environment_map = 0;
64
65 if (!((irradiance_map = gfx_make_irradiance_map(
66 renderer->ibl, gfxcore, light->environment_map,
67 IRRADIANCE_MAP_WIDTH, IRRADIANCE_MAP_HEIGHT)))) {
68 goto cleanup;
69 }
70
71 int max_mip_level = 0;
72 if (!((prefiltered_environment_map = gfx_make_prefiltered_environment_map(
73 renderer->ibl, gfxcore, light->environment_map,
74 PREFILTERED_ENVIRONMENT_MAP_WIDTH,
75 PREFILTERED_ENVIRONMENT_MAP_HEIGHT, &max_mip_level)))) {
76 goto cleanup;
77 }
78
79 light->irradiance_map = irradiance_map;
80 light->prefiltered_environment_map = prefiltered_environment_map;
81 light->max_reflection_lod = max_mip_level;
82
83 return true;
84
85cleanup:
86 if (irradiance_map) {
87 gfx_destroy_texture(gfxcore, &irradiance_map);
88 }
89 if (prefiltered_environment_map) {
90 gfx_destroy_texture(gfxcore, &prefiltered_environment_map);
91 }
92 return false;
93}
94
95static void configure_light(ImmRenderer* renderer, Light* light) {
96 assert(renderer);
97 assert(light);
98
99 // For convenience.
100 ShaderProgram* const shader = renderer->shader;
101
102 switch (light->type) {
103 case EnvironmentLightType: {
104 EnvironmentLight* env = &light->environment;
105
106 const bool initialized = set_up_environment_light(renderer, env);
107 ASSERT(initialized);
108 assert(env->environment_map);
109 assert(env->irradiance_map);
110 assert(env->prefiltered_environment_map);
111 assert(renderer->brdf_integration_map);
112
113 gfx_set_texture_uniform(
114 shader, "BRDFIntegrationMap", renderer->brdf_integration_map);
115 gfx_set_texture_uniform(shader, "Sky", env->environment_map);
116 gfx_set_texture_uniform(shader, "IrradianceMap", env->irradiance_map);
117 gfx_set_texture_uniform(
118 shader, "PrefilteredEnvironmentMap", env->prefiltered_environment_map);
119 gfx_set_float_uniform(
120 shader, "MaxReflectionLOD", (float)env->max_reflection_lod);
121
122 break;
123 }
124 default:
125 assert(false); // TODO: Implement other light types.
126 break;
127 }
128}
129
130static void configure_state(ImmRenderer* renderer) {
131 assert(renderer);
132
133 // Check if anything changed first so that we don't call gfx_apply_uniforms()
134 // unnecessarily.
135 const bool anything_changed =
136 renderer->camera_changed || renderer->lights_changed ||
137 renderer->skeleton_changed || renderer->shader_changed;
138 if (!anything_changed) {
139 return;
140 }
141
142 // For convenience.
143 ShaderProgram* const shader = renderer->shader;
144
145 // TODO: camera_changed is not set anywhere. Need to think how imm primitive
146 // rendering and imm mesh rendering work together. We could treat imm
147 // primitive calls like setting a new shader.
148 if (renderer->camera_changed || renderer->shader_changed) {
149 renderer->camera_changed = false;
150
151 // Set all supported camera-related uniforms. Shaders can choose which ones
152 // to use.
153 // TODO: Check to see which ones the shader actually uses and avoid
154 // computing the unnecessary matrices.
155 const mat4* const model = &renderer->matrix_stack[renderer->stack_pointer];
156 const mat4 modelview = mat4_mul(renderer->view, *model);
157 const mat4 view_proj = mat4_mul(renderer->projection, renderer->view);
158 const mat4 mvp = mat4_mul(renderer->projection, modelview);
159
160 gfx_set_mat4_uniform(shader, "ModelMatrix", model);
161 gfx_set_mat4_uniform(shader, "Modelview", &modelview);
162 gfx_set_mat4_uniform(shader, "View", &renderer->view);
163 gfx_set_mat4_uniform(shader, "Projection", &renderer->projection);
164 gfx_set_mat4_uniform(shader, "ViewProjection", &view_proj);
165 gfx_set_mat4_uniform(shader, "MVP", &mvp);
166 gfx_set_vec3_uniform(shader, "CameraPosition", renderer->camera_position);
167 }
168
169 if (renderer->lights_changed || renderer->shader_changed) {
170 renderer->lights_changed = false;
171
172 // TODO: Could do better by only setting the lights that have actually
173 // changed.
174 // TODO: Will also need to pass the number of lights to the shader once the
175 // other light types are implemented.
176 for (int i = 0; i < renderer->num_lights; ++i) {
177 configure_light(renderer, renderer->lights[i]);
178 }
179 }
180
181 if (renderer->skeleton_changed || renderer->shader_changed) {
182 renderer->skeleton_changed = false;
183
184 gfx_set_mat4_array_uniform(
185 shader, "JointMatrices", renderer->joint_matrices,
186 renderer->num_joints);
187 }
188
189 if (renderer->shader_changed) {
190 renderer->shader_changed = false;
191 gfx_activate_shader_program(renderer->shader);
192 }
193
194 // Must be called after activating the program.
195 gfx_apply_uniforms(renderer->shader);
196}
197
198bool gfx_imm_make(ImmRenderer* renderer, GfxCore* gfxcore) {
199 assert(renderer);
200 assert(gfxcore);
201
202 const size_t num_triangle_verts = IMM_MAX_NUM_TRIANGLES * 3;
203
204 renderer->gfxcore = gfxcore;
205
206 renderer->triangles = gfx_make_geometry(
207 gfxcore,
208 &(GeometryDesc){.type = Triangles,
209 .buffer_usage = BufferDynamic,
210 .num_verts = num_triangle_verts,
211 .positions3d = (BufferView3d){
212 .size_bytes = num_triangle_verts * sizeof(vec3)}});
213 if (!renderer->triangles) {
214 goto cleanup;
215 }
216
217 renderer->imm_shader = gfx_make_immediate_mode_shader(gfxcore);
218 if (!renderer->imm_shader) {
219 goto cleanup;
220 }
221 renderer->shader = renderer->imm_shader;
222
223 if (!init_ibl(renderer)) {
224 goto cleanup;
225 }
226
227 gfx_imm_load_identity(renderer);
228 gfx_imm_set_colour(renderer, vec4_make(0.0f, 0.0f, 0.0f, 1.0f));
229
230 return true;
231
232cleanup:
233 gfx_imm_destroy(renderer);
234 return false;
235}
236
237void gfx_imm_destroy(ImmRenderer* renderer) {
238 assert(renderer);
239 assert(renderer->gfxcore);
240
241 if (renderer->triangles) {
242 gfx_destroy_geometry(renderer->gfxcore, &renderer->triangles);
243 // TODO: Could also destroy the geometry's buffers here.
244 }
245
246 if (renderer->imm_shader) {
247 gfx_destroy_shader_program(renderer->gfxcore, &renderer->imm_shader);
248 }
249
250 if (renderer->brdf_integration_map) {
251 gfx_destroy_texture(renderer->gfxcore, &renderer->brdf_integration_map);
252 }
253
254 // TODO: Do this once the IBL from the scene renderer is gone.
255 if (renderer->ibl) {
256 // gfx_destroy_ibl(renderer->gfxcore, &renderer->ibl);
257 }
258}
259
260void gfx_imm_flush(ImmRenderer* renderer) {
261 assert(renderer);
262
263 if (renderer->num_triangle_verts > 0) {
264 configure_state(renderer);
265
266 gfx_update_geometry(
267 renderer->triangles,
268 &(GeometryDesc){
269 .num_verts = renderer->num_triangle_verts,
270 .positions3d = (BufferView3d){
271 .data = renderer->triangle_verts,
272 .size_bytes = renderer->num_triangle_verts * sizeof(vec3)}
273 });
274
275 gfx_apply_uniforms(renderer->shader);
276 gfx_render_geometry(renderer->triangles);
277
278 renderer->num_triangle_verts = 0;
279 }
280}
281
282void gfx_imm_set_shader(ImmRenderer* renderer, ShaderProgram* shader) {
283 assert(renderer);
284 assert(shader);
285
286 // TODO: It would probably be best to make the imm renderer work in terms of a
287 // new LLR renderer. Otherwise we need to constantly flush stuff everywhere
288 // "just in case". This would still allow the imm to render meshes with
289 // lighting etc. We just need to create an actual Mesh out of the 'triangles'
290 // Geometry that imm currently has. The change would greatly simplify the
291 // implementation of this otherwise coupled LLR-IMM renderer.
292 // Need to decide where to put the matrix stack manipulation. Might be good
293 // to move to the LLR. (Currently, manipulating the stack causes an imm
294 // flush, but that's because we have coupled imm with stack manipulation,
295 // which the new design seems like would address.)
296 gfx_imm_flush(renderer);
297
298 // It's important to not set shader_changed unnecessarily, since that would
299 // re-trigger the setting of uniforms.
300 if (renderer->shader != shader) {
301 renderer->shader = shader;
302 renderer->shader_changed = true;
303 }
304}
305
306void gfx_imm_start(ImmRenderer* renderer) {
307 assert(renderer);
308
309 // Shader uniforms are applied lazily.
310 // TODO: In the event that gfx_activate_shader_program() activates uniforms
311 // automatically for convenience, call an overload here that doesn't do so.
312 gfx_activate_shader_program(renderer->shader);
313}
314
315void gfx_imm_end(ImmRenderer* renderer) {
316 assert(renderer);
317
318 gfx_imm_flush(renderer);
319 gfx_deactivate_shader_program(renderer->shader);
320
321 // TODO: Should we clear all of the render state here as well? At least set
322 // the 'changed' variables to false, for example.
323}
324
325void gfx_imm_push_light(ImmRenderer* renderer, Light* light) {
326 assert(renderer);
327 assert(light);
328 assert(renderer->num_lights >= 0);
329 ASSERT(renderer->num_lights < IMM_MAX_NUM_LIGHTS);
330
331 renderer->lights[renderer->num_lights++] = light;
332 renderer->lights_changed = true;
333}
334
335void gfx_imm_pop_light(ImmRenderer* renderer) {
336 assert(renderer);
337 ASSERT(renderer->num_lights > 0);
338
339 renderer->lights[--renderer->num_lights] = 0;
340 renderer->lights_changed = true;
341}
342
343void gfx_imm_set_skeleton(
344 ImmRenderer* renderer, const Anima* anima, const Skeleton* skeleton) {
345 assert(renderer);
346 assert(anima);
347 assert(skeleton);
348 assert(skeleton->num_joints <= GFX_MAX_NUM_JOINTS);
349
350 for (size_t i = 0; i < skeleton->num_joints; ++i) {
351 const joint_idx joint_index = skeleton->joints[i];
352 const Joint* joint = &anima->joints[joint_index];
353 renderer->joint_matrices[i] = joint->joint_matrix;
354 }
355 renderer->num_joints = skeleton->num_joints;
356 renderer->skeleton_changed = true;
357}
358
359void gfx_imm_unset_skeleton(ImmRenderer* renderer) {
360 assert(renderer);
361
362 renderer->num_joints = 0;
363 renderer->skeleton_changed = true;
364}
365
366void gfx_imm_render_mesh(ImmRenderer* renderer, const Mesh* mesh) {
367 assert(renderer);
368 assert(mesh);
369 assert(mesh->geometry);
370 assert(mesh->material);
371
372 configure_state(renderer);
373 gfx_render_geometry(mesh->geometry);
374}
375
376void gfx_imm_draw_triangles(
377 ImmRenderer* renderer, const vec3 verts[], size_t num_triangles) {
378 assert(renderer);
379 assert(verts);
380 const size_t new_verts = num_triangles * 3;
381 assert(
382 renderer->num_triangle_verts + new_verts < (IMM_MAX_NUM_TRIANGLES * 3));
383
384 memcpy(
385 renderer->triangle_verts + renderer->num_triangle_verts, verts,
386 new_verts * sizeof(vec3));
387
388 renderer->num_triangle_verts += new_verts;
389}
390
391void gfx_imm_draw_triangle(ImmRenderer* renderer, const vec3 verts[3]) {
392 gfx_imm_draw_triangles(renderer, verts, 1);
393}
394
395void gfx_imm_draw_aabb2(ImmRenderer* renderer, aabb2 box) {
396 assert(renderer);
397
398 // clang-format off
399 const vec3 verts[4] = {
400 vec3_make(box.min.x, box.min.y, 0), // 3 ---- 2
401 vec3_make(box.max.x, box.min.y, 0), // | |
402 vec3_make(box.max.x, box.max.y, 0), // | |
403 vec3_make(box.min.x, box.max.y, 0)}; // 0 ---- 1
404 // clang-format on
405
406#define tri(i0, i1, i2) verts[i0], verts[i1], verts[i2]
407 const vec3 tris[6] = {tri(0, 1, 2), tri(0, 2, 3)};
408#undef tri
409
410 gfx_imm_draw_triangles(renderer, tris, 2);
411}
412
413void gfx_imm_draw_aabb3(ImmRenderer* renderer, aabb3 box) {
414 assert(renderer);
415
416 // clang-format off
417 const vec3 vertices[8] = {
418 vec3_make(box.min.x, box.min.y, box.max.z), // 7 ----- 6
419 vec3_make(box.max.x, box.min.y, box.max.z), // / /|
420 vec3_make(box.max.x, box.max.y, box.max.z), // 3 ----- 2 |
421 vec3_make(box.min.x, box.max.y, box.max.z), // | | |
422 vec3_make(box.min.x, box.min.y, box.min.z), // | 4 ----- 5
423 vec3_make(box.max.x, box.min.y, box.min.z), // |/ |/
424 vec3_make(box.max.x, box.max.y, box.min.z), // 0 ----- 1
425 vec3_make(box.min.x, box.max.y, box.min.z)};
426 // clang-format on
427
428 gfx_imm_draw_box3(renderer, vertices);
429}
430
431void gfx_imm_draw_box3(ImmRenderer* renderer, const vec3 vertices[8]) {
432 assert(renderer);
433 assert(vertices);
434
435 // 7 ----- 6
436 // / /|
437 // 3 ----- 2 |
438 // | | |
439 // | 4 ----- 5
440 // |/ |/
441 // 0 ----- 1
442
443#define tri(i0, i1, i2) vertices[i0], vertices[i1], vertices[i2]
444 const vec3 tris[36] = {
445 // Front.
446 tri(0, 1, 2), tri(0, 2, 3),
447 // Right.
448 tri(1, 5, 6), tri(1, 6, 2),
449 // Back.
450 tri(5, 4, 7), tri(5, 7, 6),
451 // Left.
452 tri(4, 0, 03), tri(4, 3, 7),
453 // Top.
454 tri(3, 2, 6), tri(3, 6, 7),
455 // Bottom.
456 tri(0, 4, 5), tri(0, 5, 1)};
457
458 gfx_imm_draw_triangles(renderer, tris, 12);
459}
460
461void gfx_imm_set_colour(ImmRenderer* renderer, vec4 colour) {
462 assert(renderer);
463 assert(renderer->shader);
464
465 gfx_imm_flush(renderer);
466
467 gfx_set_vec4_uniform(renderer->shader, "Colour", colour);
468}
469
470// Load the top of the matrix stack into the shader.
471static void update_shader_model_matrix(ImmRenderer* renderer) {
472 assert(renderer);
473
474 gfx_imm_flush(renderer);
475
476 gfx_set_mat4_uniform(
477 renderer->shader, "Model",
478 &renderer->matrix_stack[renderer->stack_pointer]);
479}
480
481void gfx_imm_load_identity(ImmRenderer* renderer) {
482 assert(renderer);
483
484 renderer->matrix_stack[0] = mat4_id();
485 renderer->stack_pointer = 0;
486 update_shader_model_matrix(renderer);
487}
488
489void gfx_imm_push_matrix(ImmRenderer* renderer, const mat4* matrix) {
490 assert(renderer);
491 assert(matrix);
492 assert(renderer->stack_pointer >= 0);
493 ASSERT(renderer->stack_pointer < IMM_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
499 update_shader_model_matrix(renderer);
500}
501
502void gfx_imm_pop_matrix(ImmRenderer* renderer) {
503 assert(renderer);
504 ASSERT(renderer->stack_pointer > 0);
505
506 // For debugging, zero out the matrix stack as matrices are popped out.
507 memset(
508 &renderer->matrix_stack[renderer->stack_pointer], 0,
509 sizeof(renderer->matrix_stack[0]));
510 renderer->stack_pointer -= 1;
511
512 update_shader_model_matrix(renderer);
513}
514
515void gfx_imm_translate(ImmRenderer* renderer, vec3 offset) {
516 assert(renderer);
517
518 const mat4 mat = mat4_translate(offset);
519 gfx_imm_push_matrix(renderer, &mat);
520}
521
522void gfx_imm_set_model_matrix(ImmRenderer* renderer, const mat4* model) {
523 assert(renderer);
524 assert(model);
525
526 gfx_imm_flush(renderer);
527
528 renderer->matrix_stack[0] = *model;
529 renderer->stack_pointer = 0;
530 update_shader_model_matrix(renderer);
531}
532
533void gfx_imm_set_camera(ImmRenderer* renderer, const Camera* camera) {
534 assert(renderer);
535 assert(renderer->shader);
536
537 gfx_imm_flush(renderer);
538
539 const mat4 view = spatial3_inverse_transform(&camera->spatial);
540 // const mat4 view_proj = mat4_mul(camera->projection, view);
541 // gfx_imm_set_view_projection_matrix(renderer, &view_proj);
542 renderer->view = view;
543 renderer->projection = camera->projection;
544 renderer->camera_changed = true;
545}
546
547// void gfx_imm_set_view_projection_matrix(
548// ImmRenderer* renderer, const mat4* view_proj) {
549// assert(renderer);
550// assert(renderer->shader);
551//
552// gfx_imm_flush(renderer);
553// gfx_set_mat4_uniform(renderer->shader, "ViewProjection", view_proj);
554// }