GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 of 236, 0 excluded
0.0%
Functions:
0 of 18, 0 excluded
0.0%
Branches:
0 of 409, 0 excluded
0.0%

libs/render/src/eu/render/renderer.cc
Line Branch Exec Source
1 #include "eu/render/renderer.h"
2
3 #include <algorithm>
4
5 #include "eu/log/log.h"
6
7 #include "eu/render/camera.h"
8 #include "eu/render/fullscreen.h"
9 #include "eu/core/geom.builder.h"
10 #include "eu/core/geom.h"
11 #include "eu/render/opengl_utils.h"
12 #include "eu/render/renderer.pimpl.h"
13 #include "eu/render/state.h"
14 #include "eu/render/constants.h"
15
16 namespace eu::render
17 {
18
19 Renderer::Renderer(State* states, Assets* default_assets, const RenderSettings& set)
20 : assets(default_assets)
21 , settings(set)
22 , pimpl(std::make_unique<RendererPimpl>(states, *assets, set, FullScreenGeom{}))
23 {
24 }
25
26 Renderer::~Renderer() = default;
27
28 Skybox Renderer::make_skybox(std::shared_ptr<TextureCubemap> texture) const
29 {
30 constexpr float size = 1.0f;
31
32 const auto layout = pimpl->shaders_resources.skybox_shader.geom_layout;
33
34 const auto triangle = core::geom::create_box(size, size, size, core::geom::NormalsFacing::In, colors::white).to_geom();
35 auto geom = compile_geom(USE_DEBUG_LABEL_MANY("skybox") triangle, layout);
36
37 LOG_INFO("Created skybox");
38 return {geom, std::move(texture)};
39 }
40
41 struct TransparentMesh
42 {
43 std::shared_ptr<MeshInstance> mesh;
44 float squared_distance_to_camera;
45 };
46
47 std::shared_ptr<UnlitMaterial> Renderer::make_unlit_material() const
48 {
49 return std::make_shared<UnlitMaterial>(pimpl->shaders_resources);
50 }
51
52 std::shared_ptr<DefaultMaterial> Renderer::make_default_material() const
53 {
54 return std::make_shared<DefaultMaterial>(pimpl->shaders_resources);
55 }
56
57 core::CompiledGeomVertexAttributes Renderer::unlit_geom_layout() const
58 {
59 return pimpl->shaders_resources.unlit_shader_container.geom_layout;
60 }
61
62 core::CompiledGeomVertexAttributes Renderer::default_geom_layout() const
63 {
64 return pimpl->shaders_resources.default_shader_container.geom_layout;
65 }
66
67 bool Renderer::is_loaded() const
68 {
69 return pimpl->shaders_resources.is_loaded() && pimpl->debug_drawer.is_loaded();
70 }
71
72 m4 transform_from_rotation(const v3& position, const Ypr& ypr)
73 {
74 const auto translation = m4::from_translation(position);
75 const auto rotation = m4::from(Q::from(ypr)).value_or(m4_identity);
76 return translation * rotation;
77 }
78 m4 transform_from_billboard(const v3& position, Billboarding billboarding, const CompiledCamera& cc)
79 {
80 const auto calc_fixed_right = [](const n3& normal, const n3& up)
81 {
82 const auto right = normal.cross_norm(up).value_or(kk::right);
83 const auto new_up = right.cross_norm(normal).value_or(kk::up);
84 return m4::from_basis(right, new_up, normal);
85 };
86
87 const auto calc_fixed_up = [](const n3& normal, const n3& up)
88 {
89 const auto right = normal.cross_norm(up).value_or(kk::right);
90 const auto new_normal = right.cross_norm(up).value_or(kk::up);
91 return m4::from_basis(right, up, new_normal);
92 };
93
94 const auto translation = m4::from_translation(position);
95
96 // todo(Gustav): verify that the billboards are oriented correctly, grass in example 3 is twosided...
97 switch (billboarding)
98 {
99 case Billboarding::screen:
100 {
101 const auto rotation = calc_fixed_right(v3::from_to(cc.position, position).get_normalized().value_or(kk::in), kk::up);
102 return translation * rotation;
103 }
104 case Billboarding::screen_fast:
105 {
106 // todo(Gustav): move to precalculated or remove?
107 const auto rotation = calc_fixed_right(cc.in, kk::up);
108 return translation * rotation;
109 }
110 case Billboarding::axial_y:
111 {
112 const auto rotation = calc_fixed_up(v3::from_to(cc.position, position).get_normalized().value_or(kk::in), kk::up);
113 return translation * rotation;
114 }
115 case Billboarding::axial_y_fast:
116 {
117 // todo(Gustav): move to precalculated or remove?
118 const auto rotation = calc_fixed_up(cc.in, kk::up);
119 return translation * rotation;
120 }
121 default:
122 {
123 DIE("invalid billboarding");
124 const auto rotation = calc_fixed_up(cc.in, kk::up);
125 return translation * rotation;
126 }
127
128 }
129 };
130
131 void batch_lines(LineDrawer* drawer, const std::vector<DebugLine>& debug_lines, float gamma)
132 {
133 for (const auto& line: debug_lines)
134 {
135 drawer->line(line.from, line.to, linear_from_srgb(line.color, gamma));
136 }
137 drawer->submit();
138 }
139
140 void render_debug_lines(const std::vector<DebugLine>& debug_lines, State* states, LineDrawer* drawer, const CompiledCamera& compiled_camera, const Size& window_size, float gamma)
141 {
142 if (debug_lines.empty())
143 {
144 return;
145 }
146
147 drawer->shader.use();
148 drawer->set_camera(compiled_camera);
149
150 StateChanger{states}.depth_func(Compare::less_equal).depth_test(true);
151 drawer->set_line_to_solid();
152 batch_lines(drawer, debug_lines, gamma);
153
154 StateChanger{states}.depth_func(Compare::greater).depth_test(true);
155 drawer->set_line_to_dash({float_from_int(window_size.width), float_from_int(window_size.height)}, 20.0f, 20.0f);
156 batch_lines(drawer, debug_lines, gamma);
157 }
158
159 void Renderer::render_world(const Size& window_size, const World& world, const CompiledCamera& compiled_camera, const ShadowContext& shadow_context)
160 {
161 SCOPED_DEBUG_GROUP("render world call"sv);
162 const auto has_outlined_meshes = std::ranges::any_of(world.meshes,
163 [](const auto& mesh) { return mesh->outline.has_value(); }
164 );
165 StateChanger{pimpl->states}
166 .cull_face(true)
167 .cull_face_mode(CullFace::back)
168 .stencil_mask(0xFF)
169 .stencil_test(has_outlined_meshes)
170 .depth_test(true)
171 .depth_mask(true)
172 .stencil_op(StencilAction::keep, StencilAction::replace, StencilAction::replace)
173 .blend_mode(Blend::src_alpha, Blend::one_minus_src_alpha);
174
175 const auto clear_color = linear_from_srgb(world.clear_color, settings.gamma);
176 glClearColor(clear_color.r, clear_color.g, clear_color.b, 1.0f);
177 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
178
179 auto bound_camera_buffer = BoundUniformBuffer{pimpl->camera_uniform_buffer.buffer.get()};
180 pimpl->camera_uniform_buffer.set_props(compiled_camera);
181
182 std::vector<TransparentMesh> transparent_meshes;
183
184 // render solids
185 {
186 if (world.meshes.empty() == false)
187 {
188 SCOPED_DEBUG_GROUP("render basic geom"sv);
189 for (const auto& mesh: world.meshes)
190 {
191 const auto not_transparent_context
192 = RenderContext{TransformSource::Uniform, UseTransparency::no, settings.gamma, &shadow_context};
193
194 if (mesh->material->is_transparent())
195 {
196 transparent_meshes.emplace_back(
197 TransparentMesh{
198 .mesh = mesh,
199 .squared_distance_to_camera = v3::from_to(compiled_camera.position, mesh->transform.get_translation()).get_length_squared()
200 }
201 );
202
203 continue;
204 }
205 StateChanger{pimpl->states}
206 .depth_test(true)
207 .depth_mask(true)
208 .depth_func(Compare::less)
209 .blending(false)
210 .stencil_mask(0x0)
211 .stencil_func(Compare::always, 1, 0xFF);
212
213 if (mesh->outline)
214 {
215 StateChanger{pimpl->states}.stencil_func(Compare::always, 1, 0xFF).stencil_mask(0xFF);
216 }
217 mesh->material->use_shader(not_transparent_context);
218 mesh->material->set_uniforms(
219 not_transparent_context, compiled_camera, mesh->transform
220 );
221 mesh->material->bind_textures(not_transparent_context, pimpl->states, assets);
222 mesh->material->apply_lights(not_transparent_context, world.lights, settings, pimpl->states, assets);
223
224 render_geom(*mesh->geom);
225 }
226 }
227
228 if (world.instances.empty() == false)
229 {
230 SCOPED_DEBUG_GROUP("render instances"sv);
231 for (const auto& instance: world.instances)
232 {
233 const auto not_transparent_context = RenderContext{TransformSource::Instanced_mat4, UseTransparency::no, settings.gamma, &shadow_context};
234
235 StateChanger{pimpl->states}
236 .depth_test(true)
237 .depth_mask(true)
238 .depth_func(Compare::less)
239 .blending(false)
240 .stencil_mask(0x0)
241 .stencil_func(Compare::always, 1, 0xFF);
242 instance->material->use_shader(not_transparent_context);
243 instance->material->set_uniforms(
244 // todo(Gustav): should we really set the model matrix for instanced meshes?
245 not_transparent_context,
246 compiled_camera,
247 std::nullopt
248 );
249 instance->material->bind_textures(not_transparent_context, pimpl->states, assets);
250 instance->material->apply_lights(
251 not_transparent_context, world.lights, settings, pimpl->states, assets
252 );
253
254 render_geom_instanced(*instance);
255 }
256 }
257
258 if (debug.lines.empty() == false)
259 {
260 SCOPED_DEBUG_GROUP("render debug lines"sv);
261 render_debug_lines(
262 debug.lines, pimpl->states, &pimpl->debug_drawer, compiled_camera, window_size, settings.gamma
263 );
264 }
265 }
266
267 // todo(Gustav): should this be done at the start of the update so the renderer can be "const"
268 debug.lines.clear();
269
270 // render skybox
271 if (world.skybox && world.skybox->cubemap != nullptr && world.skybox->geom != nullptr)
272 {
273 SCOPED_DEBUG_GROUP("render skybox"sv);
274 StateChanger{pimpl->states}
275 .depth_test(true)
276 .depth_mask(false)
277 .depth_func(Compare::less_equal)
278 .blending(false)
279 .stencil_mask(0x0)
280 .stencil_func(Compare::always, 1, 0xFF);
281
282 auto& shader = pimpl->shaders_resources.skybox_shader;
283
284 shader.program->use();
285 bind_texture_cubemap(pimpl->states, shader.tex_skybox_uniform, *world.skybox->cubemap);
286
287 render_geom(*world.skybox->geom);
288 }
289
290 std::sort(
291 transparent_meshes.begin(),
292 transparent_meshes.end(),
293 [](const auto& lhs, const auto& rhs) { return lhs.squared_distance_to_camera > rhs.squared_distance_to_camera; }
294 );
295
296 if (transparent_meshes.empty() == false)
297 {
298 SCOPED_DEBUG_GROUP("render transparent meshes"sv);
299 for (auto& transparent_mesh: transparent_meshes)
300 {
301 const auto transparent_context = RenderContext{TransformSource::Uniform, UseTransparency::yes, settings.gamma, &shadow_context};
302
303 const auto& mesh = transparent_mesh.mesh;
304 StateChanger{pimpl->states}
305 .depth_test(true)
306 .depth_mask(true)
307 .depth_func(Compare::less)
308 .blending(true)
309 .stencil_mask(0x0)
310 .stencil_func(Compare::always, 1, 0xFF);
311
312 if (mesh->outline)
313 {
314 StateChanger{pimpl->states}.stencil_func(Compare::always, 1, 0xFF).stencil_mask(0xFF);
315 }
316 mesh->material->use_shader(transparent_context);
317 mesh->material->set_uniforms(transparent_context, compiled_camera, mesh->transform);
318 mesh->material->bind_textures(transparent_context, pimpl->states, assets);
319 mesh->material->apply_lights(transparent_context, world.lights, settings, pimpl->states, assets);
320
321 render_geom(*mesh->geom);
322 }
323 }
324
325 // render outline over all other meshes
326 if (has_outlined_meshes)
327 {
328 SCOPED_DEBUG_GROUP("render outline meshes"sv);
329 for (const auto& mesh: world.meshes)
330 {
331 if (const auto& mesh_outline = mesh->outline)
332 {
333 StateChanger{pimpl->states}
334 .stencil_func(Compare::not_equal, 1, 0xFF)
335 .stencil_mask(0x00)
336 .depth_test(false);
337 const m4 small_scale_mat = m4::from_scale(v3{OUTLINE_SCALE, OUTLINE_SCALE, OUTLINE_SCALE});
338
339 auto& shader = pimpl->shaders_resources.single_color_shader;
340 shader.program->use();
341 shader.program->set_vec4(shader.tint_color_uni, v4{vec_from_lin(linear_from_srgb(*mesh_outline, settings.gamma)), 1});
342
343 shader.program->set_mat(shader.world_from_local_uni, mesh->transform * small_scale_mat);
344
345 render_geom(*mesh->geom);
346 }
347 }
348 }
349 }
350
351 void Renderer::render_shadows(const Size& window_size, const World& world, const CompiledCamera& compiled_camera) const
352 {
353 // todo(Gustav): bind less colors and use depth only shaders
354 SCOPED_DEBUG_GROUP("render shadows call"sv);
355 StateChanger{pimpl->states}
356 .cull_face(true)
357 .cull_face_mode(CullFace::back)
358 .depth_test(true)
359 .depth_mask(true);
360
361 glClear(GL_DEPTH_BUFFER_BIT);
362
363 auto bound_camera_buffer = BoundUniformBuffer{pimpl->camera_uniform_buffer.buffer.get()};
364 pimpl->camera_uniform_buffer.set_props(compiled_camera);
365
366 // todo(Gustav): change shader to something that doesn't write color
367
368 StateChanger{pimpl->states}
369 .depth_test(true)
370 .depth_mask(true)
371 .depth_func(Compare::less)
372 .blending(false)
373 .stencil_mask(0x0)
374 .stencil_func(Compare::always, 1, 0xFF);
375
376 // render solids
377 {
378 if (world.meshes.empty() == false)
379 {
380 SCOPED_DEBUG_GROUP("render basic geom"sv);
381 for (const auto& mesh: world.meshes)
382 {
383 if (mesh->material->is_transparent())
384 {
385 continue;
386 }
387
388 auto& shader = pimpl->shaders_resources.depth_transform_uniform;
389 shader.program->use();
390
391 // todo(Gustav): the depth shader should not be a shared type between transform uniform and instanced, this should be verified at compile time with different types
392 ASSERT(shader.world_from_local_uni.has_value());
393 if (shader.world_from_local_uni)
394 {
395 shader.program->set_mat(*shader.world_from_local_uni, mesh->transform);
396 }
397
398 if (mesh->billboarding != Billboarding::none)
399 {
400 continue;
401 }
402 render_geom(*mesh->geom);
403 }
404 }
405
406 if (world.instances.empty() == false)
407 {
408 SCOPED_DEBUG_GROUP("render instances"sv);
409 for (const auto& instance: world.instances)
410 {
411 auto& shader = pimpl->shaders_resources.depth_transform_instanced_mat4;
412 shader.program->use();
413 ASSERT(shader.world_from_local_uni.has_value() == false);
414
415 render_geom_instanced(*instance);
416 }
417 }
418
419 if (debug.lines.empty() == false)
420 {
421 SCOPED_DEBUG_GROUP("render debug lines"sv);
422 render_debug_lines(
423 debug.lines, pimpl->states, &pimpl->debug_drawer, compiled_camera, window_size, settings.gamma
424 );
425 }
426 }
427 }
428
429 } // namespace eu::render
430