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 |