30 #include <stb/stb_image.h>
37 std::uint8_t dimensionality,
42 std::uint32_t mip_levels,
43 std::uint32_t array_layers,
47 const auto format_index = std::to_underlying(
format);
48 const auto gl_internal_format =
gl_format_lut[format_index][0];
51 if (gl_internal_format == 0 || gl_type == 0)
53 throw std::invalid_argument(
"Image construction used unsupported format.");
56 if (!width || !height || !depth)
58 throw std::invalid_argument(
"Image dimensions must be nonzero.");
63 throw std::invalid_argument(
"Image mip levels must be nonzero.");
66 if (mip_levels >
static_cast<std::uint32_t
>(std::bit_width(
std::max(
std::max(width, height), depth))))
68 throw std::out_of_range(
"Image mip levels exceed `1 + log2(max(width, height, depth))`.");
73 throw std::invalid_argument(
"Image array layers must be nonzero.");
76 if (dimensionality == 1)
78 if (height > 1 || depth > 1)
80 throw std::invalid_argument(
"1D image must have a height and depth of `1`.");
83 else if (dimensionality == 2)
87 throw std::invalid_argument(
"2D image must have a depth of `1`.");
90 else if (dimensionality == 3)
94 throw std::invalid_argument(
"3D image arrays not supported.");
100 if (dimensionality != 2)
102 throw std::invalid_argument(
"Cube compatible image must be 2D.");
107 throw std::invalid_argument(
"Cube compatible image width and height must be equal.");
110 if (array_layers % 6 != 0)
112 throw std::invalid_argument(
"Cube compatible image array layers must be a multiple of 6.");
116 m_dimensionality = dimensionality;
118 m_dimensions = {width, height, depth};
119 m_mip_levels = mip_levels;
120 m_array_layers = array_layers;
123 if (m_array_layers == 1)
125 switch (m_dimensionality)
128 m_gl_texture_target = GL_TEXTURE_1D;
129 glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
133 static_cast<GLsizei
>(m_mip_levels),
135 static_cast<GLsizei
>(m_dimensions[0])
140 m_gl_texture_target = GL_TEXTURE_2D;
141 glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
145 static_cast<GLsizei
>(m_mip_levels),
147 static_cast<GLsizei
>(m_dimensions[0]),
148 static_cast<GLsizei
>(m_dimensions[1])
153 m_gl_texture_target = GL_TEXTURE_3D;
154 glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
158 static_cast<GLsizei
>(m_mip_levels),
160 static_cast<GLsizei
>(m_dimensions[0]),
161 static_cast<GLsizei
>(m_dimensions[1]),
162 static_cast<GLsizei
>(m_dimensions[2])
172 switch (m_dimensionality)
175 m_gl_texture_target = GL_TEXTURE_1D_ARRAY;
176 glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
180 static_cast<GLsizei
>(m_mip_levels),
182 static_cast<GLsizei
>(m_dimensions[0]),
183 static_cast<GLsizei
>(m_array_layers)
190 if (m_array_layers == 6)
192 m_gl_texture_target = GL_TEXTURE_CUBE_MAP;
193 glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
197 static_cast<GLsizei
>(m_mip_levels),
199 static_cast<GLsizei
>(m_dimensions[0]),
200 static_cast<GLsizei
>(m_dimensions[1])
205 m_gl_texture_target = GL_TEXTURE_CUBE_MAP_ARRAY;
206 glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
210 static_cast<GLsizei
>(m_mip_levels),
212 static_cast<GLsizei
>(m_dimensions[0]),
213 static_cast<GLsizei
>(m_dimensions[1]),
214 static_cast<GLsizei
>(m_array_layers)
220 m_gl_texture_target = GL_TEXTURE_2D_ARRAY;
221 glCreateTextures(m_gl_texture_target, 1, &m_gl_texture_name);
225 static_cast<GLsizei
>(m_mip_levels),
227 static_cast<GLsizei
>(m_dimensions[0]),
228 static_cast<GLsizei
>(m_dimensions[1]),
229 static_cast<GLsizei
>(m_array_layers)
242 glDeleteTextures(1, &m_gl_texture_name);
247 std::uint32_t mip_level,
248 std::uint32_t offset_x,
249 std::uint32_t offset_y,
250 std::uint32_t offset_z,
252 std::uint32_t height,
255 std::span<std::byte> data
258 if (mip_level >= m_mip_levels)
260 throw std::out_of_range(
"Image read operation mip level out of range.");
263 const auto format_index = std::to_underlying(
format);
267 if (gl_base_format == 0 || gl_type == 0)
269 throw std::invalid_argument(
"Image read operation used unsupported format.");
275 static_cast<GLint
>(mip_level),
276 static_cast<GLint
>(offset_x),
277 static_cast<GLint
>(offset_y),
278 static_cast<GLint
>(offset_z),
279 static_cast<GLsizei
>(width),
280 static_cast<GLsizei
>(height),
281 static_cast<GLsizei
>(depth),
284 static_cast<GLsizei
>(data.size()),
291 std::uint32_t mip_level,
292 std::uint32_t offset_x,
293 std::uint32_t offset_y,
294 std::uint32_t offset_z,
296 std::uint32_t height,
299 std::span<const std::byte> data
302 if (mip_level >= m_mip_levels)
304 throw std::out_of_range(
"Image write operation mip level out of range.");
307 const auto format_index = std::to_underlying(
format);
311 if (gl_base_format == 0 || gl_type == 0)
313 throw std::invalid_argument(
"Image write operation used unsupported format.");
316 if (m_array_layers == 1)
318 if ((offset_x + width > std::max<std::uint32_t>(1, m_dimensions[0] >> mip_level)) ||
319 (offset_y + height > std::max<std::uint32_t>(1, m_dimensions[1] >> mip_level)) ||
320 (offset_z + depth > std::max<std::uint32_t>(1, m_dimensions[2] >> mip_level)))
322 throw std::out_of_range(
"Image write operation exceeded image bounds.");
325 switch (m_dimensionality)
331 static_cast<GLint
>(mip_level),
332 static_cast<GLint
>(offset_x),
333 static_cast<GLsizei
>(width),
344 static_cast<GLint
>(mip_level),
345 static_cast<GLint
>(offset_x),
346 static_cast<GLint
>(offset_y),
347 static_cast<GLsizei
>(width),
348 static_cast<GLsizei
>(height),
359 static_cast<GLint
>(mip_level),
360 static_cast<GLint
>(offset_x),
361 static_cast<GLint
>(offset_y),
362 static_cast<GLint
>(offset_z),
363 static_cast<GLsizei
>(width),
364 static_cast<GLsizei
>(height),
365 static_cast<GLsizei
>(depth),
378 switch (m_dimensionality)
381 if ((offset_x + width > std::max<std::uint32_t>(1, m_dimensions[0] >> mip_level)) ||
382 (offset_y + height > m_array_layers) ||
383 (offset_z + depth > 1))
385 throw std::out_of_range(
"Image write operation exceeded image dimensions.");
391 static_cast<GLint
>(mip_level),
392 static_cast<GLint
>(offset_x),
393 static_cast<GLint
>(offset_y),
394 static_cast<GLsizei
>(width),
395 static_cast<GLsizei
>(height),
403 if ((offset_x + width > std::max<std::uint32_t>(1, m_dimensions[0] >> mip_level)) ||
404 (offset_y + height > std::max<std::uint32_t>(1, m_dimensions[1] >> mip_level)) ||
405 (offset_z + depth > m_array_layers))
407 throw std::out_of_range(
"Image write operation exceeded image bounds.");
413 static_cast<GLint
>(mip_level),
414 static_cast<GLint
>(offset_x),
415 static_cast<GLint
>(offset_y),
416 static_cast<GLint
>(offset_z),
417 static_cast<GLsizei
>(width),
418 static_cast<GLsizei
>(height),
419 static_cast<GLsizei
>(depth),
434 std::uint32_t src_mip_level,
439 std::uint32_t dst_mip_level,
444 std::uint32_t height,
452 static_cast<GLint
>(src_mip_level),
453 static_cast<GLint
>(src_x),
454 static_cast<GLint
>(src_y),
455 static_cast<GLint
>(src_z),
456 dst_image.m_gl_texture_name,
457 dst_image.m_gl_texture_target,
458 static_cast<GLint
>(dst_mip_level),
459 static_cast<GLint
>(dst_x),
460 static_cast<GLint
>(dst_y),
461 static_cast<GLint
>(dst_z),
462 static_cast<GLsizei
>(width),
463 static_cast<GLsizei
>(height),
464 static_cast<GLsizei
>(depth)
470 if (m_mip_levels > 1)
472 glGenerateTextureMipmap(m_gl_texture_name);
480 std::uint32_t mip_levels,
481 std::uint32_t array_layers,
501 std::uint32_t height,
502 std::uint32_t mip_levels,
503 std::uint32_t array_layers,
523 std::uint32_t height,
525 std::uint32_t mip_levels,
545 std::uint32_t mip_levels,
546 std::uint32_t array_layers
563 int stb_io_read(
void* user,
char* data,
int size)
566 return static_cast<int>(ctx.
read8(
reinterpret_cast<std::byte*
>(data),
static_cast<std::size_t
>(size)));
569 void stb_io_skip(
void* user,
int n)
575 int stb_io_eof(
void* user)
578 return static_cast<int>(ctx.
eof());
581 struct stb_image_deleter
583 void operator()(
void* p)
const
589 [[nodiscard]] std::unique_ptr<gl::image> load_image_stb_image(
deserialize_context& ctx, std::uint8_t dimensionality, std::uint32_t mip_levels)
592 const stbi_io_callbacks io_callbacks
600 std::size_t component_size = stbi_is_16_bit_from_callbacks(&io_callbacks, &ctx) ?
sizeof(std::uint16_t) :
sizeof(std::uint8_t);
604 stbi_set_flip_vertically_on_load(
true);
607 std::unique_ptr<void, stb_image_deleter> data;
612 if (component_size ==
sizeof(std::uint16_t))
615 data = std::unique_ptr<void, stb_image_deleter>(stbi_load_16_from_callbacks(&io_callbacks, &ctx, &width, &height, &components, 0));
638 data = std::unique_ptr<void, stb_image_deleter>(stbi_load_from_callbacks(&io_callbacks, &ctx, &width, &height, &components, 0));
668 mip_levels =
static_cast<std::uint32_t
>(std::bit_width(
static_cast<std::uint32_t
>(
std::max(width, height))));
672 std::unique_ptr<gl::image> image;
673 switch (dimensionality)
676 image = std::make_unique<gl::image_1d>
679 static_cast<std::uint32_t
>(
std::max(width, height)),
685 image = std::make_unique<gl::image_2d>
688 static_cast<std::uint32_t
>(width),
689 static_cast<std::uint32_t
>(height),
695 image = std::make_unique<gl::image_3d>
698 static_cast<std::uint32_t
>(width),
699 static_cast<std::uint32_t
>(height),
716 image->get_dimensions()[0],
717 image->get_dimensions()[1],
718 image->get_dimensions()[2],
721 reinterpret_cast<const std::byte*>(data.get()),
722 image->get_dimensions()[0] *
723 image->get_dimensions()[1] *
724 image->get_dimensions()[2] *
725 static_cast<std::size_t>(components) *
731 image->generate_mipmaps();
736 [[nodiscard]] std::unique_ptr<gl::image> load_image_tinyexr(
deserialize_context& ctx, std::uint8_t dimensionality, std::uint32_t mip_levels)
738 const char*
error =
nullptr;
739 auto tinyexr_error = [&
error]()
741 const std::string error_message(error);
742 FreeEXRErrorMessage(error);
747 std::vector<unsigned char> file_buffer(ctx.
size());
748 ctx.
read8(
reinterpret_cast<std::byte*
>(file_buffer.data()), file_buffer.size());
751 EXRVersion exr_version;
752 if (ParseEXRVersionFromMemory(&exr_version, file_buffer.data(), file_buffer.size()) != TINYEXR_SUCCESS)
758 if (exr_version.multipart)
764 EXRHeader exr_header;
765 InitEXRHeader(&exr_header);
766 if (ParseEXRHeaderFromMemory(&exr_header, &exr_version, file_buffer.data(), file_buffer.size(), &error) != TINYEXR_SUCCESS)
772 if (exr_header.tiled)
774 FreeEXRHeader(&exr_header);
779 if (exr_header.num_channels < 1 || exr_header.num_channels > 4)
781 FreeEXRHeader(&exr_header);
786 for (
int i = 1;
i < exr_header.num_channels; ++
i)
788 if (exr_header.pixel_types[i] != exr_header.pixel_types[i - 1])
790 FreeEXRHeader(&exr_header);
791 throw deserialize_error(
"OpenEXR images must have the same pixel type per channel.");
797 InitEXRImage(&exr_image);
798 if (LoadEXRImageFromMemory(&exr_image, &exr_header, file_buffer.data(), file_buffer.size(), &error) != TINYEXR_SUCCESS)
800 FreeEXRHeader(&exr_header);
831 switch (exr_header.pixel_types[0])
833 case TINYEXR_PIXELTYPE_UINT:
834 format = uint_formats[exr_header.num_channels - 1];
835 component_size =
static_cast<int>(
sizeof(std::uint32_t));
838 case TINYEXR_PIXELTYPE_HALF:
839 format = half_formats[exr_header.num_channels - 1];
840 component_size =
static_cast<int>(
sizeof(std::uint16_t));
843 case TINYEXR_PIXELTYPE_FLOAT:
844 format = float_formats[exr_header.num_channels - 1];
845 component_size =
static_cast<int>(
sizeof(float));
855 std::vector<std::byte> data(
static_cast<std::size_t
>(exr_image.width * exr_image.height * exr_header.num_channels * component_size));
858 std::byte* component = data.data();
859 for (
auto y = exr_image.height - 1; y >= 0; --y)
861 const auto row_offset =
y * exr_image.width;
863 for (
auto x = 0;
x < exr_image.width; ++
x)
865 const auto byte_offset = (row_offset +
x) * component_size;
867 for (
auto c = exr_image.num_channels - 1; c >= 0; --c)
869 std::memcpy(component, exr_image.images[c] + byte_offset,
static_cast<std::size_t
>(component_size));
870 component += component_size;
876 const auto width =
static_cast<std::uint32_t
>(exr_image.width);
877 const auto height =
static_cast<std::uint32_t
>(exr_image.height);
880 FreeEXRImage(&exr_image);
881 FreeEXRHeader(&exr_header);
886 mip_levels =
static_cast<std::uint32_t
>(std::bit_width(
std::max(width, height)));
890 std::unique_ptr<gl::image> image;
891 switch (dimensionality)
894 image = std::make_unique<gl::image_1d>
903 image = std::make_unique<gl::image_2d>
913 image = std::make_unique<gl::image_3d>
934 image->get_dimensions()[0],
935 image->get_dimensions()[1],
936 image->get_dimensions()[2],
942 image->generate_mipmaps();
947 [[nodiscard]] std::unique_ptr<gl::image> load_image(
deserialize_context& ctx, std::uint8_t dimensionality, std::uint32_t mip_levels)
950 if (ctx.
path().extension() ==
".exr")
953 return load_image_tinyexr(ctx, dimensionality, mip_levels);
958 return load_image_stb_image(ctx, dimensionality, mip_levels);
966 return std::unique_ptr<gl::image_1d>(
static_cast<gl::image_1d*
>(load_image(ctx, 1, 0).release()));
972 return std::unique_ptr<gl::image_2d>(
static_cast<gl::image_2d*
>(load_image(ctx, 2, 0).release()));
978 return std::unique_ptr<gl::image_3d>(
static_cast<gl::image_3d*
>(load_image(ctx, 3, 0).release()));
985 auto cube_map = std::unique_ptr<gl::image_2d>(
static_cast<gl::image_2d*
>(load_image(ctx, 2, 1).release()));
991 throw deserialize_error(
"Failed to load cube image from cube map with unknown layout.");
995 throw deserialize_error(
"Failed to load cube image from cube map with unsupported layout.");
1002 auto image = std::make_unique<gl::image_cube>
1004 cube_map->get_format(),
1006 static_cast<std::uint32_t
>(std::bit_width(face_width))
1010 constexpr std::uint32_t vcross_offsets[6][2] =
1018 constexpr std::uint32_t hcross_offsets[6][2] =
1029 for (std::uint32_t i = 0; i < 6; ++i)
1031 cube_map->copy(0, 0, face_width * i, 0, *image, 0, 0, 0, i, face_width, face_width, 1);
1036 for (std::uint32_t i = 0; i < 6; ++i)
1038 cube_map->copy(0, face_width * i, 0, 0, *image, 0, 0, 0, i, face_width, face_width, 1);
1043 for (std::uint32_t i = 0; i < 6; ++i)
1045 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);
1050 for (std::uint32_t i = 0; i < 6; ++i)
1052 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);
1061 image->generate_mipmaps();
An exception of this type is thrown when an error occurs during deserialization.
image_1d(gl::format format, std::uint32_t width, std::uint32_t mip_levels=1, std::uint32_t array_layers=1, std::uint32_t flags=0)
image_2d(gl::format format, std::uint32_t width, std::uint32_t height, std::uint32_t mip_levels=1, std::uint32_t array_layers=1, std::uint32_t flags=0)
image_3d(gl::format format, std::uint32_t width, std::uint32_t height, std::uint32_t depth, std::uint32_t mip_levels=1, std::uint32_t flags=0)
image_cube(gl::format format, std::uint32_t width, std::uint32_t mip_levels=1, std::uint32_t array_layers=6)
constexpr bool is_cube_compatible() const noexcept
Returns true if the image is cube map compatible, false otherwise.
void 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
Reads pixel data from the image.
void generate_mipmaps()
Generates mip subimages.
void 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)
Writes pixel data to the image.
virtual ~image()=0
Destructs an image.
void 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
Copies pixel data from this image into another the image.
image(const image &)=delete
static std::unique_ptr< T > load(::resource_manager &resource_manager, deserialize_context &ctx)
Loads a resource.
Manages the loading, caching, and saving of resources.
@ error
Error message severity.
Graphics library interface.
@ cube_compatible
Cube map view compatible image.
format
Image and vertex formats.
std::uint32_t infer_cube_map_face_width(std::uint32_t width, std::uint32_t height, cube_map_layout layout) noexcept
Infers the width of a cube map face from its dimensons and layout.
cube_map_layout infer_cube_map_layout(std::uint32_t width, std::uint32_t height) noexcept
Infers the layout of a cube map from its dimensions.
constexpr GLenum gl_format_lut[][3]
Maps gl::format to OpenGL internal format, base format, and pixel type.
@ column
Faces are stored consecutively in a single column.
@ spherical
Faces are stored in a spherical projection.
@ horizontal_cross
Faces are stored in a horizontal cross.
@ equirectangular
Faces are stored in an equirectangular projection.
@ vertical_cross
Faces are stored in a vertical cross.
@ row
Faces are stored consecutively in a single row.
constexpr vector< T, N > max(const vector< T, N > &x, const vector< T, N > &y)
Returns a vector containing the maximum elements of two vectors.
Provides access to a deserialization state.
virtual bool eof() const noexcept=0
Returns true if the end of a file was reached.
virtual std::size_t tell() const =0
Returns the offsets from the start of the file to the current position, in bytes.
virtual void seek(std::size_t offset)=0
Seeks to a position in the file.
virtual const std::filesystem::path & path() const noexcept=0
Returns the path associated with this deserialize context.
virtual std::size_t size() const noexcept=0
Returns the size of the file, in bytes.
virtual std::size_t read8(std::byte *data, std::size_t count) noexcept(false)=0
Reads 8-bit (byte) data.