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