diff --git a/core/io/image.cpp b/core/io/image.cpp index a1fc2ec937..815cc9da3f 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -41,6 +41,8 @@ #include "core/config/project_settings.h" #include "core/error/error_macros.h" #include "core/io/image_loader.h" +#include "core/io/image_rw.h" +#include "core/io/io.h" #include "core/io/resource_loader.h" #include "core/math/math_funcs.h" #include "core/templates/hash_map.h" @@ -475,69 +477,6 @@ int Image::get_mipmap_count() const { } } -/// Using template generates perfectly optimized code due to constant expression reduction and unused variable removal present in all compilers. -template -static void _convert(int p_width, int p_height, const uint8_t *p_src, uint8_t *p_dst) { - constexpr uint32_t max_bytes = MAX(read_bytes, write_bytes); - - for (int y = 0; y < p_height; y++) { - for (int x = 0; x < p_width; x++) { - const uint8_t *rofs = &p_src[((y * p_width) + x) * (read_bytes + (read_alpha ? 1 : 0))]; - uint8_t *wofs = &p_dst[((y * p_width) + x) * (write_bytes + (write_alpha ? 1 : 0))]; - - uint8_t rgba[4] = { 0, 0, 0, 255 }; - - if constexpr (read_gray) { - rgba[0] = rofs[0]; - rgba[1] = rofs[0]; - rgba[2] = rofs[0]; - } else { - for (uint32_t i = 0; i < max_bytes; i++) { - rgba[i] = (i < read_bytes) ? rofs[i] : 0; - } - } - - if constexpr (read_alpha || write_alpha) { - rgba[3] = read_alpha ? rofs[read_bytes] : 255; - } - - if constexpr (write_gray) { - // REC.709 - const uint8_t luminance = (13938U * rgba[0] + 46869U * rgba[1] + 4729U * rgba[2] + 32768U) >> 16U; - wofs[0] = luminance; - } else { - for (uint32_t i = 0; i < write_bytes; i++) { - wofs[i] = rgba[i]; - } - } - - if constexpr (write_alpha) { - wofs[write_bytes] = rgba[3]; - } - } - } -} - -template -static void _convert_fast(int p_width, int p_height, const T *p_src, T *p_dst) { - uint32_t dst_count = 0; - uint32_t src_count = 0; - - const int resolution = p_width * p_height; - - for (int i = 0; i < resolution; i++) { - memcpy(p_dst + dst_count, p_src + src_count, MIN(read_channels, write_channels) * sizeof(T)); - - if constexpr (write_channels > read_channels) { - const T def_value[4] = { def_zero, def_zero, def_zero, def_one }; - memcpy(p_dst + dst_count + read_channels, &def_value[read_channels], (write_channels - read_channels) * sizeof(T)); - } - - dst_count += write_channels; - src_count += read_channels; - } -} - static bool _are_formats_compatible(Image::Format p_format0, Image::Format p_format1) { if (p_format0 <= Image::FORMAT_RGBA8 && p_format1 <= Image::FORMAT_RGBA8) { return true; @@ -551,6 +490,7 @@ static bool _are_formats_compatible(Image::Format p_format0, Image::Format p_for } void Image::convert(Format p_new_format) { + IO::Error err = IO::Error::Okay; ERR_FAIL_INDEX_MSG(p_new_format, FORMAT_MAX, vformat("The Image format specified (%d) is out of range. See Image's Format enum.", p_new_format)); if (data.is_empty() || p_new_format == format) { @@ -592,185 +532,75 @@ void Image::convert(Format p_new_format) { // Convert the formats in an optimized way by removing/adding color channels if necessary. Image new_img(width, height, mipmaps, p_new_format); - const int conversion_type = format | p_new_format << 8; - for (int mip = 0; mip < mipmap_count; mip++) { int64_t mip_offset = 0; int64_t mip_size = 0; int mip_width = 0; int mip_height = 0; + IO::Reader reader = {}; + IO::Writer writer = {}; + IO::Image::Reader imReader = {}; + IO::Image::Writer imWriter = {}; + ColorRGBAF32x16 block; get_mipmap_offset_size_and_dimensions(mip, mip_offset, mip_size, mip_width, mip_height); const uint8_t *rptr = data.ptr() + mip_offset; uint8_t *wptr = new_img.data.ptrw() + new_img.get_mipmap_offset(mip); - - switch (conversion_type) { - case FORMAT_L8 | (FORMAT_LA8 << 8): - _convert<1, false, 1, true, true, true>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_L8 | (FORMAT_R8 << 8): - _convert<1, false, 1, false, true, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_L8 | (FORMAT_RG8 << 8): - _convert<1, false, 2, false, true, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_L8 | (FORMAT_RGB8 << 8): - _convert<1, false, 3, false, true, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_L8 | (FORMAT_RGBA8 << 8): - _convert<1, false, 3, true, true, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_LA8 | (FORMAT_L8 << 8): - _convert<1, true, 1, false, true, true>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_LA8 | (FORMAT_R8 << 8): - _convert<1, true, 1, false, true, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_LA8 | (FORMAT_RG8 << 8): - _convert<1, true, 2, false, true, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_LA8 | (FORMAT_RGB8 << 8): - _convert<1, true, 3, false, true, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_LA8 | (FORMAT_RGBA8 << 8): - _convert<1, true, 3, true, true, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_R8 | (FORMAT_L8 << 8): - _convert<1, false, 1, false, false, true>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_R8 | (FORMAT_LA8 << 8): - _convert<1, false, 1, true, false, true>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_R8 | (FORMAT_RG8 << 8): - _convert<1, false, 2, false, false, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_R8 | (FORMAT_RGB8 << 8): - _convert<1, false, 3, false, false, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_R8 | (FORMAT_RGBA8 << 8): - _convert<1, false, 3, true, false, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RG8 | (FORMAT_L8 << 8): - _convert<2, false, 1, false, false, true>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RG8 | (FORMAT_LA8 << 8): - _convert<2, false, 1, true, false, true>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RG8 | (FORMAT_R8 << 8): - _convert<2, false, 1, false, false, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RG8 | (FORMAT_RGB8 << 8): - _convert<2, false, 3, false, false, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RG8 | (FORMAT_RGBA8 << 8): - _convert<2, false, 3, true, false, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RGB8 | (FORMAT_L8 << 8): - _convert<3, false, 1, false, false, true>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RGB8 | (FORMAT_LA8 << 8): - _convert<3, false, 1, true, false, true>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RGB8 | (FORMAT_R8 << 8): - _convert<3, false, 1, false, false, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RGB8 | (FORMAT_RG8 << 8): - _convert<3, false, 2, false, false, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RGB8 | (FORMAT_RGBA8 << 8): - _convert<3, false, 3, true, false, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RGBA8 | (FORMAT_L8 << 8): - _convert<3, true, 1, false, false, true>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RGBA8 | (FORMAT_LA8 << 8): - _convert<3, true, 1, true, false, true>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RGBA8 | (FORMAT_R8 << 8): - _convert<3, true, 1, false, false, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RGBA8 | (FORMAT_RG8 << 8): - _convert<3, true, 2, false, false, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RGBA8 | (FORMAT_RGB8 << 8): - _convert<3, true, 3, false, false, false>(mip_width, mip_height, rptr, wptr); - break; - case FORMAT_RH | (FORMAT_RGH << 8): - _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); - break; - case FORMAT_RH | (FORMAT_RGBH << 8): - _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); - break; - case FORMAT_RH | (FORMAT_RGBAH << 8): - _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); - break; - case FORMAT_RGH | (FORMAT_RH << 8): - _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); - break; - case FORMAT_RGH | (FORMAT_RGBH << 8): - _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); - break; - case FORMAT_RGH | (FORMAT_RGBAH << 8): - _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); - break; - case FORMAT_RGBH | (FORMAT_RH << 8): - _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); - break; - case FORMAT_RGBH | (FORMAT_RGH << 8): - _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); - break; - case FORMAT_RGBH | (FORMAT_RGBAH << 8): - _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); - break; - case FORMAT_RGBAH | (FORMAT_RH << 8): - _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); - break; - case FORMAT_RGBAH | (FORMAT_RGH << 8): - _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); - break; - case FORMAT_RGBAH | (FORMAT_RGBH << 8): - _convert_fast(mip_width, mip_height, (const uint16_t *)rptr, (uint16_t *)wptr); - break; - case FORMAT_RF | (FORMAT_RGF << 8): - _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); - break; - case FORMAT_RF | (FORMAT_RGBF << 8): - _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); - break; - case FORMAT_RF | (FORMAT_RGBAF << 8): - _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); - break; - case FORMAT_RGF | (FORMAT_RF << 8): - _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); - break; - case FORMAT_RGF | (FORMAT_RGBF << 8): - _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); - break; - case FORMAT_RGF | (FORMAT_RGBAF << 8): - _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); - break; - case FORMAT_RGBF | (FORMAT_RF << 8): - _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); - break; - case FORMAT_RGBF | (FORMAT_RGF << 8): - _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); - break; - case FORMAT_RGBF | (FORMAT_RGBAF << 8): - _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); - break; - case FORMAT_RGBAF | (FORMAT_RF << 8): - _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); - break; - case FORMAT_RGBAF | (FORMAT_RGF << 8): - _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); - break; - case FORMAT_RGBAF | (FORMAT_RGBF << 8): - _convert_fast(mip_width, mip_height, (const uint32_t *)rptr, (uint32_t *)wptr); - break; + IO::Reader::make( + &reader, + { + .data = (void*)rptr, + .length = (size_t)mip_size, + } + ); + err = IO::Image::Reader::make( + &imReader, + reader, + format, + mip_width, + mip_height + ); + if (err != IO::Error::Okay) + { + break; } + new_img.get_mipmap_offset_and_size(mip, mip_offset, mip_size); + err = IO::Writer::make( + &writer, + { + .data = (void*)wptr, + .length = (size_t)(mip_size), + } + ); + if (err != IO::Error::Okay) + { + break; + } + IO::Image::Writer::make( + &imWriter, + writer, + new_img.format, + mip_width, + mip_height + ); + do + { + err = IO::Image::Reader::read(imReader, &block); + if (err == IO::Error::Okay) + { + err = IO::Image::Writer::write(imWriter, &block); + } + } while (err == IO::Error::Okay); + err = IO::Image::Writer::flush(imWriter); + IO::Image::Reader::destroy(&imReader); + IO::Image::Writer::destroy(&imWriter); + IO::Reader::destroy(&reader); + IO::Writer::destroy(&writer); + if (err != IO::Error::Okay) { return; } } _copy_internals_from(new_img); + return; } Image::Format Image::get_format() const { diff --git a/core/io/image.h b/core/io/image.h index 0b9ab6b250..a3bc0621e9 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -70,6 +70,19 @@ typedef Error (*SaveDDSFunc)(const String &p_path, const Ref &p_img); typedef Vector (*SaveDDSBufferFunc)(const Ref &p_img); /// @} +// by 16 for avx512 eventually (TM) +union alignas(64) ColorRGBAF32x16 +{ + struct + { + float r[16]; + float g[16]; + float b[16]; + float a[16]; + }; + float c[4][16]; +}; + class Image : public Resource { GDCLASS(Image, Resource); diff --git a/core/io/image_rw.cpp b/core/io/image_rw.cpp new file mode 100644 index 0000000000..e80ecc09b7 --- /dev/null +++ b/core/io/image_rw.cpp @@ -0,0 +1,1109 @@ +#include "image_rw.h" +#include "core/io/image.h" + +using namespace IO::Image; + +struct UncompressedImageState +{ + uint32_t currentBlock; + uint32_t res[2]; + Slice data; + ::Image::Format format; + ColorRGBAF32x16 blocks4x4[]; +}; + +constexpr size_t CONSTANT_FACTORS[::Image::FORMAT_MAX] = { + 1, // FORMAT_L8 + 2, // FORMAT_LA8 + 1, // FORMAT_R8 + 2, // FORMAT_RG8 + 3, // FORMAT_RGB8 + 4, // FORMAT_RGBA8 + 2, // FORMAT_RGBA4444 + 2, // FORMAT_RGB565 + 4, // FORMAT_RF + 8, // FORMAT_RGF + 12, // FORMAT_RGBF + 16, // FORMAT_RGBAF + 2, // FORMAT_RH + 4, // FORMAT_RGH + 6, // FORMAT_RGBH + 8, // FORMAT_RGBAH + 4, // FORMAT_RGBE9995 + 1, // FORMAT_DXT1 + 1, // FORMAT_DXT3 + 1, // FORMAT_DXT5 + 1, // FORMAT_RGTC_R + 1, // FORMAT_RGTC_RG + 1, // FORMAT_BPTC_RGBA + 1, // FORMAT_BPTC_RGBF + 1, // FORMAT_BPTC_RGBFU + 1, // FORMAT_ETC + 1, // FORMAT_ETC2_R11 + 1, // FORMAT_ETC2_R11S + 1, // FORMAT_ETC2_RG11 + 1, // FORMAT_ETC2_RG11S + 1, // FORMAT_ETC2_RGB8 + 1, // FORMAT_ETC2_RGBA8 + 1, // FORMAT_ETC2_RGB8A1 + 1, // FORMAT_ETC2_RA_AS_RG + 1, // FORMAT_DXT5_RA_AS_RG + 1, // FORMAT_ASTC_4x4 + 1, // FORMAT_ASTC_4x4_HDR + 1, // FORMAT_ASTC_8x8 + 1, // FORMAT_ASTC_8x8_HDR +}; + +static IO::Error constantFactorStateCtor( + UncompressedImageState **state, + uint32_t width, + uint32_t height, + ::Image::Format format +) noexcept +{ + IO::Error ret = IO::Error::Okay; + // ensure we have enough 4x4 blocks to cover the width of the image + *state = (UncompressedImageState*)malloc( + sizeof(UncompressedImageState) + + ((((width << 2) * CONSTANT_FACTORS[format]) + ((size_t)0xFF)) & ~((size_t)0xFF)) + ); + if (*state) + { + (*state)->data.data = (*state)->blocks4x4; + (*state)->data.length = (width << 2) * CONSTANT_FACTORS[format]; + (*state)->res[0] = width; + (*state)->res[1] = height; + (*state)->format = format; + } + else + { + ret = IO::Error::OutOfMemory; + } + return ret; +} + +static IO::Error l8ReadScalar( + IO::Reader reader, + UncompressedImageState *state, + ColorRGBAF32x16 *colors +) noexcept +{ + IO::Error ret = IO::Error::Okay; + Slice data = state->data; + size_t read; + uint32_t width = state->res[0]; + size_t block = state->currentBlock; + if (state->currentBlock >= ((width + 3) >> 2)) + { + if (!data.data) + { + return IO::Error::OutOfMemory; + } + ret = reader.read(reader, &read, data); + if (ret == IO::Error::Okay) + { + state->currentBlock = 0; + } + } + if (ret == IO::Error::Okay) + { + for (size_t c = 0; c < 3; c += 1) + { + for (size_t i = 0; i < 4; i += 1) + { + for (size_t j = 0; j < 4; j += 1) + { + if ((((block << 2) + (width * i)) + j) < (width * (i + 1))) + { + colors->c[c][(i << 2) + j] = (*sliceAt( + data, + uint8_t, + (((block << 2) + (width * i)) + j) + )); + } + else + { + colors->c[c][(i << 2) + j] = 0; + } + colors->c[c][(i << 2) + j] /= 255; + } + } + } + for (size_t i = 0; i < 4; i += 1) + { + colors->a[(i << 2) + 0] = 1; + colors->a[(i << 2) + 1] = 1; + colors->a[(i << 2) + 2] = 1; + colors->a[(i << 2) + 3] = 1; + } + state->currentBlock += 1; + } + return ret; +} + +static IO::Error l8WriteScalar( + IO::Writer writer, + UncompressedImageState *state, + ColorRGBAF32x16 *colors +) noexcept +{ + IO::Error ret = IO::Error::Okay; + Slice data = state->data; + uint32_t width = state->res[0]; + size_t block = state->currentBlock; + for (size_t i = 0; i < 4; i += 1) + { + for (size_t j = 0; j < 4; j += 1) + { + if ((((block << 2) + (width * i)) + j) < (width * (i + 1))) + { + (*sliceAt( + data, + uint8_t, + (((block << 2) + (width * i)) + j) + )) = (13938U * ((uint64_t)colors->r[(i << 2) + j]) + + 46869U * ((uint64_t)colors->g[(i << 2) + j]) + + 4729U * ((uint64_t)colors->b[(i << 2) + j]) + + 32768U + ) >> 16U; + } + } + } + state->currentBlock += 1; + if (state->currentBlock >= ((width + 3) >> 2)) + { + ret = writer.write(writer, nullptr, state->data); + state->currentBlock = 0; + } + return ret; +} + +static IO::Error la8ReadScalar( + IO::Reader reader, + UncompressedImageState *state, + ColorRGBAF32x16 *colors +) noexcept +{ + IO::Error ret = IO::Error::Okay; + Slice data = state->data; + size_t read; + uint32_t width = state->res[0]; + size_t block = state->currentBlock; + if (state->currentBlock >= ((width + 3) >> 2)) + { + if (!data.data) + { + return IO::Error::OutOfMemory; + } + ret = reader.read(reader, &read, data); + if (ret == IO::Error::Okay) + { + state->currentBlock = 0; + } + } + if (ret == IO::Error::Okay) + { + for (size_t c = 0; c < 3; c += 1) + { + for (size_t i = 0; i < 4; i += 1) + { + for (size_t j = 0; j < 4; j += 1) + { + if ((((block << 2) + (width * i)) + j) < (width * (i + 1))) + { + colors->c[c][(i << 2) + j] = (*sliceAt( + data, + uint8_t, + (((block << 2) + (width * i)) + (j << 1)) + )); + } + else + { + colors->c[c][(i << 2) + j] = 0; + } + colors->c[c][(i << 2) + j] /= 255; + } + } + } + for (size_t i = 0; i < 4; i += 1) + { + for (size_t j = 0; j < 4; j += 1) + { + if ((((block << 2) + (width * i)) + j) < (width * (i + 1))) + { + colors->a[(i << 2) + j] = (*sliceAt( + data, + uint8_t, + (((block << 2) + (width * i)) + ((j << 1) + 1)) + )); + } + else + { + colors->a[(i << 2) + j] = 0; + } + colors->a[(i << 2) + j] /= 255; + } + } + state->currentBlock += 1; + } + return ret; +} + +static IO::Error la8WriteScalar( + IO::Writer writer, + UncompressedImageState *state, + ColorRGBAF32x16 *colors +) noexcept +{ + IO::Error ret = IO::Error::Okay; + Slice data = state->data; + uint32_t width = state->res[0]; + size_t block = state->currentBlock; + for (size_t i = 0; i < 4; i += 1) + { + for (size_t j = 0; j < 4; j += 1) + { + if ((((block << 2) + (width * i)) + j) < (width * (i + 1))) + { + (*sliceAt( + data, + uint8_t, + (((block << 2) + (width * i)) + (j << 1)) + )) = (13938U * ((uint64_t)colors->r[(i << 2) + j]) + + 46869U * ((uint64_t)colors->g[(i << 2) + j]) + + 4729U * ((uint64_t)colors->b[(i << 2) + j]) + + 32768U + ) >> 16U; + (*sliceAt( + data, + uint8_t, + (((block << 2) + (width * i)) + (j << 1) + 1) + )) = (uint8_t)(colors->a[(i << 2) + j] * 255); + } + } + } + state->currentBlock += 1; + if (state->currentBlock >= ((width + 3) >> 2)) + { + ret = writer.write(writer, nullptr, state->data); + state->currentBlock = 0; + } + return ret; +} + +constexpr size_t COMPONENT_COUNT[::Image::FORMAT_MAX] = { + 1, // FORMAT_L8 + 2, // FORMAT_LA8 + 1, // FORMAT_R8 + 2, // FORMAT_RG8 + 3, // FORMAT_RGB8 + 4, // FORMAT_RGBA8 + 4, // FORMAT_RGBA4444 + 3, // FORMAT_RGB565 + 1, // FORMAT_RF + 2, // FORMAT_RGF + 3, // FORMAT_RGBF + 4, // FORMAT_RGBAF + 1, // FORMAT_RH + 2, // FORMAT_RGH + 3, // FORMAT_RGBH + 4, // FORMAT_RGBAH + 4, // FORMAT_RGBE9995 + 1, // FORMAT_DXT1 + 1, // FORMAT_DXT3 + 1, // FORMAT_DXT5 + 1, // FORMAT_RGTC_R + 1, // FORMAT_RGTC_RG + 1, // FORMAT_BPTC_RGBA + 1, // FORMAT_BPTC_RGBF + 1, // FORMAT_BPTC_RGBFU + 1, // FORMAT_ETC + 1, // FORMAT_ETC2_R11 + 1, // FORMAT_ETC2_R11S + 1, // FORMAT_ETC2_RG11 + 1, // FORMAT_ETC2_RG11S + 1, // FORMAT_ETC2_RGB8 + 1, // FORMAT_ETC2_RGBA8 + 1, // FORMAT_ETC2_RGB8A1 + 1, // FORMAT_ETC2_RA_AS_RG + 1, // FORMAT_DXT5_RA_AS_RG + 1, // FORMAT_ASTC_4x4 + 1, // FORMAT_ASTC_4x4_HDR + 1, // FORMAT_ASTC_8x8 + 1, // FORMAT_ASTC_8x8_HDR +}; + +static IO::Error rgba8ReadScalar( + IO::Reader reader, + UncompressedImageState *state, + ColorRGBAF32x16 *colors +) noexcept +{ + IO::Error ret = IO::Error::Okay; + Slice data = state->data; + size_t read; + size_t block = state->currentBlock; + ::Image::Format format = state->format; + uint32_t width = state->res[0]; + if (state->currentBlock >= ((width + 3) >> 2)) + { + if (!data.data) + { + return IO::Error::OutOfMemory; + } + ret = reader.read(reader, &read, data); + if (ret == IO::Error::Okay) + { + state->currentBlock = 0; + } + } + if (ret == IO::Error::Okay) + { + for (size_t c = 0; c < COMPONENT_COUNT[format]; c += 1) + { + for (size_t i = 0; i < 4; i += 1) + { + for (size_t j = 0; j < 4; j += 1) + { + if ((((block << 2) + (width * i)) + j) < (width * (i + 1))) + { + colors->c[c][(i << 2) + j] = (*sliceAt( + data, + uint8_t, + (((block << 2) + (width * i)) + + (j * COMPONENT_COUNT[format]) + c) + )); + } + else + { + colors->c[c][(i << 2) + j] = 0; + } + colors->c[c][(i << 2) + j] /= 255; + } + } + } + state->currentBlock += 1; + } + return ret; +} + +static IO::Error rgba8WriteScalar( + IO::Writer writer, + UncompressedImageState *state, + ColorRGBAF32x16 *colors +) noexcept +{ + IO::Error ret = IO::Error::Okay; + Slice data = state->data; + size_t block = state->currentBlock; + ::Image::Format format = state->format; + uint32_t width = state->res[0]; + for (size_t c = 0; c < COMPONENT_COUNT[format]; c += 1) + { + for (size_t i = 0; i < 4; i += 1) + { + for (size_t j = 0; j < 4; j += 1) + { + if ((((block << 2) + (width * i)) + j) < (width * (i + 1))) + { + (*sliceAt( + data, + uint8_t, + (((block << 2) + (width * i)) + (j * COMPONENT_COUNT[format]) + c) + )) = colors->c[c][(i << 2) + j] * 255; + } + } + } + } + state->currentBlock += 1; + if (state->currentBlock >= ((width + 3) >> 2)) + { + ret = writer.write(writer, nullptr, state->data); + state->currentBlock = 0; + } + return ret; +} + +template +static IO::Error rgbafReadScalar( + IO::Reader reader, + UncompressedImageState *state, + ColorRGBAF32x16 *colors +) noexcept +{ + IO::Error ret = IO::Error::Okay; + Slice data = state->data; + size_t read; + size_t block = state->currentBlock; + ::Image::Format format = state->format; + uint32_t width = state->res[0]; + if (state->currentBlock >= ((width + 3) >> 2)) + { + if (!data.data) + { + return IO::Error::OutOfMemory; + } + ret = reader.read(reader, &read, data); + if (ret == IO::Error::Okay) + { + state->currentBlock = 0; + } + } + if (ret == IO::Error::Okay) + { + for (size_t c = 0; c < COMPONENT_COUNT[format]; c += 1) + { + for (size_t i = 0; i < 4; i += 1) + { + for (size_t j = 0; j < 4; j += 1) + { + if ((((block << 2) + (width * i)) + j) < (width * (i + 1))) + { + colors->c[c][(i << 2) + j] = (*sliceAt( + data, + T, + (((block << 2) + (width * i)) + (j * COMPONENT_COUNT[format]) + c) + )); + } + else + { + colors->c[c][(i << 2) + j] = 0; + } + } + } + } + state->currentBlock += 1; + } + return ret; +} + +template +static IO::Error rgbafWriteScalar( + IO::Writer writer, + UncompressedImageState *state, + ColorRGBAF32x16 *colors +) noexcept +{ + IO::Error ret = IO::Error::Okay; + Slice data = state->data; + size_t block = state->currentBlock; + ::Image::Format format = state->format; + uint32_t width = state->res[0]; + for (size_t c = 0; c < COMPONENT_COUNT[format]; c += 1) + { + for (size_t i = 0; i < 4; i += 1) + { + for (size_t j = 0; j < 4; j += 1) + { + if ((((block << 2) + (width * i)) + j) < (width * (i + 1))) + { + (*sliceAt( + data, + T, + (((block << 2) + (width * i)) + (j * COMPONENT_COUNT[format]) + c) + )) = colors->c[c][(i << 2) + j]; + } + } + } + } + state->currentBlock += 1; + if (state->currentBlock >= ((width + 3) >> 2)) + { + ret = writer.write(writer, nullptr, state->data); + state->currentBlock = 0; + } + return ret; +} + +static IO::Error rgba4444ReadScalar( + IO::Reader reader, + UncompressedImageState *state, + ColorRGBAF32x16 *colors +) noexcept +{ + IO::Error ret = IO::Error::Okay; + Slice data = state->data; + size_t read; + size_t block = state->currentBlock; + ::Image::Format format = state->format; + uint32_t width = state->res[0]; + if (state->currentBlock >= ((width + 3) >> 2)) + { + if (!data.data) + { + return IO::Error::OutOfMemory; + } + ret = reader.read(reader, &read, data); + if (ret == IO::Error::Okay) + { + state->currentBlock = 0; + } + } + if (ret == IO::Error::Okay) + { + for (size_t c = 0; c < COMPONENT_COUNT[format]; c += 1) + { + for (size_t i = 0; i < 4; i += 1) + { + for (size_t j = 0; j < 4; j += 1) + { + if ((((block << 2) + (width * i)) + j) < (width * (i + 1))) + { + colors->c[c][(i << 2) + j] = ((*sliceAt( + data, + uint8_t, + (((block << 2) + (width * i)) + + (((j * (COMPONENT_COUNT[format])) + c) >> 1)) + )) >> ((c & 1) << 2)) & 0x0f; + } + else + { + colors->c[c][(i << 2) + j] = 0; + } + colors->c[c][(i << 2) + j] /= 15; + } + } + } + state->currentBlock += 1; + } + return ret; +} + +static IO::Error defaultFlush( + IO::Writer writer, + UncompressedImageState *state +) noexcept +{ + IO::Error err = IO::Error::Okay; + size_t cursor; + Slice tmp = {}; + err = IO::Writer::seek(writer, 0, &cursor, IO::WHENCE_CURRENT); + if (state->currentBlock == 0) + { + return err; + } + for (size_t i = 0; i < 4; i += 1) + { + err = IO::Writer::seek( + writer, + cursor + (i * (state->res[0])), + nullptr, + IO::WHENCE_SET + ); + if (err != IO::Error::Okay) + { + return err; + } + Slice::subslice( + &tmp, + state->data, + i * (state->res[0]), + (state->currentBlock * CONSTANT_FACTORS[state->format]) << 2 + ); + err = IO::Writer::write(writer, nullptr, tmp); + if (err != IO::Error::Okay) + { + return err; + } + } + return err; +} + +static Reader::VTbl READER_SCALAR_FUNCTIONS[::Image::FORMAT_MAX] = { + { .read = (ReadProc)l8ReadScalar, .destroy = free }, // FORMAT_L8 + { .read = (ReadProc)la8ReadScalar, .destroy = free }, // FORMAT_LA8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_R8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_RG8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_RGB8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_RGBA8 + { .read = (ReadProc)rgba4444ReadScalar, .destroy = free }, // FORMAT_RGBA4444 + { .read = nullptr, .destroy = free }, // FORMAT_RGB565 + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RF + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RGF + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RGBF + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RGBAF + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RH + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RGH + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RGBH + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RGBAH + { .read = nullptr, .destroy = free }, // FORMAT_RGBE9995 + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT1 + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT3 + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT5 + { .read = nullptr, .destroy = nullptr }, // FORMAT_RGTC_R + { .read = nullptr, .destroy = nullptr }, // FORMAT_RGTC_RG + { .read = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBA + { .read = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBF + { .read = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBFU + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11S + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11S + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGBA8 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8A1 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RA_AS_RG + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT5_RA_AS_RG + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4_HDR + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8_HDR +}; + +static Writer::VTbl WRITER_SCALAR_FUNCTIONS[::Image::FORMAT_MAX] = { + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)l8WriteScalar, .destroy = free }, // FORMAT_L8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)la8WriteScalar, .destroy = free }, // FORMAT_LA8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_R8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_RG8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_RGB8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_RGBA8 + { .flush = (FlushProc)defaultFlush, .write = nullptr, .destroy = free }, // FORMAT_RGBA4444 + { .flush = (FlushProc)defaultFlush, .write = nullptr, .destroy = free }, // FORMAT_RGB565 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RGF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RGBF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RGBAF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RH + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RGH + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RGBH + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RGBAH + { .flush = (FlushProc)defaultFlush, .write = nullptr, .destroy = free }, // FORMAT_RGBE9995 + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT1 + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT3 + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT5 + { .write = nullptr, .destroy = nullptr }, // FORMAT_RGTC_R + { .write = nullptr, .destroy = nullptr }, // FORMAT_RGTC_RG + { .write = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBA + { .write = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBF + { .write = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBFU + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11S + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11S + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGBA8 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8A1 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RA_AS_RG + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT5_RA_AS_RG + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4_HDR + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8_HDR +}; + +static Reader::VTbl READER_SSE42_FUNCTIONS[::Image::FORMAT_MAX] = { + { .read = (ReadProc)l8ReadScalar, .destroy = free }, // FORMAT_L8 + { .read = (ReadProc)la8ReadScalar, .destroy = free }, // FORMAT_LA8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_R8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_RG8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_RGB8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_RGBA8 + { .read = (ReadProc)rgba4444ReadScalar, .destroy = free }, // FORMAT_RGBA4444 + { .read = nullptr, .destroy = free }, // FORMAT_RGB565 + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RF + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RGF + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RGBF + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RGBAF + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RH + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RGH + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RGBH + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RGBAH + { .read = nullptr, .destroy = free }, // FORMAT_RGBE9995 + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT1 + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT3 + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT5 + { .read = nullptr, .destroy = nullptr }, // FORMAT_RGTC_R + { .read = nullptr, .destroy = nullptr }, // FORMAT_RGTC_RG + { .read = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBA + { .read = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBF + { .read = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBFU + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11S + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11S + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGBA8 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8A1 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RA_AS_RG + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT5_RA_AS_RG + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4_HDR + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8_HDR +}; + +static Writer::VTbl WRITER_SSE42_FUNCTIONS[::Image::FORMAT_MAX] = { + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)l8WriteScalar, .destroy = free }, // FORMAT_L8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)la8WriteScalar, .destroy = free }, // FORMAT_LA8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_R8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_RG8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_RGB8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_RGBA8 + { .flush = (FlushProc)defaultFlush, .write = nullptr, .destroy = free }, // FORMAT_RGBA4444 + { .flush = (FlushProc)defaultFlush, .write = nullptr, .destroy = free }, // FORMAT_RGB565 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RGF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RGBF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RGBAF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RH + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RGH + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RGBH + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RGBAH + { .flush = (FlushProc)defaultFlush, .write = nullptr, .destroy = free }, // FORMAT_RGBE9995 + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT1 + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT3 + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT5 + { .write = nullptr, .destroy = nullptr }, // FORMAT_RGTC_R + { .write = nullptr, .destroy = nullptr }, // FORMAT_RGTC_RG + { .write = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBA + { .write = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBF + { .write = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBFU + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11S + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11S + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGBA8 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8A1 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RA_AS_RG + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT5_RA_AS_RG + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4_HDR + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8_HDR +}; + +static Reader::VTbl READER_AVX_FUNCTIONS[::Image::FORMAT_MAX] = { + { .read = (ReadProc)l8ReadScalar, .destroy = free }, // FORMAT_L8 + { .read = (ReadProc)la8ReadScalar, .destroy = free }, // FORMAT_LA8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_R8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_RG8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_RGB8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_RGBA8 + { .read = (ReadProc)rgba4444ReadScalar, .destroy = free }, // FORMAT_RGBA4444 + { .read = nullptr, .destroy = free }, // FORMAT_RGB565 + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RF + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RGF + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RGBF + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RGBAF + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RH + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RGH + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RGBH + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RGBAH + { .read = nullptr, .destroy = free }, // FORMAT_RGBE9995 + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT1 + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT3 + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT5 + { .read = nullptr, .destroy = nullptr }, // FORMAT_RGTC_R + { .read = nullptr, .destroy = nullptr }, // FORMAT_RGTC_RG + { .read = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBA + { .read = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBF + { .read = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBFU + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11S + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11S + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGBA8 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8A1 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RA_AS_RG + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT5_RA_AS_RG + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4_HDR + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8_HDR +}; + +static Writer::VTbl WRITER_AVX_FUNCTIONS[::Image::FORMAT_MAX] = { + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)l8WriteScalar, .destroy = free }, // FORMAT_L8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)la8WriteScalar, .destroy = free }, // FORMAT_LA8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_R8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_RG8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_RGB8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_RGBA8 + { .flush = (FlushProc)defaultFlush, .write = nullptr, .destroy = free }, // FORMAT_RGBA4444 + { .flush = (FlushProc)defaultFlush, .write = nullptr, .destroy = free }, // FORMAT_RGB565 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RGF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RGBF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RGBAF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RH + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RGH + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RGBH + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RGBAH + { .flush = (FlushProc)defaultFlush, .write = nullptr, .destroy = free }, // FORMAT_RGBE9995 + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT1 + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT3 + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT5 + { .write = nullptr, .destroy = nullptr }, // FORMAT_RGTC_R + { .write = nullptr, .destroy = nullptr }, // FORMAT_RGTC_RG + { .write = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBA + { .write = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBF + { .write = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBFU + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11S + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11S + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGBA8 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8A1 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RA_AS_RG + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT5_RA_AS_RG + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4_HDR + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8_HDR +}; + +static Reader::VTbl READER_AVX2_FUNCTIONS[::Image::FORMAT_MAX] = { + { .read = (ReadProc)l8ReadScalar, .destroy = free }, // FORMAT_L8 + { .read = (ReadProc)la8ReadScalar, .destroy = free }, // FORMAT_LA8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_R8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_RG8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_RGB8 + { .read = (ReadProc)rgba8ReadScalar, .destroy = free }, // FORMAT_RGBA8 + { .read = (ReadProc)rgba4444ReadScalar, .destroy = free }, // FORMAT_RGBA4444 + { .read = nullptr, .destroy = free }, // FORMAT_RGB565 + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RF + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RGF + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RGBF + { .read = (ReadProc)rgbafReadScalar, .destroy = free }, // FORMAT_RGBAF + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RH + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RGH + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RGBH + { .read = (ReadProc)rgbafReadScalar<_Float16>, .destroy = free }, // FORMAT_RGBAH + { .read = nullptr, .destroy = free }, // FORMAT_RGBE9995 + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT1 + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT3 + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT5 + { .read = nullptr, .destroy = nullptr }, // FORMAT_RGTC_R + { .read = nullptr, .destroy = nullptr }, // FORMAT_RGTC_RG + { .read = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBA + { .read = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBF + { .read = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBFU + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11S + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11S + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGBA8 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8A1 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RA_AS_RG + { .read = nullptr, .destroy = nullptr }, // FORMAT_DXT5_RA_AS_RG + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4_HDR + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8 + { .read = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8_HDR +}; + +static Writer::VTbl WRITER_AVX2_FUNCTIONS[::Image::FORMAT_MAX] = { + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)l8WriteScalar, .destroy = free }, // FORMAT_L8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)la8WriteScalar, .destroy = free }, // FORMAT_LA8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_R8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_RG8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_RGB8 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgba8WriteScalar, .destroy = free }, // FORMAT_RGBA8 + { .flush = (FlushProc)defaultFlush, .write = nullptr, .destroy = free }, // FORMAT_RGBA4444 + { .flush = (FlushProc)defaultFlush, .write = nullptr, .destroy = free }, // FORMAT_RGB565 + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RGF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RGBF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar, .destroy = free }, // FORMAT_RGBAF + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RH + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RGH + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RGBH + { .flush = (FlushProc)defaultFlush, .write = (WriteProc)rgbafWriteScalar<_Float16>, .destroy = free }, // FORMAT_RGBAH + { .flush = (FlushProc)defaultFlush, .write = nullptr, .destroy = free }, // FORMAT_RGBE9995 + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT1 + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT3 + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT5 + { .write = nullptr, .destroy = nullptr }, // FORMAT_RGTC_R + { .write = nullptr, .destroy = nullptr }, // FORMAT_RGTC_RG + { .write = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBA + { .write = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBF + { .write = nullptr, .destroy = nullptr }, // FORMAT_BPTC_RGBFU + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_R11S + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RG11S + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGBA8 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RGB8A1 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ETC2_RA_AS_RG + { .write = nullptr, .destroy = nullptr }, // FORMAT_DXT5_RA_AS_RG + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_4x4_HDR + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8 + { .write = nullptr, .destroy = nullptr }, // FORMAT_ASTC_8x8_HDR +}; + +using ConstructorProc = IO::Error(*)( + void **state, + uint32_t width, + uint32_t height, + ::Image::Format format +); + +static ConstructorProc READER_STATE_CONSTRUCTOR[::Image::FORMAT_MAX] = { + (ConstructorProc)constantFactorStateCtor, // FORMAT_L8 + (ConstructorProc)constantFactorStateCtor, // FORMAT_LA8 + (ConstructorProc)constantFactorStateCtor, // FORMAT_R8 + (ConstructorProc)constantFactorStateCtor, // FORMAT_RG8 + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGB8 + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBA8 + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBA4444 + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGB565 + (ConstructorProc)constantFactorStateCtor, // FORMAT_RF + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGF + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBF + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBAF + (ConstructorProc)constantFactorStateCtor, // FORMAT_RH + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGH + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBH + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBAH + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBE9995 + nullptr, // FORMAT_DXT1 + nullptr, // FORMAT_DXT3 + nullptr, // FORMAT_DXT5 + nullptr, // FORMAT_RGTC_R + nullptr, // FORMAT_RGTC_RG + nullptr, // FORMAT_BPTC_RGBA + nullptr, // FORMAT_BPTC_RGBF + nullptr, // FORMAT_BPTC_RGBFU + nullptr, // FORMAT_ETC + nullptr, // FORMAT_ETC2_R11 + nullptr, // FORMAT_ETC2_R11S + nullptr, // FORMAT_ETC2_RG11 + nullptr, // FORMAT_ETC2_RG11S + nullptr, // FORMAT_ETC2_RGB8 + nullptr, // FORMAT_ETC2_RGBA8 + nullptr, // FORMAT_ETC2_RGB8A1 + nullptr, // FORMAT_ETC2_RA_AS_RG + nullptr, // FORMAT_DXT5_RA_AS_RG + nullptr, // FORMAT_ASTC_4x4 + nullptr, // FORMAT_ASTC_4x4_HDR + nullptr, // FORMAT_ASTC_8x8 + nullptr, // FORMAT_ASTC_8x8_HDR +}; + +static ConstructorProc WRITER_STATE_CONSTRUCTOR[::Image::FORMAT_MAX] = { + (ConstructorProc)constantFactorStateCtor, // FORMAT_L8 + (ConstructorProc)constantFactorStateCtor, // FORMAT_LA8 + (ConstructorProc)constantFactorStateCtor, // FORMAT_R8 + (ConstructorProc)constantFactorStateCtor, // FORMAT_RG8 + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGB8 + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBA8 + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBA4444 + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGB565 + (ConstructorProc)constantFactorStateCtor, // FORMAT_RF + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGF + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBF + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBAF + (ConstructorProc)constantFactorStateCtor, // FORMAT_RH + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGH + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBH + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBAH + (ConstructorProc)constantFactorStateCtor, // FORMAT_RGBE9995 + nullptr, // FORMAT_DXT1 + nullptr, // FORMAT_DXT3 + nullptr, // FORMAT_DXT5 + nullptr, // FORMAT_RGTC_R + nullptr, // FORMAT_RGTC_RG + nullptr, // FORMAT_BPTC_RGBA + nullptr, // FORMAT_BPTC_RGBF + nullptr, // FORMAT_BPTC_RGBFU + nullptr, // FORMAT_ETC + nullptr, // FORMAT_ETC2_R11 + nullptr, // FORMAT_ETC2_R11S + nullptr, // FORMAT_ETC2_RG11 + nullptr, // FORMAT_ETC2_RG11S + nullptr, // FORMAT_ETC2_RGB8 + nullptr, // FORMAT_ETC2_RGBA8 + nullptr, // FORMAT_ETC2_RGB8A1 + nullptr, // FORMAT_ETC2_RA_AS_RG + nullptr, // FORMAT_DXT5_RA_AS_RG + nullptr, // FORMAT_ASTC_4x4 + nullptr, // FORMAT_ASTC_4x4_HDR + nullptr, // FORMAT_ASTC_8x8 + nullptr, // FORMAT_ASTC_8x8_HDR +}; + +IO::Error Reader::make( + Reader *dst, + IO::Reader r, + ::Image::Format format, + uint32_t width, + uint32_t height +) noexcept +{ + dst->reader = r; + if (__builtin_cpu_supports("avx2")) + { + dst->vtbl = &(READER_AVX2_FUNCTIONS[format]); + } + else if (__builtin_cpu_supports("avx")) + { + dst->vtbl = &(READER_AVX_FUNCTIONS[format]); + } + else if (__builtin_cpu_supports("sse4.2")) + { + dst->vtbl = &(READER_SSE42_FUNCTIONS[format]); + } + else + { + dst->vtbl = &(READER_SCALAR_FUNCTIONS[format]); + } + if (dst->vtbl->read == nullptr) + { + return IO::Error::NotImplemented; + } + if (READER_STATE_CONSTRUCTOR[format]) + { + return READER_STATE_CONSTRUCTOR[format](&(dst->state), width, height, format); + } + return IO::Error::Okay; +} + +IO::Error Writer::make( + Writer *dst, + IO::Writer w, + ::Image::Format format, + uint32_t width, + uint32_t height +) noexcept +{ + dst->writer = w; + if (__builtin_cpu_supports("avx2")) + { + dst->vtbl = &(WRITER_AVX2_FUNCTIONS[format]); + } + else if (__builtin_cpu_supports("avx")) + { + dst->vtbl = &(WRITER_AVX_FUNCTIONS[format]); + } + else if (__builtin_cpu_supports("sse4.2")) + { + dst->vtbl = &(WRITER_SSE42_FUNCTIONS[format]); + } + else + { + dst->vtbl = &(WRITER_SCALAR_FUNCTIONS[format]); + } + if (dst->vtbl->write == nullptr) + { + return IO::Error::NotImplemented; + } + if (WRITER_STATE_CONSTRUCTOR[format]) + { + return WRITER_STATE_CONSTRUCTOR[format](&(dst->state), width, height, format); + } + return IO::Error::Okay; +} diff --git a/core/io/image_rw.h b/core/io/image_rw.h new file mode 100644 index 0000000000..d01582ee67 --- /dev/null +++ b/core/io/image_rw.h @@ -0,0 +1,128 @@ +#pragma once + +#include "core/io/image.h" +#include "io.h" + +/* +# Image Readers and Writers + +Image readers and writers operate on 4x4 blocks of pixels due to it being a +convenient size for SIMD and the compressed formats supported (namely 4x4 +block compression and 4x4 or 8x8 adaptive scalable texture compression) +*/ + +namespace IO::Image +{ + using ReadProc = Error (*)( + IO::Reader reader, + void *state, + ColorRGBAF32x16 *colors + ) noexcept; + using WriteProc = Error (*)( + IO::Writer writer, + void *state, + ColorRGBAF32x16 *colors + ) noexcept; + using FlushProc = Error (*)(IO::Writer writer, void *state) noexcept; + struct Reader + { + struct VTbl + { + ReadProc read; + DestroyProc destroy; + }; + IO::Reader reader; + void *state; + VTbl *vtbl; + + static IO::Error make( + Reader *dst, + IO::Reader r, + ::Image::Format format, + uint32_t width, + uint32_t height + ) noexcept; + + constexpr static inline IO::Error read( + Reader reader, + ColorRGBAF32x16 *colors + ) noexcept + { + return reader.vtbl->read(reader.reader, reader.state, colors); + } + + constexpr static inline IO::Error close(Reader reader) noexcept + { + return IO::Reader::close(reader.reader); + } + + constexpr static inline IO::Error size( + Reader reader, + size_t *dst + ) noexcept + { + return IO::Reader::size(reader.reader, dst); + } + + static inline void destroy(Reader *reader) noexcept + { + if (reader->vtbl->destroy) + { + reader->vtbl->destroy(reader->state); + } + *reader = {}; + } + }; + struct Writer + { + struct VTbl + { + FlushProc flush; + WriteProc write; + DestroyProc destroy; + }; + IO::Writer writer; + void *state; + const VTbl *vtbl; + + static IO::Error make( + Writer *dst, + IO::Writer w, + ::Image::Format format, + uint32_t width, + uint32_t height + ) noexcept; + + constexpr static inline IO::Error write( + Writer writer, + ColorRGBAF32x16 *colors + ) noexcept + { + return writer.vtbl->write(writer.writer, writer.state, colors); + } + + constexpr static inline IO::Error close(Writer writer) noexcept + { + return IO::Writer::close(writer.writer); + } + + constexpr static inline IO::Error flush(Writer writer) noexcept + { + IO::Error err = writer.vtbl->flush(writer.writer, writer.state); + if (err == IO::Error::Okay) + { + err = IO::Writer::flush(writer.writer); + } + return err; + } + + static inline void destroy(Writer *writer) noexcept + { + if (writer->vtbl->destroy) + { + writer->vtbl->destroy(writer->state); + } + *writer = {}; + } + }; +} diff --git a/core/io/io.cpp b/core/io/io.cpp new file mode 100644 index 0000000000..454075fb7c --- /dev/null +++ b/core/io/io.cpp @@ -0,0 +1,184 @@ +#include "io.h" + +using namespace IO; + +struct SliceState +{ + Slice buffer; + size_t offset; + bool closed; +}; + +IO::Error sliceRead(SliceState *state, size_t *read, Slice buffer) noexcept +{ + IO::Error ret = IO::Error::Okay; + Slice tmp = Slice::nil; + size_t count = buffer.length; + if (state->closed) + { + ret = IO::Error::Closed; + } + else + { + if ((state->buffer.length - state->offset) < buffer.length) + { + count = (state->buffer.length - state->offset); + ret = IO::Error::Eof; + } + // the following will not error, and therefore, safe to ignore + Slice::subslice(&tmp, state->buffer, state->offset, count); + Slice::copy(buffer, tmp); + state->offset += count; + if (read) + { + *read = count; + } + } + return ret; +} + +IO::Error sliceWrite(SliceState *state, size_t *written, Slice buffer) noexcept +{ + IO::Error ret = IO::Error::Okay; + Slice tmp = Slice::nil; + size_t count = buffer.length; + if (state->closed) + { + return IO::Error::Closed; + } + + + if ((state->buffer.length - state->offset) < buffer.length) + { + count = (state->buffer.length - state->offset); + ret = IO::Error::Eof; + } + // the following will not error, and therefore, safe to ignore + Slice::subslice(&tmp, state->buffer, state->offset, count); + Slice::copy(tmp, buffer); + state->offset += count; + if (written) + { + *written = count; + } + + return ret; +} + +IO::Error sliceSeek( + SliceState *state, + ssize_t offset, + size_t *where, + Whence whence +) noexcept +{ + size_t newOffsets[WHENCE_END + 1] = { + 0, + state->offset, + state->buffer.length, + }; + if (state->closed) + { + return IO::Error::Closed; + } + if (whence > WHENCE_END) + { + return IO::Error::InvalidWhence; + } + if (((size_t)(newOffsets[whence] + offset)) >= state->buffer.length) + { + return IO::Error::InvalidOffset; + } + state->offset = ((size_t)(newOffsets[whence] + offset)); + if (where) + { + *where = ((size_t)(newOffsets[whence] + offset)); + } + return IO::Error::Okay; +} + +IO::Error sliceClose(SliceState *state) noexcept +{ + IO::Error ret = state->closed ? IO::Error::Closed : IO::Error::Okay; + state->closed = true; + return ret; +} + +IO::Error sliceSize(SliceState *state, size_t *dst) noexcept +{ + IO::Error ret = IO::Error::Okay; + if (state->closed) + { + return IO::Error::Closed; + } + if (dst) + { + *dst = state->buffer.length; + } + return ret; +} + +IO::Error sliceFlush(SliceState *state) noexcept +{ + IO::Error ret = state->closed ? IO::Error::Closed : IO::Error::Okay; + return ret; +} + +void sliceDestroy(SliceState *state) noexcept +{ + free(state); +} + +static Reader::VTbl SLICE_READER_VTABLE = { + .read = (ReadProc)sliceRead, + .seek = (SeekProc)sliceSeek, + .close = (CloseProc)sliceClose, + .size = (SizeProc)sliceSize, + .destroy = (DestroyProc)sliceDestroy, +}; + +static Writer::VTbl SLICE_WRITER_VTABLE = { + .write = (WriteProc)sliceWrite, + .seek = (SeekProc)sliceSeek, + .close = (CloseProc)sliceClose, + .flush = (FlushProc)sliceFlush, + .destroy = (DestroyProc)sliceDestroy, +}; + +IO::Error Reader::make(Reader* dst, Slice slice) noexcept +{ + SliceState *state = (SliceState*)malloc(sizeof(SliceState)); + IO::Error ret = IO::Error::Okay; + assert(dst); + if (!state) + { + ret = IO::Error::OutOfMemory; + } + else + { + state->buffer = slice; + state->offset = 0; + dst->vtbl = &SLICE_READER_VTABLE; + dst->state = (void*)state; + } + return ret; +} + +IO::Error Writer::make(Writer* dst, Slice slice) noexcept +{ + SliceState *state = (SliceState*)malloc(sizeof(SliceState)); + IO::Error ret = IO::Error::Okay; + assert(dst); + if (!state) + { + ret = IO::Error::OutOfMemory; + } + else + { + state->buffer = slice; + state->offset = 0; + dst->vtbl = &SLICE_WRITER_VTABLE; + dst->state = (void*)state; + } + return ret; +} diff --git a/core/io/io.h b/core/io/io.h new file mode 100644 index 0000000000..760e8cabfd --- /dev/null +++ b/core/io/io.h @@ -0,0 +1,156 @@ +#pragma once + +#include "core/io/file_access.h" +#include "core/templates/Slice.h" +#include + +namespace IO +{ + enum class Error + { + Okay, + OutOfMemory, + Eof, + InvalidWhence, + InvalidOffset, + NotImplemented, + Closed, + }; + + enum Whence : uint32_t + { + WHENCE_SET, + WHENCE_CURRENT, + WHENCE_END, + }; + + using ReadProc = Error (*)( + void *state, + size_t *read, + Slice buffer + ) noexcept; + using WriteProc = Error (*)( + void *state, + size_t *written, + Slice buffer + ) noexcept; + using SeekProc = Error (*)( + void *state, + ssize_t offset, + size_t *where, + Whence whence + ) noexcept; + using CloseProc = Error (*)(void *state) noexcept; + using SizeProc = Error (*)(void *state, size_t *dst) noexcept; + using FlushProc = Error (*)(void *state) noexcept; + // because C++ and this codebase's heavy use of RAII + using DestroyProc = void (*)(void *state) noexcept; + + struct Reader + { + struct VTbl + { + ReadProc read; + SeekProc seek; + CloseProc close; + SizeProc size; + DestroyProc destroy; + }; + void *state; + const VTbl *vtbl; + + static Error make(Reader* dst, Slice slice) noexcept; + // static Error make(Reader* dst, Ref file) noexcept; + static void destroy(Reader* reader) noexcept + { + if (reader->vtbl->destroy) + { + reader->vtbl->destroy(reader->state); + } + *reader = {}; + } + + constexpr static inline Error read( + Reader reader, + size_t *read, + Slice buffer + ) noexcept + { + return reader.vtbl->read(reader.state, read, buffer); + } + + constexpr static inline Error seek( + Reader reader, + ssize_t offset, + size_t *where, + Whence whence + ) noexcept + { + return reader.vtbl->seek(reader.state, offset, where, whence); + } + + constexpr static inline Error close(Reader reader) noexcept + { + return reader.vtbl->close(reader.state); + } + + constexpr static inline Error size(Reader reader, size_t *dst) noexcept + { + return reader.vtbl->size(reader.state, dst); + } + }; + + struct Writer + { + struct VTbl + { + WriteProc write; + SeekProc seek; + CloseProc close; + FlushProc flush; + DestroyProc destroy; + }; + void *state; + const VTbl *vtbl; + + static Error make(Writer* dst, Slice slice) noexcept; + // static Error make(Writer* dst, Ref file) noexcept; + static void destroy(Writer* writer) noexcept + { + if (writer->vtbl->destroy) + { + writer->vtbl->destroy(writer->state); + } + *writer = {}; + } + + constexpr static inline Error write( + Writer writer, + size_t *written, + Slice buffer + ) noexcept + { + return writer.vtbl->write(writer.state, written, buffer); + } + + constexpr static inline Error seek( + Writer writer, + ssize_t offset, + size_t *where, + Whence whence + ) noexcept + { + return writer.vtbl->seek(writer.state, offset, where, whence); + } + + constexpr static inline Error close(Writer writer) noexcept + { + return writer.vtbl->close(writer.state); + } + + constexpr static inline Error flush(Writer writer) noexcept + { + return writer.vtbl->flush(writer.state); + } + }; +} diff --git a/core/templates/Slice.cpp b/core/templates/Slice.cpp new file mode 100644 index 0000000000..53e622d29d --- /dev/null +++ b/core/templates/Slice.cpp @@ -0,0 +1,217 @@ +#include "core/templates/Slice.h" +#include "core/typedefs.h" + +Slice Slice::nil = {}; + +bool Slice::copy(Slice dst, Slice src) noexcept +{ + uintptr_t s = (uintptr_t)src.data; + uintptr_t d = (uintptr_t)dst.data; + size_t n = MIN(src.length, dst.length); + size_t i; + bool srcTailDoesntOverlapDstHead = s <= (d - n); + const uint8_t *u8src = (const uint8_t*)src.data; + uint8_t *u8dst = (uint8_t*)dst.data; + if (src.length > dst.length) { return false; } + if (srcTailDoesntOverlapDstHead) + { + i = dst.length; + while (i > 0) + { + switch (i) + { + case 4: + i -= 4; + *((uint32_t*)&(u8dst[i])) = *((const uint32_t*)&(u8src[i])); + break; + case 7: + case 3: + i -= 1; + u8dst[i] = u8src[i]; + [[fallthrough]]; + case 6: + case 2: + i -= 2; + *((uint16_t*)&(u8dst[i])) = *((const uint16_t*)&(u8src[i])); + break; + case 5: + case 1: + i -= 1; + u8dst[i] = u8src[i]; + break; + default: + i -= 8; + *((uint64_t*)&(u8dst[i])) = *((const uint64_t*)&(u8src[i])); + [[fallthrough]]; + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + i -= 8; + *((uint64_t*)&(u8dst[i])) = *((const uint64_t*)&(u8src[i])); + [[fallthrough]]; + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + i -= 8; + *((uint64_t*)&(u8dst[i])) = *((const uint64_t*)&(u8src[i])); + [[fallthrough]]; + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + i -= 8; + *((uint64_t*)&(u8dst[i])) = *((const uint64_t*)&(u8src[i])); + break; + } + } + } + else + { + for (i = 0; i < dst.length;) + { + switch (dst.length - i) + { + case 4: + *((uint32_t*)&(u8dst[i])) = *((const uint32_t*)&(u8src[i])); + i += 4; + break; + case 7: + case 3: + u8dst[i] = u8src[i]; + i += 1; + [[fallthrough]]; + case 6: + case 2: + *((uint16_t*)&(u8dst[i])) = *((const uint16_t*)&(u8src[i])); + i += 2; + break; + case 5: + case 1: + u8dst[i] = u8src[i]; + i += 1; + break; + default: + *((uint64_t*)&(u8dst[i])) = *((const uint64_t*)&(u8src[i])); + i += 8; + [[fallthrough]]; + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + *((uint64_t*)&(u8dst[i])) = *((const uint64_t*)&(u8src[i])); + i += 8; + [[fallthrough]]; + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + *((uint64_t*)&(u8dst[i])) = *((const uint64_t*)&(u8src[i])); + i += 8; + [[fallthrough]]; + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + *((uint64_t*)&(u8dst[i])) = *((const uint64_t*)&(u8src[i])); + i += 8; + break; + } + } + } + return true; +} + +void Slice::set(Slice dst, uint8_t n) noexcept +{ + uint16_t n16 = 0x0101 * n; + uint32_t n32 = 0x01010101 * n; + uint64_t n64 = 0x0101010101010101 * n; + for (size_t i = 0; i < dst.length; i += 1) + { + switch (dst.length - i) + { + case 4: + *((uint32_t*)get(dst, 1, i)) = n32; + i += 4; + break; + case 7: + case 3: + *((uint8_t*)get(dst, 1, i)) = n; + i += 1; + [[fallthrough]]; + case 6: + case 2: + *((uint16_t*)get(dst, 1, i)) = n16; + i += 2; + break; + case 5: + case 1: + *((uint8_t*)get(dst, 1, i)) = n; + i += 1; + break; + default: + *((uint64_t*)get(dst, 1, i)) = n64; + i += 8; + [[fallthrough]]; + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 30: + case 31: + *((uint64_t*)get(dst, 1, i)) = n64; + i += 8; + [[fallthrough]]; + case 16: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + *((uint64_t*)get(dst, 1, i)) = n64; + i += 8; + [[fallthrough]]; + case 8: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + *((uint64_t*)get(dst, 1, i)) = n64; + i += 8; + break; + } + } +} diff --git a/core/templates/Slice.h b/core/templates/Slice.h new file mode 100644 index 0000000000..7498d56e46 --- /dev/null +++ b/core/templates/Slice.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include + +struct Slice +{ + void *data; + size_t length; + + static Slice nil; + + constexpr static inline void *get( + Slice self, + size_t index, + size_t size + ) noexcept; + + constexpr static inline bool subslice( + Slice *dst, + Slice src, + size_t begin, + size_t count + ) noexcept; + + static inline bool set(Slice dst, Slice src, size_t index) noexcept; + static bool copy(Slice dst, Slice src) noexcept; + static void set(Slice dst, uint8_t n) noexcept; +}; + +constexpr inline void *Slice::get( + Slice self, + size_t index, + size_t size +) noexcept +{ + void *ret = nullptr; + if ((size * index) < self.length) + { + ret = ((uint8_t *)self.data) + (size * index); + } + return ret; +} + +constexpr inline bool Slice::subslice( + Slice *dst, + Slice src, + size_t begin, + size_t count +) noexcept +{ + bool ret = (src.length - count) < begin; + assert(dst); + if (!ret) + { + dst->data = &(((uint8_t*)src.data)[begin]); + dst->length = count; + } + return ret; +} + +inline bool Slice::set(Slice dst, Slice src, size_t index) noexcept +{ + Slice tmp = {}; + if (!subslice(&tmp, dst, index, src.length)) { return false; } + return Slice::copy(tmp, src); +} + +#define sliceAt(slice, t, i) ((t*)Slice::get(slice, i, sizeof(t))) +#define subslice_t(dst, src, t, begin, count) subslice( \ + (dst), \ + (src), \ + sizeof(t) * (begin), \ + sizeof(t) * (count) \ +) +#define sliceCount(self, t) ((self).length / sizeof(t))