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 368, 0 excluded
0.0%

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