|
|
- /*
- * Copyright (C) 2023 Christopher J. Howard
- *
- * This file is part of Antkeeper source code.
- *
- * Antkeeper source code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Antkeeper source code is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
- */
-
- #include <engine/gl/image.hpp>
- #include <engine/gl/cube-map.hpp>
- #include <engine/gl/opengl/gl-format-lut.hpp>
- #include <engine/resources/resource-loader.hpp>
- #include <engine/resources/deserialize-error.hpp>
- #include <engine/resources/deserializer.hpp>
- #include <engine/debug/log.hpp>
- #include <cmath>
- #include <stdexcept>
- #include <glad/gl.h>
- #include <stb/stb_image.h>
- #include <tinyexr.h>
-
- namespace gl {
-
- image::image
- (
- std::uint8_t dimensionality,
- gl::format format,
- std::uint32_t width,
- std::uint32_t height,
- std::uint32_t depth,
- std::uint32_t mip_levels,
- std::uint32_t array_layers,
- std::uint32_t flags
- )
- {
- const auto format_index = std::to_underlying(format);
- const auto gl_internal_format = gl_format_lut[format_index][0];
- const auto gl_type = gl_format_lut[format_index][2];
-
- if (gl_internal_format == 0 || gl_type == 0)
- {
- throw std::invalid_argument("Image construction used unsupported format.");
- }
-
- if (!width || !height || !depth)
- {
- throw std::invalid_argument("Image dimensions must be nonzero.");
- }
-
- if (!mip_levels)
- {
- throw std::invalid_argument("Image mip levels must be nonzero.");
- }
-
- if (mip_levels > static_cast<std::uint32_t>(std::bit_width(std::max(std::max(width, height), depth))))
- {
- throw std::out_of_range("Image mip levels exceed `1 + log2(max(width, height, depth))`.");
- }
-
- if (!array_layers)
- {
- throw std::invalid_argument("Image array layers must be nonzero.");
- }
-
- if (dimensionality == 1)
- {
- if (height > 1 || depth > 1)
- {
- throw std::invalid_argument("1D image must have a height and depth of `1`.");
- }
- }
- else if (dimensionality == 2)
- {
- if (depth > 1)
- {
- throw std::invalid_argument("2D image must have a depth of `1`.");
- }
- }
- else if (dimensionality == 3)
- {
- if (array_layers > 1)
- {
- throw std::invalid_argument("3D image arrays not supported.");
- }
- }
-
- if (flags & std::to_underlying(image_flag::cube_compatible))
- {
- if (dimensionality != 2)
- {
- throw std::invalid_argument("Cube compatible image must be 2D.");
- }
-
- if (width != height)
- {
- throw std::invalid_argument("Cube compatible image width and height must be equal.");
- }
-
- if (array_layers % 6 != 0)
- {
- throw std::invalid_argument("Cube compatible image array layers must be a multiple of 6.");
- }
- }
-
- m_dimensionality = dimensionality;
- m_format = format;
- m_dimensions = {width, height, depth};
- m_mip_levels = mip_levels;
- m_array_layers = array_layers;
- m_flags = flags;
-
- if (m_array_layers == 1)
- {
- switch (m_dimensionality)
- {
- case 1:
- m_gl_texture_target = GL_TEXTURE_1D;
- glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
- glTextureStorage1D
- (
- m_gl_texture_name,
- static_cast<GLsizei>(m_mip_levels),
- gl_internal_format,
- static_cast<GLsizei>(m_dimensions[0])
- );
- break;
-
- case 2:
- m_gl_texture_target = GL_TEXTURE_2D;
- glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
- glTextureStorage2D
- (
- m_gl_texture_name,
- static_cast<GLsizei>(m_mip_levels),
- gl_internal_format,
- static_cast<GLsizei>(m_dimensions[0]),
- static_cast<GLsizei>(m_dimensions[1])
- );
- break;
-
- case 3:
- m_gl_texture_target = GL_TEXTURE_3D;
- glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
- glTextureStorage3D
- (
- m_gl_texture_name,
- static_cast<GLsizei>(m_mip_levels),
- gl_internal_format,
- static_cast<GLsizei>(m_dimensions[0]),
- static_cast<GLsizei>(m_dimensions[1]),
- static_cast<GLsizei>(m_dimensions[2])
- );
- break;
-
- default:
- break;
- }
- }
- else
- {
- switch (m_dimensionality)
- {
- case 1:
- m_gl_texture_target = GL_TEXTURE_1D_ARRAY;
- glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
- glTextureStorage2D
- (
- m_gl_texture_name,
- static_cast<GLsizei>(m_mip_levels),
- gl_internal_format,
- static_cast<GLsizei>(m_dimensions[0]),
- static_cast<GLsizei>(m_array_layers)
- );
- break;
-
- case 2:
- if (is_cube_compatible())
- {
- if (m_array_layers == 6)
- {
- m_gl_texture_target = GL_TEXTURE_CUBE_MAP;
- glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
- glTextureStorage2D
- (
- m_gl_texture_name,
- static_cast<GLsizei>(m_mip_levels),
- gl_internal_format,
- static_cast<GLsizei>(m_dimensions[0]),
- static_cast<GLsizei>(m_dimensions[1])
- );
- }
- else
- {
- m_gl_texture_target = GL_TEXTURE_CUBE_MAP_ARRAY;
- glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
- glTextureStorage3D
- (
- m_gl_texture_name,
- static_cast<GLsizei>(m_mip_levels),
- gl_internal_format,
- static_cast<GLsizei>(m_dimensions[0]),
- static_cast<GLsizei>(m_dimensions[1]),
- static_cast<GLsizei>(m_array_layers)
- );
- }
- }
- else
- {
- m_gl_texture_target = GL_TEXTURE_2D_ARRAY;
- glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
- glTextureStorage3D
- (
- m_gl_texture_name,
- static_cast<GLsizei>(m_mip_levels),
- gl_internal_format,
- static_cast<GLsizei>(m_dimensions[0]),
- static_cast<GLsizei>(m_dimensions[1]),
- static_cast<GLsizei>(m_array_layers)
- );
- }
- break;
-
- default:
- break;
- }
- }
- }
-
- image::~image()
- {
- glDeleteTextures(1, &m_gl_texture_name);
- }
-
- void image::read
- (
- std::uint32_t mip_level,
- std::uint32_t offset_x,
- std::uint32_t offset_y,
- std::uint32_t offset_z,
- std::uint32_t width,
- std::uint32_t height,
- std::uint32_t depth,
- gl::format format,
- std::span<std::byte> data
- ) const
- {
- if (mip_level >= m_mip_levels)
- {
- throw std::out_of_range("Image read operation mip level out of range.");
- }
-
- const auto format_index = std::to_underlying(format);
- const auto gl_base_format = gl_format_lut[format_index][1];
- const auto gl_type = gl_format_lut[format_index][2];
-
- if (gl_base_format == 0 || gl_type == 0)
- {
- throw std::invalid_argument("Image read operation used unsupported format.");
- }
-
- glGetTextureSubImage
- (
- m_gl_texture_name,
- static_cast<GLint>(mip_level),
- static_cast<GLint>(offset_x),
- static_cast<GLint>(offset_y),
- static_cast<GLint>(offset_z),
- static_cast<GLsizei>(width),
- static_cast<GLsizei>(height),
- static_cast<GLsizei>(depth),
- gl_base_format,
- gl_type,
- static_cast<GLsizei>(data.size()),
- data.data()
- );
- }
-
- void image::write
- (
- std::uint32_t mip_level,
- std::uint32_t offset_x,
- std::uint32_t offset_y,
- std::uint32_t offset_z,
- std::uint32_t width,
- std::uint32_t height,
- std::uint32_t depth,
- gl::format format,
- std::span<const std::byte> data
- )
- {
- if (mip_level >= m_mip_levels)
- {
- throw std::out_of_range("Image write operation mip level out of range.");
- }
-
- const auto format_index = std::to_underlying(format);
- const auto gl_base_format = gl_format_lut[format_index][1];
- const auto gl_type = gl_format_lut[format_index][2];
-
- if (gl_base_format == 0 || gl_type == 0)
- {
- throw std::invalid_argument("Image write operation used unsupported format.");
- }
-
- if (m_array_layers == 1)
- {
- if ((offset_x + width > std::max<std::uint32_t>(1, m_dimensions[0] >> mip_level)) ||
- (offset_y + height > std::max<std::uint32_t>(1, m_dimensions[1] >> mip_level)) ||
- (offset_z + depth > std::max<std::uint32_t>(1, m_dimensions[2] >> mip_level)))
- {
- throw std::out_of_range("Image write operation exceeded image bounds.");
- }
-
- switch (m_dimensionality)
- {
- case 1:
- glTextureSubImage1D
- (
- m_gl_texture_name,
- static_cast<GLint>(mip_level),
- static_cast<GLint>(offset_x),
- static_cast<GLsizei>(width),
- gl_base_format,
- gl_type,
- data.data()
- );
- break;
-
- case 2:
- glTextureSubImage2D
- (
- m_gl_texture_name,
- static_cast<GLint>(mip_level),
- static_cast<GLint>(offset_x),
- static_cast<GLint>(offset_y),
- static_cast<GLsizei>(width),
- static_cast<GLsizei>(height),
- gl_base_format,
- gl_type,
- data.data()
- );
- break;
-
- case 3:
- glTextureSubImage3D
- (
- m_gl_texture_name,
- static_cast<GLint>(mip_level),
- static_cast<GLint>(offset_x),
- static_cast<GLint>(offset_y),
- static_cast<GLint>(offset_z),
- static_cast<GLsizei>(width),
- static_cast<GLsizei>(height),
- static_cast<GLsizei>(depth),
- gl_base_format,
- gl_type,
- data.data()
- );
- break;
-
- default:
- break;
- }
- }
- else
- {
- switch (m_dimensionality)
- {
- case 1:
- if ((offset_x + width > std::max<std::uint32_t>(1, m_dimensions[0] >> mip_level)) ||
- (offset_y + height > m_array_layers) ||
- (offset_z + depth > 1))
- {
- throw std::out_of_range("Image write operation exceeded image dimensions.");
- }
-
- glTextureSubImage2D
- (
- m_gl_texture_name,
- static_cast<GLint>(mip_level),
- static_cast<GLint>(offset_x),
- static_cast<GLint>(offset_y),
- static_cast<GLsizei>(width),
- static_cast<GLsizei>(height),
- gl_base_format,
- gl_type,
- data.data()
- );
- break;
-
- case 2:
- if ((offset_x + width > std::max<std::uint32_t>(1, m_dimensions[0] >> mip_level)) ||
- (offset_y + height > std::max<std::uint32_t>(1, m_dimensions[1] >> mip_level)) ||
- (offset_z + depth > m_array_layers))
- {
- throw std::out_of_range("Image write operation exceeded image bounds.");
- }
-
- glTextureSubImage3D
- (
- m_gl_texture_name,
- static_cast<GLint>(mip_level),
- static_cast<GLint>(offset_x),
- static_cast<GLint>(offset_y),
- static_cast<GLint>(offset_z),
- static_cast<GLsizei>(width),
- static_cast<GLsizei>(height),
- static_cast<GLsizei>(depth),
- gl_base_format,
- gl_type,
- data.data()
- );
- break;
-
- default:
- break;
- }
- }
- }
-
- void image::copy
- (
- std::uint32_t src_mip_level,
- std::uint32_t src_x,
- std::uint32_t src_y,
- std::uint32_t src_z,
- image& dst_image,
- std::uint32_t dst_mip_level,
- std::uint32_t dst_x,
- std::uint32_t dst_y,
- std::uint32_t dst_z,
- std::uint32_t width,
- std::uint32_t height,
- std::uint32_t depth
- ) const
- {
- glCopyImageSubData
- (
- m_gl_texture_name,
- m_gl_texture_target,
- static_cast<GLint>(src_mip_level),
- static_cast<GLint>(src_x),
- static_cast<GLint>(src_y),
- static_cast<GLint>(src_z),
- dst_image.m_gl_texture_name,
- dst_image.m_gl_texture_target,
- static_cast<GLint>(dst_mip_level),
- static_cast<GLint>(dst_x),
- static_cast<GLint>(dst_y),
- static_cast<GLint>(dst_z),
- static_cast<GLsizei>(width),
- static_cast<GLsizei>(height),
- static_cast<GLsizei>(depth)
- );
- }
-
- void image::generate_mipmaps()
- {
- if (m_mip_levels > 1)
- {
- glGenerateTextureMipmap(m_gl_texture_name);
- }
- }
-
- image_1d::image_1d
- (
- gl::format format,
- std::uint32_t width,
- std::uint32_t mip_levels,
- std::uint32_t array_layers,
- std::uint32_t flags
- ):
- image
- (
- 1,
- format,
- width,
- 1,
- 1,
- mip_levels,
- array_layers,
- flags
- )
- {}
-
- image_2d::image_2d
- (
- gl::format format,
- std::uint32_t width,
- std::uint32_t height,
- std::uint32_t mip_levels,
- std::uint32_t array_layers,
- std::uint32_t flags
- ):
- image
- (
- 2,
- format,
- width,
- height,
- 1,
- mip_levels,
- array_layers,
- flags
- )
- {}
-
- image_3d::image_3d
- (
- gl::format format,
- std::uint32_t width,
- std::uint32_t height,
- std::uint32_t depth,
- std::uint32_t mip_levels,
- std::uint32_t flags
- ):
- image
- (
- 3,
- format,
- width,
- height,
- depth,
- mip_levels,
- 1,
- flags
- )
- {}
-
- image_cube::image_cube
- (
- gl::format format,
- std::uint32_t width,
- std::uint32_t mip_levels,
- std::uint32_t array_layers
- ):
- image_2d
- (
- format,
- width,
- width,
- mip_levels,
- array_layers,
- std::to_underlying(image_flag::cube_compatible)
- )
- {}
-
- } // namespace gl
-
- namespace {
-
- int stb_io_read(void* user, char* data, int size)
- {
- deserialize_context& ctx = *static_cast<deserialize_context*>(user);
- return static_cast<int>(ctx.read8(reinterpret_cast<std::byte*>(data), static_cast<std::size_t>(size)));
- }
-
- void stb_io_skip(void* user, int n)
- {
- deserialize_context& ctx = *static_cast<deserialize_context*>(user);
- ctx.seek(ctx.tell() + n);
- }
-
- int stb_io_eof(void* user)
- {
- deserialize_context& ctx = *static_cast<deserialize_context*>(user);
- return static_cast<int>(ctx.eof());
- }
-
- struct stb_image_deleter
- {
- void operator()(void* p) const
- {
- stbi_image_free(p);
- }
- };
-
- [[nodiscard]] std::unique_ptr<gl::image> load_image_stb_image(deserialize_context& ctx, std::uint8_t dimensionality, std::uint32_t mip_levels)
- {
- // Setup IO callbacks
- const stbi_io_callbacks io_callbacks
- {
- &stb_io_read,
- &stb_io_skip,
- &stb_io_eof
- };
-
- // Determine image bit depth
- std::size_t component_size = stbi_is_16_bit_from_callbacks(&io_callbacks, &ctx) ? sizeof(std::uint16_t) : sizeof(std::uint8_t);
- ctx.seek(0);
-
- // Set vertical flip on load in order to correctly upload pixel data to OpenGL
- stbi_set_flip_vertically_on_load(true);
-
- // Load image data
- std::unique_ptr<void, stb_image_deleter> data;
- int width;
- int height;
- int components;
- gl::format format;
- if (component_size == sizeof(std::uint16_t))
- {
- // Load 16-bit image data
- data = std::unique_ptr<void, stb_image_deleter>(stbi_load_16_from_callbacks(&io_callbacks, &ctx, &width, &height, &components, 0));
-
- // Determine 16-bit image format
- format = [components]()
- {
- switch (components)
- {
- case 1:
- return gl::format::r16_unorm;
- case 2:
- return gl::format::r16g16_unorm;
- case 3:
- return gl::format::r16g16b16_unorm;
- case 4:
- return gl::format::r16g16b16a16_unorm;
- default:
- return gl::format::undefined;
- }
- }();
- }
- else
- {
- // Load 8-bit image data
- data = std::unique_ptr<void, stb_image_deleter>(stbi_load_from_callbacks(&io_callbacks, &ctx, &width, &height, &components, 0));
-
- // Determine 8-bit image format
- format = [components]()
- {
- switch (components)
- {
- case 1:
- return gl::format::r8_unorm;
- case 2:
- return gl::format::r8g8_unorm;
- case 3:
- return gl::format::r8g8b8_unorm;
- case 4:
- return gl::format::r8g8b8a8_unorm;
- default:
- return gl::format::undefined;
- }
- }();
- }
-
- // Check if image data was loaded
- if (!data)
- {
- throw deserialize_error(stbi_failure_reason());
- }
-
- // Determine number mip levels
- if (!mip_levels)
- {
- mip_levels = static_cast<std::uint32_t>(std::bit_width(static_cast<std::uint32_t>(std::max(width, height))));
- }
-
- // Allocate image
- std::unique_ptr<gl::image> image;
- switch (dimensionality)
- {
- case 1:
- image = std::make_unique<gl::image_1d>
- (
- format,
- static_cast<std::uint32_t>(std::max(width, height)),
- mip_levels
- );
- break;
-
- case 2:
- image = std::make_unique<gl::image_2d>
- (
- format,
- static_cast<std::uint32_t>(width),
- static_cast<std::uint32_t>(height),
- mip_levels
- );
- break;
-
- case 3:
- image = std::make_unique<gl::image_3d>
- (
- format,
- static_cast<std::uint32_t>(width),
- static_cast<std::uint32_t>(height),
- 1,
- mip_levels
- );
- break;
-
- default:
- break;
- }
-
- // Upload image data to image
- image->write
- (
- 0,
- 0,
- 0,
- 0,
- image->get_dimensions()[0],
- image->get_dimensions()[1],
- image->get_dimensions()[2],
- format,
- {
- reinterpret_cast<const std::byte*>(data.get()),
- image->get_dimensions()[0] *
- image->get_dimensions()[1] *
- image->get_dimensions()[2] *
- static_cast<std::size_t>(components) *
- component_size
- }
- );
-
- // Generate mipmaps
- image->generate_mipmaps();
-
- return image;
- }
-
- [[nodiscard]] std::unique_ptr<gl::image> load_image_tinyexr(deserialize_context& ctx, std::uint8_t dimensionality, std::uint32_t mip_levels)
- {
- const char* error = nullptr;
- auto tinyexr_error = [&error]()
- {
- const std::string error_message(error);
- FreeEXRErrorMessage(error);
- throw deserialize_error(error_message);
- };
-
- // Read data into file buffer
- std::vector<unsigned char> file_buffer(ctx.size());
- ctx.read8(reinterpret_cast<std::byte*>(file_buffer.data()), file_buffer.size());
-
- // Read EXR version
- EXRVersion exr_version;
- if (ParseEXRVersionFromMemory(&exr_version, file_buffer.data(), file_buffer.size()) != TINYEXR_SUCCESS)
- {
- tinyexr_error();
- }
-
- // Check if image is multipart
- if (exr_version.multipart)
- {
- throw deserialize_error("OpenEXR multipart images not supported.");
- }
-
- // Load image header
- EXRHeader exr_header;
- InitEXRHeader(&exr_header);
- if (ParseEXRHeaderFromMemory(&exr_header, &exr_version, file_buffer.data(), file_buffer.size(), &error) != TINYEXR_SUCCESS)
- {
- tinyexr_error();
- }
-
- // Check if image is tiled
- if (exr_header.tiled)
- {
- FreeEXRHeader(&exr_header);
- throw deserialize_error("OpenEXR tiled images not supported.");
- }
-
- // Check if image has a supported number of channels
- if (exr_header.num_channels < 1 || exr_header.num_channels > 4)
- {
- FreeEXRHeader(&exr_header);
- throw deserialize_error("OpenEXR images must have 1-4 channels.");
- }
-
- // Check if all channels have the same format
- for (int i = 1; i < exr_header.num_channels; ++i)
- {
- if (exr_header.pixel_types[i] != exr_header.pixel_types[i - 1])
- {
- FreeEXRHeader(&exr_header);
- throw deserialize_error("OpenEXR images must have the same pixel type per channel.");
- }
- }
-
- // Load image data
- EXRImage exr_image;
- InitEXRImage(&exr_image);
- if (LoadEXRImageFromMemory(&exr_image, &exr_header, file_buffer.data(), file_buffer.size(), &error) != TINYEXR_SUCCESS)
- {
- FreeEXRHeader(&exr_header);
- tinyexr_error();
- }
-
- // Free file buffer
- file_buffer.clear();
-
- // Determine image format
- constexpr gl::format uint_formats[4] =
- {
- gl::format::r32_uint,
- gl::format::r32g32_uint,
- gl::format::r32g32b32_uint,
- gl::format::r32g32b32a32_uint
- };
- constexpr gl::format half_formats[4] =
- {
- gl::format::r16_sfloat,
- gl::format::r16g16_sfloat,
- gl::format::r16g16b16_sfloat,
- gl::format::r16g16b16a16_sfloat
- };
- constexpr gl::format float_formats[4] =
- {
- gl::format::r32_sfloat,
- gl::format::r32g32_sfloat,
- gl::format::r32g32b32_sfloat,
- gl::format::r32g32b32a32_sfloat
- };
- gl::format format;
- int component_size;
- switch (exr_header.pixel_types[0])
- {
- case TINYEXR_PIXELTYPE_UINT:
- format = uint_formats[exr_header.num_channels - 1];
- component_size = static_cast<int>(sizeof(std::uint32_t));
- break;
-
- case TINYEXR_PIXELTYPE_HALF:
- format = half_formats[exr_header.num_channels - 1];
- component_size = static_cast<int>(sizeof(std::uint16_t));//sizeof(float16_t)
- break;
-
- case TINYEXR_PIXELTYPE_FLOAT:
- format = float_formats[exr_header.num_channels - 1];
- component_size = static_cast<int>(sizeof(float));//sizeof(float32_t)
- break;
-
- default:
- format = gl::format::undefined;
- component_size = 0;
- break;
- }
-
- // Allocate interleaved image data
- std::vector<std::byte> data(static_cast<std::size_t>(exr_image.width * exr_image.height * exr_header.num_channels * component_size));
-
- // Interleave image data from layers
- std::byte* component = data.data();
- for (auto y = exr_image.height - 1; y >= 0; --y)
- {
- const auto row_offset = y * exr_image.width;
-
- for (auto x = 0; x < exr_image.width; ++x)
- {
- const auto byte_offset = (row_offset + x) * component_size;
-
- for (auto c = exr_image.num_channels - 1; c >= 0; --c)
- {
- std::memcpy(component, exr_image.images[c] + byte_offset, static_cast<std::size_t>(component_size));
- component += component_size;
- }
- }
- }
-
- // Store image dimensions
- const auto width = static_cast<std::uint32_t>(exr_image.width);
- const auto height = static_cast<std::uint32_t>(exr_image.height);
-
- // Free loaded image data and image header
- FreeEXRImage(&exr_image);
- FreeEXRHeader(&exr_header);
-
- // Determine number mip levels
- if (!mip_levels)
- {
- mip_levels = static_cast<std::uint32_t>(std::bit_width(std::max(width, height)));
- }
-
- // Allocate image
- std::unique_ptr<gl::image> image;
- switch (dimensionality)
- {
- case 1:
- image = std::make_unique<gl::image_1d>
- (
- format,
- std::max(width, height),
- mip_levels
- );
- break;
-
- case 2:
- image = std::make_unique<gl::image_2d>
- (
- format,
- width,
- height,
- mip_levels
- );
- break;
-
- case 3:
- image = std::make_unique<gl::image_3d>
- (
- format,
- width,
- height,
- 1,
- mip_levels
- );
- break;
-
- default:
- break;
- }
-
- // Upload interleaved image data to image
- image->write
- (
- 0,
- 0,
- 0,
- 0,
- image->get_dimensions()[0],
- image->get_dimensions()[1],
- image->get_dimensions()[2],
- format,
- data
- );
-
- // Generate mipmaps
- image->generate_mipmaps();
-
- return image;
- }
-
- [[nodiscard]] std::unique_ptr<gl::image> load_image(deserialize_context& ctx, std::uint8_t dimensionality, std::uint32_t mip_levels)
- {
- // Select loader according to file extension
- if (ctx.path().extension() == ".exr")
- {
- // Load EXR images with TinyEXR
- return load_image_tinyexr(ctx, dimensionality, mip_levels);
- }
- else
- {
- // Load other image formats with stb_image
- return load_image_stb_image(ctx, dimensionality, mip_levels);
- }
- }
- }
-
- template <>
- std::unique_ptr<gl::image_1d> resource_loader<gl::image_1d>::load(::resource_manager& resource_manager, deserialize_context& ctx)
- {
- return std::unique_ptr<gl::image_1d>(static_cast<gl::image_1d*>(load_image(ctx, 1, 0).release()));
- }
-
- template <>
- std::unique_ptr<gl::image_2d> resource_loader<gl::image_2d>::load(::resource_manager& resource_manager, deserialize_context& ctx)
- {
- return std::unique_ptr<gl::image_2d>(static_cast<gl::image_2d*>(load_image(ctx, 2, 0).release()));
- }
-
- template <>
- std::unique_ptr<gl::image_3d> resource_loader<gl::image_3d>::load(::resource_manager& resource_manager, deserialize_context& ctx)
- {
- return std::unique_ptr<gl::image_3d>(static_cast<gl::image_3d*>(load_image(ctx, 3, 0).release()));
- }
-
- template <>
- std::unique_ptr<gl::image_cube> resource_loader<gl::image_cube>::load(::resource_manager& resource_manager, deserialize_context& ctx)
- {
- // Load cube map
- auto cube_map = std::unique_ptr<gl::image_2d>(static_cast<gl::image_2d*>(load_image(ctx, 2, 1).release()));
-
- // Determine cube map layout
- const auto layout = gl::infer_cube_map_layout(cube_map->get_dimensions()[0], cube_map->get_dimensions()[1]);
- if (layout == gl::cube_map_layout::unknown)
- {
- throw deserialize_error("Failed to load cube image from cube map with unknown layout.");
- }
- else if (layout == gl::cube_map_layout::equirectangular || layout == gl::cube_map_layout::spherical)
- {
- throw deserialize_error("Failed to load cube image from cube map with unsupported layout.");
- }
-
- // Determine cube map face width
- const auto face_width = gl::infer_cube_map_face_width(cube_map->get_dimensions()[0], cube_map->get_dimensions()[1], layout);
-
- // Allocate cube image
- auto image = std::make_unique<gl::image_cube>
- (
- cube_map->get_format(),
- face_width,
- static_cast<std::uint32_t>(std::bit_width(face_width))
- );
-
- // Vertical cross layout face offsets
- constexpr std::uint32_t vcross_offsets[6][2] =
- {
- {2, 2}, {0, 2}, // -x, +x
- {1, 3}, {1, 1}, // -y, +y
- {1, 0}, {1, 2} // -z, +z
- };
-
- // Horizontal cross layout face offsets
- constexpr std::uint32_t hcross_offsets[6][2] =
- {
- {2, 1}, {0, 1}, // -x, +x
- {1, 2}, {1, 0}, // -y, +y
- {3, 1}, {1, 1} // -z, +z
- };
-
- // Copy cube map faces to cube image
- switch (layout)
- {
- case gl::cube_map_layout::column:
- for (std::uint32_t i = 0; i < 6; ++i)
- {
- cube_map->copy(0, 0, face_width * i, 0, *image, 0, 0, 0, i, face_width, face_width, 1);
- }
- break;
-
- case gl::cube_map_layout::row:
- for (std::uint32_t i = 0; i < 6; ++i)
- {
- cube_map->copy(0, face_width * i, 0, 0, *image, 0, 0, 0, i, face_width, face_width, 1);
- }
- break;
-
- case gl::cube_map_layout::vertical_cross:
- for (std::uint32_t i = 0; i < 6; ++i)
- {
- cube_map->copy(0, face_width * vcross_offsets[i][0], face_width * vcross_offsets[i][1], 0, *image, 0, 0, 0, i, face_width, face_width, 1);
- }
- break;
-
- case gl::cube_map_layout::horizontal_cross:
- for (std::uint32_t i = 0; i < 6; ++i)
- {
- cube_map->copy(0, face_width * hcross_offsets[i][0], face_width * hcross_offsets[i][1], 0, *image, 0, 0, 0, i, face_width, face_width, 1);
- }
- break;
-
- default:
- break;
- }
-
- // Generate mipmaps
- image->generate_mipmaps();
-
- return image;
- }
|