GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 of 247, 0 excluded
0.0%
Functions:
0 of 33, 0 excluded
0.0%
Branches:
0 of 410, 0 excluded
0.0%

libs/render/src/eu/render/canvas.cc
Line Branch Exec Source
1 #include "eu/render/canvas.h"
2
3 #include "dependency_glad.h"
4
5 #include "eu/assert/assert.h"
6 #include "eu/base/rect.h"
7 #include "eu/base/cint.h"
8 #include "eu/base/mat4.h"
9
10 #include "eu/render/shader.h"
11 #include "eu/render/texture.h"
12 #include "eu/render/opengl_utils.h"
13 #include "eu/render/state.h"
14
15
16 using namespace std::literals;
17
18
19 namespace eu::render
20 {
21
22 SpriteBatch::SpriteBatch(State* 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 core::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(State* states)
205 // todo(Gustav): move quad_description and quad_layout to a separate setup
206 : quad_description
207 (
208 {
209 {.type = core::VertexType::position2xy, .name = "position"},
210 {.type = core::VertexType::color4, .name = "color"},
211 {.type = core::VertexType::texture2, .name = "uv"}
212 }
213 )
214 , quad_layout
215 (
216 compile_shader_layout(core::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 determine_extend_scale(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 = determine_extend_scale(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 = determine_extend_scale(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 RenderLayer2::~RenderLayer2()
391 {
392 batch->submit();
393 }
394
395
396 RenderLayer2::RenderLayer2(const Layer& l, SpriteBatch* b)
397 : Layer(l)
398 , batch(b)
399 {
400 }
401
402 RenderLayer3::RenderLayer3(const Layer& l)
403 : Layer(l)
404 {
405 }
406
407
408 Layer create_layer(const ViewportDef& vp)
409 {
410 return {.viewport_aabb_in_worldspace = Rect::from_size({vp.virtual_width, vp.virtual_height}), .screen = vp.screen_rect};
411 }
412
413
414 RenderLayer2 create_layer2(const RenderCommand& rc, const ViewportDef& vp)
415 {
416 set_gl_viewport(vp.screen_rect);
417
418 // prepare for 2d rendering
419 StateChanger{rc.states}
420 .depth_test(false)
421 .blend_mode(Blend::src_alpha, Blend::one_minus_src_alpha)
422 .blending(true);
423
424 const auto camera = m4_identity;
425 const auto projection = m4::create_ortho_lrud(0.0f, vp.virtual_width, vp.virtual_height, 0.0f, -1.0f, 1.0f);
426
427 rc.render->quad_shader.use();
428 rc.render->quad_shader.set_mat(rc.render->view_projection_uniform, projection);
429 rc.render->quad_shader.set_mat(rc.render->transform_uniform, camera);
430
431 // todo(Gustav): transform viewport according to the camera
432 return RenderLayer2{create_layer(vp), &rc.render->batch};
433 }
434
435 RenderLayer3 create_layer3(const RenderCommand&, const ViewportDef& vp)
436 {
437 // set_gl_viewport(vp.screen_rect);
438
439 // todo(Gustav): prepare states for 3d rendering
440 // StateChanger{rc.states}.depth_test(true).blending(false);
441
442 return RenderLayer3{create_layer(vp)};
443 }
444
445
446 v2 Layer::mouse_to_world(const v2& p) const
447 {
448 // transform from mouse pixels to window 0-1
449 const auto n = to_01(screen, p);
450 return from_01(viewport_aabb_in_worldspace, n);
451 }
452
453
454 ViewportDef create_viewport(const LayoutData& ld, const Size& size)
455 {
456 if(ld.style==ViewportStyle::black_bars)
457 {
458 return fit_with_black_bars(ld.requested_width, ld.requested_height, size.width, size.height);
459 }
460 else
461 {
462 return extended_viewport(ld.requested_width, ld.requested_height, size.width, size.height);
463 }
464 }
465
466
467 ViewportDef create_viewport(const LerpData& ld, const Size& size)
468 {
469 return lerp
470 (
471 create_viewport(ld.lhs, size),
472 ld.t,
473 create_viewport(ld.rhs, size)
474 );
475 }
476
477
478 void
479 RenderCommand::clear(const Rgb& color, const LayoutData& ld) const
480 {
481 if(ld.style == ViewportStyle::extended)
482 {
483 glClearColor(color.r, color.g, color.b, 1.0f);
484 glClear(GL_COLOR_BUFFER_BIT);
485 }
486 else
487 {
488 auto l = with_layer2(*this, ld);
489 Quad{ .tint = color }.draw(l.batch, l.viewport_aabb_in_worldspace);
490 }
491 }
492
493
494 bool
495 is_fullscreen(const LerpData& ld)
496 {
497 if(ld.lhs.style == ld.rhs.style || (ld.t <= 0.0f || ld.t >= 1.0f))
498 {
499 const auto style = ld.t >= 1.0f ? ld.rhs.style : ld.lhs.style;
500 return style == ViewportStyle::extended;
501 }
502
503 return false;
504 }
505
506
507 void
508 RenderCommand::clear(const Rgb& color, const LerpData& ld) const
509 {
510 if(is_fullscreen(ld))
511 {
512 glClearColor(color.r, color.g, color.b, 1.0f);
513 glClear(GL_COLOR_BUFFER_BIT);
514 }
515 else
516 {
517 auto l = with_layer2(*this, ld);
518 Quad{ .tint = color }.draw(l.batch, l.viewport_aabb_in_worldspace);
519 }
520 }
521
522
523 RenderLayer2 with_layer2(const RenderCommand& rc, const LayoutData& ld)
524 {
525 const auto vp = create_viewport(ld, rc.size);
526 return create_layer2(rc, vp);
527 }
528
529
530 RenderLayer3 with_layer3(const RenderCommand& rc, const LayoutData& ld)
531 {
532 const auto vp = create_viewport(ld, rc.size);
533 return create_layer3(rc, vp);
534 }
535
536
537 Layer with_layer(const InputCommand& rc, const LayoutData& ld)
538 {
539 const auto vp = create_viewport(ld, rc.size);
540 return create_layer(vp);
541 }
542
543
544
545
546
547 RenderLayer2 with_layer2(const RenderCommand& rc, const LerpData& ld)
548 {
549 const auto vp = create_viewport(ld, rc.size);
550 return create_layer2(rc, vp);
551 }
552
553
554 RenderLayer3 with_layer3(const RenderCommand& rc, const LerpData& ld)
555 {
556 const auto vp = create_viewport(ld, rc.size);
557 return create_layer3(rc, vp);
558 }
559
560
561 Layer with_layer(const InputCommand& rc, const LerpData& ld)
562 {
563 const auto vp = create_viewport(ld, rc.size);
564 return create_layer(vp);
565 }
566
567 }
568