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 |