GCC Code Coverage Report


./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 of 279, 0 excluded
0.0%
Functions:
0 of 44, 0 excluded
0.0%
Branches:
0 of 344, 0 excluded
0.0%

libs/render/src/render/texture.cc
Line Branch Exec Source
1 #include "render/texture.h"
2
3 #include "assert/assert.h"
4
5 #include "log/log.h"
6
7 #include "base/ints.h"
8 #include "base/cint.h"
9 // #include "base/str.h"
10 // #include "base/cpp.h"
11 #include "base/vec4.h"
12
13 #include "render/opengl_utils.h"
14 #include "render/texture.io.h"
15
16 #include "dependency_glad.h"
17
18 #include "eustb_image.h"
19
20 #include <ranges>
21 #include <algorithm>
22 #include <array>
23
24
25 namespace eu::render
26 {
27
28
29 // ------------------------------------------------------------------------------------------------
30 // general
31
32 namespace
33 {
34 /// the color of an image that failed to load, html hot-pink
35 constexpr SingleColor image_load_failure_color = color_from_rgba(0xFF, 0x69, 0xB4, 0xFF);
36
37 constexpr unsigned int invalid_id = 0;
38
39 [[nodiscard]]
40 unsigned int create_texture()
41 {
42 unsigned int texture = 0;
43 glGenTextures(1, &texture);
44 return texture;
45 }
46
47 void set_texture_wrap(GLenum target, TextureEdge te, const std::optional<v4>& border_color)
48 {
49 const auto wrap = te == TextureEdge::clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT;
50 if (border_color.has_value())
51 {
52 ASSERT(te == TextureEdge::clamp);
53 glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, border_color->get_data_ptr());
54 glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
55 glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
56 return;
57 }
58 glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap);
59 glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap);
60 }
61
62 struct MinMagFilter
63 {
64 GLint min;
65 GLint mag;
66 };
67
68 [[nodiscard]]
69 MinMagFilter min_mag_from_trs(TextureRenderStyle trs)
70 {
71 switch (trs)
72 {
73 case TextureRenderStyle::pixel: return {GL_NEAREST, GL_NEAREST};
74 case TextureRenderStyle::linear: return {GL_LINEAR, GL_LINEAR};
75 case TextureRenderStyle::mipmap: return {GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR};
76 default: DIE("Invalid texture render style"); return {GL_NEAREST, GL_NEAREST};
77 }
78 }
79
80 [[nodiscard]]
81 GLint internal_format_from_color_data(Transparency t, ColorData cd)
82 {
83 const auto include_transparency = t == Transparency::include;
84 switch(cd)
85 {
86 case ColorData::color_data: return include_transparency ? GL_SRGB_ALPHA : GL_SRGB;
87 case ColorData::dont_care:
88 case ColorData::non_color_data: return include_transparency ? GL_RGBA : GL_RGB;
89 default:
90 DIE("Invalid color data type");
91 return GL_RGBA;
92 }
93 }
94 } // namespace
95
96 // ------------------------------------------------------------------------------------------------
97 // base texture
98
99 BaseTexture::BaseTexture()
100 : id(create_texture())
101 {
102 }
103
104 BaseTexture::~BaseTexture()
105 {
106 unload();
107 }
108
109 BaseTexture::BaseTexture(BaseTexture&& rhs) noexcept
110 : id(rhs.id)
111 {
112 rhs.id = invalid_id;
113 }
114
115 BaseTexture& BaseTexture::operator=(BaseTexture&& rhs) noexcept
116 {
117 unload();
118
119 id = rhs.id;
120
121 rhs.id = invalid_id;
122
123 return *this;
124 }
125
126 void BaseTexture::unload()
127 {
128 if (id != invalid_id)
129 {
130 glDeleteTextures(1, &id);
131 id = invalid_id;
132 }
133 }
134
135 // ------------------------------------------------------------------------------------------------
136 // texture 2d
137
138 Texture2d::Texture2d(DEBUG_LABEL_ARG_MANY const void* pixel_data, unsigned int pixel_format, int width, int height, TextureEdge te, TextureRenderStyle trs, Transparency t, ColorData cd)
139 {
140 // todo(Gustav): use states
141 glBindTexture(GL_TEXTURE_2D, id);
142 SET_DEBUG_LABEL_NAMED(id, DebugLabelFor::Texture, fmt::format("TEXTURE2d {}", debug_label));
143
144 set_texture_wrap(GL_TEXTURE_2D, te, std::nullopt);
145
146 const auto filter = min_mag_from_trs(trs);
147 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter.min);
148 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter.mag);
149
150 glTexImage2D(
151 GL_TEXTURE_2D,
152 0,
153 internal_format_from_color_data(t, cd),
154 width,
155 height,
156 0,
157 pixel_format,
158 GL_UNSIGNED_BYTE,
159 pixel_data
160 );
161
162 if (trs == TextureRenderStyle::mipmap)
163 {
164 glGenerateMipmap(GL_TEXTURE_2D);
165 }
166 }
167
168 struct PixelData
169 {
170 stbi_uc* pixel_data = nullptr;
171 int width = 0;
172 int height = 0;
173
174 PixelData(const embedded_binary& image_binary, bool include_transparency, bool flip = true)
175 {
176 int junk_channels = 0;
177 stbi_set_flip_vertically_on_load(flip ? 1 : 0);
178
179 pixel_data = stbi_load_from_memory(
180 reinterpret_cast<const unsigned char*>(image_binary.data),
181 int_from_unsigned_int(image_binary.size),
182 &width,
183 &height,
184 &junk_channels,
185 include_transparency ? 4 : 3
186 );
187
188 if (pixel_data == nullptr)
189 {
190 LOG_ERR("ERROR: Failed to read pixel data");
191 width = 0;
192 height = 0;
193 }
194 }
195
196 ~PixelData()
197 {
198 if (pixel_data != nullptr)
199 {
200 stbi_image_free(pixel_data);
201 }
202 }
203
204 PixelData(const PixelData&) = delete;
205 PixelData(PixelData&&) = delete;
206 void operator=(const PixelData&) = delete;
207 void operator=(PixelData&&) = delete;
208 };
209
210 [[nodiscard]]
211 Texture2d load_image_from_embedded(
212 DEBUG_LABEL_ARG_MANY const embedded_binary& image_binary, TextureEdge te, TextureRenderStyle trs, Transparency t, ColorData cd
213 )
214 {
215 const auto include_transparency = t == Transparency::include;
216
217 auto parsed = PixelData{image_binary, include_transparency};
218 if (parsed.pixel_data == nullptr)
219 {
220 LOG_ERR("ERROR: Failed to load image from image source");
221 return load_image_from_color(SEND_DEBUG_LABEL_MANY(debug_label) image_load_failure_color, te, trs, t, cd);
222 }
223
224 const GLenum pixel_format = include_transparency ? GL_RGBA : GL_RGB;
225 return {SEND_DEBUG_LABEL_MANY(debug_label) parsed.pixel_data, pixel_format, parsed.width, parsed.height, te, trs, t, cd};
226 }
227
228 [[nodiscard]]
229 Texture2d load_image_from_color(DEBUG_LABEL_ARG_MANY SingleColor pixel, TextureEdge te, TextureRenderStyle trs, Transparency t, ColorData cd)
230 {
231 return {SEND_DEBUG_LABEL_MANY(debug_label) & pixel, GL_RGBA, 1, 1, te, trs, t, cd};
232 }
233
234 // ------------------------------------------------------------------------------------------------
235 // cubemap
236
237 TextureCubemap::TextureCubemap(DEBUG_LABEL_ARG_MANY const std::array<void*, cubemap_size>& pixel_data, int width, int height, ColorData cd)
238 {
239 // todo(Gustav): use states
240 glBindTexture(GL_TEXTURE_CUBE_MAP, id);
241 SET_DEBUG_LABEL_NAMED(id, DebugLabelFor::Texture, fmt::format("TEXTURE CUBEMAP {}", debug_label));
242
243 for (size_t index = 0; index < cubemap_size; index += 1)
244 {
245 glTexImage2D(
246 static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + index),
247 0,
248 internal_format_from_color_data(Transparency::exclude, cd),
249 width,
250 height,
251 0,
252 GL_RGB,
253 GL_UNSIGNED_BYTE,
254 pixel_data[index]
255 );
256 }
257
258 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
259 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
260 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
261 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
262 glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
263 }
264
265 [[nodiscard]]
266 TextureCubemap load_cubemap_from_color(DEBUG_LABEL_ARG_MANY SingleColor pixel, ColorData cd)
267 {
268 return {SEND_DEBUG_LABEL_MANY(debug_label) {&pixel, &pixel, &pixel, &pixel, &pixel, &pixel}, 1, 1, cd};
269 }
270
271 [[nodiscard]]
272 TextureCubemap load_cubemap_from_embedded(
273 DEBUG_LABEL_ARG_MANY
274 const std::array<embedded_binary, cubemap_size>& images,
275 ColorData cd
276 )
277 {
278 constexpr auto include_transparency = false;
279 constexpr auto flip = false;
280
281 const auto parsed
282 = images
283 | std::views::transform([](const embedded_binary& img) { return PixelData{img, include_transparency, flip}; });
284
285 const auto image_width = parsed[0].width;
286 const auto image_height = parsed[0].height;
287
288 // is loaded ok
289 if (std::ranges::any_of(
290 parsed | std::views::transform([](const auto& x) { return x.pixel_data;}),
291 [](auto* ptr) { return ptr == nullptr; }
292 ))
293 {
294 LOG_ERR("ERROR: Failed to load some cubemap from image source");
295 return load_cubemap_from_color(SEND_DEBUG_LABEL_MANY(debug_label) image_load_failure_color, cd);
296 }
297
298 if (std::ranges::all_of(
299 parsed | std::views::transform([](const auto& x) { return x.width; }),
300 [=](const int w) { return w == image_width; }
301 ))
302 {
303 // width ok
304 }
305 else
306 {
307 LOG_ERR("ERROR: cubemap has inconsistent width");
308 return load_cubemap_from_color(SEND_DEBUG_LABEL_MANY(debug_label) image_load_failure_color, cd);
309 }
310
311 if (std::ranges::all_of(
312 parsed | std::views::transform([](const auto& x) { return x.height; }),
313 [=](const int h) { return h == image_height; }
314 ))
315 {
316 // height ok
317 }
318 else
319 {
320 LOG_ERR("ERROR: cubemap has inconsistent height");
321 return load_cubemap_from_color(SEND_DEBUG_LABEL_MANY(debug_label) image_load_failure_color, cd);
322 }
323
324 // ok
325 return {
326 SEND_DEBUG_LABEL_MANY(debug_label)
327 {
328 parsed[0].pixel_data,
329 parsed[1].pixel_data,
330 parsed[2].pixel_data,
331 parsed[3].pixel_data,
332 parsed[4].pixel_data,
333 parsed[5].pixel_data
334 },
335 image_width,
336 image_height,
337 cd
338 };
339 }
340
341
342 // ------------------------------------------------------------------------------------------------
343 // framebuffer
344
345 [[nodiscard]]
346 unsigned int create_fbo()
347 {
348 unsigned int fbo = 0;
349 glGenFramebuffers(1, &fbo);
350 ASSERT(fbo != 0);
351 return fbo;
352 }
353
354 FrameBuffer::FrameBuffer(unsigned int f, const Size& s)
355 : size(s)
356 , fbo(f)
357 {
358 }
359
360 FrameBuffer::~FrameBuffer()
361 {
362 if (fbo != 0)
363 {
364 glDeleteFramebuffers(1, &fbo);
365 fbo = 0;
366 }
367 if (rbo != 0)
368 {
369 glDeleteRenderbuffers(1, &rbo);
370 rbo = 0;
371 }
372 }
373
374 BoundFbo::BoundFbo(std::shared_ptr<FrameBuffer> f)
375 : fbo(std::move(f))
376 {
377 glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo);
378 }
379
380 BoundFbo::~BoundFbo()
381 {
382 glBindFramebuffer(GL_FRAMEBUFFER, 0);
383 }
384
385 [[nodiscard]]
386 GLenum determine_fbo_internal_format(DepthBits depth, bool add_stencil)
387 {
388 switch (depth)
389 {
390 case DepthBits::use_16: return add_stencil? GL_DEPTH24_STENCIL8 : GL_DEPTH_COMPONENT16;
391 case DepthBits::use_24: return add_stencil? GL_DEPTH24_STENCIL8 : GL_DEPTH_COMPONENT24;
392 case DepthBits::use_32: return add_stencil? GL_DEPTH32F_STENCIL8 : GL_DEPTH_COMPONENT32F;
393 case DepthBits::use_none: return add_stencil ? GL_STENCIL_INDEX8 : GL_NONE;
394 default: ASSERT(false && "invalid enum depth value"); return GL_NONE;
395 }
396 }
397
398 // this is templated because opengl can't decide if the internal format is a GLint or GLenum
399 template<typename R>
400 R internal_format_from_color_bpp(ColorBitsPerPixel texture_bits, Transparency trans)
401 {
402 const auto include_transparency = trans == Transparency::include;
403
404 switch (texture_bits)
405 {
406 case ColorBitsPerPixel::use_depth: return GL_DEPTH_COMPONENT;
407 case ColorBitsPerPixel::use_8: return include_transparency ? GL_RGBA : GL_RGB;
408 case ColorBitsPerPixel::use_16: return include_transparency ? GL_RGBA16F : GL_RGB16F;
409 case ColorBitsPerPixel::use_32: return include_transparency ? GL_RGBA32F : GL_RGB32F;
410 default:
411 DIE("Invalid texture bits value");
412 return GL_RGB;
413 }
414 }
415
416
417 /// A builder class for the \ref FrameBuffer
418 struct FrameBufferBuilder
419 {
420 constexpr explicit FrameBufferBuilder(const Size& s)
421 : size(s)
422 {
423 }
424
425 Size size;
426
427 ColorBitsPerPixel color_bits_per_pixel = ColorBitsPerPixel::use_8;
428 DepthBits include_depth = DepthBits::use_none;
429 bool include_stencil = false;
430 std::optional<v4> border_color = std::nullopt;
431
432 /// 0 samples == no msaa
433 int msaa_samples = 0;
434
435 constexpr FrameBufferBuilder& with_msaa(int samples)
436 {
437 msaa_samples = samples;
438 return *this;
439 }
440
441 constexpr FrameBufferBuilder& with_depth(DepthBits bits = DepthBits::use_24)
442 {
443 include_depth = bits;
444 return *this;
445 }
446
447 constexpr FrameBufferBuilder& with_color_bits(ColorBitsPerPixel bits)
448 {
449 color_bits_per_pixel = bits;
450 return *this;
451 }
452
453 constexpr FrameBufferBuilder& with_stencil()
454 {
455 include_stencil = true;
456 return *this;
457 }
458
459 constexpr FrameBufferBuilder& with_border_color(const v4& c)
460 {
461 border_color = c;
462 return *this;
463 }
464
465 [[nodiscard]]
466 std::shared_ptr<FrameBuffer> build(DEBUG_LABEL_ARG_SINGLE) const;
467 };
468
469
470 std::shared_ptr<FrameBuffer> build_simple_framebuffer(DEBUG_LABEL_ARG_MANY const Size& size)
471 {
472 return FrameBufferBuilder{size}
473 .build(USE_DEBUG_LABEL(debug_label));
474 }
475
476
477 std::shared_ptr<FrameBuffer> build_hdr_floating_framebuffer(DEBUG_LABEL_ARG_MANY const Size& size, ColorBitsPerPixel bits_per_pixel)
478 {
479 return FrameBufferBuilder{size}
480 .with_color_bits(bits_per_pixel)
481 .build(USE_DEBUG_LABEL(debug_label));
482 }
483
484
485 std::shared_ptr<FrameBuffer> build_msaa_framebuffer(DEBUG_LABEL_ARG_MANY const Size& size, int msaa_samples, ColorBitsPerPixel bits_per_pixel)
486 {
487 return FrameBufferBuilder{size}
488 .with_msaa(msaa_samples)
489 .with_color_bits(bits_per_pixel)
490 .with_depth()
491 .with_stencil()
492 .build(USE_DEBUG_LABEL(debug_label));
493 }
494
495
496 std::shared_ptr<FrameBuffer> build_shadow_framebuffer(DEBUG_LABEL_ARG_MANY const Size& size)
497 {
498 return FrameBufferBuilder{size}
499 .with_color_bits(ColorBitsPerPixel::use_depth)
500 .with_border_color(v4{1.0f, 1.0f, 1.0f, 1.0f})
501 .build(USE_DEBUG_LABEL(debug_label));
502 }
503
504
505 std::shared_ptr<FrameBuffer> FrameBufferBuilder::build(DEBUG_LABEL_ARG_SINGLE) const
506 {
507 const auto te = TextureEdge::clamp;
508 const auto trs = TextureRenderStyle::linear;
509 const auto trans = Transparency::exclude;
510
511 const bool is_msaa = msaa_samples > 0;
512
513 LOG_INFO("Creating frame buffer %d %d", size.width, size.height);
514 auto fbo = std::make_shared<FrameBuffer>(create_fbo(), size);
515 ASSERT(fbo->id > 0);
516
517 fbo->debug_is_msaa = is_msaa;
518
519 // setup texture
520 const GLenum target = is_msaa ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
521 glBindTexture(target, fbo->id);
522 SET_DEBUG_LABEL_NAMED(fbo->id, DebugLabelFor::Texture, fmt::format("TEXTURE FRAMEBUFFER {}", debug_label));
523 if (is_msaa == false)
524 {
525 // msaa neither support min/mag filters nor texture wrapping
526 set_texture_wrap(target, te, border_color);
527 const auto filter = min_mag_from_trs(trs);
528 glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter.min);
529 glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter.mag);
530 }
531
532 if (is_msaa)
533 {
534 glTexImage2DMultisample(
535 target,
536 msaa_samples,
537 internal_format_from_color_bpp<GLenum>(color_bits_per_pixel, trans),
538 size.width,
539 size.height,
540 GL_TRUE
541 );
542 }
543 else
544 {
545 glTexImage2D(
546 target,
547 0,
548 internal_format_from_color_bpp<GLint>(color_bits_per_pixel, trans),
549 size.width,
550 size.height,
551 0,
552 // todo(Gustav): since we pass null as the data, the type doesn't matter... right?
553 color_bits_per_pixel == ColorBitsPerPixel::use_depth ? GL_DEPTH_COMPONENT : GL_RGB,
554 color_bits_per_pixel == ColorBitsPerPixel::use_depth ? GL_FLOAT : GL_UNSIGNED_BYTE,
555 nullptr
556 );
557 }
558
559 // setup fbo
560 auto bound = BoundFbo{fbo};
561 SET_DEBUG_LABEL_NAMED(fbo->fbo, DebugLabelFor::FrameBuffer, fmt::format("FBO {}", debug_label));
562 constexpr GLint mipmap_level = 0;
563 glFramebufferTexture2D(GL_FRAMEBUFFER, color_bits_per_pixel == ColorBitsPerPixel::use_depth? GL_DEPTH_ATTACHMENT : GL_COLOR_ATTACHMENT0, target, fbo->id, mipmap_level);
564
565 if (color_bits_per_pixel == ColorBitsPerPixel::use_depth)
566 {
567 // disable color buffer when using depth texture _only_
568 glDrawBuffer(GL_NONE);
569 glReadBuffer(GL_NONE);
570 }
571
572 const auto internal_format = determine_fbo_internal_format(include_depth, include_stencil);
573 if (internal_format != GL_NONE)
574 {
575 ASSERT(color_bits_per_pixel != ColorBitsPerPixel::use_depth);
576 glGenRenderbuffers(1, &fbo->rbo);
577 ASSERT(fbo->rbo != 0);
578 glBindRenderbuffer(GL_RENDERBUFFER, fbo->rbo);
579 SET_DEBUG_LABEL_NAMED(fbo->rbo, DebugLabelFor::RenderBuffer, fmt::format("TEXTURE RENDBUFF {}", debug_label));
580
581 if (is_msaa)
582 {
583 glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa_samples, internal_format, size.width, size.height);
584 }
585 else
586 {
587 glRenderbufferStorage(GL_RENDERBUFFER, internal_format, size.width, size.height);
588 }
589
590 if (include_stencil)
591 {
592 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo->rbo);
593 }
594 }
595 else
596 {
597 fbo->rbo = 0;
598 }
599
600 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
601 {
602 LOG_ERR("Failed to create frame buffer");
603 return nullptr;
604 }
605
606 return fbo;
607 }
608
609
610 void resolve_multisampled_buffer(const FrameBuffer& src, FrameBuffer* dst)
611 {
612 glBindFramebuffer(GL_READ_FRAMEBUFFER, src.fbo);
613 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst->fbo);
614
615 ASSERT(src.size == dst->size);
616
617 glBlitFramebuffer(0, 0, src.size.width, src.size.height, 0, 0, dst->size.width, dst->size.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
618
619 glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
620 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
621 }
622
623
624 }
625