./
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
0 / 279
0.0%
Branches:
0 / 368
0.0%
Functions:
0 / 44
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
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 5 not taken.
✗ 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
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ const auto wrap = te == TextureEdge::clamp ? GL_CLAMP_TO_EDGE : GL_REPEAT;
50
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 20 not taken.
✗ if (border_color.has_value())
51 {
52
0/6
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 14 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 14 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 23 not taken.
✗ 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
0/4
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 5 not taken.
✗ Branch 2 → 6 not taken.
✗ 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
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 11 not taken.
✗ 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
0/3
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 7 not taken.
✗ Branch 2 → 11 not taken.
✗ switch(cd)
85 {
86
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
✗ case ColorData::color_data: return include_transparency ? GL_SRGB_ALPHA : GL_SRGB;
87 ✗ case ColorData::dont_care:
88
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
✗ case ColorData::non_color_data: return include_transparency ? GL_RGBA : GL_RGB;
89 ✗ default:
90
0/2
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 16 not taken.
✗ 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
0/4
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 22 not taken.
✗ stbi_set_flip_vertically_on_load(flip ? 1 : 0);
105
106
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
✗ pixel_data = stbi_load_from_memory(
107
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 22 not taken.
✗ 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
0/2
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 17 not taken.
✗ if (pixel_data == nullptr)
116 {
117
0/6
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 22 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 20 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 18 not taken.
✗ LOG_ERR("ERROR: Failed to read pixel data");
118 ✗ width = 0;
119 ✗ height = 0;
120 }
121 ✗ }
122
123 ✗ ~PixelData()
124 {
125
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ 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
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 15 not taken.
✗ glGenFramebuffers(1, &fbo);
142
0/8
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 10 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 15 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 10 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 12 not taken.
✗ ASSERT(fbo != 0);
143 ✗ return fbo;
144 }
145
146 [[nodiscard]]
147 ✗ GLenum determine_fbo_internal_format(DepthBits depth, bool add_stencil)
148 {
149
0/5
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 7 not taken.
✗ Branch 2 → 11 not taken.
✗ Branch 2 → 15 not taken.
✗ Branch 2 → 19 not taken.
✗ switch (depth)
150 {
151
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 5 not taken.
✗ case DepthBits::use_16: return add_stencil ? GL_DEPTH24_STENCIL8 : GL_DEPTH_COMPONENT16;
152
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 9 not taken.
✗ case DepthBits::use_24: return add_stencil ? GL_DEPTH24_STENCIL8 : GL_DEPTH_COMPONENT24;
153
0/2
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
✗ case DepthBits::use_32: return add_stencil ? GL_DEPTH32F_STENCIL8 : GL_DEPTH_COMPONENT32F;
154
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 17 not taken.
✗ case DepthBits::use_none: return add_stencil ? GL_STENCIL_INDEX8 : GL_NONE;
155
0/4
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 25 not taken.
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 27 not taken.
✗ 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
0/10
int eu::render::(anonymous namespace)::internal_format_from_color_bpp<int>(eu::render::ColorBitsPerPixel, eu::render::Transparency):
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 8 not taken.
✗ Branch 2 → 12 not taken.
✗ Branch 2 → 16 not taken.
unsigned int eu::render::(anonymous namespace)::internal_format_from_color_bpp<unsigned int>(eu::render::ColorBitsPerPixel, eu::render::Transparency):
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 4 not taken.
✗ Branch 2 → 8 not taken.
✗ Branch 2 → 12 not taken.
✗ Branch 2 → 16 not taken.
✗ switch (texture_bits)
166 {
167 ✗ case ColorBitsPerPixel::use_depth: return GL_DEPTH_COMPONENT;
168
0/4
int eu::render::(anonymous namespace)::internal_format_from_color_bpp<int>(eu::render::ColorBitsPerPixel, eu::render::Transparency):
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
unsigned int eu::render::(anonymous namespace)::internal_format_from_color_bpp<unsigned int>(eu::render::ColorBitsPerPixel, eu::render::Transparency):
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 6 not taken.
✗ case ColorBitsPerPixel::use_8: return include_transparency ? GL_RGBA : GL_RGB;
169
0/4
int eu::render::(anonymous namespace)::internal_format_from_color_bpp<int>(eu::render::ColorBitsPerPixel, eu::render::Transparency):
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 10 not taken.
unsigned int eu::render::(anonymous namespace)::internal_format_from_color_bpp<unsigned int>(eu::render::ColorBitsPerPixel, eu::render::Transparency):
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 10 not taken.
✗ case ColorBitsPerPixel::use_16: return include_transparency ? GL_RGBA16F : GL_RGB16F;
170
0/4
int eu::render::(anonymous namespace)::internal_format_from_color_bpp<int>(eu::render::ColorBitsPerPixel, eu::render::Transparency):
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
unsigned int eu::render::(anonymous namespace)::internal_format_from_color_bpp<unsigned int>(eu::render::ColorBitsPerPixel, eu::render::Transparency):
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 14 not taken.
✗ case ColorBitsPerPixel::use_32: return include_transparency ? GL_RGBA32F : GL_RGB32F;
171 ✗ default:
172
0/4
int eu::render::(anonymous namespace)::internal_format_from_color_bpp<int>(eu::render::ColorBitsPerPixel, eu::render::Transparency):
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 21 not taken.
unsigned int eu::render::(anonymous namespace)::internal_format_from_color_bpp<unsigned int>(eu::render::ColorBitsPerPixel, eu::render::Transparency):
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 21 not taken.
✗ 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
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 5 not taken.
✗ 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
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 22 not taken.
✗ glBindTexture(GL_TEXTURE_2D, id);
274
0/4
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 19 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 17 not taken.
✗ SET_DEBUG_LABEL_NAMED(id, DebugLabelFor::texture, fmt::format("TEXTURE2d {}", debug_label));
275
276
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 21 not taken.
✗ set_texture_wrap(GL_TEXTURE_2D, te, std::nullopt);
277
278
0/2
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 22 not taken.
✗ const auto filter = min_mag_from_trs(trs);
279
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 22 not taken.
✗ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter.min);
280
0/2
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 22 not taken.
✗ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter.mag);
281
282
0/4
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 22 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 22 not taken.
✗ 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
0/2
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 16 not taken.
✗ if (trs == TextureRenderStyle::mipmap)
295 {
296
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 22 not taken.
✗ 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
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 24 not taken.
✗ auto parsed = PixelData{image_binary, include_transparency};
308
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 10 not taken.
✗ if (parsed.pixel_data == nullptr)
309 {
310
0/6
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 22 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 20 not taken.
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 18 not taken.
✗ LOG_ERR("ERROR: Failed to load image from image source");
311
0/2
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 22 not taken.
✗ return load_image_from_color(SEND_DEBUG_LABEL_MANY(debug_label) image_load_failure_color, te, trs, t, cd);
312 }
313
314
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 12 not taken.
✗ const GLenum pixel_format = include_transparency ? GL_RGBA : GL_RGB;
315
0/2
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 22 not taken.
✗ 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
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 23 not taken.
✗ glBindTexture(GL_TEXTURE_CUBE_MAP, id);
331
0/4
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 21 not taken.
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 19 not taken.
✗ SET_DEBUG_LABEL_NAMED(id, DebugLabelFor::texture, fmt::format("TEXTURE CUBEMAP {}", debug_label));
332
333
0/2
✗ Branch 12 → 8 not taken.
✗ Branch 12 → 13 not taken.
✗ for (size_t index = 0; index < cubemap_size; index += 1)
334 {
335
0/4
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 23 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 23 not taken.
✗ 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
0/2
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 23 not taken.
✗ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
349
0/2
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 23 not taken.
✗ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
350
0/2
✗ Branch 15 → 16 not taken.
✗ Branch 15 → 23 not taken.
✗ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
351
0/2
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 23 not taken.
✗ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
352
0/2
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 23 not taken.
✗ 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
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 5 not taken.
✗ 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
0/4
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 55 not taken.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 55 not taken.
✗ | std::views::transform([](const embedded_binary& img) { return PixelData{img, include_transparency, flip}; });
374
375
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 57 not taken.
✗ const auto image_width = parsed[0].width;
376
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 58 not taken.
✗ const auto image_height = parsed[0].height;
377
378 // is loaded ok
379
0/2
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 59 not taken.
✗ if (std::ranges::any_of(
380
0/6
✗ Branch 8 → 9 not taken.
✗ Branch 8 → 59 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 59 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 18 not taken.
✗ parsed | std::views::transform([](const auto& x) { return x.pixel_data;}),
381 ✗ [](auto* ptr) { return ptr == nullptr; }
382 ))
383 {
384
0/6
✗ Branch 12 → 13 not taken.
✗ Branch 12 → 99 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 64 not taken.
✗ Branch 14 → 15 not taken.
✗ Branch 14 → 62 not taken.
✗ LOG_ERR("ERROR: Failed to load some cubemap from image source");
385
0/2
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 99 not taken.
✗ return load_cubemap_from_color(SEND_DEBUG_LABEL_MANY(debug_label) image_load_failure_color, cd);
386 }
387
388
0/2
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 66 not taken.
✗ if (std::ranges::all_of(
389
0/6
✗ Branch 18 → 19 not taken.
✗ Branch 18 → 66 not taken.
✗ Branch 19 → 20 not taken.
✗ Branch 19 → 66 not taken.
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 28 not taken.
✗ 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
0/6
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 99 not taken.
✗ Branch 23 → 24 not taken.
✗ Branch 23 → 71 not taken.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 69 not taken.
✗ LOG_ERR("ERROR: cubemap has inconsistent width");
398
0/2
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 99 not taken.
✗ return load_cubemap_from_color(SEND_DEBUG_LABEL_MANY(debug_label) image_load_failure_color, cd);
399 }
400
401
0/2
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 73 not taken.
✗ if (std::ranges::all_of(
402
0/6
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 73 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 73 not taken.
✗ Branch 31 → 32 not taken.
✗ Branch 31 → 38 not taken.
✗ 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
0/6
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 99 not taken.
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 78 not taken.
✗ Branch 34 → 35 not taken.
✗ Branch 34 → 76 not taken.
✗ LOG_ERR("ERROR: cubemap has inconsistent height");
411
0/2
✗ Branch 36 → 37 not taken.
✗ Branch 36 → 99 not taken.
✗ 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
0/2
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 97 not taken.
✗ 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
0/12
✗ Branch 39 → 40 not taken.
✗ Branch 39 → 94 not taken.
✗ Branch 40 → 41 not taken.
✗ Branch 40 → 91 not taken.
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 88 not taken.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 85 not taken.
✗ Branch 43 → 44 not taken.
✗ Branch 43 → 82 not taken.
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 80 not taken.
✗ };
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
0/2
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 5 not taken.
✗ if (fbo != 0)
446 {
447 ✗ glDeleteFramebuffers(1, &fbo);
448 ✗ fbo = 0;
449 }
450
0/2
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 8 not taken.
✗ 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
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 8 not taken.
✗ 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
0/2
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 7 not taken.
✗ .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
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 8 not taken.
✗ .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
0/2
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 11 not taken.
✗ .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
0/2
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 10 not taken.
✗ .with_border_color(v4{1.0f, 1.0f, 1.0f, 1.0f})
500
0/2
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 10 not taken.
✗ .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
0/6
✗ Branch 2 → 3 not taken.
✗ Branch 2 → 141 not taken.
✗ Branch 3 → 4 not taken.
✗ Branch 3 → 105 not taken.
✗ Branch 4 → 5 not taken.
✗ Branch 4 → 103 not taken.
✗ LOG_INFO("Creating frame buffer {} {}", size.width, size.height);
513
0/4
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 107 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 107 not taken.
✗ auto fbo = std::make_shared<FrameBuffer>(create_fbo(), size);
514
0/8
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 16 not taken.
✗ Branch 10 → 11 not taken.
✗ Branch 10 → 139 not taken.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 16 not taken.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 108 not taken.
✗ ASSERT(fbo->id > 0);
515
516 ✗ fbo->debug_is_msaa = is_msaa;
517
518 // setup texture
519
0/2
✗ Branch 17 → 18 not taken.
✗ Branch 17 → 19 not taken.
✗ const GLenum target = is_msaa ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
520
0/2
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 139 not taken.
✗ glBindTexture(target, fbo->id);
521
0/4
✗ Branch 22 → 23 not taken.
✗ Branch 22 → 113 not taken.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 111 not taken.
✗ SET_DEBUG_LABEL_NAMED(fbo->id, DebugLabelFor::texture, fmt::format("TEXTURE FRAMEBUFFER {}", debug_label));
522
0/2
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 32 not taken.
✗ if (is_msaa == false)
523 {
524 // msaa neither support min/mag filters nor texture wrapping
525
0/2
✗ Branch 27 → 28 not taken.
✗ Branch 27 → 115 not taken.
✗ set_texture_wrap(target, te, border_color);
526
0/2
✗ Branch 28 → 29 not taken.
✗ Branch 28 → 115 not taken.
✗ const auto filter = min_mag_from_trs(trs);
527
0/2
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 115 not taken.
✗ glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter.min);
528
0/2
✗ Branch 30 → 31 not taken.
✗ Branch 30 → 115 not taken.
✗ glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter.mag);
529 }
530
531
0/2
✗ Branch 32 → 33 not taken.
✗ Branch 32 → 35 not taken.
✗ if (is_msaa)
532 {
533 ✗ glTexImage2DMultisample(
534 target,
535
0/2
✗ Branch 34 → 43 not taken.
✗ Branch 34 → 139 not taken.
✗ msaa_samples,
536 ✗ internal_format_from_color_bpp<GLenum>(color_bits_per_pixel, trans),
537 ✗ size.width,
538
0/2
✗ Branch 33 → 34 not taken.
✗ Branch 33 → 139 not taken.
✗ size.height,
539 GL_TRUE
540 );
541 }
542 else
543 {
544
0/2
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 139 not taken.
✗ glTexImage2D(
545 target,
546 0,
547 ✗ internal_format_from_color_bpp<GLint>(color_bits_per_pixel, trans),
548 ✗ size.width,
549
0/2
✗ Branch 41 → 42 not taken.
✗ Branch 41 → 139 not taken.
✗ size.height,
550 0,
551 // todo(Gustav): since we pass null as the data, the type doesn't matter... right?
552
0/2
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
✗ color_bits_per_pixel == ColorBitsPerPixel::use_depth ? GL_DEPTH_COMPONENT : GL_RGB,
553
0/2
✗ Branch 35 → 36 not taken.
✗ Branch 35 → 37 not taken.
✗ color_bits_per_pixel == ColorBitsPerPixel::use_depth ? GL_FLOAT : GL_UNSIGNED_BYTE,
554 nullptr
555 );
556 }
557
558 // setup fbo
559
0/2
✗ Branch 44 → 45 not taken.
✗ Branch 44 → 116 not taken.
✗ auto bound = BoundFbo{fbo};
560
0/4
✗ Branch 46 → 47 not taken.
✗ Branch 46 → 121 not taken.
✗ Branch 48 → 49 not taken.
✗ Branch 48 → 119 not taken.
✗ SET_DEBUG_LABEL_NAMED(fbo->fbo, DebugLabelFor::frame_buffer, fmt::format("FBO {}", debug_label));
561 ✗ constexpr GLint mipmap_level = 0;
562
0/4
✗ Branch 51 → 52 not taken.
✗ Branch 51 → 53 not taken.
✗ Branch 54 → 55 not taken.
✗ Branch 54 → 137 not taken.
✗ glFramebufferTexture2D(GL_FRAMEBUFFER, color_bits_per_pixel == ColorBitsPerPixel::use_depth? GL_DEPTH_ATTACHMENT : GL_COLOR_ATTACHMENT0, target, fbo->id, mipmap_level);
563
564
0/2
✗ Branch 55 → 56 not taken.
✗ Branch 55 → 58 not taken.
✗ if (color_bits_per_pixel == ColorBitsPerPixel::use_depth)
565 {
566 // disable color buffer when using depth texture _only_
567
0/2
✗ Branch 56 → 57 not taken.
✗ Branch 56 → 137 not taken.
✗ glDrawBuffer(GL_NONE);
568
0/2
✗ Branch 57 → 58 not taken.
✗ Branch 57 → 137 not taken.
✗ glReadBuffer(GL_NONE);
569 }
570
571
0/2
✗ Branch 58 → 59 not taken.
✗ Branch 58 → 137 not taken.
✗ const auto internal_format = determine_fbo_internal_format(include_depth, include_stencil);
572
0/2
✗ Branch 59 → 60 not taken.
✗ Branch 59 → 89 not taken.
✗ if (internal_format != GL_NONE)
573 {
574
0/8
✗ Branch 60 → 61 not taken.
✗ Branch 60 → 67 not taken.
✗ Branch 61 → 62 not taken.
✗ Branch 61 → 137 not taken.
✗ Branch 62 → 63 not taken.
✗ Branch 62 → 67 not taken.
✗ Branch 64 → 65 not taken.
✗ Branch 64 → 123 not taken.
✗ ASSERT(color_bits_per_pixel != ColorBitsPerPixel::use_depth);
575
0/2
✗ Branch 68 → 69 not taken.
✗ Branch 68 → 137 not taken.
✗ glGenRenderbuffers(1, &fbo->rbo);
576
0/8
✗ Branch 70 → 71 not taken.
✗ Branch 70 → 77 not taken.
✗ Branch 71 → 72 not taken.
✗ Branch 71 → 137 not taken.
✗ Branch 72 → 73 not taken.
✗ Branch 72 → 77 not taken.
✗ Branch 74 → 75 not taken.
✗ Branch 74 → 126 not taken.
✗ ASSERT(fbo->rbo != 0);
577
0/2
✗ Branch 78 → 79 not taken.
✗ Branch 78 → 137 not taken.
✗ glBindRenderbuffer(GL_RENDERBUFFER, fbo->rbo);
578
0/4
✗ Branch 79 → 80 not taken.
✗ Branch 79 → 131 not taken.
✗ Branch 81 → 82 not taken.
✗ Branch 81 → 129 not taken.
✗ SET_DEBUG_LABEL_NAMED(fbo->rbo, DebugLabelFor::render_buffer, fmt::format("TEXTURE RENDBUFF {}", debug_label));
579
580
0/2
✗ Branch 83 → 84 not taken.
✗ Branch 83 → 85 not taken.
✗ if (is_msaa)
581 {
582
0/2
✗ Branch 84 → 86 not taken.
✗ Branch 84 → 137 not taken.
✗ glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa_samples, internal_format, size.width, size.height);
583 }
584 else
585 {
586
0/2
✗ Branch 85 → 86 not taken.
✗ Branch 85 → 137 not taken.
✗ glRenderbufferStorage(GL_RENDERBUFFER, internal_format, size.width, size.height);
587 }
588
589
0/2
✗ Branch 86 → 87 not taken.
✗ Branch 86 → 91 not taken.
✗ if (include_stencil)
590 {
591
0/2
✗ Branch 88 → 91 not taken.
✗ Branch 88 → 137 not taken.
✗ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fbo->rbo);
592 }
593 }
594 else
595 {
596 ✗ fbo->rbo = 0;
597 }
598
599
0/4
✗ Branch 91 → 92 not taken.
✗ Branch 91 → 137 not taken.
✗ Branch 92 → 93 not taken.
✗ Branch 92 → 98 not taken.
✗ if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
600 {
601
0/6
✗ Branch 93 → 94 not taken.
✗ Branch 93 → 137 not taken.
✗ Branch 94 → 95 not taken.
✗ Branch 94 → 135 not taken.
✗ Branch 95 → 96 not taken.
✗ Branch 95 → 133 not taken.
✗ 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
0/6
✗ Branch 5 → 6 not taken.
✗ Branch 5 → 12 not taken.
✗ Branch 7 → 8 not taken.
✗ Branch 7 → 12 not taken.
✗ Branch 9 → 10 not taken.
✗ Branch 9 → 16 not taken.
✗ 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