GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 of 253, 0 excluded
0.0%
Functions:
0 of 34, 0 excluded
0.0%
Branches:
0 of 418, 0 excluded
0.0%

libs/render/src/render/canvas.cc
Line Branch Exec Source
1 #include "render/canvas.h"
2
3 #include "dependency_glad.h"
4
5 #include "assert/assert.h"
6 #include "base/rect.h"
7 #include "base/cint.h"
8 #include "base/mat4.h"
9
10 #include "render/shader.h"
11 #include "render/texture.h"
12 #include "render/opengl_utils.h"
13 #include "render/statechanger.h"
14
15
16 using namespace std::literals;
17
18
19 namespace eu::render
20 {
21
22 SpriteBatch::SpriteBatch(OpenglStates* the_states, ShaderProgram* quad_shader, Render2* r)
23 : states(the_states)
24 , render(r)
25 , white_texture
26 (
27 load_image_from_color
28 (
29 SEND_DEBUG_LABEL_MANY("white_texture")
30 color_from_rgba(255, 255, 255, 255),
31 TextureEdge::clamp,
32 TextureRenderStyle::pixel,
33 Transparency::include,
34 ColorData::dont_care
35 )
36 )
37 {
38 quad_shader->use();
39
40 glGenVertexArrays(1, &va);
41 glBindVertexArray(va);
42
43 constexpr auto vertex_size = 9 * sizeof(float);
44 constexpr auto max_vertices = 4 * max_quads;
45 constexpr auto max_indices = 6 * max_quads;
46
47 glGenBuffers(1, &vb);
48 glBindBuffer(GL_ARRAY_BUFFER, vb);
49 glBufferData(GL_ARRAY_BUFFER, vertex_size * max_vertices, nullptr, GL_DYNAMIC_DRAW);
50
51 auto relative_offset = [](unsigned int i)
52 {
53 return reinterpret_cast<void*>(i * sizeof(float));
54 };
55
56 unsigned int offset = 0;
57
58 glEnableVertexAttribArray(0);
59 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, vertex_size, relative_offset(offset));
60 offset += 3;
61
62 glEnableVertexAttribArray(1);
63 glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, vertex_size, relative_offset(offset));
64 offset += 4;
65
66 glEnableVertexAttribArray(2);
67 glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, vertex_size, relative_offset(offset));
68 offset += 2;
69
70
71 std::vector<u32> indices;
72 indices.reserve(max_indices);
73
74 for(auto quad_index=0; quad_index<max_quads; quad_index+=1)
75 {
76 const auto base = quad_index * 4;
77 indices.emplace_back(base + 0);
78 indices.emplace_back(base + 1);
79 indices.emplace_back(base + 2);
80
81 indices.emplace_back(base + 2);
82 indices.emplace_back(base + 3);
83 indices.emplace_back(base + 0);
84 }
85
86 ASSERT(max_indices == indices.size());
87
88 glGenBuffers(1, &ib);
89 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib);
90 glBufferData(GL_ELEMENT_ARRAY_BUFFER, max_indices * sizeof(u32), indices.data(), GL_STATIC_DRAW);
91 }
92
93
94 SpriteBatch::~SpriteBatch()
95 {
96 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
97 glDeleteBuffers(1, &ib);
98
99 glBindBuffer(GL_ARRAY_BUFFER, 0);
100 glDeleteBuffers(1, &vb);
101
102 glBindVertexArray(0);
103 glDeleteVertexArrays(1, &va);
104 }
105
106
107 void add_vertex(SpriteBatch* batch, const Vertex2& v)
108 {
109 batch->data.push_back(v.position.x);
110 batch->data.push_back(v.position.y);
111 batch->data.push_back(v.position.z);
112
113 batch->data.push_back(v.color.rgb.r);
114 batch->data.push_back(v.color.rgb.g);
115 batch->data.push_back(v.color.rgb.b);
116 batch->data.push_back(v.color.alpha);
117
118 batch->data.push_back(v.texturecoord.x);
119 batch->data.push_back(v.texturecoord.y);
120 }
121
122 void SpriteBatch::quad(std::optional<const Texture2d*> texture_argument, const Vertex2& v0, const Vertex2& v1, const Vertex2& v2, const Vertex2& v3)
123 {
124 const Texture2d* texture = texture_argument.value_or(&white_texture);
125
126 if(quads == max_quads)
127 {
128 submit();
129 }
130
131 if(current_texture == nullptr)
132 {
133 current_texture = texture;
134 }
135 else if (current_texture != texture)
136 {
137 submit();
138 current_texture = texture;
139 }
140
141 quads += 1;
142
143 add_vertex(this, v0);
144 add_vertex(this, v1);
145 add_vertex(this, v2);
146 add_vertex(this, v3);
147 }
148
149 void Quad::draw(SpriteBatch* batch, const Rect& scr)
150 {
151 const auto tc = texturecoord.value_or(Rect::from_size({1.0f, 1.0f}));
152 const auto size = scr.get_size();
153 const auto local_offset = size * (rotation ? rotation->center : v2{0.5f, 0.5f});
154 const auto offset = scr.get_bottom_left() + local_offset;
155
156 const auto rotate = [&](const v2& p)
157 {
158 if (rotation.has_value() == false)
159 {
160 return v3{ p.x, p.y, 0.0f };
161 }
162 const auto local = p - offset;
163 const auto trans = local.get_rotated(rotation->angle);
164 const auto rotated = trans + offset;
165 return v3{ rotated.x, rotated.y, 0.0f };
166 };
167
168 batch->quad
169 (
170 texture,
171 {.position = rotate({scr.left, scr.bottom}), .color = tint, .texturecoord = {tc.left, tc.bottom}},
172 {.position = rotate({scr.right, scr.bottom}), .color = tint, .texturecoord = {tc.right, tc.bottom}},
173 {.position = rotate({scr.right, scr.top}), .color = tint, .texturecoord = {tc.right, tc.top}},
174 {.position = rotate({scr.left, scr.top}), .color = tint, .texturecoord = {tc.left, tc.top}}
175 );
176 }
177
178 void SpriteBatch::submit()
179 {
180 if(quads == 0)
181 {
182 return;
183 }
184
185 bind_texture_2d(states, render->texture_uniform, *current_texture);
186 glBindVertexArray(va);
187
188 glBindBuffer(GL_ARRAY_BUFFER, vb);
189 glBufferSubData
190 (
191 GL_ARRAY_BUFFER,
192 0,
193 static_cast<GLsizeiptr>(sizeof(float) * data.size()),
194 static_cast<const void*>(data.data())
195 );
196 glDrawElements(GL_TRIANGLES, 6 * quads, GL_UNSIGNED_INT, nullptr);
197
198 data.resize(0);
199 quads = 0;
200 current_texture = nullptr;
201 }
202
203
204 Render2::Render2(OpenglStates* states)
205 // todo(Gustav): move quad_description and quad_layout to a separate setup
206 : quad_description
207 (
208 {
209 {.type = VertexType::position2xy, .name = "position"},
210 {.type = VertexType::color4, .name = "color"},
211 {.type = VertexType::texture2, .name = "uv"}
212 }
213 )
214 , quad_layout
215 (
216 compile_shader_layout(compile_attribute_layouts({quad_description}), quad_description, std::nullopt, std::nullopt)
217 )
218 , quad_shader
219 (
220 SEND_DEBUG_LABEL_MANY("QUAD SHADER")
221 R"glsl(
222 #version 450 core
223 in vec3 position;
224 in vec4 color;
225 in vec2 uv;
226
227 uniform mat4 view_projection;
228 uniform mat4 transform;
229
230 out vec4 varying_color;
231 out vec2 varying_uv;
232
233 void main()
234 {
235 varying_color = color;
236 varying_uv = uv;
237 gl_Position = view_projection * transform * vec4(position, 1.0);
238 }
239 )glsl",
240 R"glsl(
241 #version 450 core
242
243 in vec4 varying_color;
244 in vec2 varying_uv;
245
246 uniform sampler2D uniform_texture;
247
248 out vec4 color;
249
250 void main()
251 {
252 color = texture(uniform_texture, varying_uv) * varying_color;
253 }
254 )glsl",
255 quad_layout
256 )
257 , view_projection_uniform(quad_shader.get_uniform("view_projection"))
258 , transform_uniform(quad_shader.get_uniform("transform"))
259 , texture_uniform(quad_shader.get_uniform("uniform_texture"))
260 , batch(states, &quad_shader, this)
261 {
262 setup_textures(&quad_shader, {&texture_uniform});
263
264 // todo(Gustav): verify mesh layout with SpriteBatch
265 }
266
267
268 struct ViewportDef
269 {
270 Rect screen_rect;
271
272 float virtual_width;
273 float virtual_height;
274 };
275
276
277 ViewportDef
278 fit_with_black_bars
279 (
280 float width,
281 float height,
282 int window_width,
283 int window_height
284 )
285 {
286 ASSERTX(width > 0, width);
287 ASSERTX(height > 0, height);
288 ASSERTX(window_width >= 0, window_width);
289 ASSERTX(window_height >= 0, window_height);
290 const float w = float_from_int(window_width) / width;
291 const float h = float_from_int(window_height) / height;
292 const float s = std::min(w, h);
293 ASSERTX(s > 0, s, w, h);
294 const float new_width = width * s;
295 const float new_height = height * s;
296
297 return ViewportDef
298 {
299 .screen_rect = Rect::from_size({new_width, new_height}).with_bottom_left_at
300 ({
301 (float_from_int(window_width) - new_width) / 2.0f,
302 (float_from_int(window_height) - new_height) / 2.0f
303 }),
304 .virtual_width = width,
305 .virtual_height = height
306 };
307 }
308
309
310 float
311 DetermineExtendScale(float scale, float height, int window_height)
312 {
313 const auto scaled_height = height * scale;
314 const auto s = static_cast<float>(window_height) / scaled_height;
315 return s;
316 }
317
318
319 ViewportDef
320 extended_viewport
321 (
322 float width,
323 float height,
324 int window_width,
325 int window_height
326 )
327 {
328 ASSERTX(width >= 0, width);
329 ASSERTX(height >= 0, height);
330 ASSERTX(window_width >= 0, window_width);
331 ASSERTX(window_height >= 0, window_height);
332 const auto w = float_from_int(window_width) / width;
333 const auto h = float_from_int(window_height) / height;
334 const auto r = Rect::from_size({float_from_int(window_width), float_from_int(window_height)}).with_bottom_left_at({ 0, 0 });
335 if(w < h)
336 {
337 const auto s = DetermineExtendScale(w, height, window_height);
338 return ViewportDef {.screen_rect = r, .virtual_width = width, .virtual_height = height * s};
339 }
340 else
341 {
342 const auto s = DetermineExtendScale(h, width, window_width);
343 return ViewportDef {.screen_rect = r, .virtual_width = width * s, .virtual_height = height};
344 }
345 }
346
347
348 namespace
349 {
350 float lerp(float lhs, float t, float rhs)
351 {
352 return lhs + t * (rhs - lhs);
353 }
354
355 Rect lerp(const Rect& lhs, float t, const Rect& rhs)
356 {
357 #define V(x) lerp(lhs.x, t, rhs.x)
358 return Rect::from_left_right_top_bottom
359 (
360 V(left),
361 V(right),
362 V(top),
363 V(bottom)
364 );
365 #undef V
366 }
367 }
368
369
370 [[nodiscard]]
371 ViewportDef
372 lerp
373 (
374 const ViewportDef& lhs,
375 float t,
376 const ViewportDef& rhs
377 )
378 {
379 #define V(x) lerp(lhs.x, t, rhs.x)
380 return
381 {
382 .screen_rect = V(screen_rect),
383 .virtual_width = V(virtual_width),
384 .virtual_height = V(virtual_height)
385 };
386 #undef V
387 }
388
389
390
391 void set_gl_viewport(const Rect& r)
392 {
393 const auto s = r.get_size();
394 glViewport(int_from_float(r.left), int_from_float(r.bottom), int_from_float(s.x), int_from_float(s.y));
395 }
396
397
398 RenderLayer2::~RenderLayer2()
399 {
400 batch->submit();
401 }
402
403
404 RenderLayer2::RenderLayer2(const Layer& l, SpriteBatch* b)
405 : Layer(l)
406 , batch(b)
407 {
408 }
409
410 RenderLayer3::RenderLayer3(const Layer& l)
411 : Layer(l)
412 {
413 }
414
415
416 Layer create_layer(const ViewportDef& vp)
417 {
418 return {.viewport_aabb_in_worldspace = Rect::from_size({vp.virtual_width, vp.virtual_height}), .screen = vp.screen_rect};
419 }
420
421
422 RenderLayer2 create_layer2(const RenderCommand& rc, const ViewportDef& vp)
423 {
424 set_gl_viewport(vp.screen_rect);
425
426 // prepare for 2d rendering
427 StateChanger{rc.states}
428 .depth_test(false)
429 .blend_mode(Blend::src_alpha, Blend::one_minus_src_alpha)
430 .blending(true);
431
432 const auto camera = m4_identity;
433 const auto projection = m4::create_ortho_lrud(0.0f, vp.virtual_width, vp.virtual_height, 0.0f, -1.0f, 1.0f);
434
435 rc.render->quad_shader.use();
436 rc.render->quad_shader.set_mat(rc.render->view_projection_uniform, projection);
437 rc.render->quad_shader.set_mat(rc.render->transform_uniform, camera);
438
439 // todo(Gustav): transform viewport according to the camera
440 return RenderLayer2{create_layer(vp), &rc.render->batch};
441 }
442
443 RenderLayer3 create_layer3(const RenderCommand& rc, const ViewportDef& vp)
444 {
445 set_gl_viewport(vp.screen_rect);
446
447 // todo(Gustav): prepare states for 3d rendering
448 StateChanger{rc.states}.depth_test(true).blending(false);
449
450 return RenderLayer3{create_layer(vp)};
451 }
452
453
454 v2 Layer::mouse_to_world(const v2& p) const
455 {
456 // transform from mouse pixels to window 0-1
457 const auto n = to_01(screen, p);
458 return from_01(viewport_aabb_in_worldspace, n);
459 }
460
461
462 ViewportDef create_viewport(const LayoutData& ld, const Size& size)
463 {
464 if(ld.style==ViewportStyle::black_bars)
465 {
466 return fit_with_black_bars(ld.requested_width, ld.requested_height, size.width, size.height);
467 }
468 else
469 {
470 return extended_viewport(ld.requested_width, ld.requested_height, size.width, size.height);
471 }
472 }
473
474
475 ViewportDef create_viewport(const LerpData& ld, const Size& size)
476 {
477 return lerp
478 (
479 create_viewport(ld.lhs, size),
480 ld.t,
481 create_viewport(ld.rhs, size)
482 );
483 }
484
485
486 void
487 RenderCommand::clear(const Rgb& color, const LayoutData& ld) const
488 {
489 if(ld.style == ViewportStyle::extended)
490 {
491 glClearColor(color.r, color.g, color.b, 1.0f);
492 glClear(GL_COLOR_BUFFER_BIT);
493 }
494 else
495 {
496 auto l = with_layer2(*this, ld);
497 Quad{ .tint = color }.draw(l.batch, l.viewport_aabb_in_worldspace);
498 }
499 }
500
501
502 bool
503 is_fullscreen(const LerpData& ld)
504 {
505 if(ld.lhs.style == ld.rhs.style || (ld.t <= 0.0f || ld.t >= 1.0f))
506 {
507 const auto style = ld.t >= 1.0f ? ld.rhs.style : ld.lhs.style;
508 return style == ViewportStyle::extended;
509 }
510
511 return false;
512 }
513
514
515 void
516 RenderCommand::clear(const Rgb& color, const LerpData& ld) const
517 {
518 if(is_fullscreen(ld))
519 {
520 glClearColor(color.r, color.g, color.b, 1.0f);
521 glClear(GL_COLOR_BUFFER_BIT);
522 }
523 else
524 {
525 auto l = with_layer2(*this, ld);
526 Quad{ .tint = color }.draw(l.batch, l.viewport_aabb_in_worldspace);
527 }
528 }
529
530
531 RenderLayer2 with_layer2(const RenderCommand& rc, const LayoutData& ld)
532 {
533 const auto vp = create_viewport(ld, rc.size);
534 return create_layer2(rc, vp);
535 }
536
537
538 RenderLayer3 with_layer3(const RenderCommand& rc, const LayoutData& ld)
539 {
540 const auto vp = create_viewport(ld, rc.size);
541 return create_layer3(rc, vp);
542 }
543
544
545 Layer with_layer(const InputCommand& rc, const LayoutData& ld)
546 {
547 const auto vp = create_viewport(ld, rc.size);
548 return create_layer(vp);
549 }
550
551
552
553
554
555 RenderLayer2 with_layer2(const RenderCommand& rc, const LerpData& ld)
556 {
557 const auto vp = create_viewport(ld, rc.size);
558 return create_layer2(rc, vp);
559 }
560
561
562 RenderLayer3 with_layer3(const RenderCommand& rc, const LerpData& ld)
563 {
564 const auto vp = create_viewport(ld, rc.size);
565 return create_layer3(rc, vp);
566 }
567
568
569 Layer with_layer(const InputCommand& rc, const LerpData& ld)
570 {
571 const auto vp = create_viewport(ld, rc.size);
572 return create_layer(vp);
573 }
574
575 }
576