From 3c374a84d0eb5474672c9461ae5d7f02423c7a55 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Tue, 5 May 2026 11:01:46 +0200 Subject: [PATCH 01/42] feat(ktx): add initial ktx input/output support. Add limited support for input and output for the KTX2 format. Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 59 + src/cmake/externalpackages.cmake | 5 + src/cmake/testing.cmake | 6 +- src/ktx.imageio/CMakeLists.txt | 13 + src/ktx.imageio/README.md | 248 ++ src/ktx.imageio/bc7enc-rdo/bc7decomp.cpp | 680 ++++ src/ktx.imageio/bc7enc-rdo/bc7decomp.h | 180 + src/ktx.imageio/bc7enc-rdo/bc7enc.cpp | 2581 ++++++++++++++ src/ktx.imageio/bc7enc-rdo/bc7enc.h | 124 + src/ktx.imageio/bc7enc-rdo/rgbcx.cpp | 3081 +++++++++++++++++ src/ktx.imageio/bc7enc-rdo/rgbcx.h | 475 +++ .../bc7enc-rdo/rgbcx_table4_small.h | 972 ++++++ src/ktx.imageio/etcdec.h | 597 ++++ src/ktx.imageio/ktx_pvt.h | 362 ++ src/ktx.imageio/ktxinput.cpp | 1485 ++++++++ src/ktx.imageio/ktxoutput.cpp | 963 ++++++ src/libOpenImageIO/imageioplugin.cpp | 4 + testsuite/ktx/ref/out.txt | 180 + testsuite/ktx/run.py | 28 + 19 files changed, 12042 insertions(+), 1 deletion(-) create mode 100644 src/cmake/build_Ktx.cmake create mode 100644 src/ktx.imageio/CMakeLists.txt create mode 100644 src/ktx.imageio/README.md create mode 100644 src/ktx.imageio/bc7enc-rdo/bc7decomp.cpp create mode 100644 src/ktx.imageio/bc7enc-rdo/bc7decomp.h create mode 100644 src/ktx.imageio/bc7enc-rdo/bc7enc.cpp create mode 100644 src/ktx.imageio/bc7enc-rdo/bc7enc.h create mode 100644 src/ktx.imageio/bc7enc-rdo/rgbcx.cpp create mode 100644 src/ktx.imageio/bc7enc-rdo/rgbcx.h create mode 100644 src/ktx.imageio/bc7enc-rdo/rgbcx_table4_small.h create mode 100644 src/ktx.imageio/etcdec.h create mode 100644 src/ktx.imageio/ktx_pvt.h create mode 100644 src/ktx.imageio/ktxinput.cpp create mode 100644 src/ktx.imageio/ktxoutput.cpp create mode 100644 testsuite/ktx/ref/out.txt create mode 100644 testsuite/ktx/run.py diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake new file mode 100644 index 0000000000..1f12d4f1b9 --- /dev/null +++ b/src/cmake/build_Ktx.cmake @@ -0,0 +1,59 @@ +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/Academ SoftwareFoundation/OpenImageIO + +set_cache (Ktx_BUILD_VERSION v5.0.0-rc1 "Ktx version for local builds") +set (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") +set (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}") +set (Ktx_GIT_COMMIT "6269d2752ed04446c2d4749f54f3aad4f94555b5") +set_cache (Ktx_BUILD_SHARED_LIBS OFF ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} + DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) + +string (MAKE_C_IDENTIFIER ${Ktx_BUILD_VERSION} Ktx_VERSION_IDENT) + +# for detailed build instructions, see: +# https://github.com/KhronosGroup/KTX-Software/blob/main/BUILDING.md +# KTX-Software not only provides Ktx but also a set of cli tools and load +# test applications that we do not need. +build_dependency_with_cmake(Ktx + VERSION ${Ktx_BUILD_VERSION} + GIT_REPOSITORY ${Ktx_GIT_REPOSITORY} + GIT_TAG ${Ktx_GIT_TAG} + GIT_COMMIT ${Ktx_GIT_COMMIT} + # lib only contains CMakeLists.txt from tag v5.0.0 but that requires CMake min version 3.23 + # which in turn causes the CI to fail. Just give up and build the whole thing... + SOURCE_SUBDIR lib # To only build Ktx, cmake has to point to: KTX-Software/lib + CMAKE_ARGS + -D BUILD_SHARED_LIBS=${Ktx_BUILD_SHARED_LIBS} + -D CMAKE_INSTALL_LIBDIR=lib + -D CMAKE_POSITION_INDEPENDENT_CODE=ON + -D LIBKTX_VERSION_READ_ONLY=OFF + -D LIBKTX_VERSION_FULL=ON + -D LIBKTX_FEATURE_KTX1=OFF + -D LIBKTX_FEATURE_KTX2=ON + -D LIBKTX_FEATURE_VK_UPLOAD=OFF + -D LIBKTX_FEATURE_GL_UPLOAD=OFF + -D LIBKTX_FEATURE_ETC_UNPACK=ON + # as per KTX-Software: + # > Intel Macs have support for SSE, but if you're building universal + # > binaries, you have to disable SSE or the build will fail. + ) + +# Set some things up that we'll need for a subsequent find_package to work +set (Ktx_ROOT ${Ktx_LOCAL_INSTALL_DIR}) +set (Ktx_DIR ${Ktx_LOCAL_INSTALL_DIR}/lib/cmake/ktx) + +# Signal to caller that we need to find again at the installed location +# set (Ktx_REFIND TRUE) +# set (Ktx_REFIND_ARGS CONFIG) + +find_package (Ktx CONFIG REQUIRED + HINTS + ${Ktx_LOCAL_INSTALL_DIR}/lib/cmake/ktx/ + ${Ktx_LOCAL_INSTALL_DIR} + ) + +if (Ktx_BUILD_SHARED_LIBS) + # install_local_dependency_libs (pkgname libname) + install_local_dependency_libs (Ktx ktx) # notice libname is lowercase +endif () diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index 2f670f77e9..4df337e5cf 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -249,6 +249,11 @@ checked_find_package (fmt REQUIRED ) get_target_property(FMT_INCLUDE_DIR fmt::fmt-header-only INTERFACE_INCLUDE_DIRECTORIES) +# Ktx for KTX textures +checked_find_package (Ktx + VERSION_MIN 5.0.0 + BUILD_LOCAL missing +) ########################################################################### diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 93aa7345e6..c0a7a85606 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -336,6 +336,9 @@ macro (oiio_add_all_tests) oiio_add_tests (dpx ENABLEVAR ENABLE_DPX IMAGEDIR oiio-images/dpx URL "Recent checkout of OpenImageIO-images") + oiio_add_tests (ktx + ENABLEVAR ENABLE_KTX + IMAGEDIR oiio-images/ktx2) oiio_add_tests (dds ENABLEVAR ENABLE_DDS IMAGEDIR oiio-images/dds URL "Recent checkout of OpenImageIO-images") @@ -539,9 +542,10 @@ function (oiio_get_test_data name) endfunction() function (oiio_setup_test_data) + # TODO: revert this after accepting OpenImageIO-images PR and before merging (just so that the CI passes) oiio_get_test_data (oiio-images REPO https://github.com/AcademySoftwareFoundation/OpenImageIO-images.git - BRANCH dev-${OpenImageIO_VERSION_MAJOR}.${OpenImageIO_VERSION_MINOR}) + BRANCH main) oiio_get_test_data (openexr-images REPO https://github.com/AcademySoftwareFoundation/openexr-images.git BRANCH main) diff --git a/src/ktx.imageio/CMakeLists.txt b/src/ktx.imageio/CMakeLists.txt new file mode 100644 index 0000000000..b80dff5866 --- /dev/null +++ b/src/ktx.imageio/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + +if (Ktx_FOUND) + add_oiio_plugin ( ktxinput.cpp ktxoutput.cpp + # TODO: add certain warning ignores + # bc7enc_rdo dependency (for BC1, BC2, BC3, BC4, BC5 and BC7 decoding/encoding) + bc7enc-rdo/rgbcx.cpp bc7enc-rdo/bc7decomp.cpp bc7enc-rdo/bc7enc.cpp + LINK_LIBRARIES KTX::ktx) +else () + message (WARNING "KTX plugin will not be built, no libktx") +endif () diff --git a/src/ktx.imageio/README.md b/src/ktx.imageio/README.md new file mode 100644 index 0000000000..17caf5ae09 --- /dev/null +++ b/src/ktx.imageio/README.md @@ -0,0 +1,248 @@ +# About + +This KTX plugin support obviously nulifies the benefits of using KTX in the +first place. That being said, this plugin is still useful so that end users +don't have to convert back and forth between KTX <-> supported format (e.g., PNG). +It is also useful to convert to and from KTX2 format. + +An example usecase would be Blender and its glTf import/export plugin. + +Ideally, at some point in the future, OIIO may introduce a new API to accomodate +texture formats that are mainly used for fast texture uploads to GPUs. + +Below you will find a set of notes about why this plugin is implemented the way +it is. It took me some time to understand how libktx works and what it provides +(and why). Some terminology is also defined here. + +## KTX2 - Brief Introduction + +KTX2 (the 2 here is to distinguish it from deprecated KTX/KTX1) is a binary +container format that is intended for usage for fast loading of textures to the +GPU. KTX2 contains GPU-native formats (e.g., block compressed format BC7) with +an optional additional layer of compression (hereafter refered to as +*supercompression*). + +As per the specs, KTX formats may store downsampled texture data for each mip +level (not necessarily the whole pyramid). This introduces problems for the +KTX2 writer (at `ktxoutput.cpp`) because + +## GPU Block Compression Formats + +As opposed to compressed images, the term *compressed textures* usually refers +to GPU block-compressed textures. Compressed textures have the following +requirements: + + - Random access (to some degree, you still pay the price for decoding a very + small number of neighboring pixels to access a given pixel). + - Fixed-rate encoding (requirement for random access) + - Support for hardware-decoding on the GPU (i.e., extremely fast to decode + and results in better performance due to lower cache usage). + +### BCn + +All BCn formats encode a 4x4 block of pixels (could be 1 channel, or 2, or 3, or +4 depending on the particular format) into a fixed-size data (i.e., no +variable-rate encoding). + +#### BC1/DXT1 + +64 bytes (4x4 block) => 8 bytes +alpha channed is encoded using 1 bit + +### ASTC + +TODO +Luckily libktx provides ASTC encoders/decoders and we don't have to deal with +ASTC's extreme complexity (e.g., there are many different block sizes). + +## KTX Supercompression + +**supercompression**: a compression on top of another compression (i.e., layered +compression) for better disk storage/network transmission. Unlike GPU block +compression, supercompression has the flexibilty to employ variable-rate +encoding. In this context, supercompression is employed on top of fixed-rate, +endpoint-compressed formats (like BCn, ETC2, etc.) that have hardware-decoding +support in commodity GPUs (of course, depends on GPU - mobile vs desktop, etc.). + +Depending on the used Basis Universal codec (if any), +supercompression may be applied. **For ETC1S, supercompression must be used +(usually BasisLZ)**. This is the reason why you constantly see the notation +"BasisLZ/ETC1S" which *probably (have to verify)* reads: *BasisLZ over ETC1S*. + +For UASTC, we *may* apply Zstandard supercompression (i.e., `KTX_SS_ZSTD`). + +### KTS\_SS\_BASIS\_LZ (BasisLZ) + +This is intended to be used to supercompress Basis Universal ETC1S format. +The expected workflow is as follows: + +``` +Basis LZ -> transcode to GPU format (e.g., block-compressed BC7) +``` + +For OIIO usecase, we can directly use libktx to transcode into raw bytes: + +``` +Basis LZ → transcode (using ktxTexture2_TranscodeBasis) → raw RGBA +``` + +The `ktxTexture2_TranscodeBasis` function provided by libktx can transcode +directly into raw RGBA values which is very handy. It however doesn't +provide/expose the functionality to just decode a single miplevel/subimage +(maybe this is simply not doable with Basis LZ - have to verify). + +## Supported Encoders/Decoders + +- Supported/Tested texture kinds: + - [ ] `SINGLE_TEXTURE_1D` (TODO) + - [X] `SINGLE_TEXTURE_2D` + - [ ] `SINGLE_TEXTURE_3D` (TODO) + - [ ] `CUBEMAP_TEXTURE` (TODO) + - [ ] `ARRAY_TEXTURE_1D` (TODO) + - [ ] `ARRAY_TEXTURE_2D` (TODO) + - [ ] `ARRAY_TEXTURE_3D` (not planned) + - [ ] `ARRAY_TEXTURE_CUBEMAP` (not planned) + +- Supported/Tested VkFormats: + - [X] `VK_FORMAT_R8_UNORM` + - [X] `VK_FORMAT_R8G8_SRGB` + - [X] `VK_FORMAT_R8G8B8_SRGB` + - [X] `VK_FORMAT_R8G8B8A8_SRGB` + - [X] `VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK` + - [X] `VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK` + - [X] `VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK` + - [X] `VK_FORMAT_BC1_RGB_SRGB_BLOCK` + - [X] `VK_FORMAT_BC3_SRGB_BLOCK` + - [X] `VK_FORMAT_BC4_UNORM_BLOCK` + - [X] `VK_FORMAT_BC5_UNORM_BLOCK` + - [X] `VK_FORMAT_BC7_SRGB_BLOCK` + - [X] `VK_FORMAT_ASTC_4x4_SRGB_BLOCK` + +- BCn GPU block-compressed formats: + - [X] BC1 encoder/decoder + - [ ] BC2 encoder/decoder (not implemented) + - [X] BC3 encoder/decoder + - [X] BC4 encoder/decoder + - [X] BC5 encoder/decoder + - [ ] BC6HS/BC6HU encoder/decoder (partially implemented but not tested) + - [X] BC7 encoder/decoder + +- ETC2 GPU block-compressed formats: + - [X] `ETC2_RGB` (aka ETC1) decoder + - [X] `ETC2_RGB_A1` decoder + - [X] `ETC2_RGBA` decoder + - [ ] ETC2 encoder (there are a few ETC2 decoders - including ConvectionKernels and etcpack) + +- ASTC GPU block-compressed formats: + - [X] `ASTC` decoder (using libktx's `ktxTexture2_DecodeAstc`) + - [X] `ASTC` encoder (using libktx's `ktxTexture2_CompressAstc`) + +- Basis Universal schemes: + - [X] `UASTC` encoder/decoder + - [X] `ETC1S` encoder/decoder + +- Supercompression schemes: + - [X] `ZLIB` decompressor/compressor (using libktx's `ktxTexture2_DeflateZLIB`) + - [X] `ZSTD` decompressor/compressor (using libktx's `ktxTexture2_DeflateZstd`) + +## Limitations + +- If original KTX2 format contained generated mip maps, there is simply no way +to know which filter and its parameters that were used to regenerate these +mipmaps. To avoid any issues, we simply early quit (return false) in `open()` +if `get_int_attribute("ktx:miplevels") > 1`. + +- KTX2 supports many GPU-block-compression encoders and each one may have many +different parameters that change the encoding quality (as usual, quality-speed +tradeoff). There is simply no way to regenerate the exact same input texture +without knowing these parameters and nor the KTX2 specs nor libktx nor +KTX-Software tooling stores any (or sufficient) information about these params +in the metadata. + +- As stated in the comments in `ktxinput.cpp`, if given ktx texture is +supercompressed then it has to be all decompressed (i.e., NOT the decompression +of the underlying GPU texture format but rather the supercompression). This +means that if you just need a particular subimage/miplevel, you pay the memory +price of loading the whole KTX texture (which might be very large for 3D +textures and texture arrays). + + - Per the specs: + > Discussion: Should each mip level be supercompressed independently or should + > the scheme, zlib, zstd, etc., be applied to all levels as a unit? The latter + > may result in slightly smaller size though that is unclear. However it would + > also mean levels could not be streamed or randomly accessed. + > + > Resolved: Yes. The benefits of streaming and random access outweigh what is + > expected to be a small increase in size. + +- KTX2 writer writes the whole texture (i.e., all subimages/mipmaps) in the +`close()` function (i.e., when the ImageOutput object is destroyed or requested +to close). libktx does not provide a way to append or write subimages (is this +problematic or contrary to the way OIIO expects us to write files?). + +- KTX1 format is not yet supported. Adding support for it after finishing KTX2 +*should be* relatively straightforward (Note: KTX1 is officially deprecated and +KTX-Software provides tools to convert from KTX1 to KTX2). + +- Only LDR formats (to be more precise, only TypeDesc::UINT8). Adding support +for HDR is straightforward (conversions for large number of enum values from +VkFormat have to be written). + +- bc7enc\_rdo dependency for encoding/decoding *BC1-7* formats does not ( +contrary to what the repo description suggests) support BC6HS/BC6HU HDR formats. +See below on what we can use for BC6HU/BC6HS encoding/decoding. + +## Dependencies + +[libktx][libktx]: for general KTX@ format support (loading of KTX2 files, transcoding +support, supercompression decompression support, etc.). + + - Commit hash: see `OpenImageIO/src/cmake/build_Ktx.cmake` + - License: Many subresources. TODO: `lib/etcdec.cxx`'s license is not open + source but libktx exposes a function to decode ETC formats, do we use it? + +[bc7enc\_rdo][bc7enc]: for BC1, BC2, BC3, BC4, BC5 and BC7 decoding/encoding. + + - Commit hash: + ``` + dbe416d28a5530b4e8cc45b14bf034dc6b96bbde + ``` + - License: MIT License + - Note: I am working on a PR to push `ktxTexture2_DecodeBcn` function + to KTX-Software (libktx) so that we no longer have to include these + dependencies here. + +[etcdec][etcdec]: ETC2/EAC decoding. + + - Commit hash: + ``` + 972875d403ed8ac27e0f35c2f29d819e710a688a + ``` + - License: MIT LICENSE + - Note: libktx has etcunpack included by default. Maybe we can use it and + remove this dependency? + +Note: for BC6HS/BC6HU and ETC encoding support, we can use +[ConvectionKernels][ConvectionKernels]. It is better to have this dependency +built seperately (i.e., not copied into source ktx.imageio directory). + +Personally, for ETC encoding, I would prefer to use ETCPACK since libktx already +uses its decoder and is the more *standard* choice (i.e., seems more official). + +For BC6HU/BC6HS decoding, we can use the same dependency used by the DDS format +(bcdec.h). For encoding, we extract the needed function from DirectX Texture +Library (MIT license) or I write it myself. + +## Resources + +- [KTX2 Specs](https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html) +- [Official Implementation (KTX-Software)](https://github.com/KhronosGroup/KTX-Software) +- [Basis Universal Supercompression Implementation (used by libktx)](https://github.com/BinomialLLC/basis_universal) +- [BC1-7 encoders/decoders with RDO](https://github.com/richgel999/bc7enc_rdo) +- [Comparing-BCn-texture-decoders](https://aras-p.info/blog/2022/06/23/Comparing-BCn-texture-decoders/) +- [ConvectionKernels][ConvectionKernels] + +[libktx]: https://github.com/KhronosGroup/KTX-Software.git +[bc7enc]: https://github.com/richgel999/bc7enc_rdo.git +[etcdec]: https://github.com/iOrange/etcdec.git +[ConvectionKernels]: https://github.com/elasota/ConvectionKernels.git diff --git a/src/ktx.imageio/bc7enc-rdo/bc7decomp.cpp b/src/ktx.imageio/bc7enc-rdo/bc7decomp.cpp new file mode 100644 index 0000000000..62747ca854 --- /dev/null +++ b/src/ktx.imageio/bc7enc-rdo/bc7decomp.cpp @@ -0,0 +1,680 @@ +// clang-format off +// File: bc7decomp.c - Richard Geldreich, Jr. 3/31/2020 - MIT license or public domain (see end of file) +#include "bc7decomp.h" +#include + +#if (defined(_M_AMD64) || defined(_M_X64) || defined(__SSE2__)) +# define BC7DECOMP_USE_SSE2 +#endif + +#ifdef BC7DECOMP_USE_SSE2 +#include +#include +#endif + +namespace bc7decomp +{ + +#ifdef BC7DECOMP_USE_SSE2 + const __m128i g_bc7_weights4_sse2[8] = + { + _mm_set_epi16(4, 4, 4, 4, 0, 0, 0, 0), + _mm_set_epi16(13, 13, 13, 13, 9, 9, 9, 9), + _mm_set_epi16(21, 21, 21, 21, 17, 17, 17, 17), + _mm_set_epi16(30, 30, 30, 30, 26, 26, 26, 26), + _mm_set_epi16(38, 38, 38, 38, 34, 34, 34, 34), + _mm_set_epi16(47, 47, 47, 47, 43, 43, 43, 43), + _mm_set_epi16(55, 55, 55, 55, 51, 51, 51, 51), + _mm_set_epi16(64, 64, 64, 64, 60, 60, 60, 60), + }; +#endif + +const uint32_t g_bc7_weights2[4] = { 0, 21, 43, 64 }; +const uint32_t g_bc7_weights3[8] = { 0, 9, 18, 27, 37, 46, 55, 64 }; +const uint32_t g_bc7_weights4[16] = { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 }; + +const uint8_t g_bc7_partition2[64 * 16] = +{ + 0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1, 0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1, 0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1, 0,0,0,1,0,0,1,1,0,0,1,1,0,1,1,1, 0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,1,0,1,1,1,1,1,1,1, 0,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,1,0,0,1,1,0,1,1,1, + 0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1, 0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1, 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1, + 0,0,0,0,1,0,0,0,1,1,1,0,1,1,1,1, 0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,0, 0,1,1,1,0,0,1,1,0,0,0,1,0,0,0,0, 0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,1,0,0,0,1,1,0,0,1,1,1,0, 0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,0, 0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1, + 0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0, 0,0,0,0,1,0,0,0,1,0,0,0,1,1,0,0, 0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0, 0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0, 0,0,0,1,0,1,1,1,1,1,1,0,1,0,0,0, 0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0, 0,1,1,1,0,0,0,1,1,0,0,0,1,1,1,0, 0,0,1,1,1,0,0,1,1,0,0,1,1,1,0,0, + 0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1, 0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1, 0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0, 0,0,1,1,0,0,1,1,1,1,0,0,1,1,0,0, 0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0, 0,1,0,1,0,1,0,1,1,0,1,0,1,0,1,0, 0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1, 0,1,0,1,1,0,1,0,1,0,1,0,0,1,0,1, + 0,1,1,1,0,0,1,1,1,1,0,0,1,1,1,0, 0,0,0,1,0,0,1,1,1,1,0,0,1,0,0,0, 0,0,1,1,0,0,1,0,0,1,0,0,1,1,0,0, 0,0,1,1,1,0,1,1,1,1,0,1,1,1,0,0, 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0, 0,0,1,1,1,1,0,0,1,1,0,0,0,0,1,1, 0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1, 0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0, + 0,1,0,0,1,1,1,0,0,1,0,0,0,0,0,0, 0,0,1,0,0,1,1,1,0,0,1,0,0,0,0,0, 0,0,0,0,0,0,1,0,0,1,1,1,0,0,1,0, 0,0,0,0,0,1,0,0,1,1,1,0,0,1,0,0, 0,1,1,0,1,1,0,0,1,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,1, 0,1,1,0,0,0,1,1,1,0,0,1,1,1,0,0, 0,0,1,1,1,0,0,1,1,1,0,0,0,1,1,0, + 0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,1, 0,1,1,0,0,0,1,1,0,0,1,1,1,0,0,1, 0,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1, 0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,1, 0,0,0,0,1,1,1,1,0,0,1,1,0,0,1,1, 0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,0, 0,0,1,0,0,0,1,0,1,1,1,0,1,1,1,0, 0,1,0,0,0,1,0,0,0,1,1,1,0,1,1,1 +}; + +const uint8_t g_bc7_partition3[64 * 16] = +{ + 0,0,1,1,0,0,1,1,0,2,2,1,2,2,2,2, 0,0,0,1,0,0,1,1,2,2,1,1,2,2,2,1, 0,0,0,0,2,0,0,1,2,2,1,1,2,2,1,1, 0,2,2,2,0,0,2,2,0,0,1,1,0,1,1,1, 0,0,0,0,0,0,0,0,1,1,2,2,1,1,2,2, 0,0,1,1,0,0,1,1,0,0,2,2,0,0,2,2, 0,0,2,2,0,0,2,2,1,1,1,1,1,1,1,1, 0,0,1,1,0,0,1,1,2,2,1,1,2,2,1,1, + 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2, 0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2, 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2, 0,0,1,2,0,0,1,2,0,0,1,2,0,0,1,2, 0,1,1,2,0,1,1,2,0,1,1,2,0,1,1,2, 0,1,2,2,0,1,2,2,0,1,2,2,0,1,2,2, 0,0,1,1,0,1,1,2,1,1,2,2,1,2,2,2, 0,0,1,1,2,0,0,1,2,2,0,0,2,2,2,0, + 0,0,0,1,0,0,1,1,0,1,1,2,1,1,2,2, 0,1,1,1,0,0,1,1,2,0,0,1,2,2,0,0, 0,0,0,0,1,1,2,2,1,1,2,2,1,1,2,2, 0,0,2,2,0,0,2,2,0,0,2,2,1,1,1,1, 0,1,1,1,0,1,1,1,0,2,2,2,0,2,2,2, 0,0,0,1,0,0,0,1,2,2,2,1,2,2,2,1, 0,0,0,0,0,0,1,1,0,1,2,2,0,1,2,2, 0,0,0,0,1,1,0,0,2,2,1,0,2,2,1,0, + 0,1,2,2,0,1,2,2,0,0,1,1,0,0,0,0, 0,0,1,2,0,0,1,2,1,1,2,2,2,2,2,2, 0,1,1,0,1,2,2,1,1,2,2,1,0,1,1,0, 0,0,0,0,0,1,1,0,1,2,2,1,1,2,2,1, 0,0,2,2,1,1,0,2,1,1,0,2,0,0,2,2, 0,1,1,0,0,1,1,0,2,0,0,2,2,2,2,2, 0,0,1,1,0,1,2,2,0,1,2,2,0,0,1,1, 0,0,0,0,2,0,0,0,2,2,1,1,2,2,2,1, + 0,0,0,0,0,0,0,2,1,1,2,2,1,2,2,2, 0,2,2,2,0,0,2,2,0,0,1,2,0,0,1,1, 0,0,1,1,0,0,1,2,0,0,2,2,0,2,2,2, 0,1,2,0,0,1,2,0,0,1,2,0,0,1,2,0, 0,0,0,0,1,1,1,1,2,2,2,2,0,0,0,0, 0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0, 0,1,2,0,2,0,1,2,1,2,0,1,0,1,2,0, 0,0,1,1,2,2,0,0,1,1,2,2,0,0,1,1, + 0,0,1,1,1,1,2,2,2,2,0,0,0,0,1,1, 0,1,0,1,0,1,0,1,2,2,2,2,2,2,2,2, 0,0,0,0,0,0,0,0,2,1,2,1,2,1,2,1, 0,0,2,2,1,1,2,2,0,0,2,2,1,1,2,2, 0,0,2,2,0,0,1,1,0,0,2,2,0,0,1,1, 0,2,2,0,1,2,2,1,0,2,2,0,1,2,2,1, 0,1,0,1,2,2,2,2,2,2,2,2,0,1,0,1, 0,0,0,0,2,1,2,1,2,1,2,1,2,1,2,1, + 0,1,0,1,0,1,0,1,0,1,0,1,2,2,2,2, 0,2,2,2,0,1,1,1,0,2,2,2,0,1,1,1, 0,0,0,2,1,1,1,2,0,0,0,2,1,1,1,2, 0,0,0,0,2,1,1,2,2,1,1,2,2,1,1,2, 0,2,2,2,0,1,1,1,0,1,1,1,0,2,2,2, 0,0,0,2,1,1,1,2,1,1,1,2,0,0,0,2, 0,1,1,0,0,1,1,0,0,1,1,0,2,2,2,2, 0,0,0,0,0,0,0,0,2,1,1,2,2,1,1,2, + 0,1,1,0,0,1,1,0,2,2,2,2,2,2,2,2, 0,0,2,2,0,0,1,1,0,0,1,1,0,0,2,2, 0,0,2,2,1,1,2,2,1,1,2,2,0,0,2,2, 0,0,0,0,0,0,0,0,0,0,0,0,2,1,1,2, 0,0,0,2,0,0,0,1,0,0,0,2,0,0,0,1, 0,2,2,2,1,2,2,2,0,2,2,2,1,2,2,2, 0,1,0,1,2,2,2,2,2,2,2,2,2,2,2,2, 0,1,1,1,2,0,1,1,2,2,0,1,2,2,2,0, +}; + +const uint8_t g_bc7_table_anchor_index_second_subset[64] = { 15,15,15,15,15,15,15,15, 15,15,15,15,15,15,15,15, 15, 2, 8, 2, 2, 8, 8,15, 2, 8, 2, 2, 8, 8, 2, 2, 15,15, 6, 8, 2, 8,15,15, 2, 8, 2, 2, 2,15,15, 6, 6, 2, 6, 8,15,15, 2, 2, 15,15,15,15,15, 2, 2,15 }; + +const uint8_t g_bc7_table_anchor_index_third_subset_1[64] = +{ + 3, 3,15,15, 8, 3,15,15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8,15, 3, 3, 6,10, 5, 8, 8, 6, 8, 5,15,15, 8,15, 3, 5, 6,10, 8,15, 15, 3,15, 5,15,15,15,15, 3,15, 5, 5, 5, 8, 5,10, 5,10, 8,13,15,12, 3, 3 +}; + +const uint8_t g_bc7_table_anchor_index_third_subset_2[64] = +{ + 15, 8, 8, 3,15,15, 3, 8, 15,15,15,15,15,15,15, 8, 15, 8,15, 3,15, 8,15, 8, 3,15, 6,10,15,15,10, 8, 15, 3,15,10,10, 8, 9,10, 6,15, 8,15, 3, 6, 6, 8, 15, 3,15,15,15,15,15,15, 15,15,15,15, 3,15,15, 8 +}; + +const uint8_t g_bc7_first_byte_to_mode[256] = +{ + 8, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, +}; + +inline void insert_weight_zero(uint64_t& index_bits, uint32_t bits_per_index, uint32_t offset) +{ + uint64_t LOW_BIT_MASK = (static_cast(1) << ((bits_per_index * (offset + 1)) - 1)) - 1; + uint64_t HIGH_BIT_MASK = ~LOW_BIT_MASK; + + index_bits = ((index_bits & HIGH_BIT_MASK) << 1) | (index_bits & LOW_BIT_MASK); +} + +// BC7 mode 0-7 decompression. +// Instead of one monster routine to unpack all the BC7 modes, we're lumping the 3 subset, 2 subset, 1 subset, and dual plane modes together into simple shared routines. + +static inline uint32_t bc7_dequant(uint32_t val, uint32_t pbit, uint32_t val_bits) { assert(val < (1U << val_bits)); assert(pbit < 2); assert(val_bits >= 4 && val_bits <= 8); const uint32_t total_bits = val_bits + 1; val = (val << 1) | pbit; val <<= (8 - total_bits); val |= (val >> total_bits); assert(val <= 255); return val; } +static inline uint32_t bc7_dequant(uint32_t val, uint32_t val_bits) { assert(val < (1U << val_bits)); assert(val_bits >= 4 && val_bits <= 8); val <<= (8 - val_bits); val |= (val >> val_bits); assert(val <= 255); return val; } + +static inline uint32_t bc7_interp2(uint32_t l, uint32_t h, uint32_t w) { assert(w < 4); return (l * (64 - g_bc7_weights2[w]) + h * g_bc7_weights2[w] + 32) >> 6; } +static inline uint32_t bc7_interp3(uint32_t l, uint32_t h, uint32_t w) { assert(w < 8); return (l * (64 - g_bc7_weights3[w]) + h * g_bc7_weights3[w] + 32) >> 6; } +static inline uint32_t bc7_interp4(uint32_t l, uint32_t h, uint32_t w) { assert(w < 16); return (l * (64 - g_bc7_weights4[w]) + h * g_bc7_weights4[w] + 32) >> 6; } +static inline uint32_t bc7_interp(uint32_t l, uint32_t h, uint32_t w, uint32_t bits) +{ + assert(l <= 255 && h <= 255); + switch (bits) + { + case 2: return bc7_interp2(l, h, w); + case 3: return bc7_interp3(l, h, w); + case 4: return bc7_interp4(l, h, w); + default: + break; + } + return 0; +} + + +#ifdef BC7DECOMP_USE_SSE2 +static inline __m128i bc7_interp_sse2(__m128i l, __m128i h, __m128i w, __m128i iw) +{ + return _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(l, iw), _mm_mullo_epi16(h, w)), _mm_set1_epi16(32)), 6); +} + +static inline void bc7_interp2_sse2(const color_rgba* endpoint_pair, color_rgba* out_colors) +{ + __m128i endpoints = _mm_loadu_si64(endpoint_pair); + __m128i endpoints_16 = _mm_unpacklo_epi8(endpoints, _mm_setzero_si128()); + + __m128i endpoints_16_swapped = _mm_shuffle_epi32(endpoints_16, _MM_SHUFFLE(1, 0, 3, 2)); + + // Interpolated colors will be color 1 and 2 + __m128i interpolated_colors = bc7_interp_sse2(endpoints_16, endpoints_16_swapped, _mm_set1_epi16(21), _mm_set1_epi16(43)); + + // all_colors will be 1, 2, 0, 3 + __m128i all_colors = _mm_packus_epi16(interpolated_colors, endpoints_16); + + all_colors = _mm_shuffle_epi32(all_colors, _MM_SHUFFLE(3, 1, 0, 2)); + + _mm_storeu_si128(reinterpret_cast<__m128i*>(out_colors), all_colors); +} + +static inline void bc7_interp3_sse2(const color_rgba* endpoint_pair, color_rgba* out_colors) +{ + __m128i endpoints = _mm_loadu_si64(endpoint_pair); + __m128i endpoints_16bit = _mm_unpacklo_epi8(endpoints, _mm_setzero_si128()); + __m128i endpoints_16bit_swapped = _mm_shuffle_epi32(endpoints_16bit, _MM_SHUFFLE(1, 0, 3, 2)); + + __m128i interpolated_16 = bc7_interp_sse2(endpoints_16bit, endpoints_16bit_swapped, _mm_set1_epi16(9), _mm_set1_epi16(55)); + __m128i interpolated_23 = bc7_interp_sse2(endpoints_16bit, endpoints_16bit_swapped, _mm_set_epi16(37, 37, 37, 37, 18, 18, 18, 18), _mm_set_epi16(27, 27, 27, 27, 46, 46, 46, 46)); + __m128i interpolated_45 = bc7_interp_sse2(endpoints_16bit, endpoints_16bit_swapped, _mm_set_epi16(18, 18, 18, 18, 37, 37, 37, 37), _mm_set_epi16(46, 46, 46, 46, 27, 27, 27, 27)); + + __m128i interpolated_01 = _mm_unpacklo_epi64(endpoints_16bit, interpolated_16); + __m128i interpolated_67 = _mm_unpackhi_epi64(interpolated_16, endpoints_16bit); + + __m128i all_colors_0 = _mm_packus_epi16(interpolated_01, interpolated_23); + __m128i all_colors_1 = _mm_packus_epi16(interpolated_45, interpolated_67); + + _mm_storeu_si128(reinterpret_cast<__m128i*>(out_colors), all_colors_0); + _mm_storeu_si128(reinterpret_cast<__m128i*>(out_colors + 4), all_colors_1); +} +#endif + +bool unpack_bc7_mode0_2(uint32_t mode, const uint64_t* data_chunks, color_rgba* pPixels) +{ + //const uint32_t SUBSETS = 3; + const uint32_t ENDPOINTS = 6; + const uint32_t COMPS = 3; + const uint32_t WEIGHT_BITS = (mode == 0) ? 3 : 2; + const uint32_t WEIGHT_MASK = (1 << WEIGHT_BITS) - 1; + const uint32_t ENDPOINT_BITS = (mode == 0) ? 4 : 5; + const uint32_t ENDPOINT_MASK = (1 << ENDPOINT_BITS) - 1; + const uint32_t PBITS = (mode == 0) ? 6 : 0; + [[maybe_unused]] const uint32_t WEIGHT_VALS = 1 << WEIGHT_BITS; + const uint32_t PART_BITS = (mode == 0) ? 4 : 6; + const uint32_t PART_MASK = (1 << PART_BITS) - 1; + + const uint64_t low_chunk = data_chunks[0]; + const uint64_t high_chunk = data_chunks[1]; + + const uint32_t part = (low_chunk >> (mode + 1)) & PART_MASK; + + uint64_t channel_read_chunks[3] = { 0, 0, 0 }; + + if (mode == 0) + { + channel_read_chunks[0] = low_chunk >> 5; + channel_read_chunks[1] = low_chunk >> 29; + channel_read_chunks[2] = ((low_chunk >> 53) | (high_chunk << 11)); + } + else + { + channel_read_chunks[0] = low_chunk >> 9; + channel_read_chunks[1] = ((low_chunk >> 39) | (high_chunk << 25)); + channel_read_chunks[2] = high_chunk >> 5; + } + + color_rgba endpoints[ENDPOINTS]; + for (uint32_t c = 0; c < COMPS; c++) + { + uint64_t channel_read_chunk = channel_read_chunks[c]; + for (uint32_t e = 0; e < ENDPOINTS; e++) + { + endpoints[e][c] = static_cast(channel_read_chunk & ENDPOINT_MASK); + channel_read_chunk >>= ENDPOINT_BITS; + } + } + + uint32_t pbits[6]; + if (mode == 0) + { + uint8_t p_bits_chunk = static_cast((high_chunk >> 13) & 0xff); + + for (uint32_t p = 0; p < PBITS; p++) + pbits[p] = (p_bits_chunk >> p) & 1; + } + + uint64_t weights_read_chunk = high_chunk >> (67 - 16 * WEIGHT_BITS); + insert_weight_zero(weights_read_chunk, WEIGHT_BITS, 0); + insert_weight_zero(weights_read_chunk, WEIGHT_BITS, std::min(g_bc7_table_anchor_index_third_subset_1[part], g_bc7_table_anchor_index_third_subset_2[part])); + insert_weight_zero(weights_read_chunk, WEIGHT_BITS, std::max(g_bc7_table_anchor_index_third_subset_1[part], g_bc7_table_anchor_index_third_subset_2[part])); + + uint32_t weights[16]; + for (uint32_t i = 0; i < 16; i++) + { + weights[i] = static_cast(weights_read_chunk & WEIGHT_MASK); + weights_read_chunk >>= WEIGHT_BITS; + } + + for (uint32_t e = 0; e < ENDPOINTS; e++) + for (uint32_t c = 0; c < 4; c++) + endpoints[e][c] = static_cast((c == 3) ? 255 : (PBITS ? bc7_dequant(endpoints[e][c], pbits[e], ENDPOINT_BITS) : bc7_dequant(endpoints[e][c], ENDPOINT_BITS))); + + color_rgba block_colors[3][8]; + +#ifdef BC7DECOMP_USE_SSE2 + for (uint32_t s = 0; s < 3; s++) + { + if (WEIGHT_BITS == 2) + bc7_interp2_sse2(endpoints + s * 2, block_colors[s]); + else + bc7_interp3_sse2(endpoints + s * 2, block_colors[s]); + } +#else + for (uint32_t s = 0; s < 3; s++) + for (uint32_t i = 0; i < WEIGHT_VALS; i++) + { + for (uint32_t c = 0; c < 3; c++) + block_colors[s][i][c] = static_cast(bc7_interp(endpoints[s * 2 + 0][c], endpoints[s * 2 + 1][c], i, WEIGHT_BITS)); + block_colors[s][i][3] = 255; + } +#endif + + for (uint32_t i = 0; i < 16; i++) + pPixels[i] = block_colors[g_bc7_partition3[part * 16 + i]][weights[i]]; + + return true; +} + +bool unpack_bc7_mode1_3_7(uint32_t mode, const uint64_t* data_chunks, color_rgba* pPixels) +{ + //const uint32_t SUBSETS = 2; + const uint32_t ENDPOINTS = 4; + const uint32_t COMPS = (mode == 7) ? 4 : 3; + const uint32_t WEIGHT_BITS = (mode == 1) ? 3 : 2; + const uint32_t WEIGHT_MASK = (1 << WEIGHT_BITS) - 1; + const uint32_t ENDPOINT_BITS = (mode == 7) ? 5 : ((mode == 1) ? 6 : 7); + const uint32_t ENDPOINT_MASK = (1 << ENDPOINT_BITS) - 1; + const uint32_t PBITS = (mode == 1) ? 2 : 4; + const uint32_t SHARED_PBITS = (mode == 1) ? true : false; + [[maybe_unused]] const uint32_t WEIGHT_VALS = 1 << WEIGHT_BITS; + + const uint64_t low_chunk = data_chunks[0]; + const uint64_t high_chunk = data_chunks[1]; + + const uint32_t part = ((low_chunk >> (mode + 1)) & 0x3f); + + color_rgba endpoints[ENDPOINTS]; + + uint64_t channel_read_chunks[4] = { 0, 0, 0, 0 }; + uint64_t p_read_chunk = 0; + channel_read_chunks[0] = (low_chunk >> (mode + 7)); + uint64_t weight_read_chunk; + + switch (mode) + { + case 1: + channel_read_chunks[1] = (low_chunk >> 32); + channel_read_chunks[2] = ((low_chunk >> 56) | (high_chunk << 8)); + p_read_chunk = high_chunk >> 16; + weight_read_chunk = high_chunk >> 18; + break; + case 3: + channel_read_chunks[1] = ((low_chunk >> 38) | (high_chunk << 26)); + channel_read_chunks[2] = high_chunk >> 2; + p_read_chunk = high_chunk >> 30; + weight_read_chunk = high_chunk >> 34; + break; + case 7: + channel_read_chunks[1] = low_chunk >> 34; + channel_read_chunks[2] = ((low_chunk >> 54) | (high_chunk << 10)); + channel_read_chunks[3] = high_chunk >> 10; + p_read_chunk = (high_chunk >> 30); + weight_read_chunk = (high_chunk >> 34); + break; + default: + return false; + }; + + for (uint32_t c = 0; c < COMPS; c++) + { + uint64_t channel_read_chunk = channel_read_chunks[c]; + for (uint32_t e = 0; e < ENDPOINTS; e++) + { + endpoints[e][c] = static_cast(channel_read_chunk & ENDPOINT_MASK); + channel_read_chunk >>= ENDPOINT_BITS; + } + } + + uint32_t pbits[4]; + for (uint32_t p = 0; p < PBITS; p++) + pbits[p] = (p_read_chunk >> p) & 1; + + insert_weight_zero(weight_read_chunk, WEIGHT_BITS, 0); + insert_weight_zero(weight_read_chunk, WEIGHT_BITS, g_bc7_table_anchor_index_second_subset[part]); + + uint32_t weights[16]; + for (uint32_t i = 0; i < 16; i++) + { + weights[i] = static_cast(weight_read_chunk & WEIGHT_MASK); + weight_read_chunk >>= WEIGHT_BITS; + } + + for (uint32_t e = 0; e < ENDPOINTS; e++) + for (uint32_t c = 0; c < 4; c++) + endpoints[e][c] = static_cast((mode != 7U && c == 3U) ? 255 : bc7_dequant(endpoints[e][c], pbits[SHARED_PBITS ? (e >> 1) : e], ENDPOINT_BITS)); + + color_rgba block_colors[2][8]; +#ifdef BC7DECOMP_USE_SSE2 + for (uint32_t s = 0; s < 2; s++) + { + if (WEIGHT_BITS == 2) + bc7_interp2_sse2(endpoints + s * 2, block_colors[s]); + else + bc7_interp3_sse2(endpoints + s * 2, block_colors[s]); + } +#else + for (uint32_t s = 0; s < 2; s++) + for (uint32_t i = 0; i < WEIGHT_VALS; i++) + { + for (uint32_t c = 0; c < COMPS; c++) + block_colors[s][i][c] = static_cast(bc7_interp(endpoints[s * 2 + 0][c], endpoints[s * 2 + 1][c], i, WEIGHT_BITS)); + block_colors[s][i][3] = (COMPS == 3) ? 255 : block_colors[s][i][3]; + } +#endif + + for (uint32_t i = 0; i < 16; i++) + pPixels[i] = block_colors[g_bc7_partition2[part * 16 + i]][weights[i]]; + + return true; +} + +bool unpack_bc7_mode4_5(uint32_t mode, const uint64_t* data_chunks, color_rgba* pPixels) +{ + const uint32_t ENDPOINTS = 2; + //const uint32_t COMPS = 4; + const uint32_t WEIGHT_BITS = 2; + const uint32_t WEIGHT_MASK = (1 << WEIGHT_BITS) - 1; + const uint32_t A_WEIGHT_BITS = (mode == 4) ? 3 : 2; + const uint32_t A_WEIGHT_MASK = (1 << A_WEIGHT_BITS) - 1; + const uint32_t ENDPOINT_BITS = (mode == 4) ? 5 : 7; + const uint32_t ENDPOINT_MASK = (1 << ENDPOINT_BITS) - 1; + const uint32_t A_ENDPOINT_BITS = (mode == 4) ? 6 : 8; + const uint32_t A_ENDPOINT_MASK = (1 << A_ENDPOINT_BITS) - 1; + //const uint32_t WEIGHT_VALS = 1 << WEIGHT_BITS; + //const uint32_t A_WEIGHT_VALS = 1 << A_WEIGHT_BITS; + + const uint64_t low_chunk = data_chunks[0]; + const uint64_t high_chunk = data_chunks[1]; + + const uint32_t comp_rot = (low_chunk >> (mode + 1)) & 0x3; + const uint32_t index_mode = (mode == 4) ? static_cast((low_chunk >> 7) & 1) : 0; + + uint64_t color_read_bits = low_chunk >> 8; + + color_rgba endpoints[ENDPOINTS]; + for (uint32_t c = 0; c < 3; c++) + { + for (uint32_t e = 0; e < ENDPOINTS; e++) + { + endpoints[e][c] = static_cast(color_read_bits & ENDPOINT_MASK); + color_read_bits >>= ENDPOINT_BITS; + } + } + + endpoints[0][3] = static_cast(color_read_bits & ENDPOINT_MASK); + + uint64_t rgb_weights_chunk; + uint64_t a_weights_chunk; + if (mode == 4) + { + endpoints[0][3] = static_cast(color_read_bits & A_ENDPOINT_MASK); + endpoints[1][3] = static_cast((color_read_bits >> A_ENDPOINT_BITS) & A_ENDPOINT_MASK); + rgb_weights_chunk = ((low_chunk >> 50) | (high_chunk << 14)); + a_weights_chunk = high_chunk >> 17; + } + else if (mode == 5) + { + endpoints[0][3] = static_cast(color_read_bits & A_ENDPOINT_MASK); + endpoints[1][3] = static_cast(((low_chunk >> 58) | (high_chunk << 6)) & A_ENDPOINT_MASK); + rgb_weights_chunk = high_chunk >> 2; + a_weights_chunk = high_chunk >> 33; + } + else + return false; + + insert_weight_zero(rgb_weights_chunk, WEIGHT_BITS, 0); + insert_weight_zero(a_weights_chunk, A_WEIGHT_BITS, 0); + + const uint32_t weight_bits[2] = { index_mode ? A_WEIGHT_BITS : WEIGHT_BITS, index_mode ? WEIGHT_BITS : A_WEIGHT_BITS }; + const uint32_t weight_mask[2] = { index_mode ? A_WEIGHT_MASK : WEIGHT_MASK, index_mode ? WEIGHT_MASK : A_WEIGHT_MASK }; + + uint32_t weights[16], a_weights[16]; + + if (index_mode) + std::swap(rgb_weights_chunk, a_weights_chunk); + + for (uint32_t i = 0; i < 16; i++) + { + weights[i] = (rgb_weights_chunk & weight_mask[0]); + rgb_weights_chunk >>= weight_bits[0]; + } + + for (uint32_t i = 0; i < 16; i++) + { + a_weights[i] = (a_weights_chunk & weight_mask[1]); + a_weights_chunk >>= weight_bits[1]; + } + + for (uint32_t e = 0; e < ENDPOINTS; e++) + for (uint32_t c = 0; c < 4; c++) + endpoints[e][c] = static_cast(bc7_dequant(endpoints[e][c], (c == 3) ? A_ENDPOINT_BITS : ENDPOINT_BITS)); + + color_rgba block_colors[8]; +#ifdef BC7DECOMP_USE_SSE2 + if (weight_bits[0] == 3) + bc7_interp3_sse2(endpoints, block_colors); + else + bc7_interp2_sse2(endpoints, block_colors); +#else + for (uint32_t i = 0; i < (1U << weight_bits[0]); i++) + for (uint32_t c = 0; c < 3; c++) + block_colors[i][c] = static_cast(bc7_interp(endpoints[0][c], endpoints[1][c], i, weight_bits[0])); +#endif + + for (uint32_t i = 0; i < (1U << weight_bits[1]); i++) + block_colors[i][3] = static_cast(bc7_interp(endpoints[0][3], endpoints[1][3], i, weight_bits[1])); + + for (uint32_t i = 0; i < 16; i++) + { + pPixels[i] = block_colors[weights[i]]; + pPixels[i].a = block_colors[a_weights[i]].a; + if (comp_rot >= 1) + std::swap(pPixels[i].a, pPixels[i].m_comps[comp_rot - 1]); + } + + return true; +} + +struct bc7_mode_6 +{ + struct + { + uint64_t m_mode : 7; + uint64_t m_r0 : 7; + uint64_t m_r1 : 7; + uint64_t m_g0 : 7; + uint64_t m_g1 : 7; + uint64_t m_b0 : 7; + uint64_t m_b1 : 7; + uint64_t m_a0 : 7; + uint64_t m_a1 : 7; + uint64_t m_p0 : 1; + } m_lo; + + union + { + struct + { + uint64_t m_p1 : 1; + uint64_t m_s00 : 3; + uint64_t m_s10 : 4; + uint64_t m_s20 : 4; + uint64_t m_s30 : 4; + + uint64_t m_s01 : 4; + uint64_t m_s11 : 4; + uint64_t m_s21 : 4; + uint64_t m_s31 : 4; + + uint64_t m_s02 : 4; + uint64_t m_s12 : 4; + uint64_t m_s22 : 4; + uint64_t m_s32 : 4; + + uint64_t m_s03 : 4; + uint64_t m_s13 : 4; + uint64_t m_s23 : 4; + uint64_t m_s33 : 4; + + } m_hi; + + uint64_t m_hi_bits; + }; +}; + +bool unpack_bc7_mode6(const void *pBlock_bits, color_rgba *pPixels) +{ + static_assert(sizeof(bc7_mode_6) == 16, "sizeof(bc7_mode_6) == 16"); + + const bc7_mode_6 &block = *static_cast(pBlock_bits); + + if (block.m_lo.m_mode != (1 << 6)) + return false; + + const uint32_t r0 = static_cast((block.m_lo.m_r0 << 1) | block.m_lo.m_p0); + const uint32_t g0 = static_cast((block.m_lo.m_g0 << 1) | block.m_lo.m_p0); + const uint32_t b0 = static_cast((block.m_lo.m_b0 << 1) | block.m_lo.m_p0); + const uint32_t a0 = static_cast((block.m_lo.m_a0 << 1) | block.m_lo.m_p0); + const uint32_t r1 = static_cast((block.m_lo.m_r1 << 1) | block.m_hi.m_p1); + const uint32_t g1 = static_cast((block.m_lo.m_g1 << 1) | block.m_hi.m_p1); + const uint32_t b1 = static_cast((block.m_lo.m_b1 << 1) | block.m_hi.m_p1); + const uint32_t a1 = static_cast((block.m_lo.m_a1 << 1) | block.m_hi.m_p1); + + color_rgba vals[16]; +#ifdef BC7DECOMP_USE_SSE2 + __m128i vep0 = _mm_set_epi16((short)a0, (short)b0, (short)g0, (short)r0, (short)a0, (short)b0, (short)g0, (short)r0); + __m128i vep1 = _mm_set_epi16((short)a1, (short)b1, (short)g1, (short)r1, (short)a1, (short)b1, (short)g1, (short)r1); + + for (uint32_t i = 0; i < 16; i += 4) + { + const __m128i w0 = g_bc7_weights4_sse2[i / 4 * 2 + 0]; + const __m128i w1 = g_bc7_weights4_sse2[i / 4 * 2 + 1]; + + const __m128i iw0 = _mm_sub_epi16(_mm_set1_epi16(64), w0); + const __m128i iw1 = _mm_sub_epi16(_mm_set1_epi16(64), w1); + + __m128i first_half = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(vep0, iw0), _mm_mullo_epi16(vep1, w0)), _mm_set1_epi16(32)), 6); + __m128i second_half = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(vep0, iw1), _mm_mullo_epi16(vep1, w1)), _mm_set1_epi16(32)), 6); + __m128i combined = _mm_packus_epi16(first_half, second_half); + + _mm_storeu_si128(reinterpret_cast<__m128i*>(vals + i), combined); + } +#else + for (uint32_t i = 0; i < 16; i++) + { + const uint32_t w = g_bc7_weights4[i]; + const uint32_t iw = 64 - w; + vals[i].set_noclamp_rgba( + (r0 * iw + r1 * w + 32) >> 6, + (g0 * iw + g1 * w + 32) >> 6, + (b0 * iw + b1 * w + 32) >> 6, + (a0 * iw + a1 * w + 32) >> 6); + } +#endif + + pPixels[0] = vals[block.m_hi.m_s00]; + pPixels[1] = vals[block.m_hi.m_s10]; + pPixels[2] = vals[block.m_hi.m_s20]; + pPixels[3] = vals[block.m_hi.m_s30]; + + pPixels[4] = vals[block.m_hi.m_s01]; + pPixels[5] = vals[block.m_hi.m_s11]; + pPixels[6] = vals[block.m_hi.m_s21]; + pPixels[7] = vals[block.m_hi.m_s31]; + + pPixels[8] = vals[block.m_hi.m_s02]; + pPixels[9] = vals[block.m_hi.m_s12]; + pPixels[10] = vals[block.m_hi.m_s22]; + pPixels[11] = vals[block.m_hi.m_s32]; + + pPixels[12] = vals[block.m_hi.m_s03]; + pPixels[13] = vals[block.m_hi.m_s13]; + pPixels[14] = vals[block.m_hi.m_s23]; + pPixels[15] = vals[block.m_hi.m_s33]; + + return true; +} + +bool unpack_bc7(const void *pBlock, color_rgba *pPixels) +{ + const uint8_t *block_bytes = static_cast(pBlock); + uint8_t mode = g_bc7_first_byte_to_mode[block_bytes[0]]; + + uint64_t data_chunks[2]; + + uint64_t endian_check = 1; + if (*reinterpret_cast(&endian_check) == 1) + memcpy(data_chunks, pBlock, 16); + else + { + data_chunks[0] = data_chunks[1] = 0; + for (int chunk_index = 0; chunk_index < 2; chunk_index++) + { + for (int byte_index = 0; byte_index < 8; byte_index++) + data_chunks[chunk_index] |= static_cast(block_bytes[chunk_index * 8 + byte_index]) << (byte_index * 8); + } + } + + switch (mode) + { + case 0: + case 2: + return unpack_bc7_mode0_2(mode, data_chunks, pPixels); + case 1: + case 3: + case 7: + return unpack_bc7_mode1_3_7(mode, data_chunks, pPixels); + case 4: + case 5: + return unpack_bc7_mode4_5(mode, data_chunks, pPixels); + case 6: + return unpack_bc7_mode6(data_chunks, pPixels); + default: + // reinterpret_cast to void* to silence the -Wclass-memaccess warning + memset(reinterpret_cast(pPixels), 0, sizeof(color_rgba) * 16); + break; + } + + return false; +} + +} // namespace bc7decomp + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright(c) 2020 Richard Geldreich, Jr. +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files(the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions : +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain(www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non - commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain.We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors.We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/ktx.imageio/bc7enc-rdo/bc7decomp.h b/src/ktx.imageio/bc7enc-rdo/bc7decomp.h new file mode 100644 index 0000000000..a9dcbccf59 --- /dev/null +++ b/src/ktx.imageio/bc7enc-rdo/bc7decomp.h @@ -0,0 +1,180 @@ +// clang-format off +#pragma once + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union +#endif + +#include +#include +#include +#include +#include + +namespace bc7decomp +{ + +enum eNoClamp { cNoClamp }; + +template inline S clamp(S value, S low, S high) { return (value < low) ? low : ((value > high) ? high : value); } + +class color_rgba +{ +public: + union + { + uint8_t m_comps[4]; + + struct + { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + }; + }; + + inline color_rgba() + { + static_assert(sizeof(*this) == 4, "sizeof(*this) != 4"); + } + + inline color_rgba(int y) + { + set(y); + } + + inline color_rgba(int y, int na) + { + set(y, na); + } + + inline color_rgba(int sr, int sg, int sb, int sa) + { + set(sr, sg, sb, sa); + } + + inline color_rgba(eNoClamp, int sr, int sg, int sb, int sa) + { + set_noclamp_rgba((uint8_t)sr, (uint8_t)sg, (uint8_t)sb, (uint8_t)sa); + } + + inline color_rgba& set_noclamp_y(int y) + { + m_comps[0] = (uint8_t)y; + m_comps[1] = (uint8_t)y; + m_comps[2] = (uint8_t)y; + m_comps[3] = (uint8_t)255; + return *this; + } + + inline color_rgba &set_noclamp_rgba(int sr, int sg, int sb, int sa) + { + m_comps[0] = (uint8_t)sr; + m_comps[1] = (uint8_t)sg; + m_comps[2] = (uint8_t)sb; + m_comps[3] = (uint8_t)sa; + return *this; + } + + inline color_rgba &set(int y) + { + m_comps[0] = static_cast(clamp(y, 0, 255)); + m_comps[1] = m_comps[0]; + m_comps[2] = m_comps[0]; + m_comps[3] = 255; + return *this; + } + + inline color_rgba &set(int y, int na) + { + m_comps[0] = static_cast(clamp(y, 0, 255)); + m_comps[1] = m_comps[0]; + m_comps[2] = m_comps[0]; + m_comps[3] = static_cast(clamp(na, 0, 255)); + return *this; + } + + inline color_rgba &set(int sr, int sg, int sb, int sa) + { + m_comps[0] = static_cast(clamp(sr, 0, 255)); + m_comps[1] = static_cast(clamp(sg, 0, 255)); + m_comps[2] = static_cast(clamp(sb, 0, 255)); + m_comps[3] = static_cast(clamp(sa, 0, 255)); + return *this; + } + + inline color_rgba &set_rgb(int sr, int sg, int sb) + { + m_comps[0] = static_cast(clamp(sr, 0, 255)); + m_comps[1] = static_cast(clamp(sg, 0, 255)); + m_comps[2] = static_cast(clamp(sb, 0, 255)); + return *this; + } + + inline color_rgba &set_rgb(const color_rgba &other) + { + r = other.r; + g = other.g; + b = other.b; + return *this; + } + + inline const uint8_t &operator[] (uint32_t index) const { assert(index < 4); return m_comps[index]; } + inline uint8_t &operator[] (uint32_t index) { assert(index < 4); return m_comps[index]; } + + inline void clear() + { + m_comps[0] = 0; + m_comps[1] = 0; + m_comps[2] = 0; + m_comps[3] = 0; + } + + inline bool operator== (const color_rgba &rhs) const + { + if (m_comps[0] != rhs.m_comps[0]) return false; + if (m_comps[1] != rhs.m_comps[1]) return false; + if (m_comps[2] != rhs.m_comps[2]) return false; + if (m_comps[3] != rhs.m_comps[3]) return false; + return true; + } + + inline bool operator!= (const color_rgba &rhs) const + { + return !(*this == rhs); + } + + inline bool operator<(const color_rgba &rhs) const + { + for (int i = 0; i < 4; i++) + { + if (m_comps[i] < rhs.m_comps[i]) + return true; + else if (m_comps[i] != rhs.m_comps[i]) + return false; + } + return false; + } + + inline int get_601_luma() const { return (19595U * m_comps[0] + 38470U * m_comps[1] + 7471U * m_comps[2] + 32768U) >> 16U; } + inline int get_709_luma() const { return (13938U * m_comps[0] + 46869U * m_comps[1] + 4729U * m_comps[2] + 32768U) >> 16U; } + inline int get_luma(bool luma_601) const { return luma_601 ? get_601_luma() : get_709_luma(); } + + static color_rgba comp_min(const color_rgba& a, const color_rgba& b) { return color_rgba(std::min(a[0], b[0]), std::min(a[1], b[1]), std::min(a[2], b[2]), std::min(a[3], b[3])); } + static color_rgba comp_max(const color_rgba& a, const color_rgba& b) { return color_rgba(std::max(a[0], b[0]), std::max(a[1], b[1]), std::max(a[2], b[2]), std::max(a[3], b[3])); } +}; + +bool unpack_bc7(const void *pBlock, color_rgba *pPixels); + +} // namespace bc7decomp + +namespace bc7decomp_ref +{ + bool unpack_bc7(const void* pBlock, bc7decomp::color_rgba* pPixels); +} // namespace bc7decomp_ref + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/src/ktx.imageio/bc7enc-rdo/bc7enc.cpp b/src/ktx.imageio/bc7enc-rdo/bc7enc.cpp new file mode 100644 index 0000000000..2cc00d2ebf --- /dev/null +++ b/src/ktx.imageio/bc7enc-rdo/bc7enc.cpp @@ -0,0 +1,2581 @@ +// clang-format off +// File: bc7enc.c - Richard Geldreich, Jr. 3/31/2020 - MIT license or public domain (see end of file) +// Currently supports modes 1, 6 for RGB blocks, and modes 5, 6, 7 for RGBA blocks. +#include "bc7enc.h" +#include +#include +#include +#include +#include + +// Helpers +static inline int32_t clampi(int32_t value, int32_t low, int32_t high) { if (value < low) value = low; else if (value > high) value = high; return value; } +static inline float clampf(float value, float low, float high) { if (value < low) value = low; else if (value > high) value = high; return value; } +static inline float saturate(float value) { return clampf(value, 0, 1.0f); } +static inline uint8_t minimumub(uint8_t a, uint8_t b) { return (a < b) ? a : b; } +static inline int32_t minimumi(int32_t a, int32_t b) { return (a < b) ? a : b; } +static inline uint32_t minimumu(uint32_t a, uint32_t b) { return (a < b) ? a : b; } +static inline float minimumf(float a, float b) { return (a < b) ? a : b; } +static inline uint8_t maximumub(uint8_t a, uint8_t b) { return (a > b) ? a : b; } +static inline uint32_t maximumu(uint32_t a, uint32_t b) { return (a > b) ? a : b; } +static inline int32_t maximumi(int32_t a, int32_t b) { return (a > b) ? a : b; } +static inline float maximumf(float a, float b) { return (a > b) ? a : b; } +static inline int squarei(int i) { return i * i; } +static inline float squaref(float i) { return i * i; } +template inline T0 lerp(T0 a, T0 b, T1 c) { return a + (b - a) * c; } + +static inline int32_t iabs32(int32_t v) { uint32_t msk = v >> 31; return (v ^ msk) - msk; } +static inline void swapub(uint8_t* a, uint8_t* b) { uint8_t t = *a; *a = *b; *b = t; } +static inline void swapu(uint32_t* a, uint32_t* b) { uint32_t t = *a; *a = *b; *b = t; } +static inline void swapf(float* a, float* b) { float t = *a; *a = *b; *b = t; } + +struct vec4F { float m_c[4]; }; + +static inline color_rgba *color_quad_u8_set_clamped(color_rgba *pRes, int32_t r, int32_t g, int32_t b, int32_t a) { pRes->m_c[0] = (uint8_t)clampi(r, 0, 255); pRes->m_c[1] = (uint8_t)clampi(g, 0, 255); pRes->m_c[2] = (uint8_t)clampi(b, 0, 255); pRes->m_c[3] = (uint8_t)clampi(a, 0, 255); return pRes; } +static inline color_rgba *color_quad_u8_set(color_rgba *pRes, int32_t r, int32_t g, int32_t b, int32_t a) { assert((uint32_t)(r | g | b | a) <= 255); pRes->m_c[0] = (uint8_t)r; pRes->m_c[1] = (uint8_t)g; pRes->m_c[2] = (uint8_t)b; pRes->m_c[3] = (uint8_t)a; return pRes; } +static inline bool color_quad_u8_notequals(const color_rgba *pLHS, const color_rgba *pRHS) { return (pLHS->m_c[0] != pRHS->m_c[0]) || (pLHS->m_c[1] != pRHS->m_c[1]) || (pLHS->m_c[2] != pRHS->m_c[2]) || (pLHS->m_c[3] != pRHS->m_c[3]); } +static inline vec4F *vec4F_set_scalar(vec4F *pV, float x) { pV->m_c[0] = x; pV->m_c[1] = x; pV->m_c[2] = x; pV->m_c[3] = x; return pV; } +static inline vec4F *vec4F_set(vec4F *pV, float x, float y, float z, float w) { pV->m_c[0] = x; pV->m_c[1] = y; pV->m_c[2] = z; pV->m_c[3] = w; return pV; } +static inline vec4F *vec4F_saturate_in_place(vec4F *pV) { pV->m_c[0] = saturate(pV->m_c[0]); pV->m_c[1] = saturate(pV->m_c[1]); pV->m_c[2] = saturate(pV->m_c[2]); pV->m_c[3] = saturate(pV->m_c[3]); return pV; } +static inline vec4F vec4F_saturate(const vec4F *pV) { vec4F res; res.m_c[0] = saturate(pV->m_c[0]); res.m_c[1] = saturate(pV->m_c[1]); res.m_c[2] = saturate(pV->m_c[2]); res.m_c[3] = saturate(pV->m_c[3]); return res; } +static inline vec4F vec4F_from_color(const color_rgba *pC) { vec4F res; vec4F_set(&res, pC->m_c[0], pC->m_c[1], pC->m_c[2], pC->m_c[3]); return res; } +static inline vec4F vec4F_add(const vec4F *pLHS, const vec4F *pRHS) { vec4F res; vec4F_set(&res, pLHS->m_c[0] + pRHS->m_c[0], pLHS->m_c[1] + pRHS->m_c[1], pLHS->m_c[2] + pRHS->m_c[2], pLHS->m_c[3] + pRHS->m_c[3]); return res; } +static inline vec4F vec4F_sub(const vec4F *pLHS, const vec4F *pRHS) { vec4F res; vec4F_set(&res, pLHS->m_c[0] - pRHS->m_c[0], pLHS->m_c[1] - pRHS->m_c[1], pLHS->m_c[2] - pRHS->m_c[2], pLHS->m_c[3] - pRHS->m_c[3]); return res; } +static inline float vec4F_dot(const vec4F *pLHS, const vec4F *pRHS) { return pLHS->m_c[0] * pRHS->m_c[0] + pLHS->m_c[1] * pRHS->m_c[1] + pLHS->m_c[2] * pRHS->m_c[2] + pLHS->m_c[3] * pRHS->m_c[3]; } +static inline vec4F vec4F_mul(const vec4F *pLHS, float s) { vec4F res; vec4F_set(&res, pLHS->m_c[0] * s, pLHS->m_c[1] * s, pLHS->m_c[2] * s, pLHS->m_c[3] * s); return res; } +static inline vec4F *vec4F_normalize_in_place(vec4F *pV) { float s = pV->m_c[0] * pV->m_c[0] + pV->m_c[1] * pV->m_c[1] + pV->m_c[2] * pV->m_c[2] + pV->m_c[3] * pV->m_c[3]; if (s != 0.0f) { s = 1.0f / sqrtf(s); pV->m_c[0] *= s; pV->m_c[1] *= s; pV->m_c[2] *= s; pV->m_c[3] *= s; } return pV; } + +// Various BC7 tables +static const uint32_t g_bc7_weights2[4] = { 0, 21, 43, 64 }; +static const uint32_t g_bc7_weights3[8] = { 0, 9, 18, 27, 37, 46, 55, 64 }; +static const uint32_t g_bc7_weights4[16] = { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 }; +// Precomputed weight constants used during least fit determination. For each entry in g_bc7_weights[]: w * w, (1.0f - w) * w, (1.0f - w) * (1.0f - w), w +static const float g_bc7_weights2x[4 * 4] = { 0.000000f, 0.000000f, 1.000000f, 0.000000f, 0.107666f, 0.220459f, 0.451416f, 0.328125f, 0.451416f, 0.220459f, 0.107666f, 0.671875f, 1.000000f, 0.000000f, 0.000000f, 1.000000f }; +static const float g_bc7_weights3x[8 * 4] = { 0.000000f, 0.000000f, 1.000000f, 0.000000f, 0.019775f, 0.120850f, 0.738525f, 0.140625f, 0.079102f, 0.202148f, 0.516602f, 0.281250f, 0.177979f, 0.243896f, 0.334229f, 0.421875f, 0.334229f, 0.243896f, 0.177979f, 0.578125f, 0.516602f, 0.202148f, + 0.079102f, 0.718750f, 0.738525f, 0.120850f, 0.019775f, 0.859375f, 1.000000f, 0.000000f, 0.000000f, 1.000000f }; +static const float g_bc7_weights4x[16 * 4] = { 0.000000f, 0.000000f, 1.000000f, 0.000000f, 0.003906f, 0.058594f, 0.878906f, 0.062500f, 0.019775f, 0.120850f, 0.738525f, 0.140625f, 0.041260f, 0.161865f, 0.635010f, 0.203125f, 0.070557f, 0.195068f, 0.539307f, 0.265625f, 0.107666f, 0.220459f, + 0.451416f, 0.328125f, 0.165039f, 0.241211f, 0.352539f, 0.406250f, 0.219727f, 0.249023f, 0.282227f, 0.468750f, 0.282227f, 0.249023f, 0.219727f, 0.531250f, 0.352539f, 0.241211f, 0.165039f, 0.593750f, 0.451416f, 0.220459f, 0.107666f, 0.671875f, 0.539307f, 0.195068f, 0.070557f, 0.734375f, + 0.635010f, 0.161865f, 0.041260f, 0.796875f, 0.738525f, 0.120850f, 0.019775f, 0.859375f, 0.878906f, 0.058594f, 0.003906f, 0.937500f, 1.000000f, 0.000000f, 0.000000f, 1.000000f }; + +static const uint8_t g_bc7_partition1[16] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; +static const uint8_t g_bc7_partition2[64 * 16] = +{ + 0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1, 0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1, 0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1, 0,0,0,1,0,0,1,1,0,0,1,1,0,1,1,1, 0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,1,0,1,1,1,1,1,1,1, 0,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,1,0,0,1,1,0,1,1,1, + 0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1, 0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1, 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1, + 0,0,0,0,1,0,0,0,1,1,1,0,1,1,1,1, 0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,0, 0,1,1,1,0,0,1,1,0,0,0,1,0,0,0,0, 0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,1,0,0,0,1,1,0,0,1,1,1,0, 0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,0, 0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1, + 0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0, 0,0,0,0,1,0,0,0,1,0,0,0,1,1,0,0, 0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0, 0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0, 0,0,0,1,0,1,1,1,1,1,1,0,1,0,0,0, 0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0, 0,1,1,1,0,0,0,1,1,0,0,0,1,1,1,0, 0,0,1,1,1,0,0,1,1,0,0,1,1,1,0,0, + 0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1, 0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1, 0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0, 0,0,1,1,0,0,1,1,1,1,0,0,1,1,0,0, 0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0, 0,1,0,1,0,1,0,1,1,0,1,0,1,0,1,0, 0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1, 0,1,0,1,1,0,1,0,1,0,1,0,0,1,0,1, + 0,1,1,1,0,0,1,1,1,1,0,0,1,1,1,0, 0,0,0,1,0,0,1,1,1,1,0,0,1,0,0,0, 0,0,1,1,0,0,1,0,0,1,0,0,1,1,0,0, 0,0,1,1,1,0,1,1,1,1,0,1,1,1,0,0, 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0, 0,0,1,1,1,1,0,0,1,1,0,0,0,0,1,1, 0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1, 0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0, + 0,1,0,0,1,1,1,0,0,1,0,0,0,0,0,0, 0,0,1,0,0,1,1,1,0,0,1,0,0,0,0,0, 0,0,0,0,0,0,1,0,0,1,1,1,0,0,1,0, 0,0,0,0,0,1,0,0,1,1,1,0,0,1,0,0, 0,1,1,0,1,1,0,0,1,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,1, 0,1,1,0,0,0,1,1,1,0,0,1,1,1,0,0, 0,0,1,1,1,0,0,1,1,1,0,0,0,1,1,0, + 0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,1, 0,1,1,0,0,0,1,1,0,0,1,1,1,0,0,1, 0,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1, 0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,1, 0,0,0,0,1,1,1,1,0,0,1,1,0,0,1,1, 0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,0, 0,0,1,0,0,0,1,0,1,1,1,0,1,1,1,0, 0,1,0,0,0,1,0,0,0,1,1,1,0,1,1,1 +}; + +static const uint8_t g_bc7_partition3[64 * 16] = +{ + 0,0,1,1,0,0,1,1,0,2,2,1,2,2,2,2, 0,0,0,1,0,0,1,1,2,2,1,1,2,2,2,1, 0,0,0,0,2,0,0,1,2,2,1,1,2,2,1,1, 0,2,2,2,0,0,2,2,0,0,1,1,0,1,1,1, 0,0,0,0,0,0,0,0,1,1,2,2,1,1,2,2, 0,0,1,1,0,0,1,1,0,0,2,2,0,0,2,2, 0,0,2,2,0,0,2,2,1,1,1,1,1,1,1,1, 0,0,1,1,0,0,1,1,2,2,1,1,2,2,1,1, + 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2, 0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2, 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2, 0,0,1,2,0,0,1,2,0,0,1,2,0,0,1,2, 0,1,1,2,0,1,1,2,0,1,1,2,0,1,1,2, 0,1,2,2,0,1,2,2,0,1,2,2,0,1,2,2, 0,0,1,1,0,1,1,2,1,1,2,2,1,2,2,2, 0,0,1,1,2,0,0,1,2,2,0,0,2,2,2,0, + 0,0,0,1,0,0,1,1,0,1,1,2,1,1,2,2, 0,1,1,1,0,0,1,1,2,0,0,1,2,2,0,0, 0,0,0,0,1,1,2,2,1,1,2,2,1,1,2,2, 0,0,2,2,0,0,2,2,0,0,2,2,1,1,1,1, 0,1,1,1,0,1,1,1,0,2,2,2,0,2,2,2, 0,0,0,1,0,0,0,1,2,2,2,1,2,2,2,1, 0,0,0,0,0,0,1,1,0,1,2,2,0,1,2,2, 0,0,0,0,1,1,0,0,2,2,1,0,2,2,1,0, + 0,1,2,2,0,1,2,2,0,0,1,1,0,0,0,0, 0,0,1,2,0,0,1,2,1,1,2,2,2,2,2,2, 0,1,1,0,1,2,2,1,1,2,2,1,0,1,1,0, 0,0,0,0,0,1,1,0,1,2,2,1,1,2,2,1, 0,0,2,2,1,1,0,2,1,1,0,2,0,0,2,2, 0,1,1,0,0,1,1,0,2,0,0,2,2,2,2,2, 0,0,1,1,0,1,2,2,0,1,2,2,0,0,1,1, 0,0,0,0,2,0,0,0,2,2,1,1,2,2,2,1, + 0,0,0,0,0,0,0,2,1,1,2,2,1,2,2,2, 0,2,2,2,0,0,2,2,0,0,1,2,0,0,1,1, 0,0,1,1,0,0,1,2,0,0,2,2,0,2,2,2, 0,1,2,0,0,1,2,0,0,1,2,0,0,1,2,0, 0,0,0,0,1,1,1,1,2,2,2,2,0,0,0,0, 0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0, 0,1,2,0,2,0,1,2,1,2,0,1,0,1,2,0, 0,0,1,1,2,2,0,0,1,1,2,2,0,0,1,1, + 0,0,1,1,1,1,2,2,2,2,0,0,0,0,1,1, 0,1,0,1,0,1,0,1,2,2,2,2,2,2,2,2, 0,0,0,0,0,0,0,0,2,1,2,1,2,1,2,1, 0,0,2,2,1,1,2,2,0,0,2,2,1,1,2,2, 0,0,2,2,0,0,1,1,0,0,2,2,0,0,1,1, 0,2,2,0,1,2,2,1,0,2,2,0,1,2,2,1, 0,1,0,1,2,2,2,2,2,2,2,2,0,1,0,1, 0,0,0,0,2,1,2,1,2,1,2,1,2,1,2,1, + 0,1,0,1,0,1,0,1,0,1,0,1,2,2,2,2, 0,2,2,2,0,1,1,1,0,2,2,2,0,1,1,1, 0,0,0,2,1,1,1,2,0,0,0,2,1,1,1,2, 0,0,0,0,2,1,1,2,2,1,1,2,2,1,1,2, 0,2,2,2,0,1,1,1,0,1,1,1,0,2,2,2, 0,0,0,2,1,1,1,2,1,1,1,2,0,0,0,2, 0,1,1,0,0,1,1,0,0,1,1,0,2,2,2,2, 0,0,0,0,0,0,0,0,2,1,1,2,2,1,1,2, + 0,1,1,0,0,1,1,0,2,2,2,2,2,2,2,2, 0,0,2,2,0,0,1,1,0,0,1,1,0,0,2,2, 0,0,2,2,1,1,2,2,1,1,2,2,0,0,2,2, 0,0,0,0,0,0,0,0,0,0,0,0,2,1,1,2, 0,0,0,2,0,0,0,1,0,0,0,2,0,0,0,1, 0,2,2,2,1,2,2,2,0,2,2,2,1,2,2,2, 0,1,0,1,2,2,2,2,2,2,2,2,2,2,2,2, 0,1,1,1,2,0,1,1,2,2,0,1,2,2,2,0, +}; + +static const uint8_t g_bc7_table_anchor_index_third_subset_1[64] = +{ + 3, 3,15,15, 8, 3,15,15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8,15, 3, 3, 6,10, 5, 8, 8, 6, 8, 5,15,15, 8,15, 3, 5, 6,10, 8,15, 15, 3,15, 5,15,15,15,15, 3,15, 5, 5, 5, 8, 5,10, 5,10, 8,13,15,12, 3, 3 +}; + +static const uint8_t g_bc7_table_anchor_index_third_subset_2[64] = +{ + 15, 8, 8, 3,15,15, 3, 8, 15,15,15,15,15,15,15, 8, 15, 8,15, 3,15, 8,15, 8, 3,15, 6,10,15,15,10, 8, 15, 3,15,10,10, 8, 9,10, 6,15, 8,15, 3, 6, 6, 8, 15, 3,15,15,15,15,15,15, 15,15,15,15, 3,15,15, 8 +}; + +static const uint8_t g_bc7_table_anchor_index_second_subset[64] = { 15,15,15,15,15,15,15,15, 15,15,15,15,15,15,15,15, 15, 2, 8, 2, 2, 8, 8,15, 2, 8, 2, 2, 8, 8, 2, 2, 15,15, 6, 8, 2, 8,15,15, 2, 8, 2, 2, 2,15,15, 6, 6, 2, 6, 8,15,15, 2, 2, 15,15,15,15,15, 2, 2,15 }; +static const uint8_t g_bc7_num_subsets[8] = { 3, 2, 3, 2, 1, 1, 1, 2 }; +static const uint8_t g_bc7_partition_bits[8] = { 4, 6, 6, 6, 0, 0, 0, 6 }; +static const uint8_t g_bc7_color_index_bitcount[8] = { 3, 3, 2, 2, 2, 2, 4, 2 }; +static int get_bc7_color_index_size(int mode, int index_selection_bit) { return g_bc7_color_index_bitcount[mode] + index_selection_bit; } +static uint8_t g_bc7_alpha_index_bitcount[8] = { 0, 0, 0, 0, 3, 2, 4, 2 }; +static int get_bc7_alpha_index_size(int mode, int index_selection_bit) { return g_bc7_alpha_index_bitcount[mode] - index_selection_bit; } +static const uint8_t g_bc7_mode_has_p_bits[8] = { 1, 1, 0, 1, 0, 0, 1, 1 }; +static const uint8_t g_bc7_mode_has_shared_p_bits[8] = { 0, 1, 0, 0, 0, 0, 0, 0 }; +static const uint8_t g_bc7_color_precision_table[8] = { 4, 6, 5, 7, 5, 7, 7, 5 }; +static const int8_t g_bc7_alpha_precision_table[8] = { 0, 0, 0, 0, 6, 8, 7, 5 }; +static bool get_bc7_mode_has_seperate_alpha_selectors(int mode) { return (mode == 4) || (mode == 5); } + +typedef struct { uint16_t m_error; uint8_t m_lo; uint8_t m_hi; } endpoint_err; + +static endpoint_err g_bc7_mode_1_optimal_endpoints[256][2]; // [c][pbit] +static const uint32_t BC7ENC_MODE_1_OPTIMAL_INDEX = 2; + +static endpoint_err g_bc7_mode_7_optimal_endpoints[256][2][2]; // [c][pbit][hp][lp] +const uint32_t BC7E_MODE_7_OPTIMAL_INDEX = 1; + +static float g_mode1_rgba_midpoints[64][2]; +static float g_mode5_rgba_midpoints[128]; +static float g_mode7_rgba_midpoints[32][2]; + +static uint8_t g_mode6_reduced_quant[2048][2]; + +static bool g_initialized; + +// Initialize the lookup table used for optimal single color compression in mode 1/7. Must be called before encoding. +void bc7enc_compress_block_init() +{ + if (g_initialized) + return; + + // Mode 7 endpoint midpoints + for (uint32_t p = 0; p < 2; p++) + { + for (uint32_t i = 0; i < 32; i++) + { + uint32_t vl = ((i << 1) | p) << 2; + vl |= (vl >> 6); + float lo = vl / 255.0f; + + uint32_t vh = ((minimumi(31, (i + 1)) << 1) | p) << 2; + vh |= (vh >> 6); + float hi = vh / 255.0f; + + //g_mode7_quant_values[i][p] = lo; + if (i == 31) + g_mode7_rgba_midpoints[i][p] = 1.0f; + else + g_mode7_rgba_midpoints[i][p] = (lo + hi) / 2.0f; + } + } + + // Mode 1 endpoint midpoints + for (uint32_t p = 0; p < 2; p++) + { + for (uint32_t i = 0; i < 64; i++) + { + uint32_t vl = ((i << 1) | p) << 1; + vl |= (vl >> 7); + float lo = vl / 255.0f; + + uint32_t vh = ((minimumi(63, (i + 1)) << 1) | p) << 1; + vh |= (vh >> 7); + float hi = vh / 255.0f; + + //g_mode1_quant_values[i][p] = lo; + if (i == 63) + g_mode1_rgba_midpoints[i][p] = 1.0f; + else + g_mode1_rgba_midpoints[i][p] = (lo + hi) / 2.0f; + } + } + + // Mode 5 endpoint midpoints + for (uint32_t i = 0; i < 128; i++) + { + uint32_t vl = (i << 1); + vl |= (vl >> 7); + float lo = vl / 255.0f; + + uint32_t vh = minimumi(127, i + 1) << 1; + vh |= (vh >> 7); + float hi = vh / 255.0f; + + if (i == 127) + g_mode5_rgba_midpoints[i] = 1.0f; + else + g_mode5_rgba_midpoints[i] = (lo + hi) / 2.0f; + } + + for (uint32_t p = 0; p < 2; p++) + { + for (uint32_t i = 0; i < 2048; i++) + { + float f = i / 2047.0f; + + float best_err = 1e+9f; + int best_index = 0; + for (int j = 0; j < 64; j++) + { + int ik = (j * 127 + 31) / 63; + float k = ((ik << 1) + p) / 255.0f; + + float e = fabsf(k - f); + if (e < best_err) + { + best_err = e; + best_index = ik; + } + } + + g_mode6_reduced_quant[i][p] = (uint8_t)best_index; + } + } // p + + // Mode 1 + for (int c = 0; c < 256; c++) + { + for (uint32_t lp = 0; lp < 2; lp++) + { + endpoint_err best; + best.m_error = (uint16_t)UINT16_MAX; + for (uint32_t l = 0; l < 64; l++) + { + uint32_t low = ((l << 1) | lp) << 1; + low |= (low >> 7); + for (uint32_t h = 0; h < 64; h++) + { + uint32_t high = ((h << 1) | lp) << 1; + high |= (high >> 7); + const int k = (low * (64 - g_bc7_weights3[BC7ENC_MODE_1_OPTIMAL_INDEX]) + high * g_bc7_weights3[BC7ENC_MODE_1_OPTIMAL_INDEX] + 32) >> 6; + const int err = (k - c) * (k - c); + if (err < best.m_error) + { + best.m_error = (uint16_t)err; + best.m_lo = (uint8_t)l; + best.m_hi = (uint8_t)h; + } + } // h + } // l + g_bc7_mode_1_optimal_endpoints[c][lp] = best; + } // lp + } // c + + // Mode 7: 555.1 2-bit indices + for (int c = 0; c < 256; c++) + { + for (uint32_t hp = 0; hp < 2; hp++) + { + for (uint32_t lp = 0; lp < 2; lp++) + { + endpoint_err best; + best.m_error = (uint16_t)UINT16_MAX; + best.m_lo = 0; + best.m_hi = 0; + + for (uint32_t l = 0; l < 32; l++) + { + uint32_t low = ((l << 1) | lp) << 2; + low |= (low >> 6); + + for (uint32_t h = 0; h < 32; h++) + { + uint32_t high = ((h << 1) | hp) << 2; + high |= (high >> 6); + + const int k = (low * (64 - g_bc7_weights2[BC7E_MODE_7_OPTIMAL_INDEX]) + high * g_bc7_weights2[BC7E_MODE_7_OPTIMAL_INDEX] + 32) >> 6; + + const int err = (k - c) * (k - c); + if (err < best.m_error) + { + best.m_error = (uint16_t)err; + best.m_lo = (uint8_t)l; + best.m_hi = (uint8_t)h; + } + } // h + } // l + + g_bc7_mode_7_optimal_endpoints[c][hp][lp] = best; + + } // hp + + } // lp + + } // c + + g_initialized = true; +} + +static void compute_least_squares_endpoints_rgba(uint32_t N, const uint8_t *pSelectors, const vec4F *pSelector_weights, vec4F *pXl, vec4F *pXh, const color_rgba *pColors) +{ + // Least squares using normal equations: http://www.cs.cornell.edu/~bindel/class/cs3220-s12/notes/lec10.pdf + // I did this in matrix form first, expanded out all the ops, then optimized it a bit. + float z00 = 0.0f, z01 = 0.0f, z10 = 0.0f, z11 = 0.0f; + float q00_r = 0.0f, q10_r = 0.0f, t_r = 0.0f; + float q00_g = 0.0f, q10_g = 0.0f, t_g = 0.0f; + float q00_b = 0.0f, q10_b = 0.0f, t_b = 0.0f; + float q00_a = 0.0f, q10_a = 0.0f, t_a = 0.0f; + for (uint32_t i = 0; i < N; i++) + { + const uint32_t sel = pSelectors[i]; + z00 += pSelector_weights[sel].m_c[0]; + z10 += pSelector_weights[sel].m_c[1]; + z11 += pSelector_weights[sel].m_c[2]; + float w = pSelector_weights[sel].m_c[3]; + q00_r += w * pColors[i].m_c[0]; t_r += pColors[i].m_c[0]; + q00_g += w * pColors[i].m_c[1]; t_g += pColors[i].m_c[1]; + q00_b += w * pColors[i].m_c[2]; t_b += pColors[i].m_c[2]; + q00_a += w * pColors[i].m_c[3]; t_a += pColors[i].m_c[3]; + } + + q10_r = t_r - q00_r; + q10_g = t_g - q00_g; + q10_b = t_b - q00_b; + q10_a = t_a - q00_a; + + z01 = z10; + + float det = z00 * z11 - z01 * z10; + if (det != 0.0f) + det = 1.0f / det; + + float iz00, iz01, iz10, iz11; + iz00 = z11 * det; + iz01 = -z01 * det; + iz10 = -z10 * det; + iz11 = z00 * det; + + pXl->m_c[0] = (float)(iz00 * q00_r + iz01 * q10_r); pXh->m_c[0] = (float)(iz10 * q00_r + iz11 * q10_r); + pXl->m_c[1] = (float)(iz00 * q00_g + iz01 * q10_g); pXh->m_c[1] = (float)(iz10 * q00_g + iz11 * q10_g); + pXl->m_c[2] = (float)(iz00 * q00_b + iz01 * q10_b); pXh->m_c[2] = (float)(iz10 * q00_b + iz11 * q10_b); + pXl->m_c[3] = (float)(iz00 * q00_a + iz01 * q10_a); pXh->m_c[3] = (float)(iz10 * q00_a + iz11 * q10_a); + + for (uint32_t c = 0; c < 4; c++) + { + if ((pXl->m_c[c] < 0.0f) || (pXh->m_c[c] > 255.0f)) + { + uint32_t lo_v = UINT32_MAX, hi_v = 0; + for (uint32_t i = 0; i < N; i++) + { + lo_v = minimumu(lo_v, pColors[i].m_c[c]); + hi_v = maximumu(hi_v, pColors[i].m_c[c]); + } + + if (lo_v == hi_v) + { + pXl->m_c[c] = (float)lo_v; + pXh->m_c[c] = (float)hi_v; + } + } + } +} + +static void compute_least_squares_endpoints_rgb(uint32_t N, const uint8_t *pSelectors, const vec4F *pSelector_weights, vec4F *pXl, vec4F *pXh, const color_rgba*pColors) +{ + float z00 = 0.0f, z01 = 0.0f, z10 = 0.0f, z11 = 0.0f; + float q00_r = 0.0f, q10_r = 0.0f, t_r = 0.0f; + float q00_g = 0.0f, q10_g = 0.0f, t_g = 0.0f; + float q00_b = 0.0f, q10_b = 0.0f, t_b = 0.0f; + for (uint32_t i = 0; i < N; i++) + { + const uint32_t sel = pSelectors[i]; + z00 += pSelector_weights[sel].m_c[0]; + z10 += pSelector_weights[sel].m_c[1]; + z11 += pSelector_weights[sel].m_c[2]; + float w = pSelector_weights[sel].m_c[3]; + q00_r += w * pColors[i].m_c[0]; t_r += pColors[i].m_c[0]; + q00_g += w * pColors[i].m_c[1]; t_g += pColors[i].m_c[1]; + q00_b += w * pColors[i].m_c[2]; t_b += pColors[i].m_c[2]; + } + + q10_r = t_r - q00_r; + q10_g = t_g - q00_g; + q10_b = t_b - q00_b; + + z01 = z10; + + float det = z00 * z11 - z01 * z10; + if (det != 0.0f) + det = 1.0f / det; + + float iz00, iz01, iz10, iz11; + iz00 = z11 * det; + iz01 = -z01 * det; + iz10 = -z10 * det; + iz11 = z00 * det; + + pXl->m_c[0] = (float)(iz00 * q00_r + iz01 * q10_r); pXh->m_c[0] = (float)(iz10 * q00_r + iz11 * q10_r); + pXl->m_c[1] = (float)(iz00 * q00_g + iz01 * q10_g); pXh->m_c[1] = (float)(iz10 * q00_g + iz11 * q10_g); + pXl->m_c[2] = (float)(iz00 * q00_b + iz01 * q10_b); pXh->m_c[2] = (float)(iz10 * q00_b + iz11 * q10_b); + pXl->m_c[3] = 255.0f; pXh->m_c[3] = 255.0f; + + for (uint32_t c = 0; c < 3; c++) + { + if ((pXl->m_c[c] < 0.0f) || (pXh->m_c[c] > 255.0f)) + { + uint32_t lo_v = UINT32_MAX, hi_v = 0; + for (uint32_t i = 0; i < N; i++) + { + lo_v = minimumu(lo_v, pColors[i].m_c[c]); + hi_v = maximumu(hi_v, pColors[i].m_c[c]); + } + + if (lo_v == hi_v) + { + pXl->m_c[c] = (float)lo_v; + pXh->m_c[c] = (float)hi_v; + } + } + } +} + +static void compute_least_squares_endpoints_a(uint32_t N, const uint8_t* pSelectors, const vec4F* pSelector_weights, float* pXl, float* pXh, const color_rgba *pColors) +{ + // Least squares using normal equations: http://www.cs.cornell.edu/~bindel/class/cs3220-s12/notes/lec10.pdf + // I did this in matrix form first, expanded out all the ops, then optimized it a bit. + float z00 = 0.0f, z01 = 0.0f, z10 = 0.0f, z11 = 0.0f; + float q00_a = 0.0f, q10_a = 0.0f, t_a = 0.0f; + for (uint32_t i = 0; i < N; i++) + { + const uint32_t sel = pSelectors[i]; + + z00 += pSelector_weights[sel].m_c[0]; + z10 += pSelector_weights[sel].m_c[1]; + z11 += pSelector_weights[sel].m_c[2]; + + float w = pSelector_weights[sel].m_c[3]; + + q00_a += w * pColors[i].m_c[3]; t_a += pColors[i].m_c[3]; + } + + q10_a = t_a - q00_a; + + z01 = z10; + + float det = z00 * z11 - z01 * z10; + if (det != 0.0f) + det = 1.0f / det; + + float iz00, iz01, iz10, iz11; + iz00 = z11 * det; + iz01 = -z01 * det; + iz10 = -z10 * det; + iz11 = z00 * det; + + *pXl = (float)(iz00 * q00_a + iz01 * q10_a); *pXh = (float)(iz10 * q00_a + iz11 * q10_a); + + if ((*pXl < 0.0f) || (*pXh > 255.0f)) + { + uint32_t lo_v = UINT32_MAX, hi_v = 0; + for (uint32_t i = 0; i < N; i++) + { + lo_v = minimumu(lo_v, pColors[i].m_c[3]); + hi_v = maximumu(hi_v, pColors[i].m_c[3]); + } + + if (lo_v == hi_v) + { + *pXl = (float)lo_v; + *pXh = (float)hi_v; + } + } +} + +struct color_cell_compressor_params +{ + uint32_t m_num_pixels; + const color_rgba *m_pPixels; + uint32_t m_num_selector_weights; + const uint32_t *m_pSelector_weights; + const vec4F *m_pSelector_weightsx; + uint32_t m_comp_bits; + uint32_t m_weights[4]; + bool m_has_alpha; + bool m_has_pbits; + bool m_endpoints_share_pbit; + bool m_perceptual; +}; + +struct color_cell_compressor_results +{ + uint64_t m_best_overall_err; + color_rgba m_low_endpoint; + color_rgba m_high_endpoint; + uint32_t m_pbits[2]; + uint8_t *m_pSelectors; + uint8_t *m_pSelectors_temp; +}; + +static inline color_rgba scale_color(const color_rgba *pC, const color_cell_compressor_params *pParams) +{ + color_rgba results; + + const uint32_t n = pParams->m_comp_bits + (pParams->m_has_pbits ? 1 : 0); + assert((n >= 4) && (n <= 8)); + + for (uint32_t i = 0; i < 4; i++) + { + uint32_t v = pC->m_c[i] << (8 - n); + v |= (v >> n); + assert(v <= 255); + results.m_c[i] = (uint8_t)(v); + } + + return results; +} + +static inline uint64_t compute_color_distance_rgb(const color_rgba *pE1, const color_rgba *pE2, bool perceptual, const uint32_t weights[4]) +{ + int dr, dg, db; + + if (perceptual) + { + const int l1 = pE1->m_c[0] * 109 + pE1->m_c[1] * 366 + pE1->m_c[2] * 37; + const int cr1 = ((int)pE1->m_c[0] << 9) - l1; + const int cb1 = ((int)pE1->m_c[2] << 9) - l1; + const int l2 = pE2->m_c[0] * 109 + pE2->m_c[1] * 366 + pE2->m_c[2] * 37; + const int cr2 = ((int)pE2->m_c[0] << 9) - l2; + const int cb2 = ((int)pE2->m_c[2] << 9) - l2; + dr = (l1 - l2) >> 8; + dg = (cr1 - cr2) >> 8; + db = (cb1 - cb2) >> 8; + } + else + { + dr = (int)pE1->m_c[0] - (int)pE2->m_c[0]; + dg = (int)pE1->m_c[1] - (int)pE2->m_c[1]; + db = (int)pE1->m_c[2] - (int)pE2->m_c[2]; + } + + return weights[0] * (uint32_t)(dr * dr) + weights[1] * (uint32_t)(dg * dg) + weights[2] * (uint32_t)(db * db); +} + +static inline uint64_t compute_color_distance_rgba(const color_rgba *pE1, const color_rgba *pE2, bool perceptual, const uint32_t weights[4]) +{ + int da = (int)pE1->m_c[3] - (int)pE2->m_c[3]; + return compute_color_distance_rgb(pE1, pE2, perceptual, weights) + (weights[3] * (uint32_t)(da * da)); +} + +static uint64_t pack_mode1_to_one_color(const color_cell_compressor_params *pParams, color_cell_compressor_results *pResults, uint32_t r, uint32_t g, uint32_t b, uint8_t *pSelectors) +{ + uint32_t best_err = UINT_MAX; + uint32_t best_p = 0; + + for (uint32_t p = 0; p < 2; p++) + { + uint32_t err = g_bc7_mode_1_optimal_endpoints[r][p].m_error + g_bc7_mode_1_optimal_endpoints[g][p].m_error + g_bc7_mode_1_optimal_endpoints[b][p].m_error; + if (err < best_err) + { + best_err = err; + best_p = p; + if (!best_err) + break; + } + } + + const endpoint_err *pEr = &g_bc7_mode_1_optimal_endpoints[r][best_p]; + const endpoint_err *pEg = &g_bc7_mode_1_optimal_endpoints[g][best_p]; + const endpoint_err *pEb = &g_bc7_mode_1_optimal_endpoints[b][best_p]; + + color_quad_u8_set(&pResults->m_low_endpoint, pEr->m_lo, pEg->m_lo, pEb->m_lo, 0); + color_quad_u8_set(&pResults->m_high_endpoint, pEr->m_hi, pEg->m_hi, pEb->m_hi, 0); + pResults->m_pbits[0] = best_p; + pResults->m_pbits[1] = 0; + + memset(pSelectors, BC7ENC_MODE_1_OPTIMAL_INDEX, pParams->m_num_pixels); + + color_rgba p; + for (uint32_t i = 0; i < 3; i++) + { + uint32_t low = ((pResults->m_low_endpoint.m_c[i] << 1) | pResults->m_pbits[0]) << 1; + low |= (low >> 7); + + uint32_t high = ((pResults->m_high_endpoint.m_c[i] << 1) | pResults->m_pbits[0]) << 1; + high |= (high >> 7); + + p.m_c[i] = (uint8_t)((low * (64 - g_bc7_weights3[BC7ENC_MODE_1_OPTIMAL_INDEX]) + high * g_bc7_weights3[BC7ENC_MODE_1_OPTIMAL_INDEX] + 32) >> 6); + } + p.m_c[3] = 255; + + uint64_t total_err = 0; + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + total_err += compute_color_distance_rgb(&p, &pParams->m_pPixels[i], pParams->m_perceptual, pParams->m_weights); + + pResults->m_best_overall_err = total_err; + + return total_err; +} + +static uint64_t pack_mode7_to_one_color(const color_cell_compressor_params* pParams, color_cell_compressor_results* pResults, uint32_t r, uint32_t g, uint32_t b, uint32_t a, + uint8_t* pSelectors, uint32_t num_pixels, const color_rgba *pPixels) +{ + uint32_t best_err = UINT_MAX; + uint32_t best_p = 0; + + for (uint32_t p = 0; p < 4; p++) + { + uint32_t hi_p = p >> 1; + uint32_t lo_p = p & 1; + uint32_t err = g_bc7_mode_7_optimal_endpoints[r][hi_p][lo_p].m_error + g_bc7_mode_7_optimal_endpoints[g][hi_p][lo_p].m_error + g_bc7_mode_7_optimal_endpoints[b][hi_p][lo_p].m_error + g_bc7_mode_7_optimal_endpoints[a][hi_p][lo_p].m_error; + if (err < best_err) + { + best_err = err; + best_p = p; + if (!best_err) + break; + } + } + + uint32_t best_hi_p = best_p >> 1; + uint32_t best_lo_p = best_p & 1; + + const endpoint_err* pEr = &g_bc7_mode_7_optimal_endpoints[r][best_hi_p][best_lo_p]; + const endpoint_err* pEg = &g_bc7_mode_7_optimal_endpoints[g][best_hi_p][best_lo_p]; + const endpoint_err* pEb = &g_bc7_mode_7_optimal_endpoints[b][best_hi_p][best_lo_p]; + const endpoint_err* pEa = &g_bc7_mode_7_optimal_endpoints[a][best_hi_p][best_lo_p]; + + color_quad_u8_set(&pResults->m_low_endpoint, pEr->m_lo, pEg->m_lo, pEb->m_lo, pEa->m_lo); + color_quad_u8_set(&pResults->m_high_endpoint, pEr->m_hi, pEg->m_hi, pEb->m_hi, pEa->m_hi); + pResults->m_pbits[0] = best_lo_p; + pResults->m_pbits[1] = best_hi_p; + + for (uint32_t i = 0; i < num_pixels; i++) + pSelectors[i] = (uint8_t)BC7E_MODE_7_OPTIMAL_INDEX; + + color_rgba p; + + for (uint32_t i = 0; i < 4; i++) + { + uint32_t low = (pResults->m_low_endpoint.m_c[i] << 1) | pResults->m_pbits[0]; + uint32_t high = (pResults->m_high_endpoint.m_c[i] << 1) | pResults->m_pbits[1]; + + low = (low << 2) | (low >> 6); + high = (high << 2) | (high >> 6); + + p.m_c[i] = (uint8_t)((low * (64 - g_bc7_weights2[BC7E_MODE_7_OPTIMAL_INDEX]) + high * g_bc7_weights2[BC7E_MODE_7_OPTIMAL_INDEX] + 32) >> 6); + } + + uint64_t total_err = 0; + for (uint32_t i = 0; i < num_pixels; i++) + total_err += compute_color_distance_rgba(&p, &pPixels[i], pParams->m_perceptual, pParams->m_weights); + + pResults->m_best_overall_err = total_err; + + return total_err; +} + +static uint64_t evaluate_solution(const color_rgba *pLow, const color_rgba *pHigh, const uint32_t pbits[2], const color_cell_compressor_params *pParams, color_cell_compressor_results *pResults, + const bc7enc_compress_block_params* pComp_params) +{ + color_rgba quantMinColor = *pLow; + color_rgba quantMaxColor = *pHigh; + + if (pParams->m_has_pbits) + { + uint32_t minPBit, maxPBit; + + if (pParams->m_endpoints_share_pbit) + maxPBit = minPBit = pbits[0]; + else + { + minPBit = pbits[0]; + maxPBit = pbits[1]; + } + + quantMinColor.m_c[0] = (uint8_t)((pLow->m_c[0] << 1) | minPBit); + quantMinColor.m_c[1] = (uint8_t)((pLow->m_c[1] << 1) | minPBit); + quantMinColor.m_c[2] = (uint8_t)((pLow->m_c[2] << 1) | minPBit); + quantMinColor.m_c[3] = (uint8_t)((pLow->m_c[3] << 1) | minPBit); + + quantMaxColor.m_c[0] = (uint8_t)((pHigh->m_c[0] << 1) | maxPBit); + quantMaxColor.m_c[1] = (uint8_t)((pHigh->m_c[1] << 1) | maxPBit); + quantMaxColor.m_c[2] = (uint8_t)((pHigh->m_c[2] << 1) | maxPBit); + quantMaxColor.m_c[3] = (uint8_t)((pHigh->m_c[3] << 1) | maxPBit); + } + + color_rgba actualMinColor = scale_color(&quantMinColor, pParams); + color_rgba actualMaxColor = scale_color(&quantMaxColor, pParams); + + const uint32_t N = pParams->m_num_selector_weights; + + color_rgba weightedColors[16]; + weightedColors[0] = actualMinColor; + weightedColors[N - 1] = actualMaxColor; + + const uint32_t nc = pParams->m_has_alpha ? 4 : 3; + for (uint32_t i = 1; i < (N - 1); i++) + for (uint32_t j = 0; j < nc; j++) + weightedColors[i].m_c[j] = (uint8_t)((actualMinColor.m_c[j] * (64 - pParams->m_pSelector_weights[i]) + actualMaxColor.m_c[j] * pParams->m_pSelector_weights[i] + 32) >> 6); + + const int lr = actualMinColor.m_c[0]; + const int lg = actualMinColor.m_c[1]; + const int lb = actualMinColor.m_c[2]; + const int dr = actualMaxColor.m_c[0] - lr; + const int dg = actualMaxColor.m_c[1] - lg; + const int db = actualMaxColor.m_c[2] - lb; + + uint64_t total_err = 0; + + if (pComp_params->m_force_selectors) + { + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + { + const uint32_t best_sel = pComp_params->m_selectors[i]; + + uint64_t best_err; + if (pParams->m_has_alpha) + best_err = compute_color_distance_rgba(&weightedColors[best_sel], &pParams->m_pPixels[i], pParams->m_perceptual, pParams->m_weights); + else + best_err = compute_color_distance_rgb(&weightedColors[best_sel], &pParams->m_pPixels[i], pParams->m_perceptual, pParams->m_weights); + + total_err += best_err; + + pResults->m_pSelectors_temp[i] = (uint8_t)best_sel; + } + } + else if (!pParams->m_perceptual) + { + if (pParams->m_has_alpha) + { + const int la = actualMinColor.m_c[3]; + const int da = actualMaxColor.m_c[3] - la; + + const float f = N / (float)(squarei(dr) + squarei(dg) + squarei(db) + squarei(da) + .00000125f); + + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + { + const color_rgba *pC = &pParams->m_pPixels[i]; + int r = pC->m_c[0]; + int g = pC->m_c[1]; + int b = pC->m_c[2]; + int a = pC->m_c[3]; + + int best_sel = (int)((float)((r - lr) * dr + (g - lg) * dg + (b - lb) * db + (a - la) * da) * f + .5f); + best_sel = clampi(best_sel, 1, N - 1); + + uint64_t err0 = compute_color_distance_rgba(&weightedColors[best_sel - 1], pC, false, pParams->m_weights); + uint64_t err1 = compute_color_distance_rgba(&weightedColors[best_sel], pC, false, pParams->m_weights); + + if (err1 > err0) + { + err1 = err0; + --best_sel; + } + total_err += err1; + + pResults->m_pSelectors_temp[i] = (uint8_t)best_sel; + } + } + else + { + const float f = N / (float)(squarei(dr) + squarei(dg) + squarei(db) + .00000125f); + + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + { + const color_rgba *pC = &pParams->m_pPixels[i]; + int r = pC->m_c[0]; + int g = pC->m_c[1]; + int b = pC->m_c[2]; + + int sel = (int)((float)((r - lr) * dr + (g - lg) * dg + (b - lb) * db) * f + .5f); + sel = clampi(sel, 1, N - 1); + + uint64_t err0 = compute_color_distance_rgb(&weightedColors[sel - 1], pC, false, pParams->m_weights); + uint64_t err1 = compute_color_distance_rgb(&weightedColors[sel], pC, false, pParams->m_weights); + + int best_sel = sel; + uint64_t best_err = err1; + if (err0 < best_err) + { + best_err = err0; + best_sel = sel - 1; + } + + total_err += best_err; + + pResults->m_pSelectors_temp[i] = (uint8_t)best_sel; + } + } + } + else + { + // TODO: This could be improved. + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + { + uint64_t best_err = UINT64_MAX; + uint32_t best_sel = 0; + + if (pParams->m_has_alpha) + { + for (uint32_t j = 0; j < N; j++) + { + uint64_t err = compute_color_distance_rgba(&weightedColors[j], &pParams->m_pPixels[i], true, pParams->m_weights); + if (err < best_err) + { + best_err = err; + best_sel = j; + } + } + } + else + { + for (uint32_t j = 0; j < N; j++) + { + uint64_t err = compute_color_distance_rgb(&weightedColors[j], &pParams->m_pPixels[i], true, pParams->m_weights); + if (err < best_err) + { + best_err = err; + best_sel = j; + } + } + } + + total_err += best_err; + + pResults->m_pSelectors_temp[i] = (uint8_t)best_sel; + } + } + + if (total_err < pResults->m_best_overall_err) + { + pResults->m_best_overall_err = total_err; + + pResults->m_low_endpoint = *pLow; + pResults->m_high_endpoint = *pHigh; + + pResults->m_pbits[0] = pbits[0]; + pResults->m_pbits[1] = pbits[1]; + + memcpy(pResults->m_pSelectors, pResults->m_pSelectors_temp, sizeof(pResults->m_pSelectors[0]) * pParams->m_num_pixels); + } + + return total_err; +} + +static void fixDegenerateEndpoints(uint32_t mode, color_rgba *pTrialMinColor, color_rgba *pTrialMaxColor, const vec4F *pXl, const vec4F *pXh, uint32_t iscale, + const bc7enc_compress_block_params* pComp_params) +{ + //if ((mode == 1) || (mode == 7)) + //if (mode == 1) + if ( (mode == 1) || ((mode == 6) && (pComp_params->m_quant_mode6_endpoints)) ) + { + // fix degenerate case where the input collapses to a single colorspace voxel, and we loose all freedom (test with grayscale ramps) + for (uint32_t i = 0; i < 3; i++) + { + if (pTrialMinColor->m_c[i] == pTrialMaxColor->m_c[i]) + { + if (fabs(pXl->m_c[i] - pXh->m_c[i]) > 0.0f) + { + if (pTrialMinColor->m_c[i] > (iscale >> 1)) + { + if (pTrialMinColor->m_c[i] > 0) + pTrialMinColor->m_c[i]--; + else + if (pTrialMaxColor->m_c[i] < iscale) + pTrialMaxColor->m_c[i]++; + } + else + { + if (pTrialMaxColor->m_c[i] < iscale) + pTrialMaxColor->m_c[i]++; + else if (pTrialMinColor->m_c[i] > 0) + pTrialMinColor->m_c[i]--; + } + } + } + } + } +} + +static uint64_t find_optimal_solution(uint32_t mode, vec4F xl, vec4F xh, const color_cell_compressor_params *pParams, color_cell_compressor_results *pResults, + const bc7enc_compress_block_params* pComp_params) +{ + vec4F_saturate_in_place(&xl); vec4F_saturate_in_place(&xh); + + if (pParams->m_has_pbits) + { + const int iscalep = (1 << (pParams->m_comp_bits + 1)) - 1; + const float scalep = (float)iscalep; + + const int32_t totalComps = pParams->m_has_alpha ? 4 : 3; + + uint32_t best_pbits[2]; + color_rgba bestMinColor, bestMaxColor; + + if (!pParams->m_endpoints_share_pbit) + { + if ((pParams->m_comp_bits == 7) && (pComp_params->m_quant_mode6_endpoints)) + { + best_pbits[0] = 0; + bestMinColor.m_c[0] = g_mode6_reduced_quant[(int)((xl.m_c[0] * 2047.0f) + .5f)][0]; + bestMinColor.m_c[1] = g_mode6_reduced_quant[(int)((xl.m_c[1] * 2047.0f) + .5f)][0]; + bestMinColor.m_c[2] = g_mode6_reduced_quant[(int)((xl.m_c[2] * 2047.0f) + .5f)][0]; + bestMinColor.m_c[3] = g_mode6_reduced_quant[(int)((xl.m_c[3] * 2047.0f) + .5f)][0]; + + best_pbits[1] = 1; + bestMaxColor.m_c[0] = g_mode6_reduced_quant[(int)((xh.m_c[0] * 2047.0f) + .5f)][1]; + bestMaxColor.m_c[1] = g_mode6_reduced_quant[(int)((xh.m_c[1] * 2047.0f) + .5f)][1]; + bestMaxColor.m_c[2] = g_mode6_reduced_quant[(int)((xh.m_c[2] * 2047.0f) + .5f)][1]; + bestMaxColor.m_c[3] = g_mode6_reduced_quant[(int)((xh.m_c[3] * 2047.0f) + .5f)][1]; + } + else + { + float best_err0 = 1e+9; + float best_err1 = 1e+9; + + for (int p = 0; p < 2; p++) + { + color_rgba xMinColor, xMaxColor; + + // Notes: The pbit controls which quantization intervals are selected. + // total_levels=2^(comp_bits+1), where comp_bits=4 for mode 0, etc. + // pbit 0: v=(b*2)/(total_levels-1), pbit 1: v=(b*2+1)/(total_levels-1) where b is the component bin from [0,total_levels/2-1] and v is the [0,1] component value + // rearranging you get for pbit 0: b=floor(v*(total_levels-1)/2+.5) + // rearranging you get for pbit 1: b=floor((v*(total_levels-1)-1)/2+.5) + if (pParams->m_comp_bits == 5) + { + for (uint32_t c = 0; c < 4; c++) + { + int vl = (int)(xl.m_c[c] * 31.0f); + vl += (xl.m_c[c] > g_mode7_rgba_midpoints[vl][p]); + xMinColor.m_c[c] = (uint8_t)clampi(vl * 2 + p, p, 63 - 1 + p); + + int vh = (int)(xh.m_c[c] * 31.0f); + vh += (xh.m_c[c] > g_mode7_rgba_midpoints[vh][p]); + xMaxColor.m_c[c] = (uint8_t)clampi(vh * 2 + p, p, 63 - 1 + p); + } + } + else + { + for (uint32_t c = 0; c < 4; c++) + { + xMinColor.m_c[c] = (uint8_t)(clampi(((int)((xl.m_c[c] * scalep - p) / 2.0f + .5f)) * 2 + p, p, iscalep - 1 + p)); + xMaxColor.m_c[c] = (uint8_t)(clampi(((int)((xh.m_c[c] * scalep - p) / 2.0f + .5f)) * 2 + p, p, iscalep - 1 + p)); + } + } + + color_rgba scaledLow = scale_color(&xMinColor, pParams); + color_rgba scaledHigh = scale_color(&xMaxColor, pParams); + + float err0 = 0, err1 = 0; + for (int i = 0; i < totalComps; i++) + { + err0 += squaref(scaledLow.m_c[i] - xl.m_c[i] * 255.0f); + err1 += squaref(scaledHigh.m_c[i] - xh.m_c[i] * 255.0f); + } + + if (p == 1) + { + err0 *= pComp_params->m_pbit1_weight; + err1 *= pComp_params->m_pbit1_weight; + } + + if (err0 < best_err0) + { + best_err0 = err0; + best_pbits[0] = p; + + bestMinColor.m_c[0] = xMinColor.m_c[0] >> 1; + bestMinColor.m_c[1] = xMinColor.m_c[1] >> 1; + bestMinColor.m_c[2] = xMinColor.m_c[2] >> 1; + bestMinColor.m_c[3] = xMinColor.m_c[3] >> 1; + } + + if (err1 < best_err1) + { + best_err1 = err1; + best_pbits[1] = p; + + bestMaxColor.m_c[0] = xMaxColor.m_c[0] >> 1; + bestMaxColor.m_c[1] = xMaxColor.m_c[1] >> 1; + bestMaxColor.m_c[2] = xMaxColor.m_c[2] >> 1; + bestMaxColor.m_c[3] = xMaxColor.m_c[3] >> 1; + } + } + } + } + else + { + if ((mode == 1) && (pComp_params->m_bias_mode1_pbits)) + { + float x = 0.0f; + for (uint32_t c = 0; c < 3; c++) + x = std::max(std::max(x, xl.m_c[c]), xh.m_c[c]); + + int p = 0; + if (x > (253.0f / 255.0f)) + p = 1; + + color_rgba xMinColor, xMaxColor; + for (uint32_t c = 0; c < 4; c++) + { + int vl = (int)(xl.m_c[c] * 63.0f); + vl += (xl.m_c[c] > g_mode1_rgba_midpoints[vl][p]); + xMinColor.m_c[c] = (uint8_t)clampi(vl * 2 + p, p, 127 - 1 + p); + + int vh = (int)(xh.m_c[c] * 63.0f); + vh += (xh.m_c[c] > g_mode1_rgba_midpoints[vh][p]); + xMaxColor.m_c[c] = (uint8_t)clampi(vh * 2 + p, p, 127 - 1 + p); + } + + best_pbits[0] = p; + best_pbits[1] = p; + for (uint32_t j = 0; j < 4; j++) + { + bestMinColor.m_c[j] = xMinColor.m_c[j] >> 1; + bestMaxColor.m_c[j] = xMaxColor.m_c[j] >> 1; + } + } + else + { + // Endpoints share pbits + float best_err = 1e+9; + + for (int p = 0; p < 2; p++) + { + color_rgba xMinColor, xMaxColor; + if (pParams->m_comp_bits == 6) + { + for (uint32_t c = 0; c < 4; c++) + { + int vl = (int)(xl.m_c[c] * 63.0f); + vl += (xl.m_c[c] > g_mode1_rgba_midpoints[vl][p]); + xMinColor.m_c[c] = (uint8_t)clampi(vl * 2 + p, p, 127 - 1 + p); + + int vh = (int)(xh.m_c[c] * 63.0f); + vh += (xh.m_c[c] > g_mode1_rgba_midpoints[vh][p]); + xMaxColor.m_c[c] = (uint8_t)clampi(vh * 2 + p, p, 127 - 1 + p); + } + } + else + { + for (uint32_t c = 0; c < 4; c++) + { + xMinColor.m_c[c] = (uint8_t)(clampi(((int)((xl.m_c[c] * scalep - p) / 2.0f + .5f)) * 2 + p, p, iscalep - 1 + p)); + xMaxColor.m_c[c] = (uint8_t)(clampi(((int)((xh.m_c[c] * scalep - p) / 2.0f + .5f)) * 2 + p, p, iscalep - 1 + p)); + } + } + + color_rgba scaledLow = scale_color(&xMinColor, pParams); + color_rgba scaledHigh = scale_color(&xMaxColor, pParams); + + float err = 0; + for (int i = 0; i < totalComps; i++) + err += squaref((scaledLow.m_c[i] / 255.0f) - xl.m_c[i]) + squaref((scaledHigh.m_c[i] / 255.0f) - xh.m_c[i]); + + if (p == 1) + err *= pComp_params->m_pbit1_weight; + + if (err < best_err) + { + best_err = err; + best_pbits[0] = p; + best_pbits[1] = p; + for (uint32_t j = 0; j < 4; j++) + { + bestMinColor.m_c[j] = xMinColor.m_c[j] >> 1; + bestMaxColor.m_c[j] = xMaxColor.m_c[j] >> 1; + } + } + } + } + } + + fixDegenerateEndpoints(mode, &bestMinColor, &bestMaxColor, &xl, &xh, iscalep >> 1, pComp_params); + + if ((pResults->m_best_overall_err == UINT64_MAX) || color_quad_u8_notequals(&bestMinColor, &pResults->m_low_endpoint) || color_quad_u8_notequals(&bestMaxColor, &pResults->m_high_endpoint) || (best_pbits[0] != pResults->m_pbits[0]) || (best_pbits[1] != pResults->m_pbits[1])) + evaluate_solution(&bestMinColor, &bestMaxColor, best_pbits, pParams, pResults, pComp_params); + } + else + { + const int iscale = (1 << pParams->m_comp_bits) - 1; + const float scale = (float)iscale; + + color_rgba trialMinColor, trialMaxColor; + if (pParams->m_comp_bits == 7) + { + for (uint32_t c = 0; c < 4; c++) + { + int vl = (int)(xl.m_c[c] * 127.0f); + vl += (xl.m_c[c] > g_mode5_rgba_midpoints[vl]); + trialMinColor.m_c[c] = (uint8_t)clampi(vl, 0, 127); + + int vh = (int)(xh.m_c[c] * 127.0f); + vh += (xh.m_c[c] > g_mode5_rgba_midpoints[vh]); + trialMaxColor.m_c[c] = (uint8_t)clampi(vh, 0, 127); + } + } + else + { + color_quad_u8_set_clamped(&trialMinColor, (int)(xl.m_c[0] * scale + .5f), (int)(xl.m_c[1] * scale + .5f), (int)(xl.m_c[2] * scale + .5f), (int)(xl.m_c[3] * scale + .5f)); + color_quad_u8_set_clamped(&trialMaxColor, (int)(xh.m_c[0] * scale + .5f), (int)(xh.m_c[1] * scale + .5f), (int)(xh.m_c[2] * scale + .5f), (int)(xh.m_c[3] * scale + .5f)); + } + + fixDegenerateEndpoints(mode, &trialMinColor, &trialMaxColor, &xl, &xh, iscale, pComp_params); + + if ((pResults->m_best_overall_err == UINT64_MAX) || color_quad_u8_notequals(&trialMinColor, &pResults->m_low_endpoint) || color_quad_u8_notequals(&trialMaxColor, &pResults->m_high_endpoint)) + evaluate_solution(&trialMinColor, &trialMaxColor, pResults->m_pbits, pParams, pResults, pComp_params); + } + + return pResults->m_best_overall_err; +} + +static uint64_t color_cell_compression(uint32_t mode, const color_cell_compressor_params *pParams, color_cell_compressor_results *pResults, const bc7enc_compress_block_params *pComp_params) +{ + assert((mode == 6) || (mode == 7) || (!pParams->m_has_alpha)); + + pResults->m_best_overall_err = UINT64_MAX; + + // If the partition's colors are all the same in mode 1, then just pack them as a single color. + if (mode == 1) + { + const uint32_t cr = pParams->m_pPixels[0].m_c[0], cg = pParams->m_pPixels[0].m_c[1], cb = pParams->m_pPixels[0].m_c[2]; + + bool allSame = true; + for (uint32_t i = 1; i < pParams->m_num_pixels; i++) + { + if ((cr != pParams->m_pPixels[i].m_c[0]) || (cg != pParams->m_pPixels[i].m_c[1]) || (cb != pParams->m_pPixels[i].m_c[2])) + { + allSame = false; + break; + } + } + + if (allSame) + return pack_mode1_to_one_color(pParams, pResults, cr, cg, cb, pResults->m_pSelectors); + } + else if (mode == 7) + { + const uint32_t cr = pParams->m_pPixels[0].m_c[0], cg = pParams->m_pPixels[0].m_c[1], cb = pParams->m_pPixels[0].m_c[2], ca = pParams->m_pPixels[0].m_c[3]; + + bool allSame = true; + for (uint32_t i = 1; i < pParams->m_num_pixels; i++) + { + if ((cr != pParams->m_pPixels[i].m_c[0]) || (cg != pParams->m_pPixels[i].m_c[1]) || (cb != pParams->m_pPixels[i].m_c[2]) || (ca != pParams->m_pPixels[i].m_c[3])) + { + allSame = false; + break; + } + } + + if (allSame) + return pack_mode7_to_one_color(pParams, pResults, cr, cg, cb, ca, pResults->m_pSelectors, pParams->m_num_pixels, pParams->m_pPixels); + } + + // Compute partition's mean color and principle axis. + vec4F meanColor, axis; + vec4F_set_scalar(&meanColor, 0.0f); + + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + { + vec4F color = vec4F_from_color(&pParams->m_pPixels[i]); + meanColor = vec4F_add(&meanColor, &color); + } + + vec4F meanColorScaled = vec4F_mul(&meanColor, 1.0f / (float)(pParams->m_num_pixels)); + + meanColor = vec4F_mul(&meanColor, 1.0f / (float)(pParams->m_num_pixels * 255.0f)); + vec4F_saturate_in_place(&meanColor); + + if (pParams->m_has_alpha) + { + // Use incremental PCA for RGBA PCA, because it's simple. + vec4F_set_scalar(&axis, 0.0f); + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + { + vec4F color = vec4F_from_color(&pParams->m_pPixels[i]); + color = vec4F_sub(&color, &meanColorScaled); + vec4F a = vec4F_mul(&color, color.m_c[0]); + vec4F b = vec4F_mul(&color, color.m_c[1]); + vec4F c = vec4F_mul(&color, color.m_c[2]); + vec4F d = vec4F_mul(&color, color.m_c[3]); + vec4F n = i ? axis : color; + vec4F_normalize_in_place(&n); + axis.m_c[0] += vec4F_dot(&a, &n); + axis.m_c[1] += vec4F_dot(&b, &n); + axis.m_c[2] += vec4F_dot(&c, &n); + axis.m_c[3] += vec4F_dot(&d, &n); + } + vec4F_normalize_in_place(&axis); + } + else + { + // Use covar technique for RGB PCA, because it doesn't require per-pixel normalization. + float cov[6] = { 0, 0, 0, 0, 0, 0 }; + + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + { + const color_rgba *pV = &pParams->m_pPixels[i]; + float r = pV->m_c[0] - meanColorScaled.m_c[0]; + float g = pV->m_c[1] - meanColorScaled.m_c[1]; + float b = pV->m_c[2] - meanColorScaled.m_c[2]; + cov[0] += r*r; cov[1] += r*g; cov[2] += r*b; cov[3] += g*g; cov[4] += g*b; cov[5] += b*b; + } + + float vfr = .9f, vfg = 1.0f, vfb = .7f; + for (uint32_t iter = 0; iter < 3; iter++) + { + float r = vfr*cov[0] + vfg*cov[1] + vfb*cov[2]; + float g = vfr*cov[1] + vfg*cov[3] + vfb*cov[4]; + float b = vfr*cov[2] + vfg*cov[4] + vfb*cov[5]; + + float m = maximumf(maximumf(fabsf(r), fabsf(g)), fabsf(b)); + if (m > 1e-10f) + { + m = 1.0f / m; + r *= m; g *= m; b *= m; + } + + vfr = r; vfg = g; vfb = b; + } + + float len = vfr*vfr + vfg*vfg + vfb*vfb; + if (len < 1e-10f) + vec4F_set_scalar(&axis, 0.0f); + else + { + len = 1.0f / sqrtf(len); + vfr *= len; vfg *= len; vfb *= len; + vec4F_set(&axis, vfr, vfg, vfb, 0); + } + } + + // TODO: Try picking the 2 colors with the largest projection onto the axis, instead of computing new colors along the axis. + + if (vec4F_dot(&axis, &axis) < .5f) + { + if (pParams->m_perceptual) + vec4F_set(&axis, .213f, .715f, .072f, pParams->m_has_alpha ? .715f : 0); + else + vec4F_set(&axis, 1.0f, 1.0f, 1.0f, pParams->m_has_alpha ? 1.0f : 0); + vec4F_normalize_in_place(&axis); + } + + float l = 1e+9f, h = -1e+9f; + + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + { + vec4F color = vec4F_from_color(&pParams->m_pPixels[i]); + + vec4F q = vec4F_sub(&color, &meanColorScaled); + float d = vec4F_dot(&q, &axis); + + l = minimumf(l, d); + h = maximumf(h, d); + } + + l *= (1.0f / 255.0f); + h *= (1.0f / 255.0f); + + vec4F b0 = vec4F_mul(&axis, l); + vec4F b1 = vec4F_mul(&axis, h); + vec4F c0 = vec4F_add(&meanColor, &b0); + vec4F c1 = vec4F_add(&meanColor, &b1); + vec4F minColor = vec4F_saturate(&c0); + vec4F maxColor = vec4F_saturate(&c1); + + vec4F whiteVec; + vec4F_set_scalar(&whiteVec, 1.0f); + + if (vec4F_dot(&minColor, &whiteVec) > vec4F_dot(&maxColor, &whiteVec)) + { +#if 0 + // Don't compile correctly with VC 2019 in release. + vec4F temp = minColor; + minColor = maxColor; + maxColor = temp; +#else + float a = minColor.m_c[0], b = minColor.m_c[1], c = minColor.m_c[2], d = minColor.m_c[3]; + minColor.m_c[0] = maxColor.m_c[0]; + minColor.m_c[1] = maxColor.m_c[1]; + minColor.m_c[2] = maxColor.m_c[2]; + minColor.m_c[3] = maxColor.m_c[3]; + maxColor.m_c[0] = a; + maxColor.m_c[1] = b; + maxColor.m_c[2] = c; + maxColor.m_c[3] = d; +#endif + } + + // First find a solution using the block's PCA. + if (!find_optimal_solution(mode, minColor, maxColor, pParams, pResults, pComp_params)) + return 0; + + if (pComp_params->m_try_least_squares) + { + // Now try to refine the solution using least squares by computing the optimal endpoints from the current selectors. + vec4F xl, xh; + vec4F_set_scalar(&xl, 0.0f); + vec4F_set_scalar(&xh, 0.0f); + if (pParams->m_has_alpha) + compute_least_squares_endpoints_rgba(pParams->m_num_pixels, pResults->m_pSelectors, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); + else + compute_least_squares_endpoints_rgb(pParams->m_num_pixels, pResults->m_pSelectors, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); + + xl = vec4F_mul(&xl, (1.0f / 255.0f)); + xh = vec4F_mul(&xh, (1.0f / 255.0f)); + + if (!find_optimal_solution(mode, xl, xh, pParams, pResults, pComp_params)) + return 0; + } + + if (pComp_params->m_uber_level > 0) + { + // In uber level 1, try varying the selectors a little, somewhat like cluster fit would. First try incrementing the minimum selectors, + // then try decrementing the selectrors, then try both. + uint8_t selectors_temp[16], selectors_temp1[16]; + memcpy(selectors_temp, pResults->m_pSelectors, pParams->m_num_pixels); + + const int max_selector = pParams->m_num_selector_weights - 1; + + uint32_t min_sel = 16; + uint32_t max_sel = 0; + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + { + uint32_t sel = selectors_temp[i]; + min_sel = minimumu(min_sel, sel); + max_sel = maximumu(max_sel, sel); + } + + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + { + uint32_t sel = selectors_temp[i]; + if ((sel == min_sel) && (sel < (pParams->m_num_selector_weights - 1))) + sel++; + selectors_temp1[i] = (uint8_t)sel; + } + + vec4F xl, xh; + vec4F_set_scalar(&xl, 0.0f); + vec4F_set_scalar(&xh, 0.0f); + if (pParams->m_has_alpha) + compute_least_squares_endpoints_rgba(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); + else + compute_least_squares_endpoints_rgb(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); + + xl = vec4F_mul(&xl, (1.0f / 255.0f)); + xh = vec4F_mul(&xh, (1.0f / 255.0f)); + + if (!find_optimal_solution(mode, xl, xh, pParams, pResults, pComp_params)) + return 0; + + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + { + uint32_t sel = selectors_temp[i]; + if ((sel == max_sel) && (sel > 0)) + sel--; + selectors_temp1[i] = (uint8_t)sel; + } + + if (pParams->m_has_alpha) + compute_least_squares_endpoints_rgba(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); + else + compute_least_squares_endpoints_rgb(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); + + xl = vec4F_mul(&xl, (1.0f / 255.0f)); + xh = vec4F_mul(&xh, (1.0f / 255.0f)); + + if (!find_optimal_solution(mode, xl, xh, pParams, pResults, pComp_params)) + return 0; + + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + { + uint32_t sel = selectors_temp[i]; + if ((sel == min_sel) && (sel < (pParams->m_num_selector_weights - 1))) + sel++; + else if ((sel == max_sel) && (sel > 0)) + sel--; + selectors_temp1[i] = (uint8_t)sel; + } + + if (pParams->m_has_alpha) + compute_least_squares_endpoints_rgba(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); + else + compute_least_squares_endpoints_rgb(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); + + xl = vec4F_mul(&xl, (1.0f / 255.0f)); + xh = vec4F_mul(&xh, (1.0f / 255.0f)); + + if (!find_optimal_solution(mode, xl, xh, pParams, pResults, pComp_params)) + return 0; + + // In uber levels 2+, try taking more advantage of endpoint extrapolation by scaling the selectors in one direction or another. + const uint32_t uber_err_thresh = (pParams->m_num_pixels * 56) >> 4; + if ((pComp_params->m_uber_level >= 2) && (pResults->m_best_overall_err > uber_err_thresh)) + { + const int Q = (pComp_params->m_uber_level >= 4) ? (pComp_params->m_uber_level - 2) : 1; + for (int ly = -Q; ly <= 1; ly++) + { + for (int hy = max_selector - 1; hy <= (max_selector + Q); hy++) + { + if ((ly == 0) && (hy == max_selector)) + continue; + + for (uint32_t i = 0; i < pParams->m_num_pixels; i++) + selectors_temp1[i] = (uint8_t)clampf(floorf((float)max_selector * ((float)selectors_temp[i] - (float)ly) / ((float)hy - (float)ly) + .5f), 0, (float)max_selector); + + //vec4F xl, xh; + vec4F_set_scalar(&xl, 0.0f); + vec4F_set_scalar(&xh, 0.0f); + if (pParams->m_has_alpha) + compute_least_squares_endpoints_rgba(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); + else + compute_least_squares_endpoints_rgb(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); + + xl = vec4F_mul(&xl, (1.0f / 255.0f)); + xh = vec4F_mul(&xh, (1.0f / 255.0f)); + + if (!find_optimal_solution(mode, xl, xh, pParams, pResults, pComp_params)) + return 0; + } + } + } + } + + if (mode == 1) + { + // Try encoding the partition as a single color by using the optimal singe colors tables to encode the block to its mean. + color_cell_compressor_results avg_results = *pResults; + const uint32_t r = (int)(.5f + meanColor.m_c[0] * 255.0f), g = (int)(.5f + meanColor.m_c[1] * 255.0f), b = (int)(.5f + meanColor.m_c[2] * 255.0f); + uint64_t avg_err = pack_mode1_to_one_color(pParams, &avg_results, r, g, b, pResults->m_pSelectors_temp); + if (avg_err < pResults->m_best_overall_err) + { + *pResults = avg_results; + memcpy(pResults->m_pSelectors, pResults->m_pSelectors_temp, sizeof(pResults->m_pSelectors[0]) * pParams->m_num_pixels); + pResults->m_best_overall_err = avg_err; + } + } + else if (mode == 7) + { + // Try encoding the partition as a single color by using the optimal singe colors tables to encode the block to its mean. + color_cell_compressor_results avg_results = *pResults; + const uint32_t r = (int)(.5f + meanColor.m_c[0] * 255.0f), g = (int)(.5f + meanColor.m_c[1] * 255.0f), b = (int)(.5f + meanColor.m_c[2] * 255.0f), a = (int)(.5f + meanColor.m_c[3] * 255.0f); + uint64_t avg_err = pack_mode7_to_one_color(pParams, &avg_results, r, g, b, a, pResults->m_pSelectors_temp, pParams->m_num_pixels, pParams->m_pPixels); + if (avg_err < pResults->m_best_overall_err) + { + *pResults = avg_results; + memcpy(pResults->m_pSelectors, pResults->m_pSelectors_temp, sizeof(pResults->m_pSelectors[0]) * pParams->m_num_pixels); + pResults->m_best_overall_err = avg_err; + } + } + + return pResults->m_best_overall_err; +} + +static uint64_t color_cell_compression_est_mode1(uint32_t num_pixels, const color_rgba *pPixels, bool perceptual, uint32_t pweights[4], uint64_t best_err_so_far) +{ + // Find RGB bounds as an approximation of the block's principle axis + uint32_t lr = 255, lg = 255, lb = 255; + uint32_t hr = 0, hg = 0, hb = 0; + for (uint32_t i = 0; i < num_pixels; i++) + { + const color_rgba *pC = &pPixels[i]; + if (pC->m_c[0] < lr) lr = pC->m_c[0]; + if (pC->m_c[1] < lg) lg = pC->m_c[1]; + if (pC->m_c[2] < lb) lb = pC->m_c[2]; + if (pC->m_c[0] > hr) hr = pC->m_c[0]; + if (pC->m_c[1] > hg) hg = pC->m_c[1]; + if (pC->m_c[2] > hb) hb = pC->m_c[2]; + } + + color_rgba lowColor; color_quad_u8_set(&lowColor, lr, lg, lb, 0); + color_rgba highColor; color_quad_u8_set(&highColor, hr, hg, hb, 0); + + // Place endpoints at bbox diagonals and compute interpolated colors + const uint32_t N = 8; + color_rgba weightedColors[8]; + + weightedColors[0] = lowColor; + weightedColors[N - 1] = highColor; + for (uint32_t i = 1; i < (N - 1); i++) + { + weightedColors[i].m_c[0] = (uint8_t)((lowColor.m_c[0] * (64 - g_bc7_weights3[i]) + highColor.m_c[0] * g_bc7_weights3[i] + 32) >> 6); + weightedColors[i].m_c[1] = (uint8_t)((lowColor.m_c[1] * (64 - g_bc7_weights3[i]) + highColor.m_c[1] * g_bc7_weights3[i] + 32) >> 6); + weightedColors[i].m_c[2] = (uint8_t)((lowColor.m_c[2] * (64 - g_bc7_weights3[i]) + highColor.m_c[2] * g_bc7_weights3[i] + 32) >> 6); + } + + // Compute dots and thresholds + const int ar = highColor.m_c[0] - lowColor.m_c[0]; + const int ag = highColor.m_c[1] - lowColor.m_c[1]; + const int ab = highColor.m_c[2] - lowColor.m_c[2]; + + int dots[8]; + for (uint32_t i = 0; i < N; i++) + dots[i] = weightedColors[i].m_c[0] * ar + weightedColors[i].m_c[1] * ag + weightedColors[i].m_c[2] * ab; + + int thresh[8 - 1]; + for (uint32_t i = 0; i < (N - 1); i++) + thresh[i] = (dots[i] + dots[i + 1] + 1) >> 1; + + uint64_t total_err = 0; + if (perceptual) + { + // Transform block's interpolated colors to YCbCr + int l1[8], cr1[8], cb1[8]; + for (int j = 0; j < 8; j++) + { + const color_rgba *pE1 = &weightedColors[j]; + l1[j] = pE1->m_c[0] * 109 + pE1->m_c[1] * 366 + pE1->m_c[2] * 37; + cr1[j] = ((int)pE1->m_c[0] << 9) - l1[j]; + cb1[j] = ((int)pE1->m_c[2] << 9) - l1[j]; + } + + for (uint32_t i = 0; i < num_pixels; i++) + { + const color_rgba *pC = &pPixels[i]; + + int d = ar * pC->m_c[0] + ag * pC->m_c[1] + ab * pC->m_c[2]; + + // Find approximate selector + uint32_t s = 0; + if (d >= thresh[6]) + s = 7; + else if (d >= thresh[5]) + s = 6; + else if (d >= thresh[4]) + s = 5; + else if (d >= thresh[3]) + s = 4; + else if (d >= thresh[2]) + s = 3; + else if (d >= thresh[1]) + s = 2; + else if (d >= thresh[0]) + s = 1; + + // Compute error + const int l2 = pC->m_c[0] * 109 + pC->m_c[1] * 366 + pC->m_c[2] * 37; + const int cr2 = ((int)pC->m_c[0] << 9) - l2; + const int cb2 = ((int)pC->m_c[2] << 9) - l2; + + const int dl = (l1[s] - l2) >> 8; + const int dcr = (cr1[s] - cr2) >> 8; + const int dcb = (cb1[s] - cb2) >> 8; + + int ie = (pweights[0] * dl * dl) + (pweights[1] * dcr * dcr) + (pweights[2] * dcb * dcb); + + total_err += ie; + if (total_err > best_err_so_far) + break; + } + } + else + { + for (uint32_t i = 0; i < num_pixels; i++) + { + const color_rgba *pC = &pPixels[i]; + + int d = ar * pC->m_c[0] + ag * pC->m_c[1] + ab * pC->m_c[2]; + + // Find approximate selector + uint32_t s = 0; + if (d >= thresh[6]) + s = 7; + else if (d >= thresh[5]) + s = 6; + else if (d >= thresh[4]) + s = 5; + else if (d >= thresh[3]) + s = 4; + else if (d >= thresh[2]) + s = 3; + else if (d >= thresh[1]) + s = 2; + else if (d >= thresh[0]) + s = 1; + + // Compute error + const color_rgba *pE1 = &weightedColors[s]; + + int dr = (int)pE1->m_c[0] - (int)pC->m_c[0]; + int dg = (int)pE1->m_c[1] - (int)pC->m_c[1]; + int db = (int)pE1->m_c[2] - (int)pC->m_c[2]; + + total_err += pweights[0] * (dr * dr) + pweights[1] * (dg * dg) + pweights[2] * (db * db); + if (total_err > best_err_so_far) + break; + } + } + + return total_err; +} + +static uint64_t color_cell_compression_est_mode7(uint32_t num_pixels, const color_rgba * pPixels, bool perceptual, uint32_t pweights[4], uint64_t best_err_so_far) +{ + // Find RGB bounds as an approximation of the block's principle axis + uint32_t lr = 255, lg = 255, lb = 255, la = 255; + uint32_t hr = 0, hg = 0, hb = 0, ha = 0; + for (uint32_t i = 0; i < num_pixels; i++) + { + const color_rgba* pC = &pPixels[i]; + if (pC->m_c[0] < lr) lr = pC->m_c[0]; + if (pC->m_c[1] < lg) lg = pC->m_c[1]; + if (pC->m_c[2] < lb) lb = pC->m_c[2]; + if (pC->m_c[3] < la) la = pC->m_c[3]; + + if (pC->m_c[0] > hr) hr = pC->m_c[0]; + if (pC->m_c[1] > hg) hg = pC->m_c[1]; + if (pC->m_c[2] > hb) hb = pC->m_c[2]; + if (pC->m_c[3] > ha) ha = pC->m_c[3]; + } + + color_rgba lowColor; color_quad_u8_set(&lowColor, lr, lg, lb, la); + color_rgba highColor; color_quad_u8_set(&highColor, hr, hg, hb, ha); + + // Place endpoints at bbox diagonals and compute interpolated colors + const uint32_t N = 4; + color_rgba weightedColors[4]; + + weightedColors[0] = lowColor; + weightedColors[N - 1] = highColor; + for (uint32_t i = 1; i < (N - 1); i++) + { + weightedColors[i].m_c[0] = (uint8_t)((lowColor.m_c[0] * (64 - g_bc7_weights2[i]) + highColor.m_c[0] * g_bc7_weights2[i] + 32) >> 6); + weightedColors[i].m_c[1] = (uint8_t)((lowColor.m_c[1] * (64 - g_bc7_weights2[i]) + highColor.m_c[1] * g_bc7_weights2[i] + 32) >> 6); + weightedColors[i].m_c[2] = (uint8_t)((lowColor.m_c[2] * (64 - g_bc7_weights2[i]) + highColor.m_c[2] * g_bc7_weights2[i] + 32) >> 6); + weightedColors[i].m_c[3] = (uint8_t)((lowColor.m_c[3] * (64 - g_bc7_weights2[i]) + highColor.m_c[3] * g_bc7_weights2[i] + 32) >> 6); + } + + // Compute dots and thresholds + const int ar = highColor.m_c[0] - lowColor.m_c[0]; + const int ag = highColor.m_c[1] - lowColor.m_c[1]; + const int ab = highColor.m_c[2] - lowColor.m_c[2]; + const int aa = highColor.m_c[3] - lowColor.m_c[3]; + + int dots[4]; + for (uint32_t i = 0; i < N; i++) + dots[i] = weightedColors[i].m_c[0] * ar + weightedColors[i].m_c[1] * ag + weightedColors[i].m_c[2] * ab + weightedColors[i].m_c[3] * aa; + + int thresh[4 - 1]; + for (uint32_t i = 0; i < (N - 1); i++) + thresh[i] = (dots[i] + dots[i + 1] + 1) >> 1; + + uint64_t total_err = 0; + if (perceptual) + { + // Transform block's interpolated colors to YCbCr + int l1[4], cr1[4], cb1[4]; + for (int j = 0; j < 4; j++) + { + const color_rgba* pE1 = &weightedColors[j]; + l1[j] = pE1->m_c[0] * 109 + pE1->m_c[1] * 366 + pE1->m_c[2] * 37; + cr1[j] = ((int)pE1->m_c[0] << 9) - l1[j]; + cb1[j] = ((int)pE1->m_c[2] << 9) - l1[j]; + } + + for (uint32_t i = 0; i < num_pixels; i++) + { + const color_rgba* pC = &pPixels[i]; + + int d = ar * pC->m_c[0] + ag * pC->m_c[1] + ab * pC->m_c[2] + aa * pC->m_c[3]; + + // Find approximate selector + uint32_t s = 0; + if (d >= thresh[2]) + s = 3; + else if (d >= thresh[1]) + s = 2; + else if (d >= thresh[0]) + s = 1; + + // Compute error + const int l2 = pC->m_c[0] * 109 + pC->m_c[1] * 366 + pC->m_c[2] * 37; + const int cr2 = ((int)pC->m_c[0] << 9) - l2; + const int cb2 = ((int)pC->m_c[2] << 9) - l2; + + const int dl = (l1[s] - l2) >> 8; + const int dcr = (cr1[s] - cr2) >> 8; + const int dcb = (cb1[s] - cb2) >> 8; + + const int dca = (int)pC->m_c[3] - (int)weightedColors[s].m_c[3]; + + int ie = (pweights[0] * dl * dl) + (pweights[1] * dcr * dcr) + (pweights[2] * dcb * dcb) + (pweights[3] * dca * dca); + + total_err += ie; + if (total_err > best_err_so_far) + break; + } + } + else + { + for (uint32_t i = 0; i < num_pixels; i++) + { + const color_rgba* pC = &pPixels[i]; + + int d = ar * pC->m_c[0] + ag * pC->m_c[1] + ab * pC->m_c[2] + aa * pC->m_c[3]; + + // Find approximate selector + uint32_t s = 0; + if (d >= thresh[2]) + s = 3; + else if (d >= thresh[1]) + s = 2; + else if (d >= thresh[0]) + s = 1; + + // Compute error + const color_rgba* pE1 = &weightedColors[s]; + + int dr = (int)pE1->m_c[0] - (int)pC->m_c[0]; + int dg = (int)pE1->m_c[1] - (int)pC->m_c[1]; + int db = (int)pE1->m_c[2] - (int)pC->m_c[2]; + int da = (int)pE1->m_c[3] - (int)pC->m_c[3]; + + total_err += pweights[0] * (dr * dr) + pweights[1] * (dg * dg) + pweights[2] * (db * db) + pweights[3] * (da * da); + if (total_err > best_err_so_far) + break; + } + } + + return total_err; +} + +// This table contains bitmasks indicating which "key" partitions must be best ranked before this partition is worth evaluating. +// We first rank the best/most used 14 partitions (sorted by usefulness), record the best one found as the key partition, then use +// that to control the other partitions to evaluate. The quality loss is ~.08 dB RGB PSNR, the perf gain is up to ~11% (at uber level 0). +static const uint32_t g_partition_predictors[35] = +{ + UINT32_MAX, + UINT32_MAX, + UINT32_MAX, + UINT32_MAX, + UINT32_MAX, + (1 << 1) | (1 << 2) | (1 << 8), + (1 << 1) | (1 << 3) | (1 << 7), + UINT32_MAX, + UINT32_MAX, + (1 << 2) | (1 << 8) | (1 << 16), + (1 << 7) | (1 << 3) | (1 << 15), + UINT32_MAX, + (1 << 8) | (1 << 14) | (1 << 16), + (1 << 7) | (1 << 14) | (1 << 15), + UINT32_MAX, + UINT32_MAX, + UINT32_MAX, + UINT32_MAX, + (1 << 14) | (1 << 15), + (1 << 16) | (1 << 22) | (1 << 14), + (1 << 17) | (1 << 24) | (1 << 14), + (1 << 2) | (1 << 14) | (1 << 15) | (1 << 1), + UINT32_MAX, + (1 << 1) | (1 << 3) | (1 << 14) | (1 << 16) | (1 << 22), + UINT32_MAX, + (1 << 1) | (1 << 2) | (1 << 15) | (1 << 17) | (1 << 24), + (1 << 1) | (1 << 3) | (1 << 22), + UINT32_MAX, + UINT32_MAX, + UINT32_MAX, + (1 << 14) | (1 << 15) | (1 << 16) | (1 << 17), + UINT32_MAX, + UINT32_MAX, + (1 << 1) | (1 << 2) | (1 << 3) | (1 << 27) | (1 << 4) | (1 << 24), + (1 << 14) | (1 << 15) | (1 << 16) | (1 << 11) | (1 << 17) | (1 << 27) +}; + +// Estimate the partition used by modes 1/7. This scans through each partition and computes an approximate error for each. +static uint32_t estimate_partition(const color_rgba *pPixels, const bc7enc_compress_block_params *pComp_params, uint32_t pweights[4], uint32_t mode) +{ + const uint32_t total_partitions = minimumu(pComp_params->m_max_partitions, BC7ENC_MAX_PARTITIONS); + if (total_partitions <= 1) + return 0; + + uint64_t best_err = UINT64_MAX; + uint32_t best_partition = 0; + + // Partition order sorted by usage frequency across a large test corpus. Pattern 34 (checkerboard) must appear in slot 34. + // Using a sorted order allows the user to decrease the # of partitions to scan with minimal loss in quality. + static const uint8_t s_sorted_partition_order[64] = + { + 1 - 1, 14 - 1, 2 - 1, 3 - 1, 16 - 1, 15 - 1, 11 - 1, 17 - 1, + 4 - 1, 24 - 1, 27 - 1, 7 - 1, 8 - 1, 22 - 1, 20 - 1, 30 - 1, + 9 - 1, 5 - 1, 10 - 1, 21 - 1, 6 - 1, 32 - 1, 23 - 1, 18 - 1, + 19 - 1, 12 - 1, 13 - 1, 31 - 1, 25 - 1, 26 - 1, 29 - 1, 28 - 1, + 33 - 1, 34 - 1, 35 - 1, 46 - 1, 47 - 1, 52 - 1, 50 - 1, 51 - 1, + 49 - 1, 39 - 1, 40 - 1, 38 - 1, 54 - 1, 53 - 1, 55 - 1, 37 - 1, + 58 - 1, 59 - 1, 56 - 1, 42 - 1, 41 - 1, 43 - 1, 44 - 1, 60 - 1, + 45 - 1, 57 - 1, 48 - 1, 36 - 1, 61 - 1, 64 - 1, 63 - 1, 62 - 1 + }; + + assert(s_sorted_partition_order[34] == 34); + + int best_key_partition = 0; + + for (uint32_t partition_iter = 0; (partition_iter < total_partitions) && (best_err > 0); partition_iter++) + { + const uint32_t partition = s_sorted_partition_order[partition_iter]; + + // Check to see if we should bother evaluating this partition at all, depending on the best partition found from the first 14. + if (pComp_params->m_mode17_partition_estimation_filterbank) + { + if ((partition_iter >= 14) && (partition_iter <= 34)) + { + const uint32_t best_key_partition_bitmask = 1 << (best_key_partition + 1); + if ((g_partition_predictors[partition] & best_key_partition_bitmask) == 0) + { + if (partition_iter == 34) + break; + + continue; + } + } + } + + const uint8_t *pPartition = &g_bc7_partition2[partition * 16]; + + color_rgba subset_colors[2][16]; + uint32_t subset_total_colors[2] = { 0, 0 }; + for (uint32_t index = 0; index < 16; index++) + subset_colors[pPartition[index]][subset_total_colors[pPartition[index]]++] = pPixels[index]; + + uint64_t total_subset_err = 0; + for (uint32_t subset = 0; (subset < 2) && (total_subset_err < best_err); subset++) + { + if (mode == 7) + total_subset_err += color_cell_compression_est_mode7(subset_total_colors[subset], &subset_colors[subset][0], pComp_params->m_perceptual, pweights, best_err); + else + total_subset_err += color_cell_compression_est_mode1(subset_total_colors[subset], &subset_colors[subset][0], pComp_params->m_perceptual, pweights, best_err); + } + + if (partition < 16) + { + total_subset_err = (uint64_t)((double)total_subset_err * pComp_params->m_low_frequency_partition_weight + .5f); + } + + if (total_subset_err < best_err) + { + best_err = total_subset_err; + best_partition = partition; + } + + // If the checkerboard pattern doesn't get the highest ranking vs. the previous (lower frequency) patterns, then just stop now because statistically the subsequent patterns won't do well either. + if ((partition == 34) && (best_partition != 34)) + break; + + if (partition_iter == 13) + best_key_partition = best_partition; + + } // partition + + return best_partition; +} + +static void set_block_bits(uint8_t *pBytes, uint32_t val, uint32_t num_bits, uint32_t *pCur_ofs) +{ + assert((num_bits <= 32) && (val < (1ULL << num_bits))); + while (num_bits) + { + const uint32_t n = minimumu(8 - (*pCur_ofs & 7), num_bits); + pBytes[*pCur_ofs >> 3] |= (uint8_t)(val << (*pCur_ofs & 7)); + val >>= n; + num_bits -= n; + *pCur_ofs += n; + } + assert(*pCur_ofs <= 128); +} + +struct bc7_optimization_results +{ + uint32_t m_mode; + uint32_t m_partition; + uint8_t m_selectors[16]; + uint8_t m_alpha_selectors[16]; + color_rgba m_low[3]; + color_rgba m_high[3]; + uint32_t m_pbits[3][2]; + uint32_t m_rotation; + uint32_t m_index_selector; +}; + +void encode_bc7_block(void* pBlock, const bc7_optimization_results* pResults) +{ + assert(pResults->m_index_selector <= 1); + assert(pResults->m_rotation <= 3); + + const uint32_t best_mode = pResults->m_mode; + + const uint32_t total_subsets = g_bc7_num_subsets[best_mode]; + const uint32_t total_partitions = 1 << g_bc7_partition_bits[best_mode]; + //const uint32_t num_rotations = 1 << g_bc7_rotation_bits[best_mode]; + //const uint32_t num_index_selectors = (best_mode == 4) ? 2 : 1; + + const uint8_t* pPartition; + if (total_subsets == 1) + pPartition = &g_bc7_partition1[0]; + else if (total_subsets == 2) + pPartition = &g_bc7_partition2[pResults->m_partition * 16]; + else + pPartition = &g_bc7_partition3[pResults->m_partition * 16]; + + uint8_t color_selectors[16]; + memcpy(color_selectors, pResults->m_selectors, 16); + + uint8_t alpha_selectors[16]; + memcpy(alpha_selectors, pResults->m_alpha_selectors, 16); + + color_rgba low[3], high[3]; + memcpy(low, pResults->m_low, sizeof(low)); + memcpy(high, pResults->m_high, sizeof(high)); + + uint32_t pbits[3][2]; + memcpy(pbits, pResults->m_pbits, sizeof(pbits)); + + int anchor[3] = { -1, -1, -1 }; + + for (uint32_t k = 0; k < total_subsets; k++) + { + uint32_t anchor_index = 0; + if (k) + { + if ((total_subsets == 3) && (k == 1)) + anchor_index = g_bc7_table_anchor_index_third_subset_1[pResults->m_partition]; + else if ((total_subsets == 3) && (k == 2)) + anchor_index = g_bc7_table_anchor_index_third_subset_2[pResults->m_partition]; + else + anchor_index = g_bc7_table_anchor_index_second_subset[pResults->m_partition]; + } + + anchor[k] = anchor_index; + + const uint32_t color_index_bits = get_bc7_color_index_size(best_mode, pResults->m_index_selector); + const uint32_t num_color_indices = 1 << color_index_bits; + + if (color_selectors[anchor_index] & (num_color_indices >> 1)) + { + for (uint32_t i = 0; i < 16; i++) + if (pPartition[i] == k) + color_selectors[i] = (uint8_t)((num_color_indices - 1) - color_selectors[i]); + + if (get_bc7_mode_has_seperate_alpha_selectors(best_mode)) + { + for (uint32_t q = 0; q < 3; q++) + { + uint8_t t = low[k].m_c[q]; + low[k].m_c[q] = high[k].m_c[q]; + high[k].m_c[q] = t; + } + } + else + { + color_rgba tmp = low[k]; + low[k] = high[k]; + high[k] = tmp; + } + + if (!g_bc7_mode_has_shared_p_bits[best_mode]) + { + uint32_t t = pbits[k][0]; + pbits[k][0] = pbits[k][1]; + pbits[k][1] = t; + } + } + + if (get_bc7_mode_has_seperate_alpha_selectors(best_mode)) + { + const uint32_t alpha_index_bits = get_bc7_alpha_index_size(best_mode, pResults->m_index_selector); + const uint32_t num_alpha_indices = 1 << alpha_index_bits; + + if (alpha_selectors[anchor_index] & (num_alpha_indices >> 1)) + { + for (uint32_t i = 0; i < 16; i++) + if (pPartition[i] == k) + alpha_selectors[i] = (uint8_t)((num_alpha_indices - 1) - alpha_selectors[i]); + + uint8_t t = low[k].m_c[3]; + low[k].m_c[3] = high[k].m_c[3]; + high[k].m_c[3] = t; + } + } + } + + uint8_t* pBlock_bytes = (uint8_t*)(pBlock); + memset(pBlock_bytes, 0, BC7ENC_BLOCK_SIZE); + + uint32_t cur_bit_ofs = 0; + set_block_bits(pBlock_bytes, 1 << best_mode, best_mode + 1, &cur_bit_ofs); + + if ((best_mode == 4) || (best_mode == 5)) + set_block_bits(pBlock_bytes, pResults->m_rotation, 2, &cur_bit_ofs); + + if (best_mode == 4) + set_block_bits(pBlock_bytes, pResults->m_index_selector, 1, &cur_bit_ofs); + + if (total_partitions > 1) + set_block_bits(pBlock_bytes, pResults->m_partition, (total_partitions == 64) ? 6 : 4, &cur_bit_ofs); + + const uint32_t total_comps = (best_mode >= 4) ? 4 : 3; + for (uint32_t comp = 0; comp < total_comps; comp++) + { + for (uint32_t subset = 0; subset < total_subsets; subset++) + { + set_block_bits(pBlock_bytes, low[subset].m_c[comp], (comp == 3) ? g_bc7_alpha_precision_table[best_mode] : g_bc7_color_precision_table[best_mode], &cur_bit_ofs); + set_block_bits(pBlock_bytes, high[subset].m_c[comp], (comp == 3) ? g_bc7_alpha_precision_table[best_mode] : g_bc7_color_precision_table[best_mode], &cur_bit_ofs); + } + } + + if (g_bc7_mode_has_p_bits[best_mode]) + { + for (uint32_t subset = 0; subset < total_subsets; subset++) + { + set_block_bits(pBlock_bytes, pbits[subset][0], 1, &cur_bit_ofs); + if (!g_bc7_mode_has_shared_p_bits[best_mode]) + set_block_bits(pBlock_bytes, pbits[subset][1], 1, &cur_bit_ofs); + } + } + + for (uint32_t y = 0; y < 4; y++) + { + for (uint32_t x = 0; x < 4; x++) + { + int idx = x + y * 4; + + uint32_t n = pResults->m_index_selector ? get_bc7_alpha_index_size(best_mode, pResults->m_index_selector) : get_bc7_color_index_size(best_mode, pResults->m_index_selector); + + if ((idx == anchor[0]) || (idx == anchor[1]) || (idx == anchor[2])) + n--; + + set_block_bits(pBlock_bytes, pResults->m_index_selector ? alpha_selectors[idx] : color_selectors[idx], n, &cur_bit_ofs); + } + } + + if (get_bc7_mode_has_seperate_alpha_selectors(best_mode)) + { + for (uint32_t y = 0; y < 4; y++) + { + for (uint32_t x = 0; x < 4; x++) + { + int idx = x + y * 4; + + uint32_t n = pResults->m_index_selector ? get_bc7_color_index_size(best_mode, pResults->m_index_selector) : get_bc7_alpha_index_size(best_mode, pResults->m_index_selector); + + if ((idx == anchor[0]) || (idx == anchor[1]) || (idx == anchor[2])) + n--; + + set_block_bits(pBlock_bytes, pResults->m_index_selector ? color_selectors[idx] : alpha_selectors[idx], n, &cur_bit_ofs); + } + } + } + + assert(cur_bit_ofs == 128); +} + +static void handle_alpha_block_mode5(const color_rgba* pPixels, const bc7enc_compress_block_params* pComp_params, color_cell_compressor_params* pParams, uint32_t lo_a, uint32_t hi_a, bc7_optimization_results* pOpt_results5, uint64_t* pMode5_err, uint64_t* pMode5_alpha_err) +{ + pParams->m_pSelector_weights = g_bc7_weights2; + pParams->m_pSelector_weightsx = (const vec4F*)g_bc7_weights2x; + pParams->m_num_selector_weights = 4; + + pParams->m_comp_bits = 7; + pParams->m_has_pbits = false; + pParams->m_endpoints_share_pbit = false; + pParams->m_has_alpha = false; + + pParams->m_perceptual = pComp_params->m_perceptual; + + pParams->m_num_pixels = 16; + pParams->m_pPixels = pPixels; + + color_cell_compressor_results results5; + results5.m_pSelectors = pOpt_results5->m_selectors; + + uint8_t selectors_temp[16]; + results5.m_pSelectors_temp = selectors_temp; + + *pMode5_err = color_cell_compression(5, pParams, &results5, pComp_params); + assert(*pMode5_err == results5.m_best_overall_err); + + pOpt_results5->m_low[0] = results5.m_low_endpoint; + pOpt_results5->m_high[0] = results5.m_high_endpoint; + + if (lo_a == hi_a) + { + *pMode5_alpha_err = 0; + pOpt_results5->m_low[0].m_c[3] = (uint8_t)lo_a; + pOpt_results5->m_high[0].m_c[3] = (uint8_t)hi_a; + memset(pOpt_results5->m_alpha_selectors, 0, sizeof(pOpt_results5->m_alpha_selectors)); + } + else + { + *pMode5_alpha_err = UINT64_MAX; + + const uint32_t total_passes = (pComp_params->m_uber_level >= 1) ? 3 : 2; + for (uint32_t pass = 0; pass < total_passes; pass++) + { + int32_t vals[4]; + vals[0] = lo_a; + vals[3] = hi_a; + + const int32_t w_s1 = 21, w_s2 = 43; + vals[1] = (vals[0] * (64 - w_s1) + vals[3] * w_s1 + 32) >> 6; + vals[2] = (vals[0] * (64 - w_s2) + vals[3] * w_s2 + 32) >> 6; + + uint8_t trial_alpha_selectors[16]; + + uint64_t trial_alpha_err = 0; + for (uint32_t i = 0; i < 16; i++) + { + const int32_t a = pParams->m_pPixels[i].m_c[3]; + + int s = 0; + int32_t be = iabs32(a - vals[0]); + int e = iabs32(a - vals[1]); if (e < be) { be = e; s = 1; } + e = iabs32(a - vals[2]); if (e < be) { be = e; s = 2; } + e = iabs32(a - vals[3]); if (e < be) { be = e; s = 3; } + + trial_alpha_selectors[i] = (uint8_t)s; + + uint32_t a_err = (uint32_t)(be * be) * pParams->m_weights[3]; + + trial_alpha_err += a_err; + } + + if (trial_alpha_err < *pMode5_alpha_err) + { + *pMode5_alpha_err = trial_alpha_err; + pOpt_results5->m_low[0].m_c[3] = (uint8_t)lo_a; + pOpt_results5->m_high[0].m_c[3] = (uint8_t)hi_a; + memcpy(pOpt_results5->m_alpha_selectors, trial_alpha_selectors, sizeof(pOpt_results5->m_alpha_selectors)); + } + + if (pass != (total_passes - 1U)) + { + float xl, xh; + compute_least_squares_endpoints_a(16, trial_alpha_selectors, (const vec4F*)g_bc7_weights2x, &xl, &xh, pParams->m_pPixels); + + uint32_t new_lo_a = clampi((int)floor(xl + .5f), 0, 255); + uint32_t new_hi_a = clampi((int)floor(xh + .5f), 0, 255); + if (new_lo_a > new_hi_a) + swapu(&new_lo_a, &new_hi_a); + + if ((new_lo_a == lo_a) && (new_hi_a == hi_a)) + break; + + lo_a = new_lo_a; + hi_a = new_hi_a; + } + } + + *pMode5_err += *pMode5_alpha_err; + } +} + +static void handle_alpha_block(void *pBlock, const color_rgba *pPixels, const bc7enc_compress_block_params *pComp_params, color_cell_compressor_params *pParams) +{ + assert((pComp_params->m_mode_mask & (1 << 6)) || (pComp_params->m_mode_mask & (1 << 5)) || (pComp_params->m_mode_mask & (1 << 7))); + + pParams->m_pSelector_weights = g_bc7_weights4; + pParams->m_pSelector_weightsx = (const vec4F *)g_bc7_weights4x; + pParams->m_num_selector_weights = 16; + pParams->m_comp_bits = 7; + pParams->m_has_pbits = true; + pParams->m_endpoints_share_pbit = false; + pParams->m_has_alpha = true; + pParams->m_perceptual = pComp_params->m_perceptual; + pParams->m_num_pixels = 16; + pParams->m_pPixels = pPixels; + + bc7_optimization_results opt_results6, opt_results5, opt_results7; + color_cell_compressor_results results6; + memset(&results6, 0, sizeof(results6)); + + uint64_t best_err = UINT64_MAX; + uint32_t best_mode = 0; + uint8_t selectors_temp[16]; + + if (pComp_params->m_mode_mask & (1 << 6)) + { + results6.m_pSelectors = opt_results6.m_selectors; + results6.m_pSelectors_temp = selectors_temp; + + best_err = (uint64_t)(color_cell_compression(6, pParams, &results6, pComp_params) * pComp_params->m_mode6_error_weight + .5f); + best_mode = 6; + } + + if ((best_err > 0) && (pComp_params->m_mode_mask & (1 << 5))) + { + uint32_t lo_a = 255, hi_a = 0; + for (uint32_t i = 0; i < 16; i++) + { + uint32_t a = pPixels[i].m_c[3]; + lo_a = minimumu(lo_a, a); + hi_a = maximumu(hi_a, a); + } + + uint64_t mode5_err, mode5_alpha_err; + handle_alpha_block_mode5(pPixels, pComp_params, pParams, lo_a, hi_a, &opt_results5, &mode5_err, &mode5_alpha_err); + + mode5_err = (uint64_t)(mode5_err * pComp_params->m_mode5_error_weight + .5f); + + if (mode5_err < best_err) + { + best_err = mode5_err; + best_mode = 5; + } + } + + if ((best_err > 0) && (pComp_params->m_mode_mask & (1 << 7))) + { + const uint32_t trial_partition = estimate_partition(pPixels, pComp_params, pParams->m_weights, 7); + + pParams->m_pSelector_weights = g_bc7_weights2; + pParams->m_pSelector_weightsx = (const vec4F*)g_bc7_weights2x; + pParams->m_num_selector_weights = 4; + pParams->m_comp_bits = 5; + pParams->m_has_pbits = true; + pParams->m_endpoints_share_pbit = false; + pParams->m_has_alpha = true; + + const uint8_t* pPartition = &g_bc7_partition2[trial_partition * 16]; + + color_rgba subset_colors[2][16]; + + uint32_t subset_total_colors7[2] = { 0, 0 }; + + uint8_t subset_pixel_index7[2][16]; + uint8_t subset_selectors7[2][16]; + color_cell_compressor_results subset_results7[2]; + + for (uint32_t idx = 0; idx < 16; idx++) + { + const uint32_t p = pPartition[idx]; + subset_colors[p][subset_total_colors7[p]] = pPixels[idx]; + subset_pixel_index7[p][subset_total_colors7[p]] = (uint8_t)idx; + subset_total_colors7[p]++; + } + + uint64_t trial_err = 0; + for (uint32_t subset = 0; subset < 2; subset++) + { + pParams->m_num_pixels = subset_total_colors7[subset]; + pParams->m_pPixels = &subset_colors[subset][0]; + + color_cell_compressor_results* pResults = &subset_results7[subset]; + pResults->m_pSelectors = &subset_selectors7[subset][0]; + pResults->m_pSelectors_temp = selectors_temp; + uint64_t err = color_cell_compression(7, pParams, pResults, pComp_params); + trial_err += err; + if ((uint64_t)(trial_err * pComp_params->m_mode7_error_weight + .5f) > best_err) + break; + + } // subset + + const uint64_t mode7_trial_err = (uint64_t)(trial_err * pComp_params->m_mode7_error_weight + .5f); + + if (mode7_trial_err < best_err) + { + best_err = mode7_trial_err; + best_mode = 7; + opt_results7.m_mode = 7; + opt_results7.m_partition = trial_partition; + opt_results7.m_index_selector = 0; + opt_results7.m_rotation = 0; + for (uint32_t subset = 0; subset < 2; subset++) + { + for (uint32_t i = 0; i < subset_total_colors7[subset]; i++) + opt_results7.m_selectors[subset_pixel_index7[subset][i]] = subset_selectors7[subset][i]; + opt_results7.m_low[subset] = subset_results7[subset].m_low_endpoint; + opt_results7.m_high[subset] = subset_results7[subset].m_high_endpoint; + opt_results7.m_pbits[subset][0] = subset_results7[subset].m_pbits[0]; + opt_results7.m_pbits[subset][1] = subset_results7[subset].m_pbits[1]; + } + } + } + + if (best_mode == 7) + { + encode_bc7_block(pBlock, &opt_results7); + } + else if (best_mode == 5) + { + opt_results5.m_mode = 5; + opt_results5.m_partition = 0; + opt_results5.m_rotation = 0; + opt_results5.m_index_selector = 0; + + encode_bc7_block(pBlock, &opt_results5); + } + else if (best_mode == 6) + { + opt_results6.m_mode = 6; + opt_results6.m_partition = 0; + opt_results6.m_low[0] = results6.m_low_endpoint; + opt_results6.m_high[0] = results6.m_high_endpoint; + opt_results6.m_pbits[0][0] = results6.m_pbits[0]; + opt_results6.m_pbits[0][1] = results6.m_pbits[1]; + opt_results6.m_rotation = 0; + opt_results6.m_index_selector = 0; + + encode_bc7_block(pBlock, &opt_results6); + } + else + { + assert(0); + } +} + +static void handle_opaque_block(void *pBlock, const color_rgba *pPixels, const bc7enc_compress_block_params *pComp_params, color_cell_compressor_params *pParams) +{ + assert((pComp_params->m_mode_mask & (1 << 6)) || (pComp_params->m_mode_mask & (1 << 1))); + + uint8_t selectors_temp[16]; + + bc7_optimization_results opt_results; + + uint64_t best_err = UINT64_MAX; + + pParams->m_perceptual = pComp_params->m_perceptual; + pParams->m_num_pixels = 16; + pParams->m_pPixels = pPixels; + pParams->m_has_alpha = false; + + opt_results.m_partition = 0; + opt_results.m_index_selector = 0; + opt_results.m_rotation = 0; + + // Mode 6 + if (pComp_params->m_mode_mask & (1 << 6)) + { + pParams->m_pSelector_weights = g_bc7_weights4; + pParams->m_pSelector_weightsx = (const vec4F*)g_bc7_weights4x; + pParams->m_num_selector_weights = 16; + pParams->m_comp_bits = 7; + pParams->m_has_pbits = true; + pParams->m_endpoints_share_pbit = false; + + color_cell_compressor_results results6; + results6.m_pSelectors = opt_results.m_selectors; + results6.m_pSelectors_temp = selectors_temp; + + best_err = (uint64_t)(color_cell_compression(6, pParams, &results6, pComp_params) * pComp_params->m_mode6_error_weight + .5f); + + opt_results.m_mode = 6; + opt_results.m_low[0] = results6.m_low_endpoint; + opt_results.m_high[0] = results6.m_high_endpoint; + opt_results.m_pbits[0][0] = results6.m_pbits[0]; + opt_results.m_pbits[0][1] = results6.m_pbits[1]; + } + + // Mode 1 + if ((best_err > 0) && (pComp_params->m_max_partitions > 0) && (pComp_params->m_mode_mask & (1 << 1))) + { + const uint32_t trial_partition = estimate_partition(pPixels, pComp_params, pParams->m_weights, 1); + + pParams->m_pSelector_weights = g_bc7_weights3; + pParams->m_pSelector_weightsx = (const vec4F *)g_bc7_weights3x; + pParams->m_num_selector_weights = 8; + pParams->m_comp_bits = 6; + pParams->m_has_pbits = true; + pParams->m_endpoints_share_pbit = true; + + const uint8_t *pPartition = &g_bc7_partition2[trial_partition * 16]; + + color_rgba subset_colors[2][16]; + + uint32_t subset_total_colors1[2] = { 0, 0 }; + + uint8_t subset_pixel_index1[2][16]; + uint8_t subset_selectors1[2][16]; + color_cell_compressor_results subset_results1[2]; + + for (uint32_t idx = 0; idx < 16; idx++) + { + const uint32_t p = pPartition[idx]; + subset_colors[p][subset_total_colors1[p]] = pPixels[idx]; + subset_pixel_index1[p][subset_total_colors1[p]] = (uint8_t)idx; + subset_total_colors1[p]++; + } + + uint64_t trial_err = 0; + for (uint32_t subset = 0; subset < 2; subset++) + { + pParams->m_num_pixels = subset_total_colors1[subset]; + pParams->m_pPixels = &subset_colors[subset][0]; + + color_cell_compressor_results *pResults = &subset_results1[subset]; + pResults->m_pSelectors = &subset_selectors1[subset][0]; + pResults->m_pSelectors_temp = selectors_temp; + uint64_t err = color_cell_compression(1, pParams, pResults, pComp_params); + + trial_err += err; + if ((uint64_t)(trial_err * pComp_params->m_mode1_error_weight + .5f) > best_err) + break; + + } // subset + + const uint64_t mode1_trial_err = (uint64_t)(trial_err * pComp_params->m_mode1_error_weight + .5f); + if (mode1_trial_err < best_err) + { + best_err = mode1_trial_err; + opt_results.m_mode = 1; + opt_results.m_partition = trial_partition; + for (uint32_t subset = 0; subset < 2; subset++) + { + for (uint32_t i = 0; i < subset_total_colors1[subset]; i++) + opt_results.m_selectors[subset_pixel_index1[subset][i]] = subset_selectors1[subset][i]; + opt_results.m_low[subset] = subset_results1[subset].m_low_endpoint; + opt_results.m_high[subset] = subset_results1[subset].m_high_endpoint; + opt_results.m_pbits[subset][0] = subset_results1[subset].m_pbits[0]; + } + } + } + + encode_bc7_block(pBlock, &opt_results); +} + +bool bc7enc_compress_block(void *pBlock, const void *pPixelsRGBA, const bc7enc_compress_block_params *pComp_params) +{ + assert(g_bc7_mode_1_optimal_endpoints[255][0].m_hi != 0); + + const color_rgba *pPixels = (const color_rgba *)(pPixelsRGBA); + + color_cell_compressor_params params; + if (pComp_params->m_perceptual) + { + // https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.709_conversion + const float pr_weight = (.5f / (1.0f - .2126f)) * (.5f / (1.0f - .2126f)); + const float pb_weight = (.5f / (1.0f - .0722f)) * (.5f / (1.0f - .0722f)); + params.m_weights[0] = (int)(pComp_params->m_weights[0] * 4.0f); + params.m_weights[1] = (int)(pComp_params->m_weights[1] * 4.0f * pr_weight); + params.m_weights[2] = (int)(pComp_params->m_weights[2] * 4.0f * pb_weight); + params.m_weights[3] = pComp_params->m_weights[3] * 4; + } + else + memcpy(params.m_weights, pComp_params->m_weights, sizeof(params.m_weights)); + + if (pComp_params->m_force_alpha) + { + handle_alpha_block(pBlock, pPixels, pComp_params, ¶ms); + return true; + } + + for (uint32_t i = 0; i < 16; i++) + { + if (pPixels[i].m_c[3] < 255) + { + handle_alpha_block(pBlock, pPixels, pComp_params, ¶ms); + return true; + } + } + handle_opaque_block(pBlock, pPixels, pComp_params, ¶ms); + return false; +} + +static const uint8_t g_tdefl_small_dist_extra[512] = +{ + 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7 +}; + +static const uint8_t g_tdefl_large_dist_extra[128] = +{ + 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 +}; + +static inline uint32_t compute_match_cost_estimate(uint32_t dist, uint32_t match_len_in_bytes) +{ + assert(match_len_in_bytes <= 258); + + uint32_t len_cost = 6; + if (match_len_in_bytes >= 12) + len_cost = 9; + else if (match_len_in_bytes >= 8) + len_cost = 8; + else if (match_len_in_bytes >= 6) + len_cost = 7; + + uint32_t dist_cost = 5; + if (dist < 512) + dist_cost += g_tdefl_small_dist_extra[dist & 511]; + else + { + dist_cost += g_tdefl_large_dist_extra[std::min(dist, 32767) >> 8]; + while (dist >= 32768) + { + dist_cost++; + dist >>= 1; + } + } + return len_cost + dist_cost; +} + +class tracked_stat +{ +public: + tracked_stat() { clear(); } + + void clear() { m_num = 0; m_total = 0; m_total2 = 0; } + + void update(uint32_t val) { m_num++; m_total += val; m_total2 += val * val; } + + tracked_stat& operator += (uint32_t val) { update(val); return *this; } + + uint32_t get_number_of_values() { return m_num; } + uint64_t get_total() const { return m_total; } + uint64_t get_total2() const { return m_total2; } + + float get_average() const { return m_num ? (float)m_total / m_num : 0.0f; }; + float get_std_dev() const { return m_num ? sqrtf((float)(m_num * m_total2 - m_total * m_total)) / m_num : 0.0f; } + float get_variance() const { float s = get_std_dev(); return s * s; } + +private: + uint32_t m_num; + uint64_t m_total; + uint64_t m_total2; +}; + +static inline float compute_block_max_std_dev(const color_rgba* pPixels) +{ + tracked_stat r_stats, g_stats, b_stats, a_stats; + + for (uint32_t i = 0; i < 16; i++) + { + r_stats.update(pPixels[i].m_c[0]); + g_stats.update(pPixels[i].m_c[1]); + b_stats.update(pPixels[i].m_c[2]); + a_stats.update(pPixels[i].m_c[3]); + } + + return std::max(std::max(std::max(r_stats.get_std_dev(), g_stats.get_std_dev()), b_stats.get_std_dev()), a_stats.get_std_dev()); +} + +struct bc7_block +{ + uint8_t m_bytes[16]; + + uint32_t get_mode() const + { + uint32_t bc7_mode = 0; + while (((m_bytes[0] & (1 << bc7_mode)) == 0) && (bc7_mode < 8)) + bc7_mode++; + return bc7_mode; + } +}; + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +If you use this software in a product, attribution / credits is requested but not required. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright(c) 2020-2021 Richard Geldreich, Jr. +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files(the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions : +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain(www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non - commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain.We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors.We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/ktx.imageio/bc7enc-rdo/bc7enc.h b/src/ktx.imageio/bc7enc-rdo/bc7enc.h new file mode 100644 index 0000000000..3f410ec3af --- /dev/null +++ b/src/ktx.imageio/bc7enc-rdo/bc7enc.h @@ -0,0 +1,124 @@ +// clang-format off +#pragma once + +// File: bc7enc.h - Richard Geldreich, Jr. - MIT license or public domain (see end of bc7enc.c) +// If you use this software in a product, attribution / credits is requested but not required. +#include +#include +#include +#include + +#define BC7ENC_BLOCK_SIZE (16) +#define BC7ENC_MAX_PARTITIONS (64) +#define BC7ENC_MAX_UBER_LEVEL (4) + +struct color_rgba { uint8_t m_c[4]; }; + +struct bc7enc_compress_block_params +{ + uint32_t m_mode_mask; + + // m_max_partitions may range from 0 (disables mode 1) to BC7ENC_MAX_PARTITIONS. The higher this value, the slower the compressor, but the higher the quality. + uint32_t m_max_partitions; + + // Relative RGBA or YCbCrA weights. + uint32_t m_weights[4]; + + // m_uber_level may range from 0 to BC7ENC_MAX_UBER_LEVEL. The higher this value, the slower the compressor, but the higher the quality. + uint32_t m_uber_level; + + // If m_perceptual is true, colorspace error is computed in YCbCr space, otherwise RGB. + bool m_perceptual; + + // Set m_try_least_squares to false for slightly faster/lower quality compression. + bool m_try_least_squares; + + // When m_mode17_partition_estimation_filterbank, the mode1 partition estimator skips lesser used partition patterns unless they are strongly predicted to be potentially useful. + // There's a slight loss in quality with this enabled (around .08 dB RGB PSNR or .05 dB Y PSNR), but up to a 11% gain in speed depending on the other settings. + bool m_mode17_partition_estimation_filterbank; + + bool m_force_alpha; + + bool m_force_selectors; + uint8_t m_selectors[16]; + + bool m_quant_mode6_endpoints; + bool m_bias_mode1_pbits; + + float m_pbit1_weight; + + float m_mode1_error_weight; + float m_mode5_error_weight; + float m_mode6_error_weight; + float m_mode7_error_weight; + + float m_low_frequency_partition_weight; + + void clear() + { + memset(this, 0, sizeof(*this)); + } + + void print() + { + printf("Mode mask: 0x%X\n", m_mode_mask); + printf("Max partitions: %u\n", m_max_partitions); + printf("Weights: %u %u %u %u\n", m_weights[0], m_weights[1], m_weights[2], m_weights[3]); + printf("Uber level: %u\n", m_uber_level); + printf("Perceptual: %u\n", m_perceptual); + printf("Try least squares: %u\n", m_try_least_squares); + printf("Mode 1/7 partition estimation filterbank: %u\n", m_mode17_partition_estimation_filterbank); + printf("Force alpha: %u\n", m_force_alpha); + printf("Quant mode 6 endpoints: %u\n", m_quant_mode6_endpoints); + printf("Bias mode 1 p-bits: %u\n", m_bias_mode1_pbits); + printf("p-bit 1 weight: %f\n", m_pbit1_weight); + printf("Mode error weights: %f %f %f %f\n", m_mode1_error_weight, m_mode5_error_weight, m_mode6_error_weight, m_mode7_error_weight); + printf("Low frequency partition weight: %f\n", m_low_frequency_partition_weight); + } +}; + +inline void bc7enc_compress_block_params_init_linear_weights(bc7enc_compress_block_params *p) +{ + p->m_perceptual = false; + p->m_weights[0] = 1; + p->m_weights[1] = 1; + p->m_weights[2] = 1; + p->m_weights[3] = 1; +} + +inline void bc7enc_compress_block_params_init_perceptual_weights(bc7enc_compress_block_params *p) +{ + p->m_perceptual = true; + p->m_weights[0] = 128; + p->m_weights[1] = 64; + p->m_weights[2] = 16; + p->m_weights[3] = 32; +} + +inline void bc7enc_compress_block_params_init(bc7enc_compress_block_params *p) +{ + p->m_mode_mask = UINT32_MAX; + p->m_max_partitions = BC7ENC_MAX_PARTITIONS; + p->m_try_least_squares = true; + p->m_mode17_partition_estimation_filterbank = true; + p->m_uber_level = 0; + p->m_force_selectors = false; + p->m_force_alpha = false; + p->m_quant_mode6_endpoints = false; + p->m_bias_mode1_pbits = false; + p->m_pbit1_weight = 1.0f; + p->m_mode1_error_weight = 1.0f; + p->m_mode5_error_weight = 1.0f; + p->m_mode6_error_weight = 1.0f; + p->m_mode7_error_weight = 1.0f; + p->m_low_frequency_partition_weight = 1.0f; + bc7enc_compress_block_params_init_perceptual_weights(p); +} + +// bc7enc_compress_block_init() MUST be called before calling bc7enc_compress_block() (or you'll get artifacts). +void bc7enc_compress_block_init(); + +// Packs a single block of 16x16 RGBA pixels (R first in memory) to 128-bit BC7 block pBlock, using either mode 1 and/or 6. +// Alpha blocks will always use mode 6, and by default opaque blocks will use either modes 1 or 6. +// Returns true if the block had any pixels with alpha < 255, otherwise it return false. (This is not an error code - a block is always encoded.) +bool bc7enc_compress_block(void *pBlock, const void *pPixelsRGBA, const bc7enc_compress_block_params *pComp_params); diff --git a/src/ktx.imageio/bc7enc-rdo/rgbcx.cpp b/src/ktx.imageio/bc7enc-rdo/rgbcx.cpp new file mode 100644 index 0000000000..06aa368128 --- /dev/null +++ b/src/ktx.imageio/bc7enc-rdo/rgbcx.cpp @@ -0,0 +1,3081 @@ +// clang-format off +// rgbcx.cpp - see license at end of rgbcx.h +#include "rgbcx.h" +#include +#include +#include + +namespace rgbcx +{ + [[maybe_unused]] const uint8_t g_bc1_to_linear[4] = { 0, 3, 1, 2 }; + + const uint32_t NUM_UNIQUE_TOTAL_ORDERINGS4 = 969; + +#ifdef _MSC_VER +#pragma region +#endif + // All total orderings for 16 pixels 2-bit selectors. + // BC1 selector order 0, 2, 3, 1 (i.e. the selectors are reordered into linear order). + static uint8_t g_unique_total_orders4[NUM_UNIQUE_TOTAL_ORDERINGS4][4] = + { + {0,8,2,6},{4,3,9,0},{4,8,1,3},{12,0,3,1},{11,3,2,0},{6,4,6,0},{7,5,0,4},{6,0,8,2},{1,0,0,15},{3,0,8,5},{1,1,13,1},{13,1,2,0},{0,14,1,1},{0,15,1,0},{0,13,0,3},{16,0,0,0},{4,3,4,5},{8,6,0,2},{0,10,0,6},{10,0,4,2},{7,2,1,6},{4,7,5,0},{1,4,7,4},{0,14,2,0},{2,7,2,5},{9,0,5,2},{9,2,2,3},{10,0,5,1},{2,3,7,4},{4,9,0,3},{1,5,0,10},{1,1,6,8}, + {6,6,4,0},{11,5,0,0},{11,2,0,3},{4,0,10,2},{2,3,10,1},{1,13,1,1},{0,14,0,2},{2,3,3,8},{12,3,1,0},{14,0,0,2},{9,1,3,3},{6,4,0,6},{1,1,5,9},{5,9,0,2},{2,10,1,3},{12,0,0,4},{4,6,6,0},{0,6,4,6},{3,7,4,2},{0,13,3,0},{3,10,0,3},{10,2,1,3},{1,12,1,2},{2,0,13,1},{11,0,5,0},{12,1,3,0},{6,4,5,1},{10,4,2,0},{3,6,1,6},{7,3,6,0},{10,4,0,2},{10,0,2,4}, + {0,5,9,2},{0,9,3,4},{6,4,2,4},{3,4,7,2},{3,3,5,5},{4,2,9,1},{6,2,8,0},{3,5,3,5},{4,10,1,1},{10,1,3,2},{5,7,0,4},{5,3,7,1},{6,8,1,1},{8,8,0,0},{11,1,0,4},{14,1,0,1},{9,3,2,2},{8,2,1,5},{0,0,2,14},{3,3,9,1},{10,1,5,0},{8,3,1,4},{1,5,8,2},{6,1,9,0},{3,2,1,10},{3,11,1,1},{7,6,3,0},{9,0,3,4},{5,2,5,4},{0,2,3,11},{15,0,0,1},{0,6,6,4}, + {3,4,9,0},{4,7,0,5},{0,4,4,8},{0,13,2,1},{2,4,1,9},{3,2,5,6},{10,6,0,0},{3,5,6,2},{8,0,4,4},{1,3,6,6},{7,7,0,2},{6,1,4,5},{0,11,1,4},{2,2,8,4},{0,1,2,13},{15,0,1,0},{7,2,6,1},{8,1,7,0},{1,8,4,3},{2,13,1,0},{1,0,7,8},{14,2,0,0},{1,8,1,6},{9,3,3,1},{0,0,7,9},{4,4,1,7},{9,0,6,1},{10,2,4,0},{1,7,3,5},{0,3,8,5},{5,2,4,5},{1,2,5,8}, + {0,8,7,1},{10,3,2,1},{12,0,4,0},{2,1,4,9},{5,2,2,7},{1,9,3,3},{15,1,0,0},{6,3,4,3},{9,5,0,2},{1,6,9,0},{6,6,0,4},{13,2,1,0},{5,1,8,2},{0,5,11,0},{7,1,0,8},{1,2,12,1},{0,3,3,10},{7,4,2,3},{5,1,4,6},{7,0,3,6},{3,12,0,1},{3,4,5,4},{1,10,0,5},{7,4,3,2},{10,5,0,1},{13,3,0,0},{2,5,4,5},{3,10,1,2},{5,1,2,8},{14,0,1,1},{1,5,4,6},{1,4,5,6}, + {2,3,11,0},{11,0,4,1},{11,2,2,1},{5,3,8,0},{1,3,10,2},{0,1,13,2},{3,1,4,8},{4,2,4,6},{1,5,6,4},{2,1,11,2},{1,2,9,4},{4,7,3,2},{6,2,5,3},{7,2,2,5},{8,1,4,3},{3,2,8,3},{12,1,0,3},{7,8,1,0},{7,0,2,7},{5,10,0,1},{0,2,14,0},{2,9,3,2},{7,0,0,9},{11,1,4,0},{10,4,1,1},{2,2,9,3},{5,7,2,2},{1,3,1,11},{13,2,0,1},{4,2,8,2},{2,3,1,10},{4,2,5,5}, + {7,0,7,2},{10,0,0,6},{0,8,5,3},{4,4,0,8},{12,4,0,0},{0,1,14,1},{8,0,1,7},{5,1,5,5},{11,0,3,2},{0,4,1,11},{0,8,8,0},{0,2,5,9},{7,3,2,4},{7,8,0,1},{1,0,3,12},{7,4,5,0},{1,6,7,2},{7,6,1,2},{9,6,1,0},{12,2,0,2},{4,1,6,5},{4,0,1,11},{8,4,4,0},{13,0,1,2},{8,6,2,0},{4,12,0,0},{2,7,5,2},{2,0,5,9},{5,4,5,2},{3,8,5,0},{7,3,3,3},{4,4,8,0}, + {2,1,3,10},{5,0,1,10},{6,4,3,3},{4,9,1,2},{1,4,0,11},{11,3,1,1},{4,0,12,0},{13,0,0,3},{6,1,6,3},{9,0,4,3},{8,0,0,8},{8,4,0,4},{0,12,1,3},{0,4,10,2},{3,4,8,1},{1,3,8,4},{9,2,5,0},{5,7,4,0},{1,0,11,4},{4,10,0,2},{1,3,12,0},{6,9,0,1},{5,0,9,2},{5,9,2,0},{13,1,0,2},{9,3,4,0},{9,4,0,3},{3,1,12,0},{2,4,3,7},{1,2,13,0},{2,2,4,8},{6,8,0,2}, + {9,2,1,4},{9,5,1,1},{2,0,4,10},{5,4,0,7},{0,0,6,10},{1,2,0,13},{4,7,2,3},{6,5,5,0},{3,3,1,9},{1,6,1,8},{12,2,1,1},{4,4,5,3},{1,0,6,9},{0,6,10,0},{4,8,3,1},{4,3,2,7},{2,1,7,6},{1,9,1,5},{3,1,3,9},{8,7,1,0},{1,2,3,10},{14,1,1,0},{5,4,4,3},{3,7,0,6},{7,4,1,4},{3,7,5,1},{1,1,0,14},{0,10,3,3},{0,4,3,9},{1,7,7,1},{2,0,10,4},{5,8,0,3}, + {6,7,3,0},{0,8,4,4},{5,7,3,1},{7,9,0,0},{7,6,2,1},{0,4,5,7},{6,3,5,2},{1,2,1,12},{5,2,0,9},{8,5,0,3},{4,6,1,5},{1,1,7,7},{10,5,1,0},{1,2,8,5},{1,8,2,5},{5,1,0,10},{6,9,1,0},{13,0,2,1},{8,3,5,0},{6,3,6,1},{2,11,3,0},{3,7,3,3},{1,5,2,8},{7,5,2,2},{0,6,7,3},{13,1,1,1},{5,3,4,4},{7,2,7,0},{5,8,3,0},{3,13,0,0},{0,7,9,0},{8,0,3,5}, + {1,3,7,5},{4,0,2,10},{12,0,1,3},{1,7,6,2},{3,9,0,4},{7,2,0,7},{0,1,7,8},{2,1,8,5},{0,13,1,2},{0,8,1,7},{5,0,11,0},{5,6,2,3},{0,3,0,13},{2,3,4,7},{5,6,3,2},{4,2,10,0},{3,3,7,3},{7,2,5,2},{1,1,11,3},{12,3,0,1},{5,1,1,9},{1,15,0,0},{9,7,0,0},{9,1,2,4},{0,7,3,6},{3,0,13,0},{3,0,11,2},{0,6,5,5},{8,2,2,4},{6,10,0,0},{4,8,4,0},{0,0,3,13}, + {0,4,12,0},{7,1,6,2},{3,5,0,8},{8,0,6,2},{6,2,3,5},{2,10,0,4},{4,11,0,1},{6,1,5,4},{5,1,3,7},{0,11,3,2},{4,6,0,6},{2,6,0,8},{3,1,7,5},{2,14,0,0},{2,9,2,3},{0,3,4,9},{11,0,1,4},{13,0,3,0},{8,3,0,5},{0,5,3,8},{5,11,0,0},{0,1,4,11},{2,1,9,4},{3,4,4,5},{7,1,2,6},{12,2,2,0},{9,4,1,2},{6,0,2,8},{4,6,2,4},{11,2,3,0},{3,2,2,9},{10,3,1,2}, + {1,1,2,12},{0,5,2,9},{0,1,11,4},{6,2,4,4},{2,8,2,4},{0,9,4,3},{11,0,2,3},{0,2,11,3},{6,0,7,3},{0,3,6,7},{4,5,5,2},{1,2,6,7},{7,5,1,3},{9,0,2,5},{2,6,4,4},{4,1,9,2},{4,8,2,2},{1,12,3,0},{0,9,6,1},{0,10,6,0},{3,1,5,7},{2,13,0,1},{2,2,1,11},{3,6,0,7},{5,6,5,0},{5,5,4,2},{4,0,3,9},{3,4,1,8},{0,11,2,3},{2,12,1,1},{7,1,3,5},{7,0,9,0}, + {8,0,8,0},{1,0,2,13},{3,3,10,0},{2,4,4,6},{2,3,8,3},{1,10,5,0},{7,3,0,6},{2,9,0,5},{1,4,6,5},{6,6,3,1},{5,6,0,5},{6,3,0,7},{3,10,2,1},{2,5,5,4},{3,8,4,1},{1,14,0,1},{10,3,3,0},{3,5,7,1},{1,1,3,11},{2,4,0,10},{9,3,1,3},{5,10,1,0},{3,0,6,7},{3,1,9,3},{11,2,1,2},{5,3,3,5},{0,5,1,10},{4,1,11,0},{10,2,0,4},{7,6,0,3},{2,7,0,7},{4,2,2,8}, + {6,1,7,2},{4,9,2,1},{0,0,8,8},{3,7,2,4},{9,6,0,1},{0,12,4,0},{6,7,1,2},{0,7,2,7},{1,0,10,5},{0,0,14,2},{2,7,3,4},{5,0,0,11},{7,7,1,1},{6,2,7,1},{4,5,3,4},{3,5,1,7},{5,9,1,1},{6,2,1,7},{3,2,0,11},{0,11,0,5},{3,11,2,0},{10,1,4,1},{7,0,4,5},{11,4,0,1},{10,3,0,3},{0,2,4,10},{0,15,0,1},{0,11,5,0},{6,7,2,1},{1,12,2,1},{4,1,3,8},{1,0,13,2}, + {1,8,5,2},{7,0,1,8},{3,12,1,0},{9,2,4,1},{1,7,4,4},{11,4,1,0},{4,3,8,1},{2,8,4,2},{1,11,3,1},{1,1,4,10},{4,10,2,0},{8,2,5,1},{1,0,9,6},{5,3,2,6},{0,9,7,0},{10,2,2,2},{5,8,1,2},{8,7,0,1},{0,3,12,1},{1,0,1,14},{4,8,0,4},{3,8,0,5},{4,6,5,1},{0,9,5,2},{10,2,3,1},{2,3,9,2},{1,0,12,3},{11,3,0,2},{4,5,2,5},{0,2,12,2},{9,1,0,6},{9,2,0,5}, + {1,2,7,6},{4,7,4,1},{0,12,2,2},{0,0,0,16},{2,8,3,3},{3,6,2,5},{0,6,3,7},{7,5,4,0},{3,3,3,7},{3,3,0,10},{5,0,6,5},{0,0,10,6},{8,5,3,0},{8,1,5,2},{6,0,9,1},{11,1,2,2},{2,11,2,1},{9,5,2,0},{3,0,4,9},{2,2,12,0},{2,6,6,2},{2,1,13,0},{6,0,5,5},{2,0,14,0},{2,11,1,2},{4,4,7,1},{2,0,11,3},{3,1,1,11},{2,9,4,1},{3,7,6,0},{14,0,2,0},{1,10,4,1}, + {8,0,7,1},{3,6,5,2},{0,3,11,2},{2,5,6,3},{11,1,3,1},{6,5,3,2},{3,8,1,4},{0,2,7,7},{2,10,2,2},{1,6,2,7},{11,0,0,5},{12,1,1,2},{12,1,2,1},{0,7,1,8},{0,3,9,4},{0,2,1,13},{7,1,4,4},{10,1,0,5},{4,0,8,4},{5,2,7,2},{0,2,0,14},{4,3,7,2},{2,7,1,6},{1,2,2,11},{6,3,3,4},{1,14,1,0},{2,4,6,4},{5,3,6,2},{5,3,5,3},{8,4,1,3},{1,3,0,12},{3,5,2,6}, + {1,8,7,0},{0,7,4,5},{2,1,6,7},{4,11,1,0},{7,2,4,3},{6,1,3,6},{4,5,4,3},{2,11,0,3},{1,5,7,3},{12,0,2,2},{5,0,4,7},{1,13,0,2},{7,7,2,0},{4,1,7,4},{4,5,0,7},{5,0,5,6},{6,5,4,1},{2,4,2,8},{1,10,1,4},{6,3,1,6},{3,3,8,2},{0,7,7,2},{4,4,2,6},{1,1,8,6},{1,12,0,3},{2,1,12,1},{1,9,2,4},{1,11,0,4},{2,5,2,7},{10,0,3,3},{4,6,3,3},{3,7,1,5}, + {1,9,0,6},{7,1,7,1},{1,6,5,4},{9,2,3,2},{6,2,2,6},{2,2,2,10},{8,3,3,2},{0,1,8,7},{2,0,8,6},{0,3,1,12},{9,4,2,1},{9,4,3,0},{6,2,6,2},{1,8,0,7},{5,1,10,0},{0,5,5,6},{8,2,4,2},{2,3,2,9},{6,0,3,7},{2,2,6,6},{2,6,2,6},{1,13,2,0},{9,3,0,4},{7,3,5,1},{6,5,2,3},{5,2,6,3},{2,0,12,2},{5,7,1,3},{8,1,3,4},{3,1,10,2},{1,0,15,0},{0,8,0,8}, + {5,0,7,4},{4,4,6,2},{0,1,0,15},{10,0,1,5},{7,3,4,2},{4,9,3,0},{2,5,7,2},{3,4,2,7},{8,3,2,3},{5,1,6,4},{0,10,2,4},{6,6,1,3},{6,0,0,10},{4,4,3,5},{1,3,9,3},{7,5,3,1},{3,0,7,6},{1,8,6,1},{4,3,0,9},{3,11,0,2},{6,0,6,4},{0,1,3,12},{0,4,2,10},{5,5,6,0},{4,1,4,7},{8,1,6,1},{5,6,4,1},{8,4,2,2},{4,3,1,8},{3,0,2,11},{1,11,4,0},{0,8,3,5}, + {5,1,7,3},{7,0,8,1},{4,3,5,4},{4,6,4,2},{3,2,4,7},{1,6,3,6},{0,7,8,1},{3,0,1,12},{9,1,4,2},{7,4,0,5},{1,7,0,8},{5,4,1,6},{9,1,5,1},{1,1,9,5},{4,1,1,10},{5,3,0,8},{2,2,5,7},{4,0,0,12},{9,0,7,0},{3,4,0,9},{0,2,6,8},{8,2,0,6},{3,2,6,5},{4,2,6,4},{3,6,4,3},{2,8,6,0},{5,0,3,8},{0,4,0,12},{0,16,0,0},{0,9,2,5},{4,0,11,1},{1,6,4,5}, + {0,1,6,9},{3,4,6,3},{3,0,10,3},{7,0,6,3},{1,4,9,2},{1,5,3,7},{8,5,2,1},{0,12,0,4},{7,2,3,4},{0,5,6,5},{11,1,1,3},{6,5,0,5},{2,1,5,8},{1,4,11,0},{9,1,1,5},{0,0,13,3},{5,8,2,1},{2,12,0,2},{3,3,6,4},{4,1,10,1},{4,0,5,7},{8,1,0,7},{5,1,9,1},{4,3,3,6},{0,2,2,12},{6,3,2,5},{0,0,12,4},{1,5,1,9},{2,6,5,3},{3,6,3,4},{2,12,2,0},{1,6,8,1}, + {10,1,1,4},{1,3,4,8},{7,4,4,1},{1,11,1,3},{1,2,10,3},{3,9,3,1},{8,5,1,2},{2,10,4,0},{4,2,0,10},{2,7,6,1},{8,2,3,3},{1,5,5,5},{3,1,0,12},{3,10,3,0},{8,0,5,3},{0,6,8,2},{0,3,13,0},{0,0,16,0},{1,9,4,2},{4,1,8,3},{1,6,6,3},{0,10,5,1},{0,1,12,3},{4,0,6,6},{3,8,3,2},{0,5,4,7},{1,0,14,1},{0,4,6,6},{3,9,1,3},{3,5,8,0},{3,6,6,1},{5,4,7,0}, + {3,0,12,1},{8,6,1,1},{2,9,5,0},{6,1,1,8},{4,1,2,9},{3,9,4,0},{5,2,9,0},{0,12,3,1},{1,4,10,1},{4,0,7,5},{3,1,2,10},{5,4,2,5},{5,5,5,1},{4,2,3,7},{1,7,5,3},{2,8,0,6},{8,1,2,5},{3,8,2,3},{6,1,2,7},{3,9,2,2},{9,0,0,7},{0,8,6,2},{8,4,3,1},{0,2,8,6},{6,5,1,4},{2,3,5,6},{2,10,3,1},{0,7,0,9},{4,2,7,3},{2,4,8,2},{7,1,1,7},{2,4,7,3}, + {2,4,10,0},{0,1,10,5},{4,7,1,4},{0,10,4,2},{9,0,1,6},{1,9,6,0},{3,3,4,6},{4,5,7,0},{5,5,2,4},{2,8,1,5},{2,3,6,5},{0,1,1,14},{3,2,3,8},{10,1,2,3},{9,1,6,0},{3,4,3,6},{2,2,0,12},{0,0,9,7},{4,0,9,3},{7,0,5,4},{4,5,6,1},{2,5,1,8},{2,5,9,0},{3,5,4,4},{1,3,11,1},{7,1,5,3},{3,2,7,4},{1,4,2,9},{1,11,2,2},{2,2,3,9},{5,0,10,1},{3,2,11,0}, + {1,10,3,2},{8,3,4,1},{3,6,7,0},{0,7,5,4},{1,3,3,9},{2,2,10,2},{1,9,5,1},{0,5,0,11},{3,0,3,10},{0,4,8,4},{2,7,7,0},{2,0,2,12},{1,2,11,2},{6,3,7,0},{0,6,2,8},{0,10,1,5},{0,9,0,7},{6,4,4,2},{6,0,1,9},{1,5,10,0},{5,4,6,1},{5,5,3,3},{0,0,4,12},{0,3,2,11},{1,4,1,10},{3,0,9,4},{5,5,0,6},{1,7,8,0},{2,0,3,11},{6,4,1,5},{10,0,6,0},{0,6,0,10}, + {0,4,11,1},{3,1,6,6},{2,5,8,1},{0,2,10,4},{3,1,11,1},{6,6,2,2},{1,1,10,4},{2,1,2,11},{6,1,8,1},{0,2,13,1},{0,7,6,3},{6,8,2,0},{3,0,0,13},{4,4,4,4},{6,2,0,8},{7,3,1,5},{0,11,4,1},{6,7,0,3},{2,6,3,5},{5,2,1,8},{7,1,8,0},{5,5,1,5},{1,8,3,4},{8,2,6,0},{6,0,10,0},{5,6,1,4},{1,4,4,7},{2,7,4,3},{1,4,8,3},{5,4,3,4},{1,10,2,3},{2,9,1,4}, + {2,2,11,1},{2,5,0,9},{0,0,1,15},{0,0,11,5},{0,4,7,5},{0,1,15,0},{2,1,0,13},{0,3,10,3},{8,0,2,6},{3,3,2,8},{3,5,5,3},{1,7,1,7},{1,3,2,10},{4,0,4,8},{2,0,9,5},{1,1,1,13},{2,2,7,5},{2,1,10,3},{4,2,1,9},{4,3,6,3},{1,3,5,7},{2,5,3,6},{1,0,8,7},{5,0,2,9},{2,8,5,1},{1,6,0,9},{0,0,5,11},{0,4,9,3},{2,0,7,7},{1,7,2,6},{2,1,1,12},{2,4,9,1}, + {0,5,7,4},{6,0,4,6},{3,2,10,1},{0,6,1,9},{2,6,1,7},{0,5,8,3},{4,1,0,11},{1,2,4,9},{4,1,5,6},{6,1,0,9},{1,4,3,8},{4,5,1,6},{1,0,5,10},{5,3,1,7},{0,9,1,6},{2,0,1,13},{2,0,6,8},{8,1,1,6},{1,5,9,1},{0,6,9,1},{0,3,5,8},{0,2,9,5},{5,2,8,1},{1,1,14,0},{3,2,9,2},{5,0,8,3},{0,5,10,1},{5,2,3,6},{2,6,7,1},{2,3,0,11},{0,1,9,6},{1,0,4,11}, + {3,0,5,8},{0,0,15,1},{2,4,5,5},{0,3,7,6},{2,0,0,14},{1,1,12,2},{2,6,8,0},{3,1,8,4},{0,1,5,10} + }; + + // All total orderings for 16 pixels [0,2] 2-bit selectors. + // BC1 selector order: 0, 1, 2 + // Note this is different from g_unique_total_orders4[], which reorders the selectors into linear order. + const uint32_t NUM_UNIQUE_TOTAL_ORDERINGS3 = 153; + static uint8_t g_unique_total_orders3[NUM_UNIQUE_TOTAL_ORDERINGS3][3] = + { + {6,0,10},{3,6,7},{3,0,13},{13,3,0},{12,4,0},{9,1,6},{2,13,1},{4,7,5},{7,5,4},{9,6,1},{7,4,5},{8,6,2},{16,0,0},{10,6,0},{2,7,7}, + {0,0,16},{0,3,13},{1,15,0},{0,2,14},{1,4,11},{15,1,0},{1,12,3},{9,2,5},{14,1,1},{8,2,6},{3,3,10},{4,2,10},{14,0,2},{0,14,2},{1,7,8},{6,6,4}, + {11,5,0},{6,4,6},{11,3,2},{4,3,9},{7,1,8},{10,4,2},{12,1,3},{11,0,5},{9,3,4},{1,0,15},{9,0,7},{2,6,8},{12,2,2},{6,2,8},{6,8,2},{15,0,1}, + {4,8,4},{0,4,12},{8,5,3},{5,9,2},{11,2,3},{12,3,1},{6,3,7},{1,1,14},{2,9,5},{1,8,7},{4,10,2},{7,7,2},{13,1,2},{0,15,1},{3,2,11},{7,0,9}, + {4,4,8},{3,8,5},{0,5,11},{13,2,1},{1,10,5},{4,11,1},{3,10,3},{5,10,1},{10,2,4},{0,6,10},{14,2,0},{11,4,1},{3,12,1},{1,13,2},{1,5,10},{5,11,0}, + {12,0,4},{8,1,7},{6,10,0},{3,13,0},{7,2,7},{0,7,9},{5,8,3},{0,12,4},{11,1,4},{13,0,3},{0,16,0},{5,7,4},{10,3,3},{10,0,6},{0,13,3},{4,6,6}, + {2,8,6},{2,5,9},{7,8,1},{2,1,13},{2,0,14},{7,3,6},{5,1,10},{3,11,2},{5,4,7},{8,3,5},{10,5,1},{6,9,1},{1,3,12},{4,5,7},{2,2,12},{4,1,11}, + {0,8,8},{4,12,0},{6,5,5},{8,7,1},{5,5,6},{3,7,6},{7,9,0},{4,9,3},{0,10,6},{8,0,8},{5,3,8},{10,1,5},{6,1,9},{7,6,3},{9,5,2},{0,1,15}, + {9,7,0},{2,14,0},{3,4,9},{8,4,4},{9,4,3},{0,9,7},{1,9,6},{3,9,4},{5,2,9},{2,3,11},{5,6,5},{1,14,1},{6,7,3},{2,4,10},{2,12,2},{8,8,0}, + {2,10,4},{4,0,12},{0,11,5},{2,11,3},{1,11,4},{3,5,8},{5,0,11},{3,1,12},{1,2,13},{1,6,9} + }; + + // For each total ordering, this table indicates which other total orderings are likely to improve quality using a least squares pass. Each array is sorted by usefulness. + static uint16_t g_best_total_orderings4[NUM_UNIQUE_TOTAL_ORDERINGS4][MAX_TOTAL_ORDERINGS4] = + { +#if RGBCX_USE_SMALLER_TABLES + #include "rgbcx_table4_small.h" +#else + #include "rgbcx_table4.h" +#endif + }; + + static uint8_t g_best_total_orderings3[NUM_UNIQUE_TOTAL_ORDERINGS3][32] = + { + { 12,1,3,5,27,2,4,38,8,7,16,18,6,10,41,79,40,23,46,9,20,88,22,37,14,19,24,126,99,119,35,11 }, + { 7,64,116,14,94,30,8,42,1,108,47,55,137,10,134,95,96,115,69,32,63,29,90,113,11,148,16,103,19,9,34,25 }, + { 12,1,0,5,3,7,4,27,8,6,38,40,41,16,18,46,9,10,20,23,79,62,14,22,88,99,37,126,92,19,120,11 }, + { 16,88,27,18,46,48,126,107,79,19,59,38,37,65,23,66,0,2,3,43,12,151,28,25,5,87,72,40,1,20,52,92 }, + { 79,48,88,16,27,65,18,38,46,19,37,4,72,33,126,41,52,0,12,92,5,1,2,107,3,77,23,91,43,51,22,74 }, + { 1,8,41,122,10,22,2,0,87,24,37,120,38,7,39,4,5,3,9,92,62,59,23,16,104,11,27,79,19,26,25,32 }, + { 2,76,99,28,40,86,93,21,138,60,6,0,17,128,145,119,98,144,141,82,147,54,67,75,5,12,27,132,146,1,38,14 }, + { 47,7,64,90,1,118,116,85,57,14,30,94,50,45,137,134,8,42,69,139,55,68,58,108,95,29,10,115,0,32,2,11 }, + { 49,8,10,30,124,11,32,113,130,58,125,9,100,53,104,115,131,103,24,7,1,39,45,36,139,0,137,22,90,44,114,105 }, + { 9,38,72,125,49,41,84,11,13,5,27,0,16,92,8,2,65,105,10,18,48,29,127,131,36,14,1,46,111,79,130,12 }, + { 130,8,10,100,104,131,49,32,53,39,30,36,113,24,11,22,124,44,83,58,7,103,1,4,9,125,5,0,91,33,115,74 }, + { 114,11,58,8,120,49,9,124,142,111,41,30,10,0,97,130,62,84,38,5,72,125,92,127,100,27,139,113,13,132,32,1 }, + { 60,46,28,27,40,20,0,17,18,2,126,16,6,38,86,23,79,54,1,93,5,88,41,14,21,111,7,48,3,84,72,62 }, + { 72,92,38,65,84,48,41,79,27,16,29,111,88,5,18,46,1,0,152,14,37,19,77,42,132,7,22,13,119,56,12,2 }, + { 7,55,1,95,29,56,64,116,143,8,14,30,47,94,152,90,65,67,10,133,42,72,146,84,16,48,6,0,25,108,77,21 }, + { 27,23,20,5,0,79,38,2,3,1,59,46,4,41,33,86,37,87,88,92,7,126,43,8,22,152,151,150,149,148,147,146 }, + { 12,0,1,2,7,6,3,5,28,4,8,14,60,40,17,19,21,86,126,93,10,18,9,29,48,99,65,25,84,119,72,41 }, + { 60,40,99,2,54,12,0,1,19,28,98,93,6,138,21,5,27,17,151,14,76,46,16,18,38,29,86,144,107,7,25,41 }, + { 12,0,1,2,3,5,6,7,4,28,8,60,14,40,16,17,21,10,19,9,86,38,126,41,93,27,29,48,62,84,79,99 }, + { 0,1,2,10,5,8,3,25,4,29,32,34,63,7,77,26,16,48,65,56,14,22,129,103,72,24,18,152,140,53,96,42 }, + { 46,126,18,54,12,16,1,0,5,2,27,98,20,23,6,3,88,48,28,7,19,8,4,60,151,38,37,21,79,14,65,40 }, + { 76,6,141,86,119,2,138,67,28,145,0,93,17,1,40,60,146,99,147,14,21,144,132,7,5,29,55,27,16,75,19,12 }, + { 71,5,51,39,22,80,0,43,10,122,8,62,41,24,104,87,35,37,2,91,33,120,36,38,1,131,9,100,130,66,3,4 }, + { 126,18,46,27,20,16,88,23,12,79,54,59,48,0,73,1,37,151,5,19,28,38,2,66,60,3,65,98,14,26,6,43 }, + { 22,10,8,5,0,71,35,80,104,39,24,51,100,1,62,32,2,130,11,41,7,9,53,43,49,83,122,120,30,44,37,38 }, + { 1,34,14,129,53,63,42,26,121,148,7,44,96,10,0,24,100,32,64,116,140,22,5,19,29,103,135,108,8,61,39,83 }, + { 1,7,34,63,44,25,135,14,24,108,22,0,83,94,5,129,35,101,47,121,2,19,42,53,6,110,103,8,148,10,16,123 }, + { 12,28,16,60,18,1,6,21,14,0,86,19,2,48,93,17,38,29,7,5,65,126,46,72,41,79,84,119,40,56,54,88 }, + { 0,2,12,27,5,46,38,40,41,79,88,99,3,23,1,62,20,4,22,37,92,35,18,8,16,24,10,60,7,120,98,54 }, + { 1,7,14,56,8,0,84,67,10,2,133,72,42,111,5,30,21,4,9,3,25,94,16,116,47,11,65,18,132,90,55,64 }, + { 30,8,124,139,45,11,58,90,113,137,7,115,10,32,1,49,94,85,9,47,108,103,0,97,63,14,50,114,53,106,100,25 }, + { 65,38,48,27,16,79,72,18,88,19,46,77,84,92,37,41,0,29,1,14,12,111,2,5,31,36,87,74,105,40,28,51 }, + { 10,8,30,113,130,100,53,32,115,103,104,7,1,121,39,49,131,44,24,36,63,137,34,45,22,90,108,83,26,11,94,139 }, + { 51,52,43,33,5,74,16,37,71,91,38,3,36,87,48,22,4,0,122,41,39,18,66,27,79,24,65,88,59,23,62,92 }, + { 1,7,63,53,108,121,94,44,103,100,14,10,129,47,32,26,24,25,148,42,135,22,0,61,83,8,39,104,5,64,115,34 }, + { 1,8,10,7,5,0,80,32,62,2,24,44,53,83,9,41,30,22,100,11,14,25,120,4,26,6,3,16,122,34,19,35 }, + { 74,4,36,48,33,91,39,79,22,16,65,5,131,38,24,71,27,52,0,105,51,18,88,104,3,31,10,37,72,19,41,130 }, + { 59,43,38,79,23,27,92,51,0,16,46,5,18,88,41,37,66,3,87,20,48,2,122,4,22,12,1,126,19,65,33,24 }, + { 12,28,1,27,0,16,2,46,65,60,21,3,5,18,6,19,48,14,4,7,79,88,86,29,22,72,93,40,23,8,17,41 }, + { 22,91,39,33,24,71,5,131,36,10,51,0,130,8,104,2,35,125,9,43,52,49,83,80,100,41,122,3,37,38,4,16 }, + { 12,0,1,2,5,3,4,8,7,27,18,38,10,6,16,46,9,20,41,23,126,79,22,14,19,99,88,54,37,48,62,35 }, + { 12,27,1,2,3,0,46,4,38,16,8,28,7,79,18,5,84,6,88,10,14,21,23,20,40,22,60,19,9,29,72,65 }, + { 1,14,7,55,95,29,8,94,30,56,10,108,77,116,152,64,32,48,63,42,143,148,16,25,137,65,11,0,115,9,19,72 }, + { 37,79,66,38,16,52,48,59,43,27,87,33,41,4,23,51,3,5,88,18,92,46,73,122,22,71,20,0,65,19,2,120 }, + { 24,32,83,22,53,1,8,10,7,30,35,5,103,0,100,101,121,113,34,123,63,2,44,25,71,115,80,14,26,108,51,39 }, + { 97,45,111,58,85,139,0,90,47,7,120,106,142,30,50,132,41,62,84,1,119,114,14,56,117,8,38,29,2,64,116,5 }, + { 12,28,16,18,1,60,6,14,2,21,0,86,126,19,48,93,7,27,17,29,5,65,54,38,72,79,84,88,119,145,8,111 }, + { 118,47,64,116,57,85,7,14,50,1,42,0,45,68,86,69,2,111,134,28,90,55,16,29,56,48,84,144,60,30,112,41 }, + { 12,1,2,0,7,6,28,5,3,4,8,14,60,21,18,40,17,86,10,9,16,29,19,93,126,79,38,84,72,27,111,119 }, + { 11,8,49,130,10,125,9,124,100,114,131,30,58,104,32,39,24,113,36,105,0,41,22,120,5,53,111,38,142,44,83,35 }, + { 50,70,47,118,85,57,106,0,45,7,64,90,81,14,2,134,28,62,86,55,69,1,78,119,68,56,18,67,16,60,29,21 }, + { 43,37,33,87,51,41,66,5,122,38,22,59,92,0,23,91,27,16,71,79,18,52,120,4,3,24,46,20,73,39,62,36 }, + { 79,48,4,16,27,88,43,33,18,38,65,37,46,3,19,51,52,22,66,87,74,5,41,91,23,59,0,71,122,72,20,92 }, + { 32,100,10,8,30,104,24,44,39,113,83,103,1,7,22,53,115,63,135,121,26,35,34,5,0,108,137,90,91,45,2,130 }, + { 0,1,2,5,16,12,6,7,14,3,19,18,29,20,4,21,40,8,17,35,23,48,126,22,25,56,26,10,98,27,38,65 }, + { 143,67,56,146,1,7,133,55,64,141,134,69,6,47,14,29,84,21,111,147,57,16,95,72,118,132,50,0,2,18,119,42 }, + { 1,7,67,14,133,111,8,84,0,21,2,47,64,132,55,10,95,147,119,42,16,5,72,56,4,3,6,29,9,25,18,30 }, + { 68,57,69,112,144,86,102,2,134,55,0,70,118,64,75,47,14,28,93,143,67,7,50,149,1,21,29,56,119,95,60,78 }, + { 58,97,114,30,124,45,11,139,8,90,0,142,7,10,41,113,84,62,49,111,85,1,9,5,137,120,32,14,2,117,47,38 }, + { 23,66,18,79,38,20,43,27,16,88,46,59,126,37,87,12,73,92,3,5,48,0,19,54,2,51,28,1,41,65,122,22 }, + { 0,12,2,27,5,40,46,38,1,41,3,79,88,23,99,4,20,62,22,54,92,18,8,37,16,35,10,7,19,120,144,24 }, + { 1,14,25,26,0,7,44,34,129,42,24,5,135,22,19,148,6,96,83,2,29,16,63,35,101,64,140,136,116,110,3,10 }, + { 12,1,2,27,3,4,38,5,7,8,18,16,46,6,0,40,41,10,79,23,88,9,20,22,14,19,37,92,48,126,28,21 }, + { 7,1,10,32,108,103,94,47,8,53,25,14,34,115,100,129,121,130,148,42,64,116,63,26,44,0,24,30,113,4,104,22 }, + { 47,134,7,14,55,69,64,95,1,29,85,118,56,116,45,57,102,143,50,90,42,30,16,94,0,8,67,75,133,2,18,48 }, + { 12,1,2,0,7,6,28,8,14,5,3,4,40,21,17,18,60,86,16,93,126,10,9,29,99,38,119,25,19,54,27,84 }, + { 59,16,27,18,23,88,79,37,46,66,38,20,73,126,3,43,48,87,92,51,41,12,19,5,52,107,65,0,151,122,54,2 }, + { 1,21,147,7,119,14,76,132,55,0,86,145,2,6,69,67,16,143,111,138,17,28,29,60,18,93,8,19,40,56,84,5 }, + { 144,86,112,2,68,102,69,0,149,93,75,28,57,55,145,60,21,67,99,134,143,40,146,119,82,110,62,6,29,26,78,14 }, + { 102,57,55,69,143,75,146,67,56,68,134,2,29,141,0,21,6,14,133,118,64,1,7,95,47,84,111,28,147,82,72,119 }, + { 0,70,57,119,50,145,2,86,28,118,69,78,149,47,60,68,67,55,93,81,134,21,14,62,64,7,5,1,132,85,41,16 }, + { 51,5,43,71,122,87,41,37,91,39,0,22,33,36,38,24,66,120,62,2,80,16,92,10,59,4,27,23,35,79,8,3 }, + { 12,1,2,0,7,6,28,5,8,14,3,21,40,4,60,17,86,18,16,93,10,9,126,119,99,29,19,41,38,27,25,92 }, + { 27,18,46,126,23,16,88,79,20,151,59,73,48,38,0,54,12,2,37,1,19,5,28,60,66,41,3,109,86,65,40,6 }, + { 48,79,4,33,16,74,65,38,88,27,91,52,18,36,22,19,46,0,37,3,51,5,71,39,72,43,24,41,92,87,2,10 }, + { 86,2,144,93,28,112,141,6,102,21,99,60,75,0,68,82,69,146,67,149,55,40,145,76,111,147,56,119,110,143,26,132 }, + { 6,138,2,99,86,17,40,93,28,21,145,141,0,60,119,147,128,76,67,54,1,12,5,27,144,14,38,98,146,41,29,19 }, + { 1,8,0,10,2,29,7,5,3,56,4,25,14,152,63,32,65,72,96,42,34,108,48,9,26,16,84,103,67,148,22,129 }, + { 149,145,0,86,2,28,93,144,62,60,119,101,21,41,5,35,78,99,26,40,12,68,57,67,110,120,69,18,55,76,132,70 }, + { 12,28,16,1,48,19,6,60,2,14,18,21,0,27,46,65,86,29,5,7,72,93,40,3,17,84,56,88,126,4,38,8 }, + { 1,8,5,10,7,24,2,62,0,41,22,122,120,9,4,3,32,87,11,37,38,83,100,44,25,104,16,26,39,80,14,6 }, + { 0,119,62,86,145,149,28,132,93,2,120,67,60,41,35,5,144,21,123,38,111,81,84,56,12,44,24,50,92,55,40,22 }, + { 2,93,99,28,40,144,60,0,86,150,76,21,149,98,6,25,1,61,82,26,12,5,54,141,7,18,145,16,27,138,110,38 }, + { 24,8,10,22,32,35,100,5,1,53,0,7,71,80,30,123,83,104,51,11,2,39,44,113,9,62,25,103,34,101,43,41 }, + { 12,1,2,0,7,6,28,5,40,60,8,16,3,18,14,4,86,21,17,93,41,10,9,99,27,119,38,19,126,22,48,145 }, + { 45,47,50,7,85,90,97,1,64,139,116,118,30,58,14,106,70,111,0,57,94,42,137,142,29,120,8,56,18,134,84,41 }, + { 12,0,2,5,27,38,1,46,41,40,79,144,3,22,88,23,28,60,99,62,6,24,26,7,4,16,10,35,37,18,14,20 }, + { 37,38,59,92,0,5,23,51,79,41,27,22,2,3,87,16,46,4,1,43,20,33,18,88,24,71,8,10,48,19,126,122 }, + { 12,28,16,60,1,18,6,21,19,14,48,0,2,86,93,5,46,29,17,27,65,7,3,72,38,126,119,40,84,37,56,4 }, + { 0,2,5,1,16,6,27,28,18,38,60,7,14,21,46,40,86,41,19,48,93,8,3,79,22,4,10,37,62,23,24,111 }, + { 85,7,90,30,47,139,45,50,94,58,137,1,8,64,14,116,118,115,113,11,124,108,0,10,97,57,32,70,42,106,29,114 }, + { 33,36,22,71,51,5,91,39,0,52,43,24,131,74,16,37,38,122,41,3,87,48,4,104,35,80,10,2,105,62,27,18 }, + { 12,1,27,2,0,16,3,28,46,18,4,6,5,72,21,79,38,7,14,60,88,8,65,19,48,29,23,40,22,20,86,126 }, + { 0,12,2,27,5,38,46,41,1,40,79,3,88,23,22,99,20,37,62,4,18,6,16,35,60,28,24,7,92,8,14,10 }, + { 7,47,1,30,137,8,116,94,90,64,14,115,108,118,57,10,148,113,42,85,32,11,63,50,103,45,124,134,55,9,69,34 }, + { 55,7,1,29,56,143,64,47,67,133,14,146,95,72,84,8,116,111,6,134,141,21,65,0,69,30,16,45,85,42,50,10 }, + { 14,1,42,8,10,29,108,63,55,148,95,32,7,19,25,115,103,34,56,129,77,0,16,152,94,30,113,26,2,5,48,4 }, + { 111,120,142,97,58,0,41,45,62,132,114,84,139,30,5,8,38,2,7,85,119,90,117,1,124,11,56,47,28,27,35,72 }, + { 1,0,14,2,6,5,16,19,7,29,42,18,3,25,12,35,21,8,26,17,40,4,20,48,109,99,22,96,55,101,10,61 }, + { 12,0,1,5,3,2,4,7,27,8,38,6,40,18,16,10,20,46,9,41,23,22,79,14,62,19,37,126,88,11,92,48 }, + { 10,8,104,39,24,32,22,83,44,100,30,130,53,91,113,5,11,1,35,33,7,49,0,2,103,71,36,124,9,80,131,34 }, + { 1,7,0,14,8,34,5,25,35,26,6,63,10,123,2,16,103,19,44,32,135,121,108,80,62,30,115,94,149,144,53,18 }, + { 75,68,146,141,102,67,2,21,6,57,69,143,0,55,82,86,28,144,147,29,93,112,56,119,133,14,76,60,84,134,111,145 }, + { 10,32,115,7,8,53,1,108,30,113,94,137,100,63,90,34,130,103,121,47,44,25,104,39,24,26,85,14,49,36,22,131 }, + { 39,24,10,22,8,130,91,104,83,49,5,33,100,11,0,35,32,131,71,36,9,44,53,2,80,51,30,1,41,7,43,62 }, + { 38,36,65,105,27,72,31,79,41,131,5,48,125,39,0,16,92,46,22,13,18,84,24,37,88,2,33,74,91,71,130,49 }, + { 0,106,62,50,45,119,85,81,132,28,2,86,41,47,38,60,35,117,5,29,7,30,145,90,55,70,14,111,18,67,93,56 }, + { 0,2,5,1,3,25,19,26,4,34,29,10,22,16,8,7,24,14,48,65,53,18,6,77,44,56,72,61,121,21,136,40 }, + { 7,1,94,8,47,115,10,32,113,103,30,108,137,63,14,64,116,148,129,42,90,25,34,118,53,57,11,49,85,9,96,50 }, + { 14,0,1,26,19,5,42,2,25,24,29,22,6,44,61,16,7,96,136,3,140,34,35,55,135,18,48,77,83,4,8,10 }, + { 1,7,14,0,25,6,34,5,26,16,63,2,19,8,35,101,108,29,94,10,18,42,123,144,129,47,61,21,3,62,149,4 }, + { 12,0,2,1,28,5,6,120,7,60,40,16,18,86,27,14,21,93,8,62,41,38,3,17,4,119,99,48,19,126,10,9 }, + { 86,144,93,2,28,149,0,60,99,112,110,145,40,21,102,26,75,62,69,1,12,101,119,25,76,67,7,68,55,5,6,14 }, + { 8,30,10,32,113,49,115,137,124,103,45,90,7,139,11,1,58,53,130,94,108,100,9,63,85,125,34,47,0,24,44,104 }, + { 120,142,111,41,58,114,97,0,11,62,84,124,5,30,8,38,132,127,27,139,92,10,72,45,49,9,28,2,29,56,16,1 }, + { 8,113,30,137,7,32,10,90,94,115,1,103,108,63,47,85,49,53,11,45,34,50,14,25,9,124,100,130,139,121,42,26 }, + { 64,7,14,47,134,55,1,42,95,69,116,90,94,30,8,29,56,137,45,108,85,10,57,16,102,143,118,19,63,32,11,50 }, + { 62,132,0,119,120,41,111,86,35,28,5,84,56,38,2,93,145,60,67,12,92,27,29,72,55,117,21,24,133,149,22,45 }, + { 57,68,69,118,134,64,50,47,55,14,7,2,102,144,0,112,70,86,85,1,95,29,116,143,42,75,16,56,28,45,21,48 }, + { 0,12,2,1,5,28,6,40,60,27,7,38,16,14,86,18,93,41,62,46,99,35,8,23,3,17,22,21,10,19,79,20 }, + { 12,1,2,27,16,3,38,111,4,0,18,5,7,46,40,8,79,6,14,28,88,10,48,41,19,84,21,9,22,23,20,72 }, + { 53,103,32,7,1,100,22,63,71,44,10,115,108,24,92,104,26,30,122,94,8,39,83,34,137,135,90,91,121,5,87,47 }, + { 87,37,41,0,22,38,2,92,1,24,4,8,3,59,10,5,39,23,71,79,122,27,16,46,33,7,91,20,18,51,9,120 }, + { 1,7,8,10,0,5,35,32,53,44,14,30,2,80,25,34,6,62,26,103,16,19,63,9,149,24,121,41,22,11,113,83 }, + { 11,58,8,30,124,49,10,113,9,114,139,45,97,32,7,137,90,1,0,130,115,125,100,24,5,94,53,41,14,13,35,38 }, + { 125,105,9,36,131,49,8,130,39,11,10,5,22,38,41,104,0,31,13,24,27,16,2,72,65,91,48,32,84,18,100,74 }, + { 12,1,0,2,6,3,7,5,4,8,14,28,16,60,18,10,21,17,19,9,40,27,86,93,29,38,54,11,25,48,46,41 }, + { 84,41,38,72,92,29,111,5,65,120,79,0,27,56,48,14,132,16,119,22,86,88,46,28,62,12,1,2,93,18,24,127 }, + { 99,28,40,60,2,93,138,0,98,17,86,54,76,12,27,1,21,144,128,38,5,14,46,18,25,16,109,6,41,145,7,29 }, + { 1,63,10,32,148,14,103,34,42,7,8,108,116,53,64,96,25,121,26,94,140,0,29,19,55,24,100,136,5,4,44,115 }, + { 131,100,130,49,10,8,36,104,39,0,48,41,11,38,4,24,27,22,16,44,79,5,33,2,53,9,125,74,91,120,32,83 }, + { 36,39,131,74,4,91,22,33,125,104,130,48,10,24,16,5,49,8,100,105,79,0,9,65,71,2,18,83,31,11,19,44 }, + { 0,12,2,1,6,5,7,28,40,60,16,14,18,62,86,27,93,8,17,38,21,41,35,99,3,19,10,23,22,4,9,48 }, + { 1,7,67,14,21,147,111,55,132,119,0,8,2,76,64,16,47,84,6,18,86,95,145,10,42,29,133,5,56,134,17,72 }, + { 69,55,47,134,102,143,7,57,118,95,14,64,29,56,1,50,75,67,146,2,0,133,68,16,21,6,141,85,116,18,72,65 }, + { 1,44,7,24,83,63,34,103,22,121,53,32,25,35,0,115,108,5,14,8,10,101,94,30,2,123,110,26,137,47,90,19 }, + { 14,1,25,42,34,0,26,96,19,29,140,5,53,10,2,121,3,24,44,22,55,77,129,7,63,16,8,4,6,61,100,48 }, + { 30,90,7,8,137,94,85,1,47,113,115,108,45,139,124,11,10,32,50,58,103,14,63,64,9,116,49,42,25,148,0,53 }, + { 40,99,2,60,28,17,0,54,93,98,86,138,6,12,21,76,1,5,27,144,128,38,19,46,14,41,145,7,16,67,3,109 }, + { 45,58,30,139,90,7,85,137,97,8,124,47,1,11,106,114,50,94,0,113,10,115,14,32,9,64,108,41,49,29,62,116 }, + { 14,42,10,1,63,96,32,25,34,8,129,29,0,103,55,19,26,53,77,5,95,2,4,7,3,16,148,56,18,24,121,108 }, + { 21,2,75,86,6,76,144,28,119,99,93,147,141,67,102,145,60,132,146,128,0,82,40,138,55,111,143,17,133,112,69,14 }, + { 111,120,41,62,84,132,0,5,38,119,56,92,72,142,27,28,29,35,58,80,2,86,65,79,12,14,1,24,145,16,21,48 }, + { 146,67,141,69,133,21,6,143,57,55,111,147,56,1,14,132,7,2,134,102,0,119,29,84,76,64,86,72,28,68,47,75 }, + { 12,1,0,5,27,3,7,4,38,8,6,41,16,40,46,10,18,79,2,9,23,86,20,22,62,14,37,88,92,19,24,11 }, + { 0,12,2,1,27,5,38,28,60,6,40,7,16,46,18,14,41,99,93,62,3,79,86,23,149,8,22,35,88,17,19,10 }, + { 141,6,21,67,147,102,146,2,76,119,132,69,55,111,86,75,28,133,143,0,1,145,14,128,56,99,17,60,29,93,84,68 }, + { 21,76,1,119,86,145,2,0,14,7,6,138,146,55,17,28,132,93,67,40,60,143,29,147,111,16,69,141,5,56,19,133 }, + { 1,8,108,14,7,116,64,42,10,63,94,32,115,103,113,96,30,34,55,47,95,148,29,140,129,25,134,53,69,26,19,11 }, + { 12,1,3,5,4,2,0,7,8,38,27,16,18,6,10,20,41,40,79,46,9,23,22,88,92,37,14,24,62,19,48,99 }, + { 1,14,7,0,6,25,5,16,19,2,42,26,29,35,61,8,18,129,101,21,3,110,34,148,96,10,17,4,22,40,12,20 }, + { 0,2,5,1,3,19,22,26,16,24,29,7,14,6,4,25,18,44,8,48,12,61,20,21,10,35,65,56,23,40,17,107 }, + { 1,7,8,29,56,0,10,14,2,42,72,5,4,65,3,30,84,94,67,9,25,133,111,11,32,108,16,63,21,96,26,48 } + }; +#ifdef _MSC_VER +#pragma endregion +#endif + + static inline uint32_t iabs(int32_t i) { return (i < 0) ? static_cast(-i) : static_cast(i); } + //static inline uint64_t iabs(int64_t i) { return (i < 0) ? static_cast(-i) : static_cast(i); } + + static inline uint8_t to_5(uint32_t v) { v = v * 31 + 128; return (uint8_t)((v + (v >> 8)) >> 8); } + static inline uint8_t to_6(uint32_t v) { v = v * 63 + 128; return (uint8_t)((v + (v >> 8)) >> 8); } + + template inline T square(T a) { return a * a; } + + static inline float clampf(float value, float low, float high) { if (value < low) value = low; else if (value > high) value = high; return value; } + + template inline S clamp(S value, S low, S high) { return (value < low) ? low : ((value > high) ? high : value); } + static inline int32_t clampi(int32_t value, int32_t low, int32_t high) { if (value < low) value = low; else if (value > high) value = high; return value; } + + static inline int squarei(int a) { return a * a; } + //static inline int absi(int a) { return (a < 0) ? -a : a; } + + template inline F lerp(F a, F b, F s) { return a + (b - a) * s; } + + static const uint32_t TOTAL_ORDER_4_0_16 = 15; + static const uint32_t TOTAL_ORDER_4_1_16 = 700; + static const uint32_t TOTAL_ORDER_4_2_16 = 753; + static const uint32_t TOTAL_ORDER_4_3_16 = 515; + static uint16_t g_total_ordering4_hash[4096]; + static float g_selector_factors4[NUM_UNIQUE_TOTAL_ORDERINGS4][3]; + + static const uint32_t TOTAL_ORDER_3_0_16 = 12; + static const uint32_t TOTAL_ORDER_3_1_16 = 15; + static const uint32_t TOTAL_ORDER_3_2_16 = 89; + static uint16_t g_total_ordering3_hash[256]; + static float g_selector_factors3[NUM_UNIQUE_TOTAL_ORDERINGS3][3]; + + struct hist4 + { + uint8_t m_hist[4]; + + hist4() + { + memset(m_hist, 0, sizeof(m_hist)); + } + + hist4(uint32_t i, uint32_t j, uint32_t k, uint32_t l) + { + m_hist[0] = (uint8_t)i; + m_hist[1] = (uint8_t)j; + m_hist[2] = (uint8_t)k; + m_hist[3] = (uint8_t)l; + } + + inline bool operator== (const hist4& h) const + { + if (m_hist[0] != h.m_hist[0]) return false; + if (m_hist[1] != h.m_hist[1]) return false; + if (m_hist[2] != h.m_hist[2]) return false; + if (m_hist[3] != h.m_hist[3]) return false; + return true; + } + + inline bool any_16() const + { + return (m_hist[0] == 16) || (m_hist[1] == 16) || (m_hist[2] == 16) || (m_hist[3] == 16); + } + + inline uint32_t lookup_total_ordering_index() const + { + if (m_hist[0] == 16) + return TOTAL_ORDER_4_0_16; + else if (m_hist[1] == 16) + return TOTAL_ORDER_4_1_16; + else if (m_hist[2] == 16) + return TOTAL_ORDER_4_2_16; + else if (m_hist[3] == 16) + return TOTAL_ORDER_4_3_16; + + // Must sum to 16, so m_hist[3] isn't needed. + return g_total_ordering4_hash[m_hist[0] | (m_hist[1] << 4) | (m_hist[2] << 8)]; + } + }; + + struct hist3 + { + uint8_t m_hist[3]; + + hist3() + { + memset(m_hist, 0, sizeof(m_hist)); + } + + hist3(uint32_t i, uint32_t j, uint32_t k) + { + m_hist[0] = (uint8_t)i; + m_hist[1] = (uint8_t)j; + m_hist[2] = (uint8_t)k; + } + + inline bool operator== (const hist3& h) const + { + if (m_hist[0] != h.m_hist[0]) return false; + if (m_hist[1] != h.m_hist[1]) return false; + if (m_hist[2] != h.m_hist[2]) return false; + return true; + } + + inline bool any_16() const + { + return (m_hist[0] == 16) || (m_hist[1] == 16) || (m_hist[2] == 16); + } + + inline uint32_t lookup_total_ordering_index() const + { + if (m_hist[0] == 16) + return TOTAL_ORDER_3_0_16; + else if (m_hist[1] == 16) + return TOTAL_ORDER_3_1_16; + else if (m_hist[2] == 16) + return TOTAL_ORDER_3_2_16; + + // Must sum to 16, so m_hist[2] isn't needed. + return g_total_ordering3_hash[m_hist[0] | (m_hist[1] << 4)]; + } + }; + + struct bc1_match_entry + { + uint8_t m_hi; + uint8_t m_lo; + uint8_t m_e; + }; + + static bc1_approx_mode g_bc1_approx_mode; + static bc1_match_entry g_bc1_match5_equals_1[256], g_bc1_match6_equals_1[256]; + static bc1_match_entry g_bc1_match5_half[256], g_bc1_match6_half[256]; + + static inline int scale_5_to_8(int v) { return (v << 3) | (v >> 2); } + static inline int scale_6_to_8(int v) { return (v << 2) | (v >> 4); } + + // v0, v1 = unexpanded DXT1 endpoint values (5/6-bits) + // c0, c1 = expanded DXT1 endpoint values (8-bits) + static inline int interp_5_6_ideal(int c0, int c1) { assert(c0 < 256 && c1 < 256); return (c0 * 2 + c1) / 3; } + static inline int interp_5_6_ideal_round(int c0, int c1) { assert(c0 < 256 && c1 < 256); return (c0 * 2 + c1 + 1) / 3; } + static inline int interp_half_5_6_ideal(int c0, int c1) { assert(c0 < 256 && c1 < 256); return (c0 + c1) / 2; } + + static inline int interp_5_nv(int v0, int v1) { assert(v0 < 32 && v1 < 32); return ((2 * v0 + v1) * 22) / 8; } + static inline int interp_6_nv(int c0, int c1) { assert(c0 < 256 && c1 < 256); const int gdiff = c1 - c0; return (256 * c0 + (gdiff / 4) + 128 + gdiff * 80) / 256; } + + static inline int interp_half_5_nv(int v0, int v1) { assert(v0 < 32 && v1 < 32); return ((v0 + v1) * 33) / 8; } + static inline int interp_half_6_nv(int c0, int c1) { assert(c0 < 256 && c1 < 256); const int gdiff = c1 - c0; return (256 * c0 + gdiff / 4 + 128 + gdiff * 128) / 256; } + + static inline int interp_5_6_amd(int c0, int c1) { assert(c0 < 256 && c1 < 256); return (c0 * 43 + c1 * 21 + 32) >> 6; } + static inline int interp_half_5_6_amd(int c0, int c1) { assert(c0 < 256 && c1 < 256); return (c0 + c1 + 1) >> 1; } + + static inline int interp_5(int v0, int v1, int c0, int c1, bc1_approx_mode mode) + { + assert(scale_5_to_8(v0) == c0 && scale_5_to_8(v1) == c1); + switch (mode) + { + case bc1_approx_mode::cBC1NVidia: return interp_5_nv(v0, v1); + case bc1_approx_mode::cBC1AMD: return interp_5_6_amd(c0, c1); + default: + case bc1_approx_mode::cBC1Ideal: return interp_5_6_ideal(c0, c1); + case bc1_approx_mode::cBC1IdealRound4: return interp_5_6_ideal_round(c0, c1); + } + } + + static inline int interp_6(int v0, int v1, int c0, int c1, bc1_approx_mode mode) + { + (void)v0; (void)v1; + assert(scale_6_to_8(v0) == c0 && scale_6_to_8(v1) == c1); + switch (mode) + { + case bc1_approx_mode::cBC1NVidia: return interp_6_nv(c0, c1); + case bc1_approx_mode::cBC1AMD: return interp_5_6_amd(c0, c1); + default: + case bc1_approx_mode::cBC1Ideal: return interp_5_6_ideal(c0, c1); + case bc1_approx_mode::cBC1IdealRound4: return interp_5_6_ideal_round(c0, c1); + } + } + + static inline int interp_half_5(int v0, int v1, int c0, int c1, bc1_approx_mode mode) + { + assert(scale_5_to_8(v0) == c0 && scale_5_to_8(v1) == c1); + switch (mode) + { + case bc1_approx_mode::cBC1NVidia: return interp_half_5_nv(v0, v1); + case bc1_approx_mode::cBC1AMD: return interp_half_5_6_amd(c0, c1); + case bc1_approx_mode::cBC1Ideal: + case bc1_approx_mode::cBC1IdealRound4: + default: + return interp_half_5_6_ideal(c0, c1); + } + } + + static inline int interp_half_6(int v0, int v1, int c0, int c1, bc1_approx_mode mode) + { + (void)v0; (void)v1; + assert(scale_6_to_8(v0) == c0 && scale_6_to_8(v1) == c1); + switch (mode) + { + case bc1_approx_mode::cBC1NVidia: return interp_half_6_nv(c0, c1); + case bc1_approx_mode::cBC1AMD: return interp_half_5_6_amd(c0, c1); + case bc1_approx_mode::cBC1Ideal: + case bc1_approx_mode::cBC1IdealRound4: + default: + return interp_half_5_6_ideal(c0, c1); + } + } + + static void prepare_bc1_single_color_table_half(bc1_match_entry* pTable, const uint8_t* pExpand, int size, bc1_approx_mode mode) + { + for (int i = 0; i < 256; i++) + { + int lowest_e = 256; + for (int lo = 0; lo < size; lo++) + { + const int lo_e = pExpand[lo]; + + for (int hi = 0; hi < size; hi++) + { + const int hi_e = pExpand[hi]; + + const int v = (size == 32) ? interp_half_5(hi, lo, hi_e, lo_e, mode) : interp_half_6(hi, lo, hi_e, lo_e, mode); + + int e = iabs(v - i); + + // We only need to factor in 3% error in BC1 ideal mode. + if ((mode == bc1_approx_mode::cBC1Ideal) || (mode == bc1_approx_mode::cBC1IdealRound4)) + e += (iabs(hi_e - lo_e) * 3) / 100; + + // Favor equal endpoints, for lower error on actual GPU's which approximate the interpolation. + if ((e < lowest_e) || ((e == lowest_e) && (lo == hi))) + { + pTable[i].m_hi = static_cast(hi); + pTable[i].m_lo = static_cast(lo); + + assert(e <= UINT8_MAX); + pTable[i].m_e = static_cast(e); + + lowest_e = e; + } + + } // hi + } // lo + } + } + + static void prepare_bc1_single_color_table(bc1_match_entry* pTable, const uint8_t* pExpand, int size, bc1_approx_mode mode) + { + for (int i = 0; i < 256; i++) + { + int lowest_e = 256; + for (int lo = 0; lo < size; lo++) + { + const int lo_e = pExpand[lo]; + + for (int hi = 0; hi < size; hi++) + { + const int hi_e = pExpand[hi]; + + const int v = (size == 32) ? interp_5(hi, lo, hi_e, lo_e, mode) : interp_6(hi, lo, hi_e, lo_e, mode); + + int e = iabs(v - i); + + if ((mode == bc1_approx_mode::cBC1Ideal) || (mode == bc1_approx_mode::cBC1IdealRound4)) + e += (iabs(hi_e - lo_e) * 3) / 100; + + // Favor equal endpoints, for lower error on actual GPU's which approximate the interpolation. + if ((e < lowest_e) || ((e == lowest_e) && (lo == hi))) + { + pTable[i].m_hi = static_cast(hi); + pTable[i].m_lo = static_cast(lo); + + assert(e <= UINT8_MAX); + pTable[i].m_e = static_cast(e); + + lowest_e = e; + } + + } // hi + } // lo + } + } + + // This table is: 9 * (w * w), 9 * ((1.0f - w) * w), 9 * ((1.0f - w) * (1.0f - w)) + // where w is [0,1/3,2/3,1]. 9 is the perfect multiplier. + static const uint32_t g_weight_vals4[4] = { 0x000009, 0x010204, 0x040201, 0x090000 }; + + // multiplier is 4 for 3-color + static const uint32_t g_weight_vals3[3] = { 0x000004, 0x040000, 0x010101 }; + + static inline void compute_selector_factors4(const hist4& h, float& iz00, float& iz10, float& iz11) + { + uint32_t weight_accum = 0; + for (uint32_t sel = 0; sel < 4; sel++) + weight_accum += g_weight_vals4[sel] * h.m_hist[sel]; + + float z00 = (float)((weight_accum >> 16) & 0xFF); + float z10 = (float)((weight_accum >> 8) & 0xFF); + float z11 = (float)(weight_accum & 0xFF); + float z01 = z10; + + float det = z00 * z11 - z01 * z10; + if (fabs(det) < 1e-8f) + det = 0.0f; + else + det = (3.0f / 255.0f) / det; + + iz00 = z11 * det; + iz10 = -z10 * det; + iz11 = z00 * det; + } + + static inline void compute_selector_factors3(const hist3& h, float& iz00, float& iz10, float& iz11) + { + uint32_t weight_accum = 0; + for (uint32_t sel = 0; sel < 3; sel++) + weight_accum += g_weight_vals3[sel] * h.m_hist[sel]; + + float z00 = (float)((weight_accum >> 16) & 0xFF); + float z10 = (float)((weight_accum >> 8) & 0xFF); + float z11 = (float)(weight_accum & 0xFF); + float z01 = z10; + + float det = z00 * z11 - z01 * z10; + if (fabs(det) < 1e-8f) + det = 0.0f; + else + det = (2.0f / 255.0f) / det; + + iz00 = z11 * det; + iz10 = -z10 * det; + iz11 = z00 * det; + } + + static bool g_initialized; + + void init(bc1_approx_mode mode) + { + g_bc1_approx_mode = mode; + + uint8_t bc1_expand5[32]; + for (int i = 0; i < 32; i++) + bc1_expand5[i] = static_cast((i << 3) | (i >> 2)); + prepare_bc1_single_color_table(g_bc1_match5_equals_1, bc1_expand5, 32, mode); + prepare_bc1_single_color_table_half(g_bc1_match5_half, bc1_expand5, 32, mode); + + uint8_t bc1_expand6[64]; + for (int i = 0; i < 64; i++) + bc1_expand6[i] = static_cast((i << 2) | (i >> 4)); + prepare_bc1_single_color_table(g_bc1_match6_equals_1, bc1_expand6, 64, mode); + prepare_bc1_single_color_table_half(g_bc1_match6_half, bc1_expand6, 64, mode); + + for (uint32_t i = 0; i < NUM_UNIQUE_TOTAL_ORDERINGS4; i++) + { + hist4 h; + h.m_hist[0] = (uint8_t)g_unique_total_orders4[i][0]; + h.m_hist[1] = (uint8_t)g_unique_total_orders4[i][1]; + h.m_hist[2] = (uint8_t)g_unique_total_orders4[i][2]; + h.m_hist[3] = (uint8_t)g_unique_total_orders4[i][3]; + + if (!h.any_16()) + { + const uint32_t index = h.m_hist[0] | (h.m_hist[1] << 4) | (h.m_hist[2] << 8); + assert(index < 4096); + g_total_ordering4_hash[index] = (uint16_t)i; + } + + compute_selector_factors4(h, g_selector_factors4[i][0], g_selector_factors4[i][1], g_selector_factors4[i][2]); + } + + for (uint32_t i = 0; i < NUM_UNIQUE_TOTAL_ORDERINGS3; i++) + { + hist3 h; + h.m_hist[0] = (uint8_t)g_unique_total_orders3[i][0]; + h.m_hist[1] = (uint8_t)g_unique_total_orders3[i][1]; + h.m_hist[2] = (uint8_t)g_unique_total_orders3[i][2]; + + if (!h.any_16()) + { + const uint32_t index = h.m_hist[0] | (h.m_hist[1] << 4); + assert(index < 256); + g_total_ordering3_hash[index] = (uint16_t)i; + } + + compute_selector_factors3(h, g_selector_factors3[i][0], g_selector_factors3[i][1], g_selector_factors3[i][2]); + } + + g_initialized = true; + } + + void encode_bc1_solid_block(void* pDst, uint32_t fr, uint32_t fg, uint32_t fb, bool allow_3color) + { + bc1_block* pDst_block = static_cast(pDst); + + uint32_t mask = 0xAA; + int max16 = -1, min16 = 0; + + if (allow_3color) + { + const uint32_t err4 = g_bc1_match5_equals_1[fr].m_e + g_bc1_match6_equals_1[fg].m_e + g_bc1_match5_equals_1[fb].m_e; + const uint32_t err3 = g_bc1_match5_half[fr].m_e + g_bc1_match6_half[fg].m_e + g_bc1_match5_half[fb].m_e; + + if (err3 < err4) + { + max16 = (g_bc1_match5_half[fr].m_hi << 11) | (g_bc1_match6_half[fg].m_hi << 5) | g_bc1_match5_half[fb].m_hi; + min16 = (g_bc1_match5_half[fr].m_lo << 11) | (g_bc1_match6_half[fg].m_lo << 5) | g_bc1_match5_half[fb].m_lo; + + if (max16 > min16) + std::swap(max16, min16); + } + } + + if (max16 == -1) + { + max16 = (g_bc1_match5_equals_1[fr].m_hi << 11) | (g_bc1_match6_equals_1[fg].m_hi << 5) | g_bc1_match5_equals_1[fb].m_hi; + min16 = (g_bc1_match5_equals_1[fr].m_lo << 11) | (g_bc1_match6_equals_1[fg].m_lo << 5) | g_bc1_match5_equals_1[fb].m_lo; + + if (min16 == max16) + { + // Always forbid 3 color blocks + // This is to guarantee that BC3 blocks never use punchthrough alpha (3 color) mode, which isn't supported on some (all?) GPU's. + mask = 0; + + // Make l > h + if (min16 > 0) + min16--; + else + { + // l = h = 0 + assert(min16 == max16 && max16 == 0); + + max16 = 1; + min16 = 0; + mask = 0x55; + } + + assert(max16 > min16); + } + + if (max16 < min16) + { + std::swap(max16, min16); + mask ^= 0x55; + } + } + + pDst_block->set_low_color(static_cast(max16)); + pDst_block->set_high_color(static_cast(min16)); + pDst_block->m_selectors[0] = static_cast(mask); + pDst_block->m_selectors[1] = static_cast(mask); + pDst_block->m_selectors[2] = static_cast(mask); + pDst_block->m_selectors[3] = static_cast(mask); + } + + static const float g_midpoint5[32] = { .015686f, .047059f, .078431f, .111765f, .145098f, .176471f, .207843f, .241176f, .274510f, .305882f, .337255f, .370588f, .403922f, .435294f, .466667f, .5f, .533333f, .564706f, .596078f, .629412f, .662745f, .694118f, .725490f, .758824f, .792157f, .823529f, .854902f, .888235f, .921569f, .952941f, .984314f, 1e+37f }; + static const float g_midpoint6[64] = { .007843f, .023529f, .039216f, .054902f, .070588f, .086275f, .101961f, .117647f, .133333f, .149020f, .164706f, .180392f, .196078f, .211765f, .227451f, .245098f, .262745f, .278431f, .294118f, .309804f, .325490f, .341176f, .356863f, .372549f, .388235f, .403922f, .419608f, .435294f, .450980f, .466667f, .482353f, .500000f, .517647f, .533333f, .549020f, .564706f, .580392f, .596078f, .611765f, .627451f, .643137f, .658824f, .674510f, .690196f, .705882f, .721569f, .737255f, .754902f, .772549f, .788235f, .803922f, .819608f, .835294f, .850980f, .866667f, .882353f, .898039f, .913725f, .929412f, .945098f, .960784f, .976471f, .992157f, 1e+37f }; + + struct vec3F { float c[3]; }; + + static inline void compute_least_squares_endpoints4_rgb( + vec3F* pXl, vec3F* pXh, + int total_r, int total_g, int total_b, + float iz00, float iz10, float iz11, + uint32_t s, const uint32_t r_sum[17], const uint32_t g_sum[17], const uint32_t b_sum[17]) + { + const float iz01 = iz10; + + const uint32_t f1 = g_unique_total_orders4[s][0]; + const uint32_t f2 = g_unique_total_orders4[s][0] + g_unique_total_orders4[s][1]; + const uint32_t f3 = g_unique_total_orders4[s][0] + g_unique_total_orders4[s][1] + g_unique_total_orders4[s][2]; + uint32_t uq00_r = (r_sum[f2] - r_sum[f1]) + (r_sum[f3] - r_sum[f2]) * 2 + (r_sum[16] - r_sum[f3]) * 3; + uint32_t uq00_g = (g_sum[f2] - g_sum[f1]) + (g_sum[f3] - g_sum[f2]) * 2 + (g_sum[16] - g_sum[f3]) * 3; + uint32_t uq00_b = (b_sum[f2] - b_sum[f1]) + (b_sum[f3] - b_sum[f2]) * 2 + (b_sum[16] - b_sum[f3]) * 3; + + float q10_r = (float)(total_r * 3 - uq00_r); + float q10_g = (float)(total_g * 3 - uq00_g); + float q10_b = (float)(total_b * 3 - uq00_b); + + pXl->c[0] = iz00 * (float)uq00_r + iz01 * q10_r; + pXh->c[0] = iz10 * (float)uq00_r + iz11 * q10_r; + + pXl->c[1] = iz00 * (float)uq00_g + iz01 * q10_g; + pXh->c[1] = iz10 * (float)uq00_g + iz11 * q10_g; + + pXl->c[2] = iz00 * (float)uq00_b + iz01 * q10_b; + pXh->c[2] = iz10 * (float)uq00_b + iz11 * q10_b; + } + + static inline bool compute_least_squares_endpoints4_rgb(const color32* pColors, const uint8_t* pSelectors, vec3F* pXl, vec3F* pXh, int total_r, int total_g, int total_b) + { + uint32_t uq00_r = 0, uq00_g = 0, uq00_b = 0; + uint32_t weight_accum = 0; + for (uint32_t i = 0; i < 16; i++) + { + const uint8_t r = pColors[i].c[0], g = pColors[i].c[1], b = pColors[i].c[2]; + const uint8_t sel = pSelectors[i]; + + weight_accum += g_weight_vals4[sel]; + uq00_r += sel * r; + uq00_g += sel * g; + uq00_b += sel * b; + } + + int q10_r = total_r * 3 - uq00_r; + int q10_g = total_g * 3 - uq00_g; + int q10_b = total_b * 3 - uq00_b; + + float z00 = (float)((weight_accum >> 16) & 0xFF); + float z10 = (float)((weight_accum >> 8) & 0xFF); + float z11 = (float)(weight_accum & 0xFF); + float z01 = z10; + + float det = z00 * z11 - z01 * z10; + if (fabs(det) < 1e-8f) + return false; + + det = (3.0f / 255.0f) / det; + + float iz00, iz01, iz10, iz11; + iz00 = z11 * det; + iz01 = -z01 * det; + iz10 = -z10 * det; + iz11 = z00 * det; + + pXl->c[0] = iz00 * (float)uq00_r + iz01 * q10_r; + pXh->c[0] = iz10 * (float)uq00_r + iz11 * q10_r; + + pXl->c[1] = iz00 * (float)uq00_g + iz01 * q10_g; + pXh->c[1] = iz10 * (float)uq00_g + iz11 * q10_g; + + pXl->c[2] = iz00 * (float)uq00_b + iz01 * q10_b; + pXh->c[2] = iz10 * (float)uq00_b + iz11 * q10_b; + + return true; + } + + static inline void compute_least_squares_endpoints3_rgb( + vec3F* pXl, vec3F* pXh, + int total_r, int total_g, int total_b, + float iz00, float iz10, float iz11, + uint32_t s, const uint32_t r_sum[17], const uint32_t g_sum[17], const uint32_t b_sum[17]) + { + const float iz01 = iz10; + + // Compensates for BC1 3-color ordering, which is selector 0, 2, 1 + const uint32_t f1 = g_unique_total_orders3[s][0]; + const uint32_t f2 = g_unique_total_orders3[s][0] + g_unique_total_orders3[s][2]; + uint32_t uq00_r = (r_sum[16] - r_sum[f2]) * 2 + (r_sum[f2] - r_sum[f1]); + uint32_t uq00_g = (g_sum[16] - g_sum[f2]) * 2 + (g_sum[f2] - g_sum[f1]); + uint32_t uq00_b = (b_sum[16] - b_sum[f2]) * 2 + (b_sum[f2] - b_sum[f1]); + + float q10_r = (float)(total_r * 2 - uq00_r); + float q10_g = (float)(total_g * 2 - uq00_g); + float q10_b = (float)(total_b * 2 - uq00_b); + + pXl->c[0] = iz00 * (float)uq00_r + iz01 * q10_r; + pXh->c[0] = iz10 * (float)uq00_r + iz11 * q10_r; + + pXl->c[1] = iz00 * (float)uq00_g + iz01 * q10_g; + pXh->c[1] = iz10 * (float)uq00_g + iz11 * q10_g; + + pXl->c[2] = iz00 * (float)uq00_b + iz01 * q10_b; + pXh->c[2] = iz10 * (float)uq00_b + iz11 * q10_b; + } + + static inline bool compute_least_squares_endpoints3_rgb(bool use_black, const color32* pColors, const uint8_t* pSelectors, vec3F* pXl, vec3F* pXh) + { + int uq00_r = 0, uq00_g = 0, uq00_b = 0; + uint32_t weight_accum = 0; + int total_r = 0, total_g = 0, total_b = 0; + for (uint32_t i = 0; i < 16; i++) + { + const uint8_t r = pColors[i].c[0], g = pColors[i].c[1], b = pColors[i].c[2]; + if (use_black) + { + if ((r | g | b) < 4) + continue; + } + + const uint8_t sel = pSelectors[i]; + assert(sel <= 3); + if (sel == 3) + continue; + + weight_accum += g_weight_vals3[sel]; + + static const uint8_t s_tran[3] = { 0, 2, 1 }; + const uint8_t tsel = s_tran[sel]; + uq00_r += tsel * r; + uq00_g += tsel * g; + uq00_b += tsel * b; + + total_r += r; + total_g += g; + total_b += b; + } + + int q10_r = total_r * 2 - uq00_r; + int q10_g = total_g * 2 - uq00_g; + int q10_b = total_b * 2 - uq00_b; + + float z00 = (float)((weight_accum >> 16) & 0xFF); + float z10 = (float)((weight_accum >> 8) & 0xFF); + float z11 = (float)(weight_accum & 0xFF); + float z01 = z10; + + float det = z00 * z11 - z01 * z10; + if (fabs(det) < 1e-8f) + return false; + + det = (2.0f / 255.0f) / det; + + float iz00, iz01, iz10, iz11; + iz00 = z11 * det; + iz01 = -z01 * det; + iz10 = -z10 * det; + iz11 = z00 * det; + + pXl->c[0] = iz00 * (float)uq00_r + iz01 * q10_r; + pXh->c[0] = iz10 * (float)uq00_r + iz11 * q10_r; + + pXl->c[1] = iz00 * (float)uq00_g + iz01 * q10_g; + pXh->c[1] = iz10 * (float)uq00_g + iz11 * q10_g; + + pXl->c[2] = iz00 * (float)uq00_b + iz01 * q10_b; + pXh->c[2] = iz10 * (float)uq00_b + iz11 * q10_b; + + return true; + } + + static inline void bc1_get_block_colors4(uint32_t block_r[4], uint32_t block_g[4], uint32_t block_b[4], uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb) + { + block_r[0] = (lr << 3) | (lr >> 2); block_g[0] = (lg << 2) | (lg >> 4); block_b[0] = (lb << 3) | (lb >> 2); + block_r[3] = (hr << 3) | (hr >> 2); block_g[3] = (hg << 2) | (hg >> 4); block_b[3] = (hb << 3) | (hb >> 2); + + if (g_bc1_approx_mode == bc1_approx_mode::cBC1Ideal) + { + block_r[1] = (block_r[0] * 2 + block_r[3]) / 3; block_g[1] = (block_g[0] * 2 + block_g[3]) / 3; block_b[1] = (block_b[0] * 2 + block_b[3]) / 3; + block_r[2] = (block_r[3] * 2 + block_r[0]) / 3; block_g[2] = (block_g[3] * 2 + block_g[0]) / 3; block_b[2] = (block_b[3] * 2 + block_b[0]) / 3; + } + else if (g_bc1_approx_mode == bc1_approx_mode::cBC1IdealRound4) + { + block_r[1] = (block_r[0] * 2 + block_r[3] + 1) / 3; block_g[1] = (block_g[0] * 2 + block_g[3] + 1) / 3; block_b[1] = (block_b[0] * 2 + block_b[3] + 1) / 3; + block_r[2] = (block_r[3] * 2 + block_r[0] + 1) / 3; block_g[2] = (block_g[3] * 2 + block_g[0] + 1) / 3; block_b[2] = (block_b[3] * 2 + block_b[0] + 1) / 3; + } + else if (g_bc1_approx_mode == bc1_approx_mode::cBC1AMD) + { + block_r[1] = interp_5_6_amd(block_r[0], block_r[3]); block_g[1] = interp_5_6_amd(block_g[0], block_g[3]); block_b[1] = interp_5_6_amd(block_b[0], block_b[3]); + block_r[2] = interp_5_6_amd(block_r[3], block_r[0]); block_g[2] = interp_5_6_amd(block_g[3], block_g[0]); block_b[2] = interp_5_6_amd(block_b[3], block_b[0]); + } + else + { + block_r[1] = interp_5_nv(lr, hr); block_g[1] = interp_6_nv(block_g[0], block_g[3]); block_b[1] = interp_5_nv(lb, hb); + block_r[2] = interp_5_nv(hr, lr); block_g[2] = interp_6_nv(block_g[3], block_g[0]); block_b[2] = interp_5_nv(hb, lb); + } + } + + static inline void bc1_get_block_colors3(uint32_t block_r[3], uint32_t block_g[3], uint32_t block_b[3], uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb) + { + block_r[0] = (lr << 3) | (lr >> 2); block_g[0] = (lg << 2) | (lg >> 4); block_b[0] = (lb << 3) | (lb >> 2); + block_r[1] = (hr << 3) | (hr >> 2); block_g[1] = (hg << 2) | (hg >> 4); block_b[1] = (hb << 3) | (hb >> 2); + + if ((g_bc1_approx_mode == bc1_approx_mode::cBC1Ideal) || (g_bc1_approx_mode == bc1_approx_mode::cBC1IdealRound4)) + { + block_r[2] = (block_r[0] + block_r[1]) / 2; block_g[2] = (block_g[0] + block_g[1]) / 2; block_b[2] = (block_b[0] + block_b[1]) / 2; + } + else if (g_bc1_approx_mode == bc1_approx_mode::cBC1AMD) + { + block_r[2] = interp_half_5_6_amd(block_r[0], block_r[1]); block_g[2] = interp_half_5_6_amd(block_g[0], block_g[1]); block_b[2] = interp_half_5_6_amd(block_b[0], block_b[1]); + } + else + { + block_r[2] = interp_half_5_nv(lr, hr); block_g[2] = interp_half_6_nv(block_g[0], block_g[1]); block_b[2] = interp_half_5_nv(lb, hb); + } + } + + static inline void bc1_find_sels4_noerr(const color32* pSrc_pixels, uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb, uint8_t sels[16], const uint8_t* pForce_selectors) + { + if (pForce_selectors) + { + memcpy(sels, pForce_selectors, 16); + return; + } + + uint32_t block_r[4], block_g[4], block_b[4]; + bc1_get_block_colors4(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); + + int ar = block_r[3] - block_r[0], ag = block_g[3] - block_g[0], ab = block_b[3] - block_b[0]; + + int dots[4]; + for (uint32_t i = 0; i < 4; i++) + dots[i] = (int)block_r[i] * ar + (int)block_g[i] * ag + (int)block_b[i] * ab; + + int t0 = dots[0] + dots[1], t1 = dots[1] + dots[2], t2 = dots[2] + dots[3]; + + ar *= 2; ag *= 2; ab *= 2; + + static const uint8_t s_sels[4] = { 3, 2, 1, 0 }; + + for (uint32_t i = 0; i < 16; i += 4) + { + const int d0 = pSrc_pixels[i + 0].r * ar + pSrc_pixels[i + 0].g * ag + pSrc_pixels[i + 0].b * ab; + const int d1 = pSrc_pixels[i + 1].r * ar + pSrc_pixels[i + 1].g * ag + pSrc_pixels[i + 1].b * ab; + const int d2 = pSrc_pixels[i + 2].r * ar + pSrc_pixels[i + 2].g * ag + pSrc_pixels[i + 2].b * ab; + const int d3 = pSrc_pixels[i + 3].r * ar + pSrc_pixels[i + 3].g * ag + pSrc_pixels[i + 3].b * ab; + + sels[i + 0] = s_sels[(d0 <= t0) + (d0 < t1) + (d0 < t2)]; + sels[i + 1] = s_sels[(d1 <= t0) + (d1 < t1) + (d1 < t2)]; + sels[i + 2] = s_sels[(d2 <= t0) + (d2 < t1) + (d2 < t2)]; + sels[i + 3] = s_sels[(d3 <= t0) + (d3 < t1) + (d3 < t2)]; + } + } + + static inline uint32_t bc1_find_sels4_fasterr(const color32* pSrc_pixels, uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb, uint8_t sels[16], uint32_t cur_err) + { + uint32_t block_r[4], block_g[4], block_b[4]; + bc1_get_block_colors4(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); + + int ar = block_r[3] - block_r[0], ag = block_g[3] - block_g[0], ab = block_b[3] - block_b[0]; + + int dots[4]; + for (uint32_t i = 0; i < 4; i++) + dots[i] = (int)block_r[i] * ar + (int)block_g[i] * ag + (int)block_b[i] * ab; + + int t0 = dots[0] + dots[1], t1 = dots[1] + dots[2], t2 = dots[2] + dots[3]; + + ar *= 2; ag *= 2; ab *= 2; + + static const uint8_t s_sels[4] = { 3, 2, 1, 0 }; + + uint32_t total_err = 0; + + for (uint32_t i = 0; i < 16; i += 4) + { + const int d0 = pSrc_pixels[i + 0].r * ar + pSrc_pixels[i + 0].g * ag + pSrc_pixels[i + 0].b * ab; + const int d1 = pSrc_pixels[i + 1].r * ar + pSrc_pixels[i + 1].g * ag + pSrc_pixels[i + 1].b * ab; + const int d2 = pSrc_pixels[i + 2].r * ar + pSrc_pixels[i + 2].g * ag + pSrc_pixels[i + 2].b * ab; + const int d3 = pSrc_pixels[i + 3].r * ar + pSrc_pixels[i + 3].g * ag + pSrc_pixels[i + 3].b * ab; + + uint8_t sel0 = s_sels[(d0 <= t0) + (d0 < t1) + (d0 < t2)]; + uint8_t sel1 = s_sels[(d1 <= t0) + (d1 < t1) + (d1 < t2)]; + uint8_t sel2 = s_sels[(d2 <= t0) + (d2 < t1) + (d2 < t2)]; + uint8_t sel3 = s_sels[(d3 <= t0) + (d3 < t1) + (d3 < t2)]; + + sels[i + 0] = sel0; + sels[i + 1] = sel1; + sels[i + 2] = sel2; + sels[i + 3] = sel3; + + total_err += squarei(pSrc_pixels[i + 0].r - block_r[sel0]) + squarei(pSrc_pixels[i + 0].g - block_g[sel0]) + squarei(pSrc_pixels[i + 0].b - block_b[sel0]); + total_err += squarei(pSrc_pixels[i + 1].r - block_r[sel1]) + squarei(pSrc_pixels[i + 1].g - block_g[sel1]) + squarei(pSrc_pixels[i + 1].b - block_b[sel1]); + total_err += squarei(pSrc_pixels[i + 2].r - block_r[sel2]) + squarei(pSrc_pixels[i + 2].g - block_g[sel2]) + squarei(pSrc_pixels[i + 2].b - block_b[sel2]); + total_err += squarei(pSrc_pixels[i + 3].r - block_r[sel3]) + squarei(pSrc_pixels[i + 3].g - block_g[sel3]) + squarei(pSrc_pixels[i + 3].b - block_b[sel3]); + + if (total_err >= cur_err) + break; + } + + return total_err; + } + + static inline uint32_t bc1_find_sels4_check2_err(const color32* pSrc_pixels, uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb, uint8_t sels[16], uint32_t cur_err) + { + uint32_t block_r[4], block_g[4], block_b[4]; + bc1_get_block_colors4(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); + + int dr = block_r[3] - block_r[0], dg = block_g[3] - block_g[0], db = block_b[3] - block_b[0]; + + const float f = 4.0f / (float)(squarei(dr) + squarei(dg) + squarei(db) + .00000125f); + + uint32_t total_err = 0; + + for (uint32_t i = 0; i < 16; i++) + { + const int r = pSrc_pixels[i].r; + const int g = pSrc_pixels[i].g; + const int b = pSrc_pixels[i].b; + + int sel = (int)((float)((r - (int)block_r[0]) * dr + (g - (int)block_g[0]) * dg + (b - (int)block_b[0]) * db) * f + .5f); + sel = clampi(sel, 1, 3); + + uint32_t err0 = squarei((int)block_r[sel - 1] - (int)r) + squarei((int)block_g[sel - 1] - (int)g) + squarei((int)block_b[sel - 1] - (int)b); + uint32_t err1 = squarei((int)block_r[sel] - (int)r) + squarei((int)block_g[sel] - (int)g) + squarei((int)block_b[sel] - (int)b); + + int best_sel = sel; + uint32_t best_err = err1; + if (err0 == err1) + { + // Prefer non-interpolation + if ((best_sel - 1) == 0) + best_sel = 0; + } + else if (err0 < best_err) + { + best_sel = sel - 1; + best_err = err0; + } + + total_err += best_err; + + if (total_err >= cur_err) + break; + + sels[i] = (uint8_t)best_sel; + } + return total_err; + } + + static inline uint32_t bc1_find_sels4_fullerr(const color32* pSrc_pixels, uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb, uint8_t sels[16], uint32_t cur_err) + { + uint32_t block_r[4], block_g[4], block_b[4]; + bc1_get_block_colors4(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); + + uint32_t total_err = 0; + + for (uint32_t i = 0; i < 16; i++) + { + const int r = pSrc_pixels[i].r; + const int g = pSrc_pixels[i].g; + const int b = pSrc_pixels[i].b; + + uint32_t best_err = squarei((int)block_r[0] - (int)r) + squarei((int)block_g[0] - (int)g) + squarei((int)block_b[0] - (int)b); + uint8_t best_sel = 0; + + for (uint32_t j = 1; (j < 4) && best_err; j++) + { + uint32_t err = squarei((int)block_r[j] - (int)r) + squarei((int)block_g[j] - (int)g) + squarei((int)block_b[j] - (int)b); + if ((err < best_err) || ((err == best_err) && (j == 3))) + { + best_err = err; + best_sel = (uint8_t)j; + } + } + + total_err += best_err; + + if (total_err >= cur_err) + break; + + sels[i] = (uint8_t)best_sel; + } + return total_err; + } + + static inline uint32_t bc1_find_sels4(uint32_t flags, const color32* pSrc_pixels, uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb, uint8_t sels[16], uint32_t cur_err, const uint8_t* pForce_selectors) + { + uint32_t err; + + if (pForce_selectors) + { + memcpy(sels, pForce_selectors, 16); + + uint32_t block_r[4], block_g[4], block_b[4]; + bc1_get_block_colors4(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); + + err = 0; + for (uint32_t i = 0; i < 16; i++) + { + const int r = pSrc_pixels[i].r; + const int g = pSrc_pixels[i].g; + const int b = pSrc_pixels[i].b; + + const uint32_t sel = pForce_selectors[i]; + assert(sel <= 3); + + err += squarei((int)block_r[sel] - (int)r) + squarei((int)block_g[sel] - (int)g) + squarei((int)block_b[sel] - (int)b); + } + } + else + { + if (flags & cEncodeBC1UseFasterMSEEval) + err = bc1_find_sels4_fasterr(pSrc_pixels, lr, lg, lb, hr, hg, hb, sels, cur_err); + else if (flags & cEncodeBC1UseFullMSEEval) + err = bc1_find_sels4_fullerr(pSrc_pixels, lr, lg, lb, hr, hg, hb, sels, cur_err); + else + err = bc1_find_sels4_check2_err(pSrc_pixels, lr, lg, lb, hr, hg, hb, sels, cur_err); + } + + return err; + } + + static inline uint32_t bc1_find_sels3_fullerr(bool use_black, const color32* pSrc_pixels, uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb, uint8_t sels[16], uint32_t cur_err, const uint8_t* pForce_selectors) + { + uint32_t block_r[4], block_g[4], block_b[4]; + bc1_get_block_colors3(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); + + uint32_t total_err = 0; + + if (pForce_selectors) + { + memcpy(sels, pForce_selectors, 16); + + //uint32_t block_r[4], block_g[4], block_b[4]; + //bc1_get_block_colors3(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); + + block_r[3] = 0; block_g[3] = 0; block_b[3] = 0; + + for (uint32_t i = 0; i < 16; i++) + { + const int r = pSrc_pixels[i].r; + const int g = pSrc_pixels[i].g; + const int b = pSrc_pixels[i].b; + + const uint32_t sel = pForce_selectors[i]; + assert(sel <= 3); + + total_err += squarei((int)block_r[sel] - (int)r) + squarei((int)block_g[sel] - (int)g) + squarei((int)block_b[sel] - (int)b); + } + } + else + { + for (uint32_t i = 0; i < 16; i++) + { + const int r = pSrc_pixels[i].r; + const int g = pSrc_pixels[i].g; + const int b = pSrc_pixels[i].b; + + uint32_t best_err = squarei((int)block_r[0] - (int)r) + squarei((int)block_g[0] - (int)g) + squarei((int)block_b[0] - (int)b); + uint32_t best_sel = 0; + + uint32_t err1 = squarei((int)block_r[1] - (int)r) + squarei((int)block_g[1] - (int)g) + squarei((int)block_b[1] - (int)b); + if (err1 < best_err) + { + best_err = err1; + best_sel = 1; + } + + uint32_t err2 = squarei((int)block_r[2] - (int)r) + squarei((int)block_g[2] - (int)g) + squarei((int)block_b[2] - (int)b); + if (err2 < best_err) + { + best_err = err2; + best_sel = 2; + } + + if (use_black) + { + uint32_t err3 = squarei(r) + squarei(g) + squarei(b); + if (err3 < best_err) + { + best_err = err3; + best_sel = 3; + } + } + + total_err += best_err; + if (total_err >= cur_err) + return total_err; + + sels[i] = (uint8_t)best_sel; + } + } + + return total_err; + } + + static inline void precise_round_565(const vec3F& xl, const vec3F& xh, + int& trial_lr, int& trial_lg, int& trial_lb, + int& trial_hr, int& trial_hg, int& trial_hb) + { + trial_lr = (int)(xl.c[0] * 31.0f); + trial_lg = (int)(xl.c[1] * 63.0f); + trial_lb = (int)(xl.c[2] * 31.0f); + + trial_hr = (int)(xh.c[0] * 31.0f); + trial_hg = (int)(xh.c[1] * 63.0f); + trial_hb = (int)(xh.c[2] * 31.0f); + + if ((uint32_t)(trial_lr | trial_lb | trial_hr | trial_hb) > 31U) + { + trial_lr = ((uint32_t)trial_lr > 31U) ? (~trial_lr >> 31) & 31 : trial_lr; + trial_hr = ((uint32_t)trial_hr > 31U) ? (~trial_hr >> 31) & 31 : trial_hr; + + trial_lb = ((uint32_t)trial_lb > 31U) ? (~trial_lb >> 31) & 31 : trial_lb; + trial_hb = ((uint32_t)trial_hb > 31U) ? (~trial_hb >> 31) & 31 : trial_hb; + } + + if ((uint32_t)(trial_lg | trial_hg) > 63U) + { + trial_lg = ((uint32_t)trial_lg > 63U) ? (~trial_lg >> 31) & 63 : trial_lg; + trial_hg = ((uint32_t)trial_hg > 63U) ? (~trial_hg >> 31) & 63 : trial_hg; + } + + trial_lr = (trial_lr + (xl.c[0] > g_midpoint5[trial_lr])) & 31; + trial_lg = (trial_lg + (xl.c[1] > g_midpoint6[trial_lg])) & 63; + trial_lb = (trial_lb + (xl.c[2] > g_midpoint5[trial_lb])) & 31; + + trial_hr = (trial_hr + (xh.c[0] > g_midpoint5[trial_hr])) & 31; + trial_hg = (trial_hg + (xh.c[1] > g_midpoint6[trial_hg])) & 63; + trial_hb = (trial_hb + (xh.c[2] > g_midpoint5[trial_hb])) & 31; + } + + static inline void precise_round_565_noscale(vec3F xl, vec3F xh, + int& trial_lr, int& trial_lg, int& trial_lb, + int& trial_hr, int& trial_hg, int& trial_hb) + { + xl.c[0] *= 1.0f / 255.0f; + xl.c[1] *= 1.0f / 255.0f; + xl.c[2] *= 1.0f / 255.0f; + + xh.c[0] *= 1.0f / 255.0f; + xh.c[1] *= 1.0f / 255.0f; + xh.c[2] *= 1.0f / 255.0f; + + precise_round_565(xl, xh, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb); + } + + static inline void bc1_encode4(bc1_block* pDst_block, int lr, int lg, int lb, int hr, int hg, int hb, const uint8_t sels[16]) + { + uint32_t lc16 = bc1_block::pack_unscaled_color(lr, lg, lb); + uint32_t hc16 = bc1_block::pack_unscaled_color(hr, hg, hb); + + // Always forbid 3 color blocks + if (lc16 == hc16) + { + uint8_t mask = 0; + + // Make l > h + if (hc16 > 0) + hc16--; + else + { + // lc16 = hc16 = 0 + assert(lc16 == hc16 && hc16 == 0); + + hc16 = 0; + lc16 = 1; + mask = 0x55; // select hc16 + } + + assert(lc16 > hc16); + pDst_block->set_low_color(static_cast(lc16)); + pDst_block->set_high_color(static_cast(hc16)); + + pDst_block->m_selectors[0] = mask; + pDst_block->m_selectors[1] = mask; + pDst_block->m_selectors[2] = mask; + pDst_block->m_selectors[3] = mask; + } + else + { + uint8_t invert_mask = 0; + if (lc16 < hc16) + { + std::swap(lc16, hc16); + invert_mask = 0x55; + } + + assert(lc16 > hc16); + pDst_block->set_low_color((uint16_t)lc16); + pDst_block->set_high_color((uint16_t)hc16); + + uint32_t packed_sels = 0; + static const uint8_t s_sel_trans[4] = { 0, 2, 3, 1 }; + for (uint32_t i = 0; i < 16; i++) + packed_sels |= ((uint32_t)s_sel_trans[sels[i]] << (i * 2)); + + pDst_block->m_selectors[0] = (uint8_t)packed_sels ^ invert_mask; + pDst_block->m_selectors[1] = (uint8_t)(packed_sels >> 8) ^ invert_mask; + pDst_block->m_selectors[2] = (uint8_t)(packed_sels >> 16) ^ invert_mask; + pDst_block->m_selectors[3] = (uint8_t)(packed_sels >> 24) ^ invert_mask; + } + } + + static inline void bc1_encode3(bc1_block* pDst_block, int lr, int lg, int lb, int hr, int hg, int hb, const uint8_t sels[16]) + { + uint32_t lc16 = bc1_block::pack_unscaled_color(lr, lg, lb); + uint32_t hc16 = bc1_block::pack_unscaled_color(hr, hg, hb); + + bool invert_flag = false; + if (lc16 > hc16) + { + std::swap(lc16, hc16); + invert_flag = true; + } + + assert(lc16 <= hc16); + + pDst_block->set_low_color((uint16_t)lc16); + pDst_block->set_high_color((uint16_t)hc16); + + uint32_t packed_sels = 0; + + if (invert_flag) + { + static const uint8_t s_sel_trans_inv[4] = { 1, 0, 2, 3 }; + + for (uint32_t i = 0; i < 16; i++) + packed_sels |= ((uint32_t)s_sel_trans_inv[sels[i]] << (i * 2)); + } + else + { + for (uint32_t i = 0; i < 16; i++) + packed_sels |= ((uint32_t)sels[i] << (i * 2)); + } + + pDst_block->m_selectors[0] = (uint8_t)packed_sels; + pDst_block->m_selectors[1] = (uint8_t)(packed_sels >> 8); + pDst_block->m_selectors[2] = (uint8_t)(packed_sels >> 16); + pDst_block->m_selectors[3] = (uint8_t)(packed_sels >> 24); + } + + struct bc1_encode_results + { + int lr, lg, lb; + int hr, hg, hb; + uint8_t sels[16]; + bool m_3color; + }; + + static bool try_3color_block_useblack(const color32* pSrc_pixels, uint32_t flags, uint32_t& cur_err, bc1_encode_results& results, const uint8_t* pForce_selectors) + { + int total_r = 0, total_g = 0, total_b = 0; + int max_r = 0, max_g = 0, max_b = 0; + int min_r = 255, min_g = 255, min_b = 255; + int total_pixels = 0; + for (uint32_t i = 0; i < 16; i++) + { + const int r = pSrc_pixels[i].r, g = pSrc_pixels[i].g, b = pSrc_pixels[i].b; + if ((r | g | b) < 4) + continue; + + max_r = std::max(max_r, r); max_g = std::max(max_g, g); max_b = std::max(max_b, b); + min_r = std::min(min_r, r); min_g = std::min(min_g, g); min_b = std::min(min_b, b); + total_r += r; total_g += g; total_b += b; + + total_pixels++; + } + + if (!total_pixels) + return false; + + int half_total_pixels = total_pixels >> 1; + int avg_r = (total_r + half_total_pixels) / total_pixels; + int avg_g = (total_g + half_total_pixels) / total_pixels; + int avg_b = (total_b + half_total_pixels) / total_pixels; + + uint32_t low_c = 0, high_c = 0; + + int icov[6] = { 0, 0, 0, 0, 0, 0 }; + for (uint32_t i = 0; i < 16; i++) + { + int r = (int)pSrc_pixels[i].r; + int g = (int)pSrc_pixels[i].g; + int b = (int)pSrc_pixels[i].b; + + if ((r | g | b) < 4) + continue; + + r -= avg_r; + g -= avg_g; + b -= avg_b; + + icov[0] += r * r; + icov[1] += r * g; + icov[2] += r * b; + icov[3] += g * g; + icov[4] += g * b; + icov[5] += b * b; + } + + float cov[6]; + for (uint32_t i = 0; i < 6; i++) + cov[i] = (float)(icov[i]) * (1.0f / 255.0f); + + float xr = (float)(max_r - min_r); + float xg = (float)(max_g - min_g); + float xb = (float)(max_b - min_b); + + if (icov[2] < 0) + xr = -xr; + + if (icov[4] < 0) + xg = -xg; + + for (uint32_t power_iter = 0; power_iter < 4; power_iter++) + { + float r = xr * cov[0] + xg * cov[1] + xb * cov[2]; + float g = xr * cov[1] + xg * cov[3] + xb * cov[4]; + float b = xr * cov[2] + xg * cov[4] + xb * cov[5]; + xr = r; xg = g; xb = b; + } + + float k = maximum(fabsf(xr), fabsf(xg), fabsf(xb)); + int saxis_r = 306, saxis_g = 601, saxis_b = 117; + if (k >= 2) + { + float m = 1024.0f / k; + saxis_r = (int)(xr * m); + saxis_g = (int)(xg * m); + saxis_b = (int)(xb * m); + } + + int low_dot = INT_MAX, high_dot = INT_MIN; + for (uint32_t i = 0; i < 16; i++) + { + int r = (int)pSrc_pixels[i].r, g = (int)pSrc_pixels[i].g, b = (int)pSrc_pixels[i].b; + + if ((r | g | b) < 4) + continue; + + int dot = r * saxis_r + g * saxis_g + b * saxis_b; + if (dot < low_dot) + { + low_dot = dot; + low_c = i; + } + if (dot > high_dot) + { + high_dot = dot; + high_c = i; + } + } + + int lr = to_5(pSrc_pixels[low_c].r); + int lg = to_6(pSrc_pixels[low_c].g); + int lb = to_5(pSrc_pixels[low_c].b); + + int hr = to_5(pSrc_pixels[high_c].r); + int hg = to_6(pSrc_pixels[high_c].g); + int hb = to_5(pSrc_pixels[high_c].b); + + uint8_t trial_sels[16]; + uint32_t trial_err = bc1_find_sels3_fullerr(true, pSrc_pixels, lr, lg, lb, hr, hg, hb, trial_sels, UINT32_MAX, pForce_selectors); + + if (trial_err) + { + const uint32_t total_ls_passes = flags & cEncodeBC1TwoLeastSquaresPasses ? 2 : 1; + for (uint32_t trials = 0; trials < total_ls_passes; trials++) + { + vec3F xl, xh; + int lr2, lg2, lb2, hr2, hg2, hb2; + if (!compute_least_squares_endpoints3_rgb(true, pSrc_pixels, trial_sels, &xl, &xh)) + { + lr2 = g_bc1_match5_half[avg_r].m_hi; + lg2 = g_bc1_match6_half[avg_g].m_hi; + lb2 = g_bc1_match5_half[avg_b].m_hi; + + hr2 = g_bc1_match5_half[avg_r].m_lo; + hg2 = g_bc1_match6_half[avg_g].m_lo; + hb2 = g_bc1_match5_half[avg_b].m_lo; + } + else + { + precise_round_565(xl, xh, hr2, hg2, hb2, lr2, lg2, lb2); + } + + if ((lr == lr2) && (lg == lg2) && (lb == lb2) && (hr == hr2) && (hg == hg2) && (hb == hb2)) + break; + + uint8_t trial_sels2[16]; + uint32_t trial_err2 = bc1_find_sels3_fullerr(true, pSrc_pixels, lr2, lg2, lb2, hr2, hg2, hb2, trial_sels2, trial_err, pForce_selectors); + + if (trial_err2 < trial_err) + { + trial_err = trial_err2; + lr = lr2; lg = lg2; lb = lb2; + hr = hr2; hg = hg2; hb = hb2; + memcpy(trial_sels, trial_sels2, sizeof(trial_sels)); + } + else + break; + } + } + + if (trial_err < cur_err) + { + results.m_3color = true; + results.lr = lr; + results.lg = lg; + results.lb = lb; + results.hr = hr; + results.hg = hg; + results.hb = hb; + memcpy(results.sels, trial_sels, 16); + + cur_err = trial_err; + + return true; + } + + return false; + } + + static bool try_3color_block(const color32* pSrc_pixels, uint32_t flags, uint32_t& cur_err, + int avg_r, int avg_g, int avg_b, int lr, int lg, int lb, int hr, int hg, int hb, int total_r, int total_g, int total_b, uint32_t total_orderings_to_try, + bc1_encode_results& results, const uint8_t* pForce_selectors) + { + if (pForce_selectors) + { + for (uint32_t i = 0; i < 16; i++) + if (pForce_selectors[i] == 3) + return false; + } + + uint8_t trial_sels[16]; + uint32_t trial_err = bc1_find_sels3_fullerr(false, pSrc_pixels, lr, lg, lb, hr, hg, hb, trial_sels, UINT32_MAX, pForce_selectors); + + if (trial_err) + { + const uint32_t total_ls_passes = flags & cEncodeBC1TwoLeastSquaresPasses ? 2 : 1; + for (uint32_t trials = 0; trials < total_ls_passes; trials++) + { + vec3F xl, xh; + int lr2, lg2, lb2, hr2, hg2, hb2; + if (!compute_least_squares_endpoints3_rgb(false, pSrc_pixels, trial_sels, &xl, &xh)) + { + lr2 = g_bc1_match5_half[avg_r].m_hi; + lg2 = g_bc1_match6_half[avg_g].m_hi; + lb2 = g_bc1_match5_half[avg_b].m_hi; + + hr2 = g_bc1_match5_half[avg_r].m_lo; + hg2 = g_bc1_match6_half[avg_g].m_lo; + hb2 = g_bc1_match5_half[avg_b].m_lo; + } + else + { + precise_round_565(xl, xh, hr2, hg2, hb2, lr2, lg2, lb2); + } + + if ((lr == lr2) && (lg == lg2) && (lb == lb2) && (hr == hr2) && (hg == hg2) && (hb == hb2)) + break; + + uint8_t trial_sels2[16]; + uint32_t trial_err2 = bc1_find_sels3_fullerr(false, pSrc_pixels, lr2, lg2, lb2, hr2, hg2, hb2, trial_sels2, trial_err, pForce_selectors); + + if (trial_err2 < trial_err) + { + trial_err = trial_err2; + lr = lr2; lg = lg2; lb = lb2; + hr = hr2; hg = hg2; hb = hb2; + memcpy(trial_sels, trial_sels2, sizeof(trial_sels)); + } + else + break; + } + } + + if ((trial_err) && (flags & cEncodeBC1UseLikelyTotalOrderings) && (total_orderings_to_try)) + { + hist3 h; + for (uint32_t i = 0; i < 16; i++) + { + assert(trial_sels[i] < 3); + h.m_hist[trial_sels[i]]++; + } + + const uint32_t orig_total_order_index = h.lookup_total_ordering_index(); + + int r0, g0, b0, r3, g3, b3; + r0 = (lr << 3) | (lr >> 2); g0 = (lg << 2) | (lg >> 4); b0 = (lb << 3) | (lb >> 2); + r3 = (hr << 3) | (hr >> 2); g3 = (hg << 2) | (hg >> 4); b3 = (hb << 3) | (hb >> 2); + + int ar = r3 - r0, ag = g3 - g0, ab = b3 - b0; + + int dots[16]; + for (uint32_t i = 0; i < 16; i++) + { + int r = pSrc_pixels[i].r; + int g = pSrc_pixels[i].g; + int b = pSrc_pixels[i].b; + int d = 0x1000000 + (r * ar + g * ag + b * ab); + assert(d >= 0); + dots[i] = (d << 4) + i; + } + + std::sort(dots, dots + 16); + + uint32_t r_sum[17], g_sum[17], b_sum[17]; + uint32_t r = 0, g = 0, b = 0; + for (uint32_t i = 0; i < 16; i++) + { + const uint32_t p = dots[i] & 15; + + r_sum[i] = r; + g_sum[i] = g; + b_sum[i] = b; + + r += pSrc_pixels[p].r; + g += pSrc_pixels[p].g; + b += pSrc_pixels[p].b; + } + + r_sum[16] = total_r; + g_sum[16] = total_g; + b_sum[16] = total_b; + + const uint32_t q_total = (flags & cEncodeBC1Exhaustive) ? NUM_UNIQUE_TOTAL_ORDERINGS3 : std::min(total_orderings_to_try, MAX_TOTAL_ORDERINGS3); + for (uint32_t q = 0; q < q_total; q++) + { + const uint32_t s = (flags & cEncodeBC1Exhaustive) ? q : g_best_total_orderings3[orig_total_order_index][q]; + + int trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb; + + vec3F xl, xh; + + if ((s == TOTAL_ORDER_3_0_16) || (s == TOTAL_ORDER_3_1_16) || (s == TOTAL_ORDER_3_2_16)) + { + trial_lr = g_bc1_match5_half[avg_r].m_hi; + trial_lg = g_bc1_match6_half[avg_g].m_hi; + trial_lb = g_bc1_match5_half[avg_b].m_hi; + + trial_hr = g_bc1_match5_half[avg_r].m_lo; + trial_hg = g_bc1_match6_half[avg_g].m_lo; + trial_hb = g_bc1_match5_half[avg_b].m_lo; + } + else + { + compute_least_squares_endpoints3_rgb(&xl, &xh, total_r, total_g, total_b, + g_selector_factors3[s][0], g_selector_factors3[s][1], g_selector_factors3[s][2], s, r_sum, g_sum, b_sum); + + precise_round_565(xl, xh, trial_hr, trial_hg, trial_hb, trial_lr, trial_lg, trial_lb); + } + + uint8_t trial_sels2[16]; + uint32_t trial_err2 = bc1_find_sels3_fullerr(false, pSrc_pixels, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb, trial_sels2, UINT32_MAX, pForce_selectors); + + if (trial_err2 < trial_err) + { + trial_err = trial_err2; + + lr = trial_lr; + lg = trial_lg; + lb = trial_lb; + + hr = trial_hr; + hg = trial_hg; + hb = trial_hb; + + memcpy(trial_sels, trial_sels2, sizeof(trial_sels)); + } + + } // s + } + + if (trial_err < cur_err) + { + results.m_3color = true; + results.lr = lr; + results.lg = lg; + results.lb = lb; + results.hr = hr; + results.hg = hg; + results.hb = hb; + memcpy(results.sels, trial_sels, 16); + + cur_err = trial_err; + + return true; + } + + return false; + } + + void encode_bc1(uint32_t level, void* pDst, const uint8_t* pPixels, bool allow_3color, bool allow_transparent_texels_for_black, const uint8_t* pForce_selectors) + { + uint32_t flags = 0, total_orderings4 = 1, total_orderings3 = 1; + + static_assert(MAX_TOTAL_ORDERINGS3 >= 32, "MAX_TOTAL_ORDERINGS3 >= 32"); + static_assert(MAX_TOTAL_ORDERINGS4 >= 32, "MAX_TOTAL_ORDERINGS4 >= 32"); + + switch (level) + { + case 0: + // Faster/higher quality than stb_dxt default. + flags = cEncodeBC1BoundingBoxInt; + break; + case 1: + // Faster/higher quality than stb_dxt default. A bit higher average quality vs. mode 0. + flags = cEncodeBC1Use2DLS; + break; + case 2: + // On average mode 2 is a little weaker than modes 0/1, but it's stronger on outliers (very tough textures). + // Slightly stronger than stb_dxt. + flags = 0; + break; + case 3: + // Slightly stronger than stb_dxt HIGHQUAL. + flags = cEncodeBC1TwoLeastSquaresPasses; + break; + case 4: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1Use6PowerIters; + break; + default: + case 5: + // stb_dxt HIGHQUAL + permit 3 color (if it's enabled). + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFasterMSEEval; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + break; + case 6: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFasterMSEEval | cEncodeBC1UseLikelyTotalOrderings; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + break; + case 7: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFasterMSEEval | cEncodeBC1UseLikelyTotalOrderings; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = 4; + break; + case 8: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFasterMSEEval | cEncodeBC1UseLikelyTotalOrderings; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = 8; + break; + case 9: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseLikelyTotalOrderings; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = 11; + total_orderings3 = 3; + break; + case 10: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseLikelyTotalOrderings; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = 20; + total_orderings3 = 8; + break; + case 11: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseLikelyTotalOrderings; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = 28; + total_orderings3 = 16; + break; + case 12: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseLikelyTotalOrderings; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = 32; + total_orderings3 = 32; + break; + case 13: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | (20 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = 32; + total_orderings3 = 32; + break; + case 14: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | (32 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = 32; + total_orderings3 = 32; + break; + case 15: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | (32 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = ((((32 + MAX_TOTAL_ORDERINGS4) / 2) + 32) / 2); + total_orderings3 = 32; + break; + case 16: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | (256 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = (32 + MAX_TOTAL_ORDERINGS4) / 2; + total_orderings3 = 32; + break; + case 17: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | (256 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = MAX_TOTAL_ORDERINGS4; + total_orderings3 = 32; + break; + case 18: + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | cEncodeBC1Iterative | (256 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = MAX_TOTAL_ORDERINGS4; + total_orderings3 = 32; + break; + case 19: + // This hidden mode is *extremely* slow and abuses the encoder. It's just for testing/training. + flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | cEncodeBC1Exhaustive | cEncodeBC1Iterative | (256 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; + flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); + total_orderings4 = 32; + total_orderings3 = 32; + break; + } + + encode_bc1(pDst, pPixels, flags, total_orderings4, total_orderings3, pForce_selectors); + } + + static inline void encode_bc1_pick_initial(const color32* pSrc_pixels, uint32_t flags, bool grayscale_flag, + int min_r, int min_g, int min_b, int max_r, int max_g, int max_b, + int avg_r, int avg_g, int avg_b, int total_r, int total_g, int total_b, + int& lr, int& lg, int& lb, int& hr, int& hg, int& hb) + { + if (grayscale_flag) + { + const int fr = pSrc_pixels[0].r; + + // Grayscale blocks are a common enough case to specialize. + if ((max_r - min_r) < 2) + { + lr = lb = hr = hb = to_5(fr); + lg = hg = to_6(fr); + } + else + { + lr = lb = to_5(min_r); + lg = to_6(min_r); + + hr = hb = to_5(max_r); + hg = to_6(max_r); + } + } + else if (flags & cEncodeBC1Use2DLS) + { + // 2D Least Squares approach from Humus's example, with added inset and optimal rounding. + int big_chan = 0, min_chan_val = min_r, max_chan_val = max_r; + if ((max_g - min_g) > (max_chan_val - min_chan_val)) + big_chan = 1, min_chan_val = min_g, max_chan_val = max_g; + + if ((max_b - min_b) > (max_chan_val - min_chan_val)) + big_chan = 2, min_chan_val = min_b, max_chan_val = max_b; + + int sum_xy_r = 0, sum_xy_g = 0, sum_xy_b = 0; + vec3F l, h; + if (big_chan == 0) + { + for (uint32_t i = 0; i < 16; i++) + { + const int r = pSrc_pixels[i].r, g = pSrc_pixels[i].g, b = pSrc_pixels[i].b; + sum_xy_r += r * r, sum_xy_g += r * g, sum_xy_b += r * b; + } + + int sum_x = total_r; + int sum_x2 = sum_xy_r; + + float div = (float)(16 * sum_x2 - sum_x * sum_x); + float b_y = 0.0f, b_z = 0.0f; + if (fabs(div) > 1e-8f) + { + div = 1.0f / div; + b_y = (16 * sum_xy_g - sum_x * total_g) * div; + b_z = (16 * sum_xy_b - sum_x * total_b) * div; + } + + float a_y = (total_g - b_y * sum_x) / 16.0f; + float a_z = (total_b - b_z * sum_x) / 16.0f; + + l.c[1] = a_y + b_y * min_chan_val; + l.c[2] = a_z + b_z * min_chan_val; + + h.c[1] = a_y + b_y * max_chan_val; + h.c[2] = a_z + b_z * max_chan_val; + + float dg = (h.c[1] - l.c[1]); + float db = (h.c[2] - l.c[2]); + + h.c[1] = l.c[1] + dg * (15.0f / 16.0f); + h.c[2] = l.c[2] + db * (15.0f / 16.0f); + + l.c[1] = l.c[1] + dg * (1.0f / 16.0f); + l.c[2] = l.c[2] + db * (1.0f / 16.0f); + + float d = (float)(max_chan_val - min_chan_val); + float fmin_chan_val = min_chan_val + d * (1.0f / 16.0f); + float fmax_chan_val = min_chan_val + d * (15.0f / 16.0f); + + l.c[0] = fmin_chan_val; + h.c[0] = fmax_chan_val; + } + else if (big_chan == 1) + { + for (uint32_t i = 0; i < 16; i++) + { + const int r = pSrc_pixels[i].r, g = pSrc_pixels[i].g, b = pSrc_pixels[i].b; + sum_xy_r += g * r, sum_xy_g += g * g, sum_xy_b += g * b; + } + + int sum_x = total_g; + int sum_x2 = sum_xy_g; + + float div = (float)(16 * sum_x2 - sum_x * sum_x); + float b_x = 0.0f, b_z = 0.0f; + if (fabs(div) > 1e-8f) + { + div = 1.0f / div; + b_x = (16 * sum_xy_r - sum_x * total_r) * div; + b_z = (16 * sum_xy_b - sum_x * total_b) * div; + } + + float a_x = (total_r - b_x * sum_x) / 16.0f; + float a_z = (total_b - b_z * sum_x) / 16.0f; + + l.c[0] = a_x + b_x * min_chan_val; + l.c[2] = a_z + b_z * min_chan_val; + + h.c[0] = a_x + b_x * max_chan_val; + h.c[2] = a_z + b_z * max_chan_val; + + float dr = (h.c[0] - l.c[0]); + float db = (h.c[2] - l.c[2]); + + h.c[0] = l.c[0] + dr * (15.0f / 16.0f); + h.c[2] = l.c[2] + db * (15.0f / 16.0f); + + l.c[0] = l.c[0] + dr * (1.0f / 16.0f); + l.c[2] = l.c[2] + db * (1.0f / 16.0f); + + float d = (float)(max_chan_val - min_chan_val); + float fmin_chan_val = min_chan_val + d * (1.0f / 16.0f); + float fmax_chan_val = min_chan_val + d * (15.0f / 16.0f); + + l.c[1] = fmin_chan_val; + h.c[1] = fmax_chan_val; + } + else + { + for (uint32_t i = 0; i < 16; i++) + { + const int r = pSrc_pixels[i].r, g = pSrc_pixels[i].g, b = pSrc_pixels[i].b; + sum_xy_r += b * r, sum_xy_g += b * g, sum_xy_b += b * b; + } + + int sum_x = total_b; + int sum_x2 = sum_xy_b; + + float div = (float)(16 * sum_x2 - sum_x * sum_x); + float b_x = 0.0f, b_y = 0.0f; + if (fabs(div) > 1e-8f) + { + div = 1.0f / div; + b_x = (16 * sum_xy_r - sum_x * total_r) * div; + b_y = (16 * sum_xy_g - sum_x * total_g) * div; + } + + float a_x = (total_r - b_x * sum_x) / 16.0f; + float a_y = (total_g - b_y * sum_x) / 16.0f; + + l.c[0] = a_x + b_x * min_chan_val; + l.c[1] = a_y + b_y * min_chan_val; + + h.c[0] = a_x + b_x * max_chan_val; + h.c[1] = a_y + b_y * max_chan_val; + + float dr = (h.c[0] - l.c[0]); + float dg = (h.c[1] - l.c[1]); + + h.c[0] = l.c[0] + dr * (15.0f / 16.0f); + h.c[1] = l.c[1] + dg * (15.0f / 16.0f); + + l.c[0] = l.c[0] + dr * (1.0f / 16.0f); + l.c[1] = l.c[1] + dg * (1.0f / 16.0f); + + float d = (float)(max_chan_val - min_chan_val); + float fmin_chan_val = min_chan_val + d * (1.0f / 16.0f); + float fmax_chan_val = min_chan_val + d * (15.0f / 16.0f); + + l.c[2] = fmin_chan_val; + h.c[2] = fmax_chan_val; + } + + precise_round_565_noscale(l, h, lr, lg, lb, hr, hg, hb); + } + else if (flags & cEncodeBC1BoundingBox) + { + // Algorithm from icbc.h compress_dxt1_fast() + vec3F l, h; + l.c[0] = min_r * (1.0f / 255.0f); + l.c[1] = min_g * (1.0f / 255.0f); + l.c[2] = min_b * (1.0f / 255.0f); + + h.c[0] = max_r * (1.0f / 255.0f); + h.c[1] = max_g * (1.0f / 255.0f); + h.c[2] = max_b * (1.0f / 255.0f); + + const float bias = 8.0f / 255.0f; + float inset_r = (h.c[0] - l.c[0] - bias) * (1.0f / 16.0f); + float inset_g = (h.c[1] - l.c[1] - bias) * (1.0f / 16.0f); + float inset_b = (h.c[2] - l.c[2] - bias) * (1.0f / 16.0f); + + l.c[0] = clampf(l.c[0] + inset_r, 0.0f, 1.0f); + l.c[1] = clampf(l.c[1] + inset_g, 0.0f, 1.0f); + l.c[2] = clampf(l.c[2] + inset_b, 0.0f, 1.0f); + + h.c[0] = clampf(h.c[0] - inset_r, 0.0f, 1.0f); + h.c[1] = clampf(h.c[1] - inset_g, 0.0f, 1.0f); + h.c[2] = clampf(h.c[2] - inset_b, 0.0f, 1.0f); + + int icov_xz = 0, icov_yz = 0; + for (uint32_t i = 0; i < 16; i++) + { + int r = (int)pSrc_pixels[i].r - avg_r; + int g = (int)pSrc_pixels[i].g - avg_g; + int b = (int)pSrc_pixels[i].b - avg_b; + icov_xz += r * b; + icov_yz += g * b; + } + + if (icov_xz < 0) + std::swap(l.c[0], h.c[0]); + + if (icov_yz < 0) + std::swap(l.c[1], h.c[1]); + + precise_round_565(l, h, lr, lg, lb, hr, hg, hb); + } + else if (flags & cEncodeBC1BoundingBoxInt) + { + // Algorithm from icbc.h compress_dxt1_fast(), but converted to integer. + int inset_r = (max_r - min_r - 8) >> 4; + int inset_g = (max_g - min_g - 8) >> 4; + int inset_b = (max_b - min_b - 8) >> 4; + + min_r += inset_r; + min_g += inset_g; + min_b += inset_b; + if ((uint32_t)(min_r | min_g | min_b) > 255U) + { + min_r = clampi(min_r, 0, 255); + min_g = clampi(min_g, 0, 255); + min_b = clampi(min_b, 0, 255); + } + + max_r -= inset_r; + max_g -= inset_g; + max_b -= inset_b; + if ((uint32_t)(max_r | max_g | max_b) > 255U) + { + max_r = clampi(max_r, 0, 255); + max_g = clampi(max_g, 0, 255); + max_b = clampi(max_b, 0, 255); + } + + int icov_xz = 0, icov_yz = 0; + for (uint32_t i = 0; i < 16; i++) + { + int r = (int)pSrc_pixels[i].r - avg_r; + int g = (int)pSrc_pixels[i].g - avg_g; + int b = (int)pSrc_pixels[i].b - avg_b; + icov_xz += r * b; + icov_yz += g * b; + } + + int x0 = min_r; + int y0 = min_g; + int x1 = max_r; + int y1 = max_g; + + if (icov_xz < 0) + std::swap(x0, x1); + + if (icov_yz < 0) + std::swap(y0, y1); + + lr = to_5(x0); + lg = to_6(y0); + lb = to_5(min_b); + + hr = to_5(x1); + hg = to_6(y1); + hb = to_5(max_b); + } + else + { + // Select 2 colors along the principle axis. (There must be a faster/simpler way.) + uint32_t low_c = 0, high_c = 0; + + int icov[6] = { 0, 0, 0, 0, 0, 0 }; + for (uint32_t i = 0; i < 16; i++) + { + int r = (int)pSrc_pixels[i].r - avg_r; + int g = (int)pSrc_pixels[i].g - avg_g; + int b = (int)pSrc_pixels[i].b - avg_b; + icov[0] += r * r; + icov[1] += r * g; + icov[2] += r * b; + icov[3] += g * g; + icov[4] += g * b; + icov[5] += b * b; + } + + int saxis_r = 306, saxis_g = 601, saxis_b = 117; + + float xr = (float)(max_r - min_r); + float xg = (float)(max_g - min_g); + float xb = (float)(max_b - min_b); + + if (icov[2] < 0) + xr = -xr; + + if (icov[4] < 0) + xg = -xg; + + float cov[6]; + for (uint32_t i = 0; i < 6; i++) + cov[i] = (float)(icov[i]) * (1.0f / 255.0f); + + const uint32_t total_power_iters = (flags & cEncodeBC1Use6PowerIters) ? 6 : 4; + for (uint32_t power_iter = 0; power_iter < total_power_iters; power_iter++) + { + float r = xr * cov[0] + xg * cov[1] + xb * cov[2]; + float g = xr * cov[1] + xg * cov[3] + xb * cov[4]; + float b = xr * cov[2] + xg * cov[4] + xb * cov[5]; + xr = r; xg = g; xb = b; + } + + float k = maximum(fabsf(xr), fabsf(xg), fabsf(xb)); + if (k >= 2) + { + float m = 2048.0f / k; + saxis_r = (int)(xr * m); + saxis_g = (int)(xg * m); + saxis_b = (int)(xb * m); + } + + int low_dot = INT_MAX, high_dot = INT_MIN; + + saxis_r = (int)((uint32_t)saxis_r << 4U); + saxis_g = (int)((uint32_t)saxis_g << 4U); + saxis_b = (int)((uint32_t)saxis_b << 4U); + + for (uint32_t i = 0; i < 16; i += 4) + { + int dot0 = ((pSrc_pixels[i].r * saxis_r + pSrc_pixels[i].g * saxis_g + pSrc_pixels[i].b * saxis_b) & ~0xF) + i; + int dot1 = ((pSrc_pixels[i + 1].r * saxis_r + pSrc_pixels[i + 1].g * saxis_g + pSrc_pixels[i + 1].b * saxis_b) & ~0xF) + i + 1; + int dot2 = ((pSrc_pixels[i + 2].r * saxis_r + pSrc_pixels[i + 2].g * saxis_g + pSrc_pixels[i + 2].b * saxis_b) & ~0xF) + i + 2; + int dot3 = ((pSrc_pixels[i + 3].r * saxis_r + pSrc_pixels[i + 3].g * saxis_g + pSrc_pixels[i + 3].b * saxis_b) & ~0xF) + i + 3; + + int min_d01 = std::min(dot0, dot1); + int max_d01 = std::max(dot0, dot1); + + int min_d23 = std::min(dot2, dot3); + int max_d23 = std::max(dot2, dot3); + + int min_d = std::min(min_d01, min_d23); + int max_d = std::max(max_d01, max_d23); + + low_dot = std::min(low_dot, min_d); + high_dot = std::max(high_dot, max_d); + } + low_c = low_dot & 15; + high_c = high_dot & 15; + + lr = to_5(pSrc_pixels[low_c].r); + lg = to_6(pSrc_pixels[low_c].g); + lb = to_5(pSrc_pixels[low_c].b); + + hr = to_5(pSrc_pixels[high_c].r); + hg = to_6(pSrc_pixels[high_c].g); + hb = to_5(pSrc_pixels[high_c].b); + } + } + + static const int8_t s_adjacent_voxels[16][4] = + { + { 1,0,0, 3 }, // 0 + { 0,1,0, 4 }, // 1 + { 0,0,1, 5 }, // 2 + { -1,0,0, 0 }, // 3 + { 0,-1,0, 1 }, // 4 + { 0,0,-1, 2 }, // 5 + { 1,1,0, 9 }, // 6 + { 1,0,1, 10 }, // 7 + { 0,1,1, 11 }, // 8 + { -1,-1,0, 6 }, // 9 + { -1,0,-1, 7 }, // 10 + { 0,-1,-1, 8 }, // 11 + { -1,1,0, 13 }, // 12 + { 1,-1,0, 12 }, // 13 + { 0,-1,1, 15 }, // 14 + { 0,1,-1, 14 }, // 15 + }; + + // From icbc's high quality mode. + static inline void encode_bc1_endpoint_search(const color32* pSrc_pixels, bool any_black_pixels, + uint32_t flags, bc1_encode_results& results, uint32_t cur_err, const uint8_t* pForce_selectors) + { + int& lr = results.lr, & lg = results.lg, & lb = results.lb, & hr = results.hr, & hg = results.hg, & hb = results.hb; + uint8_t* sels = results.sels; + + int prev_improvement_index = 0, forbidden_direction = -1; + + const int endpoint_search_rounds = (flags & cEncodeBC1EndpointSearchRoundsMask) >> cEncodeBC1EndpointSearchRoundsShift; + for (int i = 0; i < endpoint_search_rounds; i++) + { + assert(s_adjacent_voxels[s_adjacent_voxels[i & 15][3]][3] == (i & 15)); + + if (forbidden_direction == (i & 31)) + continue; + + const int8_t delta[3] = { s_adjacent_voxels[i & 15][0], s_adjacent_voxels[i & 15][1], s_adjacent_voxels[i & 15][2] }; + + int trial_lr = lr, trial_lg = lg, trial_lb = lb, trial_hr = hr, trial_hg = hg, trial_hb = hb; + + if ((i >> 4) & 1) + { + trial_lr = clampi(trial_lr + delta[0], 0, 31); + trial_lg = clampi(trial_lg + delta[1], 0, 63); + trial_lb = clampi(trial_lb + delta[2], 0, 31); + } + else + { + trial_hr = clampi(trial_hr + delta[0], 0, 31); + trial_hg = clampi(trial_hg + delta[1], 0, 63); + trial_hb = clampi(trial_hb + delta[2], 0, 31); + } + + uint8_t trial_sels[16]; + + uint32_t trial_err; + if (results.m_3color) + { + trial_err = bc1_find_sels3_fullerr( + ((any_black_pixels) && ((flags & cEncodeBC1Use3ColorBlocksForBlackPixels) != 0)), + pSrc_pixels, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb, trial_sels, cur_err, pForce_selectors); + } + else + { + trial_err = bc1_find_sels4(flags, pSrc_pixels, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb, trial_sels, cur_err, pForce_selectors); + } + + if (trial_err < cur_err) + { + cur_err = trial_err; + + forbidden_direction = s_adjacent_voxels[i & 15][3] | (i & 16); + + lr = trial_lr, lg = trial_lg, lb = trial_lb, hr = trial_hr, hg = trial_hg, hb = trial_hb; + + memcpy(sels, trial_sels, 16); + + prev_improvement_index = i; + } + + if (i - prev_improvement_index > 32) + break; + } + } + + void encode_bc1(void* pDst, const uint8_t* pPixels, uint32_t flags, uint32_t total_orderings_to_try, uint32_t total_orderings_to_try3, const uint8_t* pForce_selectors) + { + assert(g_initialized); + + const color32* pSrc_pixels = (const color32*)pPixels; + bc1_block* pDst_block = static_cast(pDst); + + int avg_r, avg_g, avg_b, min_r, min_g, min_b, max_r, max_g, max_b; + + const uint32_t fr = pSrc_pixels[0].r, fg = pSrc_pixels[0].g, fb = pSrc_pixels[0].b; + + uint32_t j; + for (j = 15; j >= 1; --j) + if ((pSrc_pixels[j].r != fr) || (pSrc_pixels[j].g != fg) || (pSrc_pixels[j].b != fb)) + break; + + if (j == 0) + { + encode_bc1_solid_block(pDst, fr, fg, fb, (flags & (cEncodeBC1Use3ColorBlocks | cEncodeBC1Use3ColorBlocksForBlackPixels)) != 0); + return; + } + + int total_r = fr, total_g = fg, total_b = fb; + + max_r = fr, max_g = fg, max_b = fb; + min_r = fr, min_g = fg, min_b = fb; + + uint32_t grayscale_flag = (fr == fg) && (fr == fb); + uint32_t any_black_pixels = (fr | fg | fb) < 4; + + for (uint32_t i = 1; i < 16; i++) + { + const int r = pSrc_pixels[i].r, g = pSrc_pixels[i].g, b = pSrc_pixels[i].b; + + grayscale_flag &= ((r == g) && (r == b)); + any_black_pixels |= ((r | g | b) < 4); + + max_r = std::max(max_r, r); max_g = std::max(max_g, g); max_b = std::max(max_b, b); + min_r = std::min(min_r, r); min_g = std::min(min_g, g); min_b = std::min(min_b, b); + total_r += r; total_g += g; total_b += b; + } + + avg_r = (total_r + 8) >> 4, avg_g = (total_g + 8) >> 4, avg_b = (total_b + 8) >> 4; + + bc1_encode_results results; + results.m_3color = false; + + uint8_t* sels = results.sels; + int& lr = results.lr, & lg = results.lg, & lb = results.lb, & hr = results.hr, & hg = results.hg, & hb = results.hb; + int orig_lr = 0, orig_lg = 0, orig_lb = 0, orig_hr = 0, orig_hg = 0, orig_hb = 0; + + lr = 0, lg = 0, lb = 0, hr = 0, hg = 0, hb = 0; + + const bool needs_block_error = ((flags & (cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use3ColorBlocks | cEncodeBC1UseFullMSEEval | cEncodeBC1EndpointSearchRoundsMask)) != 0) || + (any_black_pixels && ((flags & cEncodeBC1Use3ColorBlocksForBlackPixels) != 0)); + + uint32_t cur_err = UINT32_MAX; + + if (!needs_block_error) + { + assert((flags & cEncodeBC1TryAllInitialEndponts) == 0); + + encode_bc1_pick_initial(pSrc_pixels, flags, grayscale_flag != 0, + min_r, min_g, min_b, max_r, max_g, max_b, + avg_r, avg_g, avg_b, total_r, total_g, total_b, + lr, lg, lb, hr, hg, hb); + + orig_lr = lr, orig_lg = lg, orig_lb = lb, orig_hr = hr, orig_hg = hg, orig_hb = hb; + + bc1_find_sels4_noerr(pSrc_pixels, lr, lg, lb, hr, hg, hb, sels, pForce_selectors); + + const uint32_t total_ls_passes = flags & cEncodeBC1TwoLeastSquaresPasses ? 2 : 1; + for (uint32_t ls_pass = 0; ls_pass < total_ls_passes; ls_pass++) + { + int trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb; + + vec3F xl, xh; + if (!compute_least_squares_endpoints4_rgb(pSrc_pixels, sels, &xl, &xh, total_r, total_g, total_b)) + { + // All selectors equal - treat it as a solid block which should always be equal or better. + trial_lr = g_bc1_match5_equals_1[avg_r].m_hi; + trial_lg = g_bc1_match6_equals_1[avg_g].m_hi; + trial_lb = g_bc1_match5_equals_1[avg_b].m_hi; + + trial_hr = g_bc1_match5_equals_1[avg_r].m_lo; + trial_hg = g_bc1_match6_equals_1[avg_g].m_lo; + trial_hb = g_bc1_match5_equals_1[avg_b].m_lo; + + // In high/higher quality mode, let it try again in case the optimal tables have caused the sels to diverge. + } + else + { + precise_round_565(xl, xh, trial_hr, trial_hg, trial_hb, trial_lr, trial_lg, trial_lb); + } + + if ((lr == trial_lr) && (lg == trial_lg) && (lb == trial_lb) && (hr == trial_hr) && (hg == trial_hg) && (hb == trial_hb)) + break; + + bc1_find_sels4_noerr(pSrc_pixels, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb, sels, pForce_selectors); + + lr = trial_lr; + lg = trial_lg; + lb = trial_lb; + hr = trial_hr; + hg = trial_hg; + hb = trial_hb; + + } // ls_pass + } + else + { + const uint32_t total_rounds = (flags & cEncodeBC1TryAllInitialEndponts) ? 2 : 1; + for (uint32_t round = 0; round < total_rounds; round++) + { + uint32_t modified_flags = flags; + if (round == 1) + { + modified_flags &= ~(cEncodeBC1Use2DLS | cEncodeBC1BoundingBox); + modified_flags |= cEncodeBC1BoundingBox; + } + + int round_lr, round_lg, round_lb, round_hr, round_hg, round_hb; + uint8_t round_sels[16]; + + encode_bc1_pick_initial(pSrc_pixels, modified_flags, grayscale_flag != 0, + min_r, min_g, min_b, max_r, max_g, max_b, + avg_r, avg_g, avg_b, total_r, total_g, total_b, + round_lr, round_lg, round_lb, round_hr, round_hg, round_hb); + + int orig_round_lr = round_lr, orig_round_lg = round_lg, orig_round_lb = round_lb, orig_round_hr = round_hr, orig_round_hg = round_hg, orig_round_hb = round_hb; + + uint32_t round_err = bc1_find_sels4(flags, pSrc_pixels, round_lr, round_lg, round_lb, round_hr, round_hg, round_hb, round_sels, UINT32_MAX, pForce_selectors); + + const uint32_t total_ls_passes = flags & cEncodeBC1TwoLeastSquaresPasses ? 2 : 1; + for (uint32_t ls_pass = 0; ls_pass < total_ls_passes; ls_pass++) + { + int trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb; + + vec3F xl, xh; + if (!compute_least_squares_endpoints4_rgb(pSrc_pixels, round_sels, &xl, &xh, total_r, total_g, total_b)) + { + // All selectors equal - treat it as a solid block which should always be equal or better. + trial_lr = g_bc1_match5_equals_1[avg_r].m_hi; + trial_lg = g_bc1_match6_equals_1[avg_g].m_hi; + trial_lb = g_bc1_match5_equals_1[avg_b].m_hi; + + trial_hr = g_bc1_match5_equals_1[avg_r].m_lo; + trial_hg = g_bc1_match6_equals_1[avg_g].m_lo; + trial_hb = g_bc1_match5_equals_1[avg_b].m_lo; + + // In high/higher quality mode, let it try again in case the optimal tables have caused the sels to diverge. + } + else + { + precise_round_565(xl, xh, trial_hr, trial_hg, trial_hb, trial_lr, trial_lg, trial_lb); + } + + if ((round_lr == trial_lr) && (round_lg == trial_lg) && (round_lb == trial_lb) && (round_hr == trial_hr) && (round_hg == trial_hg) && (round_hb == trial_hb)) + break; + + uint8_t trial_sels[16]; + uint32_t trial_err = bc1_find_sels4(flags, pSrc_pixels, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb, trial_sels, round_err, pForce_selectors); + + if (trial_err < round_err) + { + round_lr = trial_lr; + round_lg = trial_lg; + round_lb = trial_lb; + + round_hr = trial_hr; + round_hg = trial_hg; + round_hb = trial_hb; + + round_err = trial_err; + memcpy(round_sels, trial_sels, 16); + } + else + break; + + } // ls_pass + + if (round_err <= cur_err) + { + cur_err = round_err; + + lr = round_lr; + lg = round_lg; + lb = round_lb; + hr = round_hr; + hg = round_hg; + hb = round_hb; + + orig_lr = orig_round_lr; + orig_lg = orig_round_lg; + orig_lb = orig_round_lb; + orig_hr = orig_round_hr; + orig_hg = orig_round_hg; + orig_hb = orig_round_hb; + + memcpy(sels, round_sels, 16); + } + + } // round + } + + if ((cur_err) && (flags & cEncodeBC1UseLikelyTotalOrderings)) + { + assert(needs_block_error); + + const uint32_t total_iters = (flags & cEncodeBC1Iterative) ? 2 : 1; + for (uint32_t iter_index = 0; iter_index < total_iters; iter_index++) + { + const uint32_t orig_err = cur_err; + + hist4 h; + for (uint32_t i = 0; i < 16; i++) + { + assert(sels[i] < 4); + h.m_hist[sels[i]]++; + } + + const uint32_t orig_total_order_index = h.lookup_total_ordering_index(); + + int r0, g0, b0, r3, g3, b3; + r0 = (lr << 3) | (lr >> 2); g0 = (lg << 2) | (lg >> 4); b0 = (lb << 3) | (lb >> 2); + r3 = (hr << 3) | (hr >> 2); g3 = (hg << 2) | (hg >> 4); b3 = (hb << 3) | (hb >> 2); + + int ar = r3 - r0, ag = g3 - g0, ab = b3 - b0; + + int dots[16]; + for (uint32_t i = 0; i < 16; i++) + { + int r = pSrc_pixels[i].r; + int g = pSrc_pixels[i].g; + int b = pSrc_pixels[i].b; + int d = 0x1000000 + (r * ar + g * ag + b * ab); + assert(d >= 0); + dots[i] = (d << 4) + i; + } + + std::sort(dots, dots + 16); + + uint32_t r_sum[17], g_sum[17], b_sum[17]; + uint32_t r = 0, g = 0, b = 0; + for (uint32_t i = 0; i < 16; i++) + { + const uint32_t p = dots[i] & 15; + + r_sum[i] = r; + g_sum[i] = g; + b_sum[i] = b; + + r += pSrc_pixels[p].r; + g += pSrc_pixels[p].g; + b += pSrc_pixels[p].b; + } + + r_sum[16] = total_r; + g_sum[16] = total_g; + b_sum[16] = total_b; + + const uint32_t q_total = (flags & cEncodeBC1Exhaustive) ? NUM_UNIQUE_TOTAL_ORDERINGS4 : clampi(total_orderings_to_try, MIN_TOTAL_ORDERINGS, MAX_TOTAL_ORDERINGS4); + for (uint32_t q = 0; q < q_total; q++) + { + const uint32_t s = (flags & cEncodeBC1Exhaustive) ? q : g_best_total_orderings4[orig_total_order_index][q]; + + int trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb; + + vec3F xl, xh; + + if ((s == TOTAL_ORDER_4_0_16) || (s == TOTAL_ORDER_4_1_16) || (s == TOTAL_ORDER_4_2_16) || (s == TOTAL_ORDER_4_3_16)) + { + trial_lr = g_bc1_match5_equals_1[avg_r].m_hi; + trial_lg = g_bc1_match6_equals_1[avg_g].m_hi; + trial_lb = g_bc1_match5_equals_1[avg_b].m_hi; + + trial_hr = g_bc1_match5_equals_1[avg_r].m_lo; + trial_hg = g_bc1_match6_equals_1[avg_g].m_lo; + trial_hb = g_bc1_match5_equals_1[avg_b].m_lo; + } + else + { + compute_least_squares_endpoints4_rgb(&xl, &xh, total_r, total_g, total_b, + g_selector_factors4[s][0], g_selector_factors4[s][1], g_selector_factors4[s][2], s, r_sum, g_sum, b_sum); + + precise_round_565(xl, xh, trial_hr, trial_hg, trial_hb, trial_lr, trial_lg, trial_lb); + } + + uint8_t trial_sels[16]; + + uint32_t trial_err = bc1_find_sels4(flags, pSrc_pixels, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb, trial_sels, cur_err, pForce_selectors); + + if (trial_err < cur_err) + { + cur_err = trial_err; + + lr = trial_lr; + lg = trial_lg; + lb = trial_lb; + + hr = trial_hr; + hg = trial_hg; + hb = trial_hb; + + memcpy(sels, trial_sels, 16); + } + + } // s + + if ((!cur_err) || (cur_err == orig_err)) + break; + + } // iter_index + } + + if (((flags & (cEncodeBC1Use3ColorBlocks | cEncodeBC1Use3ColorBlocksForBlackPixels)) != 0) && (cur_err)) + { + if (flags & cEncodeBC1Use3ColorBlocks) + { + assert(needs_block_error); + try_3color_block(pSrc_pixels, flags, cur_err, avg_r, avg_g, avg_b, orig_lr, orig_lg, orig_lb, orig_hr, orig_hg, orig_hb, total_r, total_g, total_b, total_orderings_to_try3, results, pForce_selectors); + } + + if ((any_black_pixels) && ((flags & cEncodeBC1Use3ColorBlocksForBlackPixels) != 0)) + { + assert(needs_block_error); + try_3color_block_useblack(pSrc_pixels, flags, cur_err, results, pForce_selectors); + } + } + + if ((flags & cEncodeBC1EndpointSearchRoundsMask) && (cur_err)) + { + assert(needs_block_error); + + encode_bc1_endpoint_search(pSrc_pixels, any_black_pixels != 0, flags, results, cur_err, pForce_selectors); + } + + if (results.m_3color) + bc1_encode3(pDst_block, results.lr, results.lg, results.lb, results.hr, results.hg, results.hb, results.sels); + else + bc1_encode4(pDst_block, results.lr, results.lg, results.lb, results.hr, results.hg, results.hb, results.sels); + } + + // BC3-5 + + void encode_bc4(void* pDst, const uint8_t* pPixels, uint32_t stride) + { + assert(g_initialized); + + uint32_t min0_v, max0_v, min1_v, max1_v, min2_v, max2_v, min3_v, max3_v; + + { + min0_v = max0_v = pPixels[0 * stride]; + min1_v = max1_v = pPixels[1 * stride]; + min2_v = max2_v = pPixels[2 * stride]; + min3_v = max3_v = pPixels[3 * stride]; + } + + { + uint32_t v0 = pPixels[4 * stride]; min0_v = std::min(min0_v, v0); max0_v = std::max(max0_v, v0); + uint32_t v1 = pPixels[5 * stride]; min1_v = std::min(min1_v, v1); max1_v = std::max(max1_v, v1); + uint32_t v2 = pPixels[6 * stride]; min2_v = std::min(min2_v, v2); max2_v = std::max(max2_v, v2); + uint32_t v3 = pPixels[7 * stride]; min3_v = std::min(min3_v, v3); max3_v = std::max(max3_v, v3); + } + + { + uint32_t v0 = pPixels[8 * stride]; min0_v = std::min(min0_v, v0); max0_v = std::max(max0_v, v0); + uint32_t v1 = pPixels[9 * stride]; min1_v = std::min(min1_v, v1); max1_v = std::max(max1_v, v1); + uint32_t v2 = pPixels[10 * stride]; min2_v = std::min(min2_v, v2); max2_v = std::max(max2_v, v2); + uint32_t v3 = pPixels[11 * stride]; min3_v = std::min(min3_v, v3); max3_v = std::max(max3_v, v3); + } + + { + uint32_t v0 = pPixels[12 * stride]; min0_v = std::min(min0_v, v0); max0_v = std::max(max0_v, v0); + uint32_t v1 = pPixels[13 * stride]; min1_v = std::min(min1_v, v1); max1_v = std::max(max1_v, v1); + uint32_t v2 = pPixels[14 * stride]; min2_v = std::min(min2_v, v2); max2_v = std::max(max2_v, v2); + uint32_t v3 = pPixels[15 * stride]; min3_v = std::min(min3_v, v3); max3_v = std::max(max3_v, v3); + } + + const uint32_t min_v = minimum(min0_v, min1_v, min2_v, min3_v); + const uint32_t max_v = maximum(max0_v, max1_v, max2_v, max3_v); + + uint8_t* pDst_bytes = static_cast(pDst); + pDst_bytes[0] = (uint8_t)max_v; + pDst_bytes[1] = (uint8_t)min_v; + + if (max_v == min_v) + { + memset(pDst_bytes + 2, 0, 6); + return; + } + + const uint32_t delta = max_v - min_v; + + // min_v is now 0. Compute thresholds between values by scaling max_v. It's x14 because we're adding two x7 scale factors. + const int t0 = delta * 13; + const int t1 = delta * 11; + const int t2 = delta * 9; + const int t3 = delta * 7; + const int t4 = delta * 5; + const int t5 = delta * 3; + const int t6 = delta * 1; + + // BC4 floors in its divisions, which we compensate for with the 4 bias. + // This function is optimal for all possible inputs (i.e. it outputs the same results as checking all 8 values and choosing the closest one). + const int bias = 4 - min_v * 14; + + static const uint32_t s_tran0[8] = { 1U , 7U , 6U , 5U , 4U , 3U , 2U , 0U }; + static const uint32_t s_tran1[8] = { 1U << 3U, 7U << 3U, 6U << 3U, 5U << 3U, 4U << 3U, 3U << 3U, 2U << 3U, 0U << 3U }; + static const uint32_t s_tran2[8] = { 1U << 6U, 7U << 6U, 6U << 6U, 5U << 6U, 4U << 6U, 3U << 6U, 2U << 6U, 0U << 6U }; + static const uint32_t s_tran3[8] = { 1U << 9U, 7U << 9U, 6U << 9U, 5U << 9U, 4U << 9U, 3U << 9U, 2U << 9U, 0U << 9U }; + + uint64_t a0, a1, a2, a3; + { + const int v0 = pPixels[0 * stride] * 14 + bias; + const int v1 = pPixels[1 * stride] * 14 + bias; + const int v2 = pPixels[2 * stride] * 14 + bias; + const int v3 = pPixels[3 * stride] * 14 + bias; + a0 = s_tran0[(v0 >= t0) + (v0 >= t1) + (v0 >= t2) + (v0 >= t3) + (v0 >= t4) + (v0 >= t5) + (v0 >= t6)]; + a1 = s_tran1[(v1 >= t0) + (v1 >= t1) + (v1 >= t2) + (v1 >= t3) + (v1 >= t4) + (v1 >= t5) + (v1 >= t6)]; + a2 = s_tran2[(v2 >= t0) + (v2 >= t1) + (v2 >= t2) + (v2 >= t3) + (v2 >= t4) + (v2 >= t5) + (v2 >= t6)]; + a3 = s_tran3[(v3 >= t0) + (v3 >= t1) + (v3 >= t2) + (v3 >= t3) + (v3 >= t4) + (v3 >= t5) + (v3 >= t6)]; + } + + { + const int v0 = pPixels[4 * stride] * 14 + bias; + const int v1 = pPixels[5 * stride] * 14 + bias; + const int v2 = pPixels[6 * stride] * 14 + bias; + const int v3 = pPixels[7 * stride] * 14 + bias; + a0 |= (uint64_t)(s_tran0[(v0 >= t0) + (v0 >= t1) + (v0 >= t2) + (v0 >= t3) + (v0 >= t4) + (v0 >= t5) + (v0 >= t6)] << 12U); + a1 |= (uint64_t)(s_tran1[(v1 >= t0) + (v1 >= t1) + (v1 >= t2) + (v1 >= t3) + (v1 >= t4) + (v1 >= t5) + (v1 >= t6)] << 12U); + a2 |= (uint64_t)(s_tran2[(v2 >= t0) + (v2 >= t1) + (v2 >= t2) + (v2 >= t3) + (v2 >= t4) + (v2 >= t5) + (v2 >= t6)] << 12U); + a3 |= (uint64_t)(s_tran3[(v3 >= t0) + (v3 >= t1) + (v3 >= t2) + (v3 >= t3) + (v3 >= t4) + (v3 >= t5) + (v3 >= t6)] << 12U); + } + + { + const int v0 = pPixels[8 * stride] * 14 + bias; + const int v1 = pPixels[9 * stride] * 14 + bias; + const int v2 = pPixels[10 * stride] * 14 + bias; + const int v3 = pPixels[11 * stride] * 14 + bias; + a0 |= (((uint64_t)s_tran0[(v0 >= t0) + (v0 >= t1) + (v0 >= t2) + (v0 >= t3) + (v0 >= t4) + (v0 >= t5) + (v0 >= t6)]) << 24U); + a1 |= (((uint64_t)s_tran1[(v1 >= t0) + (v1 >= t1) + (v1 >= t2) + (v1 >= t3) + (v1 >= t4) + (v1 >= t5) + (v1 >= t6)]) << 24U); + a2 |= (((uint64_t)s_tran2[(v2 >= t0) + (v2 >= t1) + (v2 >= t2) + (v2 >= t3) + (v2 >= t4) + (v2 >= t5) + (v2 >= t6)]) << 24U); + a3 |= (((uint64_t)s_tran3[(v3 >= t0) + (v3 >= t1) + (v3 >= t2) + (v3 >= t3) + (v3 >= t4) + (v3 >= t5) + (v3 >= t6)]) << 24U); + } + + { + const int v0 = pPixels[12 * stride] * 14 + bias; + const int v1 = pPixels[13 * stride] * 14 + bias; + const int v2 = pPixels[14 * stride] * 14 + bias; + const int v3 = pPixels[15 * stride] * 14 + bias; + a0 |= (((uint64_t)s_tran0[(v0 >= t0) + (v0 >= t1) + (v0 >= t2) + (v0 >= t3) + (v0 >= t4) + (v0 >= t5) + (v0 >= t6)]) << 36U); + a1 |= (((uint64_t)s_tran1[(v1 >= t0) + (v1 >= t1) + (v1 >= t2) + (v1 >= t3) + (v1 >= t4) + (v1 >= t5) + (v1 >= t6)]) << 36U); + a2 |= (((uint64_t)s_tran2[(v2 >= t0) + (v2 >= t1) + (v2 >= t2) + (v2 >= t3) + (v2 >= t4) + (v2 >= t5) + (v2 >= t6)]) << 36U); + a3 |= (((uint64_t)s_tran3[(v3 >= t0) + (v3 >= t1) + (v3 >= t2) + (v3 >= t3) + (v3 >= t4) + (v3 >= t5) + (v3 >= t6)]) << 36U); + } + + const uint64_t f = a0 | a1 | a2 | a3; + + pDst_bytes[2] = (uint8_t)f; + pDst_bytes[3] = (uint8_t)(f >> 8U); + pDst_bytes[4] = (uint8_t)(f >> 16U); + pDst_bytes[5] = (uint8_t)(f >> 24U); + pDst_bytes[6] = (uint8_t)(f >> 32U); + pDst_bytes[7] = (uint8_t)(f >> 40U); + } + + uint32_t encode_bc4_hq(void* pDst, const uint8_t* pPixels, uint32_t stride, uint32_t search_rad, uint32_t mode_flag, const uint8_t* pForce_selectors) + { + assert(mode_flag); + + uint8_t* pDst_bytes = static_cast(pDst); + + uint32_t min_val = 255, max_val = 0; + for (uint32_t i = 0; i < 16; i++) + { + uint32_t val = pPixels[i * stride]; + min_val = std::min(val, min_val); + max_val = std::max(val, max_val); + } + + if (min_val == max_val) + { + if (mode_flag & BC4_USE_MODE6_FLAG) + { + pDst_bytes[0] = (uint8_t)min_val; + pDst_bytes[1] = (uint8_t)min_val; + + memset(pDst_bytes + 2, 0, 6); + + assert(!(pDst_bytes[0] > pDst_bytes[1])); + } + else + { + // Use an 8 value encoding + if (min_val > 0) + { + pDst_bytes[0] = (uint8_t)min_val; + pDst_bytes[1] = (uint8_t)min_val - 1; + + memset(pDst_bytes + 2, 0, 6); + } + else + { + static const uint8_t s_const_1_vals[8] = { 1, 0, 0x49, 0x92, 0x24, 0x49, 0x92, 0x24 }; + memcpy(pDst_bytes, s_const_1_vals, 8); + } + + assert(pDst_bytes[0] > pDst_bytes[1]); + } + +#if defined(_DEBUG) || defined(DEBUG) + { + bc4_block* pBlock = (bc4_block*)pDst; + uint8_t pixels[16]; + unpack_bc4(pDst, pixels, 1); + for (uint32_t i = 0; i < 16; i++) + assert(pixels[i] == min_val); + if (mode_flag & BC4_USE_MODE6_FLAG) + { + assert(pBlock->is_alpha6_block()); + } + else + { + assert(!pBlock->is_alpha6_block()); + } + } +#endif + + return 0; + } + + uint32_t best_err = UINT32_MAX; + for (uint32_t mode = 0; mode < 2; mode++) + { + if ((mode_flag & (1 << mode)) == 0) + continue; + + for (int lo_delta = -(int)search_rad; lo_delta <= (int)search_rad; lo_delta++) + { + for (int hi_delta = -(int)search_rad; hi_delta <= (int)search_rad; hi_delta++) + { + bc4_block trial_block; + trial_block.m_endpoints[0] = (uint8_t)clamp(max_val + hi_delta, 0, 255); + trial_block.m_endpoints[1] = (uint8_t)clamp(min_val + lo_delta, 0, 255); + + if (trial_block.m_endpoints[0] == trial_block.m_endpoints[1]) + continue; + + if (mode == 0) + { + if (trial_block.is_alpha6_block()) + std::swap(trial_block.m_endpoints[0], trial_block.m_endpoints[1]); + } + else if (!trial_block.is_alpha6_block()) + std::swap(trial_block.m_endpoints[0], trial_block.m_endpoints[1]); + + uint8_t block_vals[8]; + trial_block.get_block_values(block_vals, trial_block.m_endpoints[0], trial_block.m_endpoints[1]); + + uint32_t trial_err = 0; + uint8_t trial_sels[16]; + + if (pForce_selectors) + { + memcpy(trial_sels, pForce_selectors, 16); + + for (uint32_t i = 0; i < 16; i++) + trial_err += squarei(block_vals[pForce_selectors[i]] - pPixels[i * stride]); + } + else + { + for (uint32_t i = 0; i < 16; i++) + { + uint32_t best_index_err = UINT32_MAX; + uint32_t best_index = 0; + for (uint32_t j = 0; j < 8; j++) + { + uint32_t err = squarei(block_vals[j] - pPixels[i * stride]); + if (err < best_index_err) + { + best_index_err = err; + best_index = j; + if (!err) + break; + } + } + + trial_err += best_index_err; + if (trial_err >= best_err) + break; + + trial_sels[i] = (uint8_t)best_index; + } // i + } + + if (trial_err < best_err) + { + best_err = trial_err; + + uint64_t sel_vals = 0; + for (uint32_t i = 0; i < 16; i++) + sel_vals |= ((uint64_t)trial_sels[i] << (i * 3)); + + trial_block.m_selectors[0] = (uint8_t)sel_vals; + trial_block.m_selectors[1] = (uint8_t)(sel_vals >> 8); + trial_block.m_selectors[2] = (uint8_t)(sel_vals >> 16); + trial_block.m_selectors[3] = (uint8_t)(sel_vals >> 24); + trial_block.m_selectors[4] = (uint8_t)(sel_vals >> 32); + trial_block.m_selectors[5] = (uint8_t)(sel_vals >> 40); + + memcpy(pDst_bytes, &trial_block, sizeof(bc4_block)); + } // if (trial_err < best_err) + + } // hi_delta + + } // lo_delta + + } // mode + + return best_err; + } + + void encode_bc3(void* pDst, const uint8_t* pPixels, uint32_t flags, uint32_t total_orderings_to_try) + { + assert(g_initialized); + + // 3-color blocks are not allowed with BC3 (on most GPU's). + flags &= ~(cEncodeBC1Use3ColorBlocksForBlackPixels | cEncodeBC1Use3ColorBlocks); + + encode_bc4(pDst, pPixels + 3, 4); + encode_bc1(static_cast(pDst) + 8, pPixels, flags, total_orderings_to_try); + } + + void encode_bc3(uint32_t level, void* pDst, const uint8_t* pPixels) + { + assert(g_initialized); + + encode_bc4(pDst, pPixels + 3, 4); + encode_bc1(level, static_cast(pDst) + 8, pPixels, false, false); + } + + void encode_bc3_hq(uint32_t level, void* pDst, const uint8_t* pPixels, uint32_t alpha_search_rad, uint32_t alpha_modes) + { + assert(g_initialized); + + encode_bc4_hq(pDst, pPixels + 3, 4, alpha_search_rad, alpha_modes); + encode_bc1(level, static_cast(pDst) + 8, pPixels, false, false); + } + + void encode_bc5(void* pDst, const uint8_t* pPixels, uint32_t chan0, uint32_t chan1, uint32_t stride) + { + assert(g_initialized); + + encode_bc4(pDst, pPixels + chan0, stride); + encode_bc4(static_cast(pDst) + 8, pPixels + chan1, stride); + } + + void encode_bc5_hq(void* pDst, const uint8_t* pPixels, uint32_t chan0, uint32_t chan1, uint32_t stride, uint32_t alpha_search_rad, uint32_t alpha_modes) + { + assert(g_initialized); + + encode_bc4_hq(pDst, pPixels + chan0, stride, alpha_search_rad, alpha_modes); + encode_bc4_hq(static_cast(pDst) + 8, pPixels + chan1, stride, alpha_search_rad, alpha_modes); + } + + bool unpack_bc1_block_colors(const void* pBlock_bits, color32* c, bc1_approx_mode mode) + { + const bc1_block* pBlock = static_cast(pBlock_bits); + + const uint32_t l = pBlock->get_low_color(); + const uint32_t h = pBlock->get_high_color(); + + const int cr0 = (l >> 11) & 31; + const int cg0 = (l >> 5) & 63; + const int cb0 = l & 31; + const int r0 = (cr0 << 3) | (cr0 >> 2); + const int g0 = (cg0 << 2) | (cg0 >> 4); + const int b0 = (cb0 << 3) | (cb0 >> 2); + + const int cr1 = (h >> 11) & 31; + const int cg1 = (h >> 5) & 63; + const int cb1 = h & 31; + const int r1 = (cr1 << 3) | (cr1 >> 2); + const int g1 = (cg1 << 2) | (cg1 >> 4); + const int b1 = (cb1 << 3) | (cb1 >> 2); + + bool used_punchthrough = false; + + if (l > h) + { + c[0].set_noclamp_rgba(r0, g0, b0, 255); + c[1].set_noclamp_rgba(r1, g1, b1, 255); + switch (mode) + { + case bc1_approx_mode::cBC1Ideal: + c[2].set_noclamp_rgba((r0 * 2 + r1) / 3, (g0 * 2 + g1) / 3, (b0 * 2 + b1) / 3, 255); + c[3].set_noclamp_rgba((r1 * 2 + r0) / 3, (g1 * 2 + g0) / 3, (b1 * 2 + b0) / 3, 255); + break; + case bc1_approx_mode::cBC1IdealRound4: + c[2].set_noclamp_rgba((r0 * 2 + r1 + 1) / 3, (g0 * 2 + g1 + 1) / 3, (b0 * 2 + b1 + 1) / 3, 255); + c[3].set_noclamp_rgba((r1 * 2 + r0 + 1) / 3, (g1 * 2 + g0 + 1) / 3, (b1 * 2 + b0 + 1) / 3, 255); + break; + case bc1_approx_mode::cBC1NVidia: + c[2].set_noclamp_rgba(interp_5_nv(cr0, cr1), interp_6_nv(g0, g1), interp_5_nv(cb0, cb1), 255); + c[3].set_noclamp_rgba(interp_5_nv(cr1, cr0), interp_6_nv(g1, g0), interp_5_nv(cb1, cb0), 255); + break; + case bc1_approx_mode::cBC1AMD: + c[2].set_noclamp_rgba(interp_5_6_amd(r0, r1), interp_5_6_amd(g0, g1), interp_5_6_amd(b0, b1), 255); + c[3].set_noclamp_rgba(interp_5_6_amd(r1, r0), interp_5_6_amd(g1, g0), interp_5_6_amd(b1, b0), 255); + break; + } + } + else + { + c[0].set_noclamp_rgba(r0, g0, b0, 255); + c[1].set_noclamp_rgba(r1, g1, b1, 255); + switch (mode) + { + case bc1_approx_mode::cBC1Ideal: + case bc1_approx_mode::cBC1IdealRound4: + c[2].set_noclamp_rgba((r0 + r1) / 2, (g0 + g1) / 2, (b0 + b1) / 2, 255); + break; + case bc1_approx_mode::cBC1NVidia: + c[2].set_noclamp_rgba(interp_half_5_nv(cr0, cr1), interp_half_6_nv(g0, g1), interp_half_5_nv(cb0, cb1), 255); + break; + case bc1_approx_mode::cBC1AMD: + c[2].set_noclamp_rgba(interp_half_5_6_amd(r0, r1), interp_half_5_6_amd(g0, g1), interp_half_5_6_amd(b0, b1), 255); + break; + } + + c[3].set_noclamp_rgba(0, 0, 0, 0); + used_punchthrough = true; + } + + return used_punchthrough; + } + + // Returns true if the block uses 3 color punchthrough alpha mode. + bool unpack_bc1(const void* pBlock_bits, void* pPixels, bool set_alpha, bc1_approx_mode mode) + { + color32* pDst_pixels = static_cast(pPixels); + + static_assert(sizeof(bc1_block) == 8, "sizeof(bc1_block) == 8"); + static_assert(sizeof(bc4_block) == 8, "sizeof(bc4_block) == 8"); + + const bc1_block* pBlock = static_cast(pBlock_bits); + + color32 c[4]; + const bool used_punchthrough = unpack_bc1_block_colors(pBlock_bits, c, mode); + + if (set_alpha) + { + for (uint32_t y = 0; y < 4; y++, pDst_pixels += 4) + { + pDst_pixels[0] = c[pBlock->get_selector(0, y)]; + pDst_pixels[1] = c[pBlock->get_selector(1, y)]; + pDst_pixels[2] = c[pBlock->get_selector(2, y)]; + pDst_pixels[3] = c[pBlock->get_selector(3, y)]; + } + } + else + { + for (uint32_t y = 0; y < 4; y++, pDst_pixels += 4) + { + pDst_pixels[0].set_rgb(c[pBlock->get_selector(0, y)]); + pDst_pixels[1].set_rgb(c[pBlock->get_selector(1, y)]); + pDst_pixels[2].set_rgb(c[pBlock->get_selector(2, y)]); + pDst_pixels[3].set_rgb(c[pBlock->get_selector(3, y)]); + } + } + + return used_punchthrough; + } + + void unpack_bc4(const void* pBlock_bits, uint8_t* pPixels, uint32_t stride) + { + static_assert(sizeof(bc4_block) == 8, "sizeof(bc4_block) == 8"); + + const bc4_block* pBlock = static_cast(pBlock_bits); + + uint8_t sel_values[8]; + bc4_block::get_block_values(sel_values, pBlock->get_low_alpha(), pBlock->get_high_alpha()); + + const uint64_t selector_bits = pBlock->get_selector_bits(); + + for (uint32_t y = 0; y < 4; y++, pPixels += (stride * 4U)) + { + pPixels[0] = sel_values[pBlock->get_selector(0, y, selector_bits)]; + pPixels[stride * 1] = sel_values[pBlock->get_selector(1, y, selector_bits)]; + pPixels[stride * 2] = sel_values[pBlock->get_selector(2, y, selector_bits)]; + pPixels[stride * 3] = sel_values[pBlock->get_selector(3, y, selector_bits)]; + } + } + + // Returns false if the block uses 3-color punchthrough alpha mode, which isn't supported on some GPU's for BC3. + bool unpack_bc3(const void* pBlock_bits, void* pPixels, bc1_approx_mode mode) + { + color32* pDst_pixels = static_cast(pPixels); + + bool success = true; + + if (unpack_bc1((const uint8_t*)pBlock_bits + sizeof(bc4_block), pDst_pixels, true, mode)) + success = false; + + unpack_bc4(pBlock_bits, &pDst_pixels[0].a, sizeof(color32)); + + return success; + } + + // writes RG + void unpack_bc5(const void* pBlock_bits, void* pPixels, uint32_t chan0, uint32_t chan1, uint32_t stride) + { + unpack_bc4(pBlock_bits, (uint8_t*)pPixels + chan0, stride); + unpack_bc4((const uint8_t*)pBlock_bits + sizeof(bc4_block), (uint8_t*)pPixels + chan1, stride); + } + +} // namespace rgbcx diff --git a/src/ktx.imageio/bc7enc-rdo/rgbcx.h b/src/ktx.imageio/bc7enc-rdo/rgbcx.h new file mode 100644 index 0000000000..5781044a98 --- /dev/null +++ b/src/ktx.imageio/bc7enc-rdo/rgbcx.h @@ -0,0 +1,475 @@ +// clang-format off +#pragma once + +// rgbcx.h v1.13 +// High-performance scalar encoders and RDO (Rate Distortion Optimization) post processors for BC1-5. +// Public Domain or MIT license (you choose - see below), written by Richard Geldreich 2020 . +// +// Influential references: +// https://tinyurl.com/y3vxz457 (Ortego and Ramchandran, "Rate-distortion Methods for Image and Video Compression", 1998) +// http://sjbrown.co.uk/2006/01/19/dxt-compression-techniques/ +// https://github.com/nothings/stb/blob/master/stb_dxt.h +// https://gist.github.com/castano/c92c7626f288f9e99e158520b14a61cf +// https://github.com/castano/icbc/blob/master/icbc.h +// http://www.humus.name/index.php?page=3D&ID=79 +// +// This is a single header file library. Be sure to "#define RGBCX_IMPLEMENTATION" in one .cpp file somewhere. +// +// Instructions: +// +// The library MUST be initialized by calling this function at least once before using any encoder or decoder functions: +// +// void rgbcx::init(bc1_approx_mode mode = cBC1Ideal); +// +// This function manipulates global state, so it is not thread safe. +// You can call it multiple times to change the global BC1 approximation mode. +// Important: BC1/3 textures encoded using non-ideal BC1 approximation modes should only be sampled on parts from that vendor. +// If you encode for AMD, average error on AMD parts will go down, but average error on NVidia parts will go up and vice versa. +// If in doubt, encode in ideal BC1 mode. +// +// Call these functions to encode BC1-5: +// void rgbcx::encode_bc1(uint32_t level, void* pDst, const uint8_t* pPixels, bool allow_3color, bool use_transparent_texels_for_black); +// void rgbcx::encode_bc3(uint32_t level, void* pDst, const uint8_t* pPixels); +// void rgbcx::encode_bc4(void* pDst, const uint8_t* pPixels, uint32_t stride = 4); +// void rgbcx::encode_bc5(void* pDst, const uint8_t* pPixels, uint32_t chan0 = 0, uint32_t chan1 = 1, uint32_t stride = 4); +// +// - level ranges from MIN_LEVEL to MAX_LEVEL. The higher the level, the slower the encoder goes, but the higher the average quality. +// levels [0,4] are fast and compete against stb_dxt (default and HIGHQUAL). The remaining levels compete against squish/NVTT/icbc and icbc HQ. +// If in doubt just use level 10, set allow_3color to true and use_transparent_texels_for_black to false, and adjust as needed. +// +// - pDst is a pointer to the 8-byte (BC1/4) or 16-byte (BC3/5) destination block. +// +// - pPixels is a pointer to the 32-bpp pixels, in either RGBX or RGBA format (R is first in memory). +// Alpha is always ignored by encode_bc1(). +// +// - allow_3color: If true the encoder will use 3-color blocks. This flag is ignored unless level is >= 5 (because lower levels compete against stb_dxt and it doesn't support 3-color blocks). +// Do not enable on BC3-5 textures. 3-color block usage slows down encoding. +// +// - use_transparent_texels_for_black: If true the encoder will use 3-color block transparent black pixels to code very dark or black texels. Your engine/shader MUST ignore the sampled +// alpha value for textures encoded in this mode. This is how NVidia's classic "nvdxt" encoder (used by many original Xbox titles) used to work by default on DXT1C textures. It increases +// average quality substantially (because dark texels/black are very common) and is highly recommended. +// Do not enable on BC3-5 textures. +// +// - stride is the source pixel stride, in bytes. It's typically 4. +// +// - chan0 and chan1 are the source channels. Typically they will be 0 and 1. +// +// All encoding and decoding functions are threade-safe. +// +// To reduce the compiled size of the encoder, set #define RGBCX_USE_SMALLER_TABLES to 1 before including this header. +// +#ifndef RGBCX_INCLUDE_H +#define RGBCX_INCLUDE_H + +#ifdef _MSC_VER +#pragma warning (disable:4201) //nameless struct/union +#endif + +#include +#include +#include +#include +#include + +// By default, the table used to accelerate cluster fit on 4 color blocks uses a 969x128 entry table. +// To reduce the executable size, set RGBCX_USE_SMALLER_TABLES to 1, which selects the smaller 969x32 entry table. +#ifndef RGBCX_USE_SMALLER_TABLES +#define RGBCX_USE_SMALLER_TABLES 1 +#endif + +namespace rgbcx +{ + enum class bc1_approx_mode + { + // The default mode. No rounding for 4-color colors 2,3. My older tools/compressors use this mode. + // This matches the D3D10 docs on BC1. + cBC1Ideal = 0, + + // NVidia GPU mode. + cBC1NVidia = 1, + + // AMD GPU mode. + cBC1AMD = 2, + + // This mode matches AMD Compressonator's output. It rounds 4-color colors 2,3 (not 3-color color 2). + // This matches the D3D9 docs on DXT1. + cBC1IdealRound4 = 3 + }; + + enum class eNoClamp { cNoClamp }; + static inline uint8_t clamp255(int32_t i) { return (uint8_t)((i & 0xFFFFFF00U) ? (~(i >> 31)) : i); } + + template inline S maximum(S a, S b) { return (a > b) ? a : b; } + template inline S maximum(S a, S b, S c) { return maximum(maximum(a, b), c); } + template inline S maximum(S a, S b, S c, S d) { return maximum(maximum(maximum(a, b), c), d); } + + template inline S minimum(S a, S b) { return (a < b) ? a : b; } + template inline S minimum(S a, S b, S c) { return minimum(minimum(a, b), c); } + template inline S minimum(S a, S b, S c, S d) { return minimum(minimum(minimum(a, b), c), d); } + + struct color32 + { + union + { + struct + { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + }; + + uint8_t c[4]; + + uint32_t m; + }; + + color32() { } + + color32(uint32_t vr, uint32_t vg, uint32_t vb, uint32_t va) { set(vr, vg, vb, va); } + color32(eNoClamp unused, uint32_t vr, uint32_t vg, uint32_t vb, uint32_t va) { (void)unused; set_noclamp_rgba(vr, vg, vb, va); } + + void set(uint32_t vr, uint32_t vg, uint32_t vb, uint32_t va) { c[0] = static_cast(vr); c[1] = static_cast(vg); c[2] = static_cast(vb); c[3] = static_cast(va); } + + void set_noclamp_rgb(uint32_t vr, uint32_t vg, uint32_t vb) { c[0] = static_cast(vr); c[1] = static_cast(vg); c[2] = static_cast(vb); } + void set_noclamp_rgba(uint32_t vr, uint32_t vg, uint32_t vb, uint32_t va) { set(vr, vg, vb, va); } + + void set_clamped(int vr, int vg, int vb, int va) { c[0] = clamp255(vr); c[1] = clamp255(vg); c[2] = clamp255(vb); c[3] = clamp255(va); } + + uint8_t operator[] (uint32_t idx) const { assert(idx < 4); return c[idx]; } + uint8_t& operator[] (uint32_t idx) { assert(idx < 4); return c[idx]; } + + bool operator== (const color32& rhs) const { return m == rhs.m; } + + void set_rgb(const color32& other) { c[0] = static_cast(other.c[0]); c[1] = static_cast(other.c[1]); c[2] = static_cast(other.c[2]); } + + static color32 comp_min(const color32& a, const color32& b) { return color32(eNoClamp::cNoClamp, std::min(a[0], b[0]), std::min(a[1], b[1]), std::min(a[2], b[2]), std::min(a[3], b[3])); } + static color32 comp_max(const color32& a, const color32& b) { return color32(eNoClamp::cNoClamp, std::max(a[0], b[0]), std::max(a[1], b[1]), std::max(a[2], b[2]), std::max(a[3], b[3])); } + }; + + // init() MUST be called once before using the BC1 encoder. + // This function may be called multiple times to change the BC1 approximation mode. + // This function initializes global state, so don't call it while other threads inside the encoder. + // Important: If you encode textures for a specific vendor's GPU's, beware that using that texture data on other GPU's may result in ugly artifacts. + // Encode to cBC1Ideal unless you know the texture data will only be deployed or used on a specific vendor's GPU. + void init(bc1_approx_mode mode = bc1_approx_mode::cBC1Ideal); + + // Optimally encodes a solid color block to BC1 format. + void encode_bc1_solid_block(void* pDst, uint32_t fr, uint32_t fg, uint32_t fb, bool allow_3color); + + // BC1 low-level API encoder flags. You can ignore this if you use the simple level API. + enum + { + // Try to improve quality using the most likely total orderings. + // The total_orderings_to_try parameter will then control the number of total orderings to try for 4 color blocks, and the + // total_orderings_to_try3 parameter will control the number of total orderings to try for 3 color blocks (if they are enabled). + cEncodeBC1UseLikelyTotalOrderings = 2, + + // Use 2 least squares pass, instead of one (same as stb_dxt's HIGHQUAL option). + // Recommended if you're enabling cEncodeBC1UseLikelyTotalOrderings. + cEncodeBC1TwoLeastSquaresPasses = 4, + + // cEncodeBC1Use3ColorBlocksForBlackPixels allows the BC1 encoder to use 3-color blocks for blocks containing black or very dark pixels. + // You shader/engine MUST ignore the alpha channel on textures encoded with this flag. + // Average quality goes up substantially for my 100 texture corpus (~.5 dB), so it's worth using if you can. + // Note the BC1 encoder does not actually support transparency in 3-color mode. + // Don't set when encoding to BC3. + cEncodeBC1Use3ColorBlocksForBlackPixels = 8, + + // If cEncodeBC1Use3ColorBlocks is set, the encoder can use 3-color mode for a small but noticeable gain in average quality, but lower perf. + // If you also specify the cEncodeBC1UseLikelyTotalOrderings flag, set the total_orderings_to_try3 paramter to the number of total orderings to try. + // Don't set when encoding to BC3. + cEncodeBC1Use3ColorBlocks = 16, + + // cEncodeBC1Iterative will greatly increase encode time, but is very slightly higher quality. + // Same as squish's iterative cluster fit option. Not really worth the tiny boost in quality, unless you just don't care about perf. at all. + cEncodeBC1Iterative = 32, + + // cEncodeBC1BoundingBox enables a fast all-integer PCA approximation on 4-color blocks. + // At level 0 options (no other flags), this is ~15% faster, and higher *average* quality. + cEncodeBC1BoundingBox = 64, + + // Use a slightly lower quality, but ~30% faster MSE evaluation function for 4-color blocks. + cEncodeBC1UseFasterMSEEval = 128, + + // Examine all colors to compute selectors/MSE (slower than default) + cEncodeBC1UseFullMSEEval = 256, + + // Use 2D least squares+inset+optimal rounding (the method used in Humus's GPU texture encoding demo), instead of PCA. + // Around 18% faster, very slightly lower average quality to better (depends on the content). + cEncodeBC1Use2DLS = 512, + + // Use 6 power iterations vs. 4 for PCA. + cEncodeBC1Use6PowerIters = 2048, + + // Check all total orderings - *very* slow. The encoder is not designed to be used in this way. + cEncodeBC1Exhaustive = 8192, + + // Try 2 different ways of choosing the initial endpoints. + cEncodeBC1TryAllInitialEndponts = 16384, + + // Same as cEncodeBC1BoundingBox, but implemented using integer math (faster, slightly less quality) + cEncodeBC1BoundingBoxInt = 32768, + + // Try refining the final endpoints by examining nearby colors. + cEncodeBC1EndpointSearchRoundsShift = 22, + cEncodeBC1EndpointSearchRoundsMask = 1023U << cEncodeBC1EndpointSearchRoundsShift, + }; + + const uint32_t MIN_TOTAL_ORDERINGS = 1; + const uint32_t MAX_TOTAL_ORDERINGS3 = 32; + +#if RGBCX_USE_SMALLER_TABLES + const uint32_t MAX_TOTAL_ORDERINGS4 = 32; +#else + const uint32_t MAX_TOTAL_ORDERINGS4 = 128; +#endif + + // DEFAULT_TOTAL_ORDERINGS_TO_TRY is around 3x faster than libsquish at slightly higher average quality. 10-16 is a good range to start to compete against libsquish. + const uint32_t DEFAULT_TOTAL_ORDERINGS_TO_TRY = 10; + + const uint32_t DEFAULT_TOTAL_ORDERINGS_TO_TRY3 = 1; + + // Encodes a 4x4 block of RGBX (X=ignored) pixels to BC1 format. + // This is the simplified interface for BC1 encoding, which accepts a level parameter and converts that to the best overall flags. + // The pixels are in RGBA format, where R is first in memory. The BC1 encoder completely ignores the alpha channel (i.e. there is no punchthrough alpha support). + // This is the recommended function to use for BC1 encoding, becuase it configures the encoder for you in the best possible way (on average). + // Note that the 3 color modes won't be used at all until level 5 or higher. + // No transparency supported, however if you set use_transparent_texels_for_black to true the encocer will use transparent selectors on very dark/black texels to reduce MSE. + const uint32_t MIN_LEVEL = 0, MAX_LEVEL = 18; + void encode_bc1(uint32_t level, void* pDst, const uint8_t* pPixels, bool allow_3color, bool use_transparent_texels_for_black, const uint8_t* pForce_selectors = nullptr); + + // Low-level interface for BC1 encoding. + // Always returns a 4 color block, unless cEncodeBC1Use3ColorBlocksForBlackPixels or cEncodeBC1Use3ColorBlock flags are specified. + // total_orderings_to_try controls the perf. vs. quality tradeoff on 4-color blocks when the cEncodeBC1UseLikelyTotalOrderings flag is used. It must range between [MIN_TOTAL_ORDERINGS, MAX_TOTAL_ORDERINGS4]. + // total_orderings_to_try3 controls the perf. vs. quality tradeoff on 3-color bocks when the cEncodeBC1UseLikelyTotalOrderings and the cEncodeBC1Use3ColorBlocks flags are used. Valid range is [0,MAX_TOTAL_ORDERINGS3] (0=disabled). + void encode_bc1(void* pDst, const uint8_t* pPixels, uint32_t flags = 0, uint32_t total_orderings_to_try = DEFAULT_TOTAL_ORDERINGS_TO_TRY, uint32_t total_orderings_to_try3 = DEFAULT_TOTAL_ORDERINGS_TO_TRY3, const uint8_t *pForce_selectors = nullptr); + + // Constants used for high quality BC4/BC5 encoding (and alpha of BC3) + const uint32_t BC4_DEFAULT_SEARCH_RAD = 3; + const uint32_t BC4_USE_MODE8_FLAG = 1; + const uint32_t BC4_USE_MODE6_FLAG = 2; + const uint32_t BC4_USE_ALL_MODES = 3; + + // Encodes a 4x4 block of RGBA pixels to BC3 format. + // There are two encode_bc3() functions. + // The first is the recommended function, which accepts a level parameter. + // The second is a low-level version that allows fine control over BC1 encoding. + void encode_bc3(uint32_t level, void* pDst, const uint8_t* pPixels); + void encode_bc3(void* pDst, const uint8_t* pPixels, uint32_t flags = 0, uint32_t total_orderings_to_try = DEFAULT_TOTAL_ORDERINGS_TO_TRY); + void encode_bc3_hq(uint32_t level, void* pDst, const uint8_t* pPixels, uint32_t alpha_search_rad = BC4_DEFAULT_SEARCH_RAD, uint32_t alpha_modes = BC4_USE_ALL_MODES); + + // Encodes a single channel to BC4. + // stride is the source pixel stride in bytes. + void encode_bc4(void* pDst, const uint8_t* pPixels, uint32_t stride = 4); + uint32_t encode_bc4_hq(void* pDst, const uint8_t* pPixels, uint32_t stride = 4, uint32_t search_rad = BC4_DEFAULT_SEARCH_RAD, uint32_t mode_flag = BC4_USE_ALL_MODES, const uint8_t* pForce_selectors = nullptr); + + // Encodes two channels to BC5. + // chan0/chan1 control which channels, stride is the source pixel stride in bytes. + void encode_bc5(void* pDst, const uint8_t* pPixels, uint32_t chan0 = 0, uint32_t chan1 = 1, uint32_t stride = 4); + void encode_bc5_hq(void* pDst, const uint8_t* pPixels, uint32_t chan0 = 0, uint32_t chan1 = 1, uint32_t stride = 4, uint32_t alpha_search_rad = BC4_DEFAULT_SEARCH_RAD, uint32_t alpha_modes = BC4_USE_ALL_MODES); + + // Decompression functions. + + bool unpack_bc1_block_colors(const void* pBlock_bits, color32* c, bc1_approx_mode mode = bc1_approx_mode::cBC1Ideal); + + // Returns true if the block uses 3 color punchthrough alpha mode. + bool unpack_bc1(const void* pBlock_bits, void* pPixels, bool set_alpha = true, bc1_approx_mode mode = bc1_approx_mode::cBC1Ideal); + + void unpack_bc4(const void* pBlock_bits, uint8_t* pPixels, uint32_t stride = 4); + + // Returns true if the block uses 3 color punchthrough alpha mode. + bool unpack_bc3(const void* pBlock_bits, void* pPixels, bc1_approx_mode mode = bc1_approx_mode::cBC1Ideal); + + void unpack_bc5(const void* pBlock_bits, void* pPixels, uint32_t chan0 = 0, uint32_t chan1 = 1, uint32_t stride = 4); + + // Rate Distortion Optimization (RDO) + enum dxt_constants + { + cDXT1SelectorBits = 2U, cDXT1SelectorValues = 1U << cDXT1SelectorBits, cDXT1SelectorMask = cDXT1SelectorValues - 1U, + cDXT5SelectorBits = 3U, cDXT5SelectorValues = 1U << cDXT5SelectorBits, cDXT5SelectorMask = cDXT5SelectorValues - 1U, + }; + + struct bc1_block + { + enum { cTotalEndpointBytes = 2, cTotalSelectorBytes = 4 }; + + uint8_t m_low_color[cTotalEndpointBytes]; + uint8_t m_high_color[cTotalEndpointBytes]; + uint8_t m_selectors[cTotalSelectorBytes]; + + inline uint32_t get_low_color() const { return m_low_color[0] | (m_low_color[1] << 8U); } + inline uint32_t get_high_color() const { return m_high_color[0] | (m_high_color[1] << 8U); } + inline bool is_3color() const { return get_low_color() <= get_high_color(); } + inline void set_low_color(uint16_t c) { m_low_color[0] = static_cast(c & 0xFF); m_low_color[1] = static_cast((c >> 8) & 0xFF); } + inline void set_high_color(uint16_t c) { m_high_color[0] = static_cast(c & 0xFF); m_high_color[1] = static_cast((c >> 8) & 0xFF); } + inline uint32_t get_selector(uint32_t x, uint32_t y) const { assert((x < 4U) && (y < 4U)); return (m_selectors[y] >> (x * cDXT1SelectorBits)) & cDXT1SelectorMask; } + inline void set_selector(uint32_t x, uint32_t y, uint32_t val) { assert((x < 4U) && (y < 4U) && (val < 4U)); m_selectors[y] &= (~(cDXT1SelectorMask << (x * cDXT1SelectorBits))); m_selectors[y] |= (val << (x * cDXT1SelectorBits)); } + + inline uint32_t get_endpoint_bits() const { return m_low_color[0] | (m_low_color[1] << 8) | (m_high_color[0] << 16) | (m_high_color[1] << 24); } + inline void set_endpoint_bits(uint32_t s) { m_low_color[0] = (uint8_t)s; m_low_color[1] = (uint8_t)(s >> 8); m_high_color[0] = (uint8_t)(s >> 16); m_high_color[1] = (uint8_t)(s >> 24); } + + inline uint32_t get_selector_bits() const { return m_selectors[0] | (m_selectors[1] << 8) | (m_selectors[2] << 16) | (m_selectors[3] << 24); } + inline void set_selector_bits(uint32_t s) { m_selectors[0] = (uint8_t)s; m_selectors[1] = (uint8_t)(s >> 8); m_selectors[2] = (uint8_t)(s >> 16); m_selectors[3] = (uint8_t)(s >> 24); } + + inline bool any_selectors_transparent() const + { + uint32_t sel_bits = get_selector_bits(); + for (uint32_t i = 0; i < 16; i++) + { + if ((sel_bits & 3) == 3) + return true; + + sel_bits >>= 2; + } + return false; + } + + static inline uint16_t pack_color(const color32& color, bool scaled, uint32_t bias = 127U) + { + uint32_t r = color.r, g = color.g, b = color.b; + if (scaled) + { + r = (r * 31U + bias) / 255U; + g = (g * 63U + bias) / 255U; + b = (b * 31U + bias) / 255U; + } + return static_cast(minimum(b, 31U) | (minimum(g, 63U) << 5U) | (minimum(r, 31U) << 11U)); + } + + static inline uint16_t pack_unscaled_color(uint32_t r, uint32_t g, uint32_t b) { return static_cast(b | (g << 5U) | (r << 11U)); } + + static inline void unpack_color(uint32_t c, uint32_t& r, uint32_t& g, uint32_t& b) + { + r = (c >> 11) & 31; + g = (c >> 5) & 63; + b = c & 31; + + r = (r << 3) | (r >> 2); + g = (g << 2) | (g >> 4); + b = (b << 3) | (b >> 2); + } + + static inline void unpack_color_unscaled(uint32_t c, uint32_t& r, uint32_t& g, uint32_t& b) + { + r = (c >> 11) & 31; + g = (c >> 5) & 63; + b = c & 31; + } + }; + + struct bc4_block + { + enum { cBC4SelectorBits = 3, cTotalSelectorBytes = 6, cMaxSelectorValues = 8 }; + uint8_t m_endpoints[2]; + + uint8_t m_selectors[cTotalSelectorBytes]; + + inline uint32_t get_low_alpha() const { return m_endpoints[0]; } + inline uint32_t get_high_alpha() const { return m_endpoints[1]; } + inline bool is_alpha6_block() const { return get_low_alpha() <= get_high_alpha(); } + + inline uint64_t get_selector_bits() const + { + return ((uint64_t)((uint32_t)m_selectors[0] | ((uint32_t)m_selectors[1] << 8U) | ((uint32_t)m_selectors[2] << 16U) | ((uint32_t)m_selectors[3] << 24U))) | + (((uint64_t)m_selectors[4]) << 32U) | + (((uint64_t)m_selectors[5]) << 40U); + } + + inline void set_selector_bits(uint64_t v) + { + for (uint32_t i = 0; i < 6; i++) + { + m_selectors[i] = (uint8_t)v; + v >>= 8; + } + } + + inline uint32_t get_selector(uint32_t x, uint32_t y, uint64_t selector_bits) const + { + assert((x < 4U) && (y < 4U)); + return (selector_bits >> (((y * 4) + x) * cBC4SelectorBits)) & (cMaxSelectorValues - 1); + } + + static inline uint32_t get_block_values6(uint8_t* pDst, uint32_t l, uint32_t h) + { + pDst[0] = static_cast(l); + pDst[1] = static_cast(h); + pDst[2] = static_cast((l * 4 + h) / 5); + pDst[3] = static_cast((l * 3 + h * 2) / 5); + pDst[4] = static_cast((l * 2 + h * 3) / 5); + pDst[5] = static_cast((l + h * 4) / 5); + pDst[6] = 0; + pDst[7] = 255; + return 6; + } + + static inline uint32_t get_block_values8(uint8_t* pDst, uint32_t l, uint32_t h) + { + pDst[0] = static_cast(l); + pDst[1] = static_cast(h); + pDst[2] = static_cast((l * 6 + h) / 7); + pDst[3] = static_cast((l * 5 + h * 2) / 7); + pDst[4] = static_cast((l * 4 + h * 3) / 7); + pDst[5] = static_cast((l * 3 + h * 4) / 7); + pDst[6] = static_cast((l * 2 + h * 5) / 7); + pDst[7] = static_cast((l + h * 6) / 7); + return 8; + } + + static inline uint32_t get_block_values(uint8_t* pDst, uint32_t l, uint32_t h) + { + if (l > h) + return get_block_values8(pDst, l, h); + else + return get_block_values6(pDst, l, h); + } + }; + +} +#endif // #ifndef RGBCX_INCLUDE_H + +#ifdef RGBCX_IMPLEMENTATION +#endif //#ifdef RGBCX_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright(c) 2020 Richard Geldreich, Jr. +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files(the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions : +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain(www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non - commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain.We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors.We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/ktx.imageio/bc7enc-rdo/rgbcx_table4_small.h b/src/ktx.imageio/bc7enc-rdo/rgbcx_table4_small.h new file mode 100644 index 0000000000..156d763e9f --- /dev/null +++ b/src/ktx.imageio/bc7enc-rdo/rgbcx_table4_small.h @@ -0,0 +1,972 @@ +// clang-format off +#pragma once + +{ 202, 120, 13, 318, 15, 23, 403, 450, 5, 51, 260, 128, 77, 21, 33, 494, 515, 523, 4, 141, 269, 1, 2, 700, 137, 49, 48, 102, 7, 64, 753, 82 }, +{ 13,141,23,217,115,51,77,2,64,21,0,4,5,317,137,269,202,33,318,7,291,352,9,10,3,180,32,6,365,102,341,349 }, +{ 29,58,262,1,52,74,6,171,5,287,151,334,27,500,75,26,331,223,53,635,220,19,50,45,46,17,14,396,163,409,324,70 }, +{ 40,51,33,453,14,23,62,56,12,196,730,475,153,99,403,775,117,130,585,34,4,17,162,11,139,57,102,38,108,47,123,440 }, +{ 33,23,51,13,102,64,202,128,12,40,15,196,153,10,1,2,77,99,141,0,515,5,117,3,120,403,700,165,22,14,269,453 }, +{ 13,23,51,4,77,141,202,33,115,64,32,128,0,11,177,40,15,102,2,217,7,137,269,21,90,59,515,1,180,403,22,6 }, +{ 26,235,19,47,648,624,78,145,27,112,122,64,444,6,630,453,25,42,65,130,711,85,390,113,416,108,665,29,730,138,644,95 }, +{ 64,141,352,751,217,247,237,437,177,269,86,954,947,875,32,318,95,77,304,92,597,180,232,291,128,864,349,588,372,202,312,1 }, +{ 642,898,180,638,901,341,82,197,10,951,15,515,165,762,700,253,811,753,752,365,143,479,244,569,8,110,351,873,55,31,499,116 }, +{ 221,23,51,125,438,254,13,21,39,49,308,656,0,115,530,159,158,401,30,166,912,386,165,688,518,9,105,627,424,22,421,33 }, +{ 143,31,1,44,197,8,180,125,116,55,13,498,23,341,638,242,93,15,2,141,0,901,752,115,36,206,165,479,338,365,515,762 }, +{ 12,23,51,13,14,15,37,99,515,38,700,117,2,196,134,153,753,64,54,33,128,120,21,0,328,5,139,82,453,719,457,1 }, +{ 13,15,23,515,961,700,457,753,51,115,4,165,197,2,38,569,1,474,0,37,99,719,5,12,629,14,11,3,33,77,64,10 }, +{ 15,515,700,753,1,0,2,4,3,23,134,12,961,5,10,197,11,33,82,120,457,51,165,7,6,341,217,21,77,9,40,180 }, +{ 13,51,23,457,719,961,730,401,165,453,0,117,386,15,134,1,758,153,12,54,515,99,11,2,700,5,753,4,308,33,6,899 }, +{ 134,898,82,117,13,33,77,102,23,260,341,351,120,901,197,153,961,111,196,110,180,457,854,10,450,8,165,40,4,115,0,365 }, +{ 60,18,126,167,35,16,191,71,24,92,121,271,68,107,212,146,118,150,199,7,21,1,9,575,727,5,566,48,0,132,108,273 }, +{ 62,136,129,123,128,41,162,17,249,211,214,789,618,710,38,678,248,507,57,64,152,269,119,3,177,183,597,106,4,179,216,90 }, +{ 403,523,51,475,494,453,817,899,202,23,450,13,421,120,102,730,33,128,4,1,805,5,7,153,757,260,318,196,77,457,326,65 }, +{ 4,59,3,62,12,33,56,193,27,21,102,17,40,77,76,84,32,0,6,123,119,177,128,11,18,611,605,25,13,51,73,210 }, +{ 43,20,319,422,414,945,0,7,819,61,5,376,325,173,804,904,470,693,97,707,14,49,22,104,147,107,95,32,426,1,330,577 }, +{ 13,23,51,2,0,115,4,141,217,33,10,77,1,15,64,180,3,515,7,6,22,102,11,5,40,9,165,700,202,197,317,341 }, +{ 28,49,0,105,1,24,65,159,35,55,95,239,16,2,109,7,9,14,170,320,347,168,424,158,10,301,124,5,67,21,64,36 }, +{ 15,515,700,753,0,1,13,2,117,4,12,10,5,165,457,3,9,134,11,7,6,51,77,64,961,82,33,197,14,341,120,141 }, +{ 7,71,14,149,97,18,60,16,150,92,398,189,140,124,24,273,35,2,69,302,154,68,0,336,517,43,66,28,118,251,230,1 }, +{ 4,102,33,77,40,59,11,624,210,12,128,342,5,503,91,139,64,32,25,494,202,678,416,0,403,275,21,450,196,318,523,177 }, +{ 25,19,42,6,122,813,256,235,85,26,436,53,297,573,680,390,445,63,27,416,80,233,65,73,389,283,45,605,194,17,250,343 }, +{ 402,102,202,128,33,300,403,23,12,77,40,21,342,117,483,99,25,494,6,4,63,32,84,569,139,757,475,318,19,26,196,134 }, +{ 158,9,0,109,39,49,65,22,35,168,55,24,68,124,159,16,185,344,333,154,254,272,175,289,1,577,95,28,105,810,30,169 }, +{ 197,180,115,237,498,165,2,5,287,546,400,3,61,34,509,13,297,80,341,52,45,186,58,881,23,873,468,176,64,17,311,250 }, +{ 120,968,373,260,704,110,450,202,137,318,77,95,269,326,217,717,661,652,851,349,93,1,518,98,827,291,21,177,82,33,848,719 }, +{ 44,116,144,268,434,489,367,384,98,127,918,93,948,31,206,940,855,0,203,137,9,22,617,141,332,105,393,492,959,282,299,131 }, +{ 13,77,23,33,51,0,64,141,102,4,2,115,1,6,202,15,10,128,269,7,177,180,3,40,22,11,515,217,117,318,700,137 }, +{ 15,515,700,753,4,11,141,40,165,23,64,180,13,202,32,3,51,125,5,197,21,128,0,93,77,1,120,82,269,117,110,59 }, +{ 176,231,585,62,34,14,412,161,56,236,527,57,17,3,51,202,4,23,369,283,128,13,472,440,84,361,136,457,381,130,719,53 }, +{ 9,0,180,217,237,101,141,352,88,100,230,64,175,317,115,498,68,39,30,1,702,83,213,36,365,208,752,13,252,321,952,546 }, +{ 28,9,22,1,49,0,109,39,83,95,86,30,13,105,128,55,141,168,158,67,31,159,208,12,96,5,185,2,160,64,137,23 }, +{ 72,4,38,12,51,89,477,11,57,76,401,308,23,474,99,148,413,179,59,13,431,152,54,569,17,3,205,629,197,421,405,15 }, +{ 457,13,23,961,15,51,515,700,165,12,753,629,11,1,719,117,0,3,2,37,569,197,40,328,33,5,153,134,99,64,38,196 }, +{ 254,100,310,9,30,1,39,625,166,265,190,0,272,557,131,731,31,98,578,688,404,93,101,88,49,21,127,264,44,36,252,478 }, +{ 51,23,12,13,15,128,99,120,10,202,515,153,64,82,700,33,165,2,5,117,403,1,141,0,3,196,37,453,753,197,260,93 }, +{ 38,99,542,139,453,117,196,23,457,13,328,111,37,134,961,11,12,51,40,775,587,401,474,54,153,477,41,629,33,475,14,277 }, +{ 6,85,25,233,343,91,26,63,138,29,19,65,283,4,81,235,42,122,605,64,648,256,174,370,74,389,718,59,45,194,445,416 }, +{ 49,5,97,20,197,21,18,193,0,64,408,729,173,350,43,422,165,7,14,104,61,32,509,713,523,102,120,95,125,397,35,232 }, +{ 144,116,268,434,384,489,367,206,93,855,940,44,98,332,617,127,959,911,137,282,203,31,22,219,141,9,131,276,417,0,1,120 }, +{ 17,106,64,62,32,255,136,292,476,162,129,241,123,141,41,237,720,214,209,352,519,211,186,148,752,247,507,90,21,77,197,119 }, +{ 2,29,52,50,5,58,14,6,27,1,366,357,45,53,17,19,171,151,26,181,133,38,218,764,287,583,61,113,3,487,600,281 }, +{ 130,59,196,412,381,730,711,236,77,210,202,402,453,99,401,108,361,803,291,283,153,4,57,51,128,183,14,719,503,117,23,11 }, +{ 13,23,51,141,77,4,33,64,115,0,217,10,180,202,2,102,11,9,15,165,40,21,128,352,22,7,197,3,317,515,269,1 }, +{ 23,13,202,51,120,15,21,5,141,1,128,269,137,515,64,102,125,48,98,33,260,523,318,93,700,165,450,77,2,12,403,82 }, +{ 1,2,14,46,29,67,38,52,5,171,58,24,103,69,96,70,83,181,54,75,163,223,16,45,112,309,155,0,186,35,18,108 }, +{ 15,515,700,753,13,0,1,2,153,5,23,10,117,3,9,7,134,165,12,6,341,33,4,14,77,457,115,21,719,180,217,82 }, +{ 197,165,509,13,391,180,308,115,23,546,5,498,2,29,3,401,901,61,34,80,14,457,250,569,237,873,38,297,45,15,468,386 }, +{ 19,73,27,250,200,714,444,472,26,53,34,17,813,322,283,390,128,297,78,123,432,14,436,136,106,690,57,122,389,80,503,3 }, +{ 3,17,21,45,62,32,38,12,155,14,2,328,5,99,401,536,828,13,227,488,106,51,719,119,540,76,165,221,115,629,209,41 }, +{ 115,341,873,197,365,13,901,180,569,752,317,1,10,498,143,634,261,0,509,15,943,237,44,31,116,601,165,127,282,23,141,64 }, +{ 453,51,23,403,33,421,475,102,15,153,196,515,13,700,117,523,12,40,753,21,4,134,0,494,670,899,22,801,730,10,11,401 }, +{ 23,13,51,33,12,117,153,134,453,196,15,99,515,40,14,700,128,102,11,753,77,64,403,202,0,401,475,37,65,2,3,38 }, +{ 2,7,5,14,70,1,29,61,52,45,6,112,66,16,21,32,592,46,38,135,87,58,186,315,290,128,113,0,64,48,227,23 }, +{ 33,23,102,51,128,13,64,202,141,1,77,10,153,40,196,117,2,3,0,5,15,269,403,12,137,134,318,165,120,6,453,99 }, +{ 16,92,7,20,43,35,126,71,60,14,107,18,68,97,0,121,279,149,24,246,191,48,118,575,55,140,362,783,230,150,375,566 }, +{ 13,23,4,33,77,64,51,102,141,128,32,10,0,202,40,115,59,22,90,11,177,21,291,6,7,318,180,117,137,2,95,165 }, +{ 507,162,129,41,4,211,62,38,123,59,57,248,183,130,99,11,3,361,202,17,402,556,266,305,803,210,128,184,152,136,313,117 }, +{ 643,123,193,650,802,18,25,389,718,256,65,289,84,91,619,511,415,90,235,63,57,510,324,216,862,102,6,183,108,397,217,736 }, +{ 13,23,15,1,515,51,0,2,700,5,753,165,141,115,12,3,4,180,21,197,457,7,6,10,120,9,33,202,77,32,8,11 }, +{ 23,51,13,453,64,403,12,21,5,202,128,475,165,141,523,95,125,115,3,1,4,730,120,32,2,494,180,719,457,197,450,401 }, +{ 204,74,135,66,6,174,192,7,138,172,85,353,348,580,280,97,95,500,29,64,426,32,87,889,65,81,25,2,52,43,568,673 }, +{ 35,0,68,69,24,9,1,16,65,103,149,133,18,114,28,50,83,2,189,7,46,14,101,336,175,124,251,55,71,218,38,238 }, +{ 16,101,0,118,9,18,24,68,35,154,71,124,60,212,191,520,55,806,694,167,28,39,364,375,1,346,252,65,604,302,22,21 }, +{ 0,9,16,35,1,24,68,18,65,21,103,67,13,149,28,189,71,23,101,238,114,7,335,133,486,141,22,212,48,50,30,118 }, +{ 13,202,23,77,33,51,128,5,21,141,115,32,102,64,4,0,318,269,10,15,291,2,494,177,11,217,3,515,22,137,6,700 }, +{ 16,92,60,35,7,18,24,68,150,149,14,71,0,375,97,126,118,107,230,191,246,273,140,55,175,653,9,575,2,28,566,517 }, +{ 76,90,21,179,316,148,205,32,464,288,184,257,245,1,89,2,460,57,152,45,38,358,645,5,12,449,350,48,37,17,4,14 }, +{ 19,27,26,813,80,297,17,495,436,53,73,200,4,378,250,59,106,25,45,128,361,42,113,469,122,390,77,40,736,6,11,136 }, +{ 6,26,235,138,19,145,112,70,331,262,25,42,52,624,27,453,122,47,500,78,648,85,29,2,630,632,409,113,50,226,108,75 }, +{ 7,16,14,24,92,35,18,2,46,9,60,140,0,87,50,5,54,13,12,38,171,23,126,21,58,64,1,70,128,71,220,163 }, +{ 90,205,257,184,32,179,460,5,245,45,2,288,769,524,57,21,152,229,17,1,497,4,292,59,619,452,432,76,476,11,266,14 }, +{ 15,515,700,753,4,5,11,141,13,1,33,3,0,128,202,23,180,21,2,64,269,32,117,134,120,40,102,318,153,17,137,352 }, +{ 47,130,711,108,453,412,730,196,390,283,78,27,51,183,381,236,128,200,719,14,153,472,503,34,59,250,3,4,57,803,123,432 }, +{ 12,277,51,474,111,153,23,99,13,37,961,94,629,542,569,431,79,139,38,134,117,453,33,188,196,40,115,15,11,157,401,515 }, +{ 17,495,469,106,26,378,80,27,161,483,19,742,527,436,383,862,73,136,53,814,297,6,119,84,62,56,25,3,209,611,4,128 }, +{ 81,681,636,91,0,750,370,104,718,138,18,693,173,784,29,397,348,74,192,673,174,65,6,207,64,280,306,52,671,32,355,319 }, +{ 15,515,700,753,33,77,4,102,115,117,40,13,1,153,134,11,5,217,23,196,2,21,3,317,32,365,0,341,291,59,12,51 }, +{ 0,9,28,35,68,1,65,67,101,39,69,175,16,238,13,22,96,124,18,24,251,30,55,12,23,2,50,141,114,5,154,103 }, +{ 23,33,77,13,117,40,11,102,64,4,51,403,153,453,10,0,196,134,128,65,12,291,86,99,95,59,15,141,202,180,137,719 }, +{ 214,90,289,6,874,64,25,65,235,42,751,249,256,312,194,85,746,875,174,32,525,288,519,835,247,348,233,544,217,524,437,352 }, +{ 1,22,2,0,36,67,28,5,49,95,12,50,168,83,105,55,7,9,14,194,103,23,114,21,584,46,10,13,38,69,208,159 }, +{ 269,141,13,202,33,180,318,77,291,137,102,352,128,23,349,51,31,217,372,317,125,197,44,21,11,5,901,1,18,0,4,494 }, +{ 435,144,274,88,203,418,30,1,190,410,96,778,100,530,521,326,466,795,686,166,960,321,382,264,367,822,131,31,692,9,213,93 }, +{ 76,72,90,21,37,179,12,205,32,428,148,38,308,405,4,413,57,184,749,245,316,221,54,645,288,1,152,155,464,257,2,14 }, +{ 77,33,64,102,13,141,23,2,40,1,51,10,0,115,6,180,202,128,4,3,177,269,15,7,22,165,291,14,217,318,137,11 }, +{ 397,81,4,32,65,788,693,804,681,11,249,21,91,64,690,494,3,0,422,56,348,725,194,123,23,59,523,319,61,510,95,90 }, +{ 60,126,16,7,92,121,314,246,35,107,150,132,14,146,24,18,199,298,232,71,359,140,672,97,392,649,5,423,95,21,22,388 }, +{ 15,515,141,217,115,700,13,23,120,317,753,180,33,260,110,137,341,51,1,365,4,77,64,202,0,40,36,352,197,269,10,21 }, +{ 111,134,117,474,23,13,961,12,569,431,37,15,51,115,515,700,277,99,753,38,197,405,457,4,72,94,629,45,11,89,54,148 }, +{ 23,13,51,5,1,15,2,21,12,202,141,0,515,165,120,32,4,64,700,3,115,197,269,125,753,7,9,128,6,180,453,403 }, +{ 13,141,4,23,5,2,115,217,202,51,180,137,269,352,77,1,317,3,21,318,0,15,9,64,10,197,11,341,33,515,752,7 }, +{ 165,125,197,13,391,21,23,558,48,380,97,120,298,33,14,426,66,115,32,386,900,180,6,98,357,237,326,509,51,278,221,457 }, +{ 120,82,15,260,515,1,351,77,450,700,13,21,141,23,753,202,217,93,110,33,51,854,5,128,326,102,137,180,817,48,269,352 }, +{ 23,13,15,51,515,700,961,753,0,457,1,2,4,115,10,453,569,5,33,165,11,719,14,40,64,197,3,21,474,629,38,401 }, +{ 264,166,39,30,9,100,435,254,93,921,190,363,1,625,411,382,897,656,203,478,404,812,438,110,473,88,18,691,156,141,274,272 }, +{ 9,0,252,100,166,39,101,265,364,68,88,329,520,18,419,676,118,167,404,604,16,1,21,30,212,158,553,49,382,274,48,13 }, +{ 15,515,700,753,4,11,141,5,3,13,202,1,180,21,2,165,269,23,40,64,0,318,12,32,128,51,77,117,523,197,120,457 }, +{ 24,1,2,69,35,16,67,18,14,50,0,46,68,9,38,7,133,71,83,149,28,108,189,218,65,114,238,29,75,54,5,96 }, +{ 90,289,214,64,874,13,77,712,66,751,4,23,51,192,32,0,202,194,312,177,33,65,234,104,875,288,59,5,835,416,102,95 }, +{ 0,9,49,127,98,31,301,28,371,159,1,395,512,737,158,761,916,623,16,44,242,39,170,18,293,105,24,272,101,22,23,385 }, +{ 17,62,136,214,123,129,32,292,119,209,710,106,141,162,128,64,45,4,77,249,11,618,211,3,207,130,519,183,38,177,21,269 }, +{ 5,107,581,356,279,32,441,362,493,660,13,298,0,534,49,147,21,22,132,121,97,423,7,590,259,683,14,786,126,508,60,246 }, +{ 51,13,15,730,453,23,515,719,386,457,12,700,403,475,899,1,6,523,753,421,99,401,165,33,2,19,361,5,0,670,120,27 }, +{ 49,28,9,159,272,22,254,131,158,327,95,105,0,39,35,168,347,286,374,55,65,627,424,912,68,578,1,24,239,175,688,169 }, +{ 15,515,700,33,753,4,77,141,341,317,1,10,13,180,102,22,40,117,115,365,5,901,23,197,134,11,217,351,64,82,21,137 }, +{ 134,15,13,515,23,700,12,753,51,474,37,961,197,10,457,569,4,0,99,2,115,38,165,153,94,3,139,11,1,82,33,5 }, +{ 7,2,20,58,5,14,128,66,6,29,32,43,21,52,16,38,631,61,74,97,46,135,113,25,202,192,13,0,884,45,112,87 }, +{ 77,13,33,202,23,128,102,4,141,342,117,0,269,318,134,22,11,21,32,153,403,291,49,64,137,51,40,15,494,5,196,98 }, +{ 2,1,14,6,46,38,29,65,5,36,67,0,103,7,22,86,133,50,108,208,52,83,24,323,283,69,28,18,10,25,23,75 }, +{ 15,515,700,753,1,5,4,2,3,13,0,11,180,341,12,33,10,197,134,365,77,23,21,901,6,117,165,7,37,32,17,102 }, +{ 203,268,206,93,417,940,31,8,120,137,44,499,959,473,202,692,728,559,0,260,10,326,141,564,817,127,341,1,450,22,110,23 }, +{ 15,82,515,120,700,0,10,753,33,8,64,165,110,31,260,93,13,197,23,22,40,4,351,44,77,9,11,153,102,51,1,196 }, +{ 60,0,16,7,14,43,20,71,28,10,2,22,154,18,13,24,92,1,51,576,35,615,805,925,68,126,124,149,97,64,23,55 }, +{ 19,6,26,80,5,84,27,17,25,2,504,129,45,240,56,123,4,119,618,1,76,106,64,51,14,3,128,65,32,710,0,42 }, +{ 15,515,700,753,13,4,77,23,33,51,0,5,8,10,11,31,44,1,82,22,202,64,110,102,93,21,291,40,141,180,9,49 }, +{ 195,98,271,223,132,167,146,407,1,360,121,834,393,591,212,199,293,259,522,107,354,147,156,191,807,590,48,18,125,16,765,541 }, +{ 128,202,77,210,402,318,33,102,6,40,403,29,342,269,196,757,99,139,2,111,42,4,494,117,275,300,13,12,678,0,177,122 }, +{ 13,33,23,40,51,102,4,117,77,64,134,0,128,153,202,196,453,11,15,12,1,22,403,141,59,14,10,475,515,65,700,95 }, +{ 7,16,14,24,18,2,28,0,92,71,1,22,6,35,60,20,168,10,154,118,5,302,124,69,97,109,703,158,420,12,149,66 }, +{ 15,1,515,23,0,13,700,2,51,753,180,5,120,165,197,21,115,4,33,9,141,7,12,6,3,457,386,202,260,523,8,31 }, +{ 60,107,121,132,146,126,199,279,150,92,16,649,441,35,955,7,21,0,423,5,18,195,598,298,493,356,32,653,22,362,953,10 }, +{ 31,44,98,276,284,299,116,935,9,201,0,131,39,127,144,662,1,137,371,492,567,489,93,254,49,268,22,28,30,293,434,737 }, +{ 13,15,23,515,700,0,1,51,753,4,2,10,77,202,5,115,3,165,197,457,9,12,11,961,33,120,22,141,180,7,6,40 }, +{ 123,162,184,257,17,183,229,130,129,3,84,136,99,152,556,383,57,497,12,205,4,62,56,452,80,266,128,14,40,119,27,106 }, +{ 196,33,117,40,153,23,134,13,51,102,453,0,15,475,12,14,515,2,22,700,4,21,753,64,401,670,730,1,9,11,10,99 }, +{ 224,219,187,131,258,385,442,871,836,31,98,908,44,574,127,944,137,839,116,36,613,1,254,39,926,160,829,96,93,371,860,827 }, +{ 121,195,156,132,146,360,590,407,786,522,883,591,259,929,626,941,150,687,5,55,296,379,467,178,586,465,279,21,1,13,60,354 }, +{ 2,1,14,29,6,5,46,52,38,19,114,75,26,65,108,96,25,50,36,70,103,309,17,236,218,74,12,86,0,3,10,112 }, +{ 15,515,82,700,120,753,10,0,8,197,260,165,351,64,13,110,117,93,31,1,9,33,22,23,457,44,450,77,102,898,40,49 }, +{ 7,66,97,2,172,74,226,52,29,135,192,232,43,324,92,5,38,20,222,14,6,568,87,107,353,620,580,16,138,174,448,32 }, +{ 62,129,123,162,136,249,618,183,507,57,4,152,17,59,11,184,117,77,3,128,211,41,130,205,12,40,33,106,64,229,38,313 }, +{ 1,13,15,2,4,515,23,0,3,115,700,5,51,77,341,141,753,180,33,217,197,202,901,6,21,165,11,365,318,317,10,102 }, +{ 6,26,235,19,145,47,112,78,64,27,453,95,29,444,25,624,85,108,648,70,32,130,74,42,711,630,632,138,65,122,113,730 }, +{ 23,51,12,15,13,99,515,153,117,10,700,37,120,82,165,2,753,64,128,0,403,3,5,1,134,197,453,31,202,457,110,21 }, +{ 16,24,18,71,64,35,92,7,246,146,9,108,60,118,199,5,140,2,267,0,230,830,32,133,1,68,50,330,247,563,36,12 }, +{ 15,515,700,753,0,1,13,2,23,3,4,217,51,5,115,8,9,180,341,10,7,6,317,77,33,372,901,197,365,11,120,165 }, +{ 234,639,178,202,77,142,5,455,450,49,416,0,147,427,198,21,315,329,13,318,325,557,120,344,113,259,22,128,61,105,23,494 }, +{ 1,31,36,44,141,180,55,2,64,22,98,116,13,352,0,115,10,127,5,164,253,498,237,165,341,197,4,86,15,170,125,23 }, +{ 15,120,13,141,23,260,217,515,1,77,51,110,180,700,317,82,269,137,115,202,21,753,64,5,351,291,0,450,352,93,36,326 }, +{ 26,6,112,396,19,145,25,122,648,287,42,74,624,222,416,45,138,66,644,151,113,651,29,573,64,280,445,27,525,85,70,58 }, +{ 156,360,5,146,121,21,271,522,354,132,49,13,18,195,16,340,60,591,446,586,727,0,107,407,167,48,1,463,199,566,32,23 }, +{ 5,61,49,147,178,612,660,120,21,182,23,427,259,683,33,4,77,70,13,3,376,98,64,0,481,344,48,595,291,263,141,51 }, +{ 89,79,468,179,358,205,94,405,115,498,72,180,365,431,37,111,341,734,188,317,482,217,11,4,245,152,413,216,12,474,490,752 }, +{ 24,16,35,68,18,71,7,92,0,108,9,14,118,101,336,175,375,302,28,124,154,55,149,60,398,1,65,2,140,273,345,230 }, +{ 51,730,421,801,453,386,23,523,13,475,719,401,670,365,899,403,115,457,758,165,33,494,450,6,423,805,629,56,569,514,958,388 }, +{ 113,45,6,311,29,2,151,614,145,491,112,80,5,27,61,74,315,66,209,631,19,25,58,17,73,26,1,243,70,64,611,287 }, +{ 4,339,188,471,11,59,79,12,377,94,99,33,77,102,51,111,37,152,13,961,474,542,40,342,3,23,128,403,202,177,184,57 }, +{ 15,4,515,11,700,33,82,40,0,120,753,10,8,110,13,93,23,165,77,260,64,31,22,51,44,102,351,1,125,9,197,21 }, +{ 16,24,18,0,35,68,28,71,124,118,60,7,9,55,14,92,109,101,419,175,22,252,154,375,149,302,158,346,2,49,1,126 }, +{ 17,45,227,21,106,3,2,243,209,5,48,32,221,62,207,50,29,186,290,270,263,52,14,496,400,119,46,255,54,430,38,721 }, +{ 340,354,586,658,156,195,698,668,1,296,9,18,883,363,447,379,303,98,411,13,31,163,51,5,371,48,919,846,121,21,360,70 }, +{ 277,153,111,12,23,51,474,99,38,37,139,117,41,457,79,453,542,13,11,33,134,157,629,188,961,14,196,401,102,569,15,94 }, +{ 0,18,16,159,49,24,9,105,35,68,7,28,22,1,60,344,55,101,109,2,14,158,13,23,71,118,455,286,272,424,5,327 }, +{ 0,105,9,49,16,18,158,28,518,24,101,320,1,68,170,301,272,127,7,286,35,890,109,39,159,98,21,344,31,55,371,23 }, +{ 141,1,180,15,13,2,365,217,515,352,317,115,341,0,4,5,269,700,23,21,3,752,197,77,753,51,31,901,10,202,8,64 }, +{ 4,23,51,33,19,17,102,153,485,880,40,403,196,26,300,453,27,117,78,0,12,200,47,5,11,14,342,99,53,77,475,2 }, +{ 62,184,56,440,130,229,183,3,556,152,99,162,12,266,17,548,136,57,305,161,123,14,452,4,383,403,257,34,40,84,33,139 }, +{ 13,23,77,141,64,202,33,51,269,115,0,102,21,4,217,128,5,32,318,137,291,9,15,2,180,10,3,317,177,515,7,6 }, +{ 1,22,36,105,170,0,86,2,31,28,239,64,55,5,10,98,9,44,127,95,654,67,301,143,13,12,49,23,320,141,83,21 }, +{ 15,515,700,753,0,1,13,2,23,901,5,8,51,82,9,180,457,4,7,12,3,6,10,120,341,141,22,898,197,351,115,260 }, +{ 1,39,274,98,100,265,190,30,438,310,166,223,88,96,909,31,264,625,530,9,382,812,21,252,593,0,254,539,44,131,23,778 }, +{ 18,212,167,118,363,1,447,411,146,60,271,16,781,121,647,9,621,562,21,478,664,68,815,5,354,98,48,101,24,446,777,463 }, +{ 24,28,22,0,7,1,2,16,14,65,35,49,158,95,109,159,55,105,10,18,124,9,67,5,239,149,12,289,108,68,21,424 }, +{ 105,22,131,272,286,98,55,239,1,31,320,9,127,327,36,185,28,374,86,219,0,64,187,44,578,164,224,913,535,115,601,13 }, +{ 22,31,28,301,127,98,44,0,105,1,512,395,9,293,109,299,95,338,239,125,242,116,36,320,55,841,900,685,599,23,13,763 }, +{ 2,1,58,29,5,14,52,46,186,334,45,155,151,50,400,75,38,69,502,61,48,227,223,7,163,17,262,67,549,21,70,113 }, +{ 7,107,135,232,97,14,2,92,66,16,172,192,278,387,298,356,38,35,448,52,46,43,60,29,20,126,324,526,357,359,64,5 }, +{ 20,43,104,426,173,7,560,414,707,784,319,81,0,861,422,819,38,74,715,52,376,97,879,32,330,22,49,64,66,95,192,526 }, +{ 104,74,636,66,204,0,355,81,222,25,29,319,145,784,20,65,90,4,174,194,7,64,6,746,138,173,750,715,91,43,192,32 }, +{ 0,9,101,35,68,39,65,28,252,124,67,154,364,336,100,166,30,1,289,55,149,346,16,114,158,88,439,24,429,22,570,194 }, +{ 57,14,4,231,236,585,176,59,369,23,361,13,719,51,300,342,12,457,56,3,62,38,202,401,34,46,2,322,11,215,210,507 }, +{ 1,2,15,3,141,0,515,5,33,700,13,64,77,180,6,128,753,10,4,269,102,202,11,7,134,197,352,120,117,318,12,291 }, +{ 5,1,21,202,13,32,48,23,0,61,259,22,494,120,70,49,51,18,137,128,465,12,178,115,2,453,403,141,58,3,90,450 }, +{ 141,205,4,72,59,79,245,11,352,94,152,76,247,216,21,188,452,217,497,12,89,37,111,339,588,77,64,875,864,115,358,464 }, +{ 15,515,700,753,0,1,2,13,5,4,23,3,8,341,365,51,115,10,120,457,6,141,77,197,31,7,165,9,202,450,961,260 }, +{ 5,2,50,14,58,38,171,46,29,1,45,186,17,52,155,218,48,281,61,487,54,36,67,21,328,334,151,227,760,114,400,133 }, +{ 457,120,70,125,318,64,23,48,795,291,202,761,751,415,77,846,269,758,21,237,96,260,391,165,87,1,128,5,221,13,137,763 }, +{ 13,23,51,33,4,40,117,102,453,64,153,196,0,77,15,11,12,475,1,65,134,10,515,22,21,14,700,59,403,141,2,753 }, +{ 229,152,57,266,452,381,432,12,313,184,99,471,17,4,62,339,157,3,129,59,128,11,369,37,77,38,40,123,5,497,188,257 }, +{ 49,28,109,22,159,9,272,95,105,131,55,35,254,168,39,327,169,0,1,286,175,374,347,158,420,67,36,194,312,424,627,346 }, +{ 5,2,61,29,45,58,80,311,1,17,209,227,52,243,106,869,454,151,592,496,48,334,14,155,6,186,46,171,75,21,255,667 }, +{ 244,44,110,141,260,30,269,352,839,131,574,228,373,276,1,406,219,717,217,137,253,224,120,93,36,31,567,116,661,187,341,88 }, +{ 12,99,79,139,11,453,196,51,277,474,111,23,542,37,94,188,33,13,401,775,40,961,313,102,4,339,153,485,629,134,300,431 }, +{ 16,35,9,0,68,24,149,69,67,18,1,114,65,230,71,7,103,133,50,167,212,118,101,191,140,64,399,28,124,283,55,565 }, +{ 88,30,274,435,131,613,190,100,93,829,166,1,187,795,530,127,382,957,960,160,31,137,466,264,39,800,406,254,28,473,521,219 }, +{ 167,16,18,118,212,24,60,71,101,68,191,9,375,411,363,35,0,1,589,199,302,21,447,55,146,126,92,271,647,121,562,48 }, +{ 64,141,86,177,77,128,147,597,304,95,269,102,275,4,352,49,120,5,372,194,465,13,588,237,947,216,202,180,612,751,107,534 }, +{ 18,65,90,403,523,289,240,214,194,102,701,475,202,217,283,862,389,51,33,0,494,421,453,817,84,64,847,899,352,13,23,437 }, +{ 13,51,23,202,5,12,21,128,15,115,0,1,141,120,64,32,4,2,515,403,165,457,3,10,700,99,453,318,719,450,308,401 }, +{ 98,223,393,31,1,271,834,791,167,44,202,64,93,697,5,116,77,125,450,446,212,18,541,293,51,120,195,132,284,13,807,765 }, +{ 15,515,700,753,4,11,23,13,40,51,82,165,0,110,93,33,141,64,120,5,10,77,3,102,180,32,202,125,8,197,31,21 }, +{ 15,515,700,753,0,1,13,2,901,23,5,341,3,51,82,8,4,180,961,9,115,10,12,6,898,7,351,141,134,22,31,120 }, +{ 234,416,77,5,315,639,325,202,147,198,113,49,450,61,455,142,0,21,22,342,329,494,178,58,102,427,318,230,13,120,43,470 }, +{ 60,146,16,18,156,126,121,271,199,360,132,24,167,0,640,10,71,522,21,92,5,340,107,354,118,150,22,195,446,35,28,212 }, +{ 4,361,11,14,56,368,377,161,27,12,300,77,59,200,17,554,202,33,40,494,495,21,210,80,757,25,128,23,19,38,444,53 }, +{ 141,82,217,351,15,352,120,1,180,260,515,64,854,36,700,317,752,372,13,269,77,753,922,21,349,23,202,110,93,137,51,373 }, +{ 15,515,700,753,77,13,0,1,23,33,102,2,51,4,3,5,291,217,10,9,450,120,341,7,317,6,11,117,115,8,260,180 }, +{ 15,515,120,13,700,23,77,141,1,260,0,753,180,51,137,202,115,365,110,291,217,5,128,9,21,341,197,269,2,450,317,165 }, +{ 174,6,348,85,138,74,280,204,66,233,192,355,289,65,81,580,636,353,25,91,104,343,673,214,64,95,42,712,792,32,194,90 }, +{ 152,497,452,59,4,216,11,79,94,77,128,188,269,339,588,33,76,529,318,32,141,471,12,202,111,21,5,51,37,90,72,177 }, +{ 417,499,10,141,253,244,110,559,8,564,180,260,728,120,352,638,642,341,951,206,143,752,901,93,137,661,922,373,44,31,811,197 }, +{ 13,77,23,33,4,51,0,102,128,59,141,40,64,115,177,10,137,22,202,2,7,11,90,1,117,180,269,14,49,6,134,3 }, +{ 1,2,22,0,36,5,67,50,14,28,12,86,38,46,83,168,194,65,103,114,49,7,10,95,21,69,23,24,128,51,55,13 }, +{ 17,106,119,207,255,306,742,378,84,62,136,45,3,5,240,80,61,56,209,383,311,790,655,32,2,440,76,151,58,29,179,263 }, +{ 3,128,1,141,2,202,33,5,64,15,0,515,102,13,269,10,700,180,134,51,120,6,77,318,23,137,17,117,753,197,82,153 }, +{ 514,38,377,328,11,57,41,248,880,266,556,4,152,361,471,757,485,403,305,102,3,211,313,99,457,130,12,14,157,40,23,54 }, +{ 68,0,167,101,9,118,264,520,16,18,21,478,562,1,124,212,100,936,664,777,191,88,806,154,48,24,759,604,35,252,265,65 }, +{ 230,689,699,213,466,352,217,831,30,443,418,144,854,201,840,855,1,251,203,317,530,957,96,93,822,539,36,752,351,137,83,800 }, +{ 33,77,102,117,15,82,13,134,23,64,0,515,120,153,51,4,40,128,700,260,202,141,196,22,753,11,351,10,1,326,95,269 }, +{ 11,40,33,51,117,13,542,328,14,134,38,153,23,12,485,231,102,54,775,37,3,377,111,139,211,4,457,403,369,475,99,719 }, +{ 33,64,77,128,141,2,1,202,102,13,23,117,0,15,3,153,51,134,10,40,6,5,515,269,137,180,318,165,700,7,196,753 }, +{ 15,515,700,753,4,1,5,11,13,21,33,180,93,141,64,2,23,77,82,3,0,102,32,40,352,341,10,197,98,110,117,901 }, +{ 1,2,14,67,50,46,38,24,103,83,0,5,36,28,29,133,114,96,65,52,18,75,54,108,22,7,238,58,160,9,361,69 }, +{ 258,201,276,137,160,860,116,261,295,843,567,144,131,44,187,268,943,219,284,31,202,935,141,98,662,203,127,96,36,93,224,1 }, +{ 7,2,14,16,46,87,75,52,92,278,29,38,140,70,1,5,35,294,24,262,135,69,171,172,58,409,112,60,50,66,97,12 }, +{ 13,23,0,2,51,1,33,4,115,10,15,141,77,3,5,180,217,515,9,7,64,11,700,6,102,40,197,22,317,753,165,202 }, +{ 74,145,6,66,25,204,42,29,222,337,138,26,7,525,192,174,746,287,544,135,415,2,609,632,112,64,87,0,85,45,712,396 }, +{ 77,33,102,15,217,13,23,141,202,515,51,700,291,4,269,753,317,180,21,64,318,115,128,0,275,2,352,196,3,5,137,11 }, +{ 187,219,258,871,44,442,160,574,137,224,908,116,839,131,36,926,276,201,93,228,202,860,31,613,144,531,406,1,902,30,190,318 }, +{ 1,372,141,5,21,77,225,744,96,30,23,349,13,291,269,284,69,442,459,144,303,839,217,622,160,330,260,48,120,410,189,352 }, +{ 66,222,2,74,29,87,135,6,7,145,52,25,294,337,226,172,138,331,42,70,97,112,26,1,632,192,43,5,415,609,461,353 }, +{ 45,17,106,209,5,2,21,29,48,207,3,186,243,155,255,263,454,119,400,496,270,14,290,62,425,1,171,32,659,52,38,56 }, +{ 93,88,141,120,30,213,260,373,100,717,459,82,110,1,166,450,180,321,217,372,36,269,131,225,22,352,326,466,473,187,244,410 }, +{ 266,57,152,381,313,471,12,229,99,369,339,62,157,3,4,37,77,38,188,17,11,162,40,184,129,59,475,775,128,452,403,453 }, +{ 217,352,317,141,752,15,180,515,372,365,700,341,753,349,77,21,291,1,115,244,64,120,13,98,269,82,5,498,864,351,23,144 }, +{ 14,514,369,102,403,377,51,719,880,153,23,13,457,11,485,4,401,12,328,453,33,40,117,57,629,38,730,236,134,670,361,961 }, +{ 107,7,172,14,92,135,2,359,60,314,46,16,126,278,232,150,279,32,38,392,298,5,35,97,24,192,259,288,330,52,356,312 }, +{ 0,4,25,13,59,90,65,23,26,19,18,12,5,216,91,51,389,33,77,11,22,85,27,81,21,177,746,45,42,194,37,123 }, +{ 5,49,315,202,416,77,455,639,450,21,197,137,350,13,408,0,329,318,494,344,61,402,64,509,347,120,113,48,95,713,308,401 }, +{ 130,47,381,390,59,90,200,214,289,6,65,472,29,64,874,648,50,751,624,26,52,32,4,194,875,714,85,249,247,33,881,19 }, +{ 51,23,453,13,719,12,457,165,37,730,99,4,386,197,401,17,11,2,3,15,5,961,475,6,515,64,54,700,32,115,0,403 }, +{ 15,515,1,13,700,2,23,0,753,5,3,180,51,4,165,12,141,21,197,457,7,115,6,9,352,10,120,202,8,341,11,77 }, +{ 0,9,1,67,35,28,68,16,24,65,18,69,50,114,103,12,22,13,5,101,2,96,23,83,149,21,39,55,7,175,433,124 }, +{ 28,105,22,0,1,320,170,9,49,301,109,95,127,31,98,55,65,35,2,24,168,159,36,713,16,740,13,338,21,44,512,23 }, +{ 13,77,4,51,23,33,102,202,128,59,40,0,64,141,117,403,115,11,15,318,153,269,22,515,475,134,10,494,177,1,90,210 }, +{ 13,23,0,51,77,33,2,141,4,10,1,64,115,102,3,6,22,15,217,11,180,7,40,515,165,202,177,9,269,128,700,5 }, +{ 456,116,492,8,949,268,867,391,203,51,499,13,719,386,31,791,457,918,125,10,23,93,479,685,417,0,22,338,506,551,870,730 }, +{ 17,237,45,180,106,62,32,64,115,41,136,498,255,21,197,129,241,13,3,227,23,352,165,752,350,365,449,155,4,546,476,38 }, +{ 1,15,180,515,0,2,341,700,901,352,4,141,13,3,752,5,753,217,317,115,365,23,197,21,51,165,31,6,269,202,77,7 }, +{ 205,141,216,269,497,4,588,76,59,152,128,452,79,77,875,11,72,94,188,217,352,12,247,37,90,64,32,1,474,23,947,372 }, +{ 64,247,217,237,317,180,752,115,349,141,498,13,437,304,23,372,352,164,579,291,33,864,177,197,0,490,72,10,482,77,269,51 }, +{ 2,1,0,13,15,141,3,77,5,515,64,33,23,180,6,700,4,117,217,7,10,11,102,165,753,197,115,134,40,352,12,269 }, +{ 11,40,38,328,33,542,12,313,41,339,23,157,377,117,369,51,471,99,775,485,13,305,457,57,14,475,37,248,4,54,188,719 }, +{ 33,77,102,40,13,23,0,51,4,128,64,202,117,141,22,196,153,10,134,15,59,269,1,137,65,11,403,318,453,86,515,177 }, +{ 472,80,34,250,495,161,17,14,469,176,128,4,389,106,283,436,216,527,3,297,483,177,53,56,231,194,119,84,719,57,255,59 }, +{ 317,352,180,141,217,752,115,341,365,244,1,269,202,901,253,15,21,498,372,4,137,515,13,2,700,318,5,197,23,143,753,349 }, +{ 9,39,101,18,265,100,333,520,252,16,0,329,593,1,553,364,68,167,310,30,121,254,118,158,363,166,60,604,272,24,286,404 }, +{ 15,515,1,180,700,901,0,2,753,341,752,4,3,13,115,365,317,5,23,197,141,217,165,352,6,22,36,9,137,51,7,10 }, +{ 131,39,9,829,166,613,578,827,1,30,716,254,100,98,31,224,0,406,228,310,616,219,44,846,127,190,938,96,265,371,856,438 }, +{ 17,64,62,106,141,751,136,292,32,129,352,41,38,476,86,128,214,237,5,177,123,209,217,45,269,954,162,710,180,3,90,4 }, +{ 25,42,235,65,650,736,605,6,630,85,123,343,233,256,26,122,63,389,141,249,416,444,368,194,19,108,138,174,90,0,544,511 }, +{ 184,229,152,57,266,432,497,452,17,381,619,257,313,12,4,205,59,3,99,471,157,128,5,129,339,369,77,11,32,45,202,2 }, +{ 137,202,160,860,141,30,93,567,36,276,295,261,131,39,9,964,201,843,1,98,800,318,116,22,943,187,10,219,206,44,269,535 }, +{ 0,493,125,64,49,9,279,10,35,18,93,55,293,31,14,13,194,165,325,48,22,132,21,107,98,389,44,581,342,259,174,137 }, +{ 15,515,700,753,4,33,13,77,23,5,51,32,102,40,93,11,349,141,21,8,82,202,64,31,110,10,117,0,1,44,3,318 }, +{ 110,253,854,811,352,141,244,951,180,642,661,384,498,143,752,317,911,10,269,206,559,351,261,120,902,533,922,959,365,160,332,217 }, +{ 2,29,70,1,75,52,6,220,26,112,145,331,74,163,19,69,38,324,46,58,14,5,25,21,278,223,50,307,66,7,67,409 }, +{ 13,23,77,33,51,4,64,141,115,102,0,2,128,177,40,11,202,10,6,180,7,15,269,1,32,217,59,22,291,3,137,515 }, +{ 340,897,691,478,658,264,914,382,100,812,363,1,724,156,166,698,88,521,39,404,682,447,296,96,303,411,30,909,9,274,656,772 }, +{ 9,18,310,101,265,159,326,120,105,158,33,363,77,195,51,55,13,39,354,132,23,7,28,639,16,137,98,1,252,272,709,49 }, +{ 57,313,471,12,99,369,157,339,266,152,38,37,475,453,328,775,11,40,59,188,77,514,401,403,342,4,139,33,377,51,229,14 }, +{ 16,7,24,14,35,140,60,92,18,69,71,2,189,1,46,230,108,388,150,38,21,172,278,67,246,267,50,309,236,135,451,0 }, +{ 206,417,93,940,959,473,499,203,8,137,559,728,31,202,44,120,450,141,10,260,116,564,22,326,269,318,268,244,0,1,253,638 }, +{ 15,515,700,753,1,0,13,2,23,4,3,51,5,217,7,77,341,115,8,9,10,33,6,180,317,349,291,120,11,165,457,901 }, +{ 1,2,5,14,48,21,290,32,50,45,38,46,263,207,155,72,76,29,17,408,425,171,89,52,7,0,292,449,3,227,513,428 }, +{ 121,132,354,167,271,223,146,98,18,463,1,668,446,195,407,60,212,447,781,48,360,363,411,522,156,393,807,9,21,16,293,13 }, +{ 131,578,105,371,219,224,716,616,187,49,9,254,737,159,385,98,258,127,272,761,0,916,623,910,28,286,39,31,22,518,924,242 }, +{ 302,467,97,6,273,1,24,484,124,51,36,18,2,398,453,421,523,69,7,23,13,403,386,150,66,0,298,65,426,165,22,158 }, +{ 30,190,530,88,1,100,778,539,625,274,382,410,96,731,960,39,795,321,9,131,264,144,840,748,44,166,669,957,36,31,435,228 }, +{ 141,1,2,128,64,33,15,202,3,0,180,5,13,77,515,134,269,102,197,700,10,137,318,6,120,165,753,352,4,82,23,117 }, +{ 44,201,567,116,131,224,295,662,489,268,219,31,434,144,187,276,110,384,93,261,699,137,36,442,120,1,613,30,228,64,141,244 }, +{ 12,15,51,23,515,37,99,13,700,0,10,117,753,38,165,82,134,120,11,453,197,64,115,569,1,629,401,22,457,474,110,153 }, +{ 7,135,2,92,172,14,66,140,38,52,97,46,29,74,16,324,278,226,6,87,1,571,262,5,357,232,35,380,69,314,24,330 }, +{ 125,386,23,963,949,60,51,391,165,221,13,197,118,21,719,193,541,421,517,150,393,7,401,453,308,5,791,551,326,558,48,173 }, +{ 6,85,42,25,138,222,174,235,280,256,525,289,26,214,64,746,90,32,544,65,204,19,66,337,355,95,348,415,74,29,5,312 }, +{ 1,14,5,50,2,67,24,0,46,69,48,21,58,103,16,12,18,38,54,96,83,7,502,45,36,181,35,9,430,28,10,155 }, +{ 811,351,642,180,951,752,110,638,253,10,82,352,197,341,365,564,499,854,873,55,9,417,282,901,244,22,559,143,206,141,28,898 }, +{ 23,13,51,15,12,453,403,165,4,515,115,719,475,457,700,523,2,21,0,99,202,197,14,5,386,753,128,401,37,308,33,117 }, +{ 120,13,23,77,141,1,15,93,217,82,260,51,137,202,110,515,21,180,165,5,128,102,64,351,291,700,269,352,326,203,177,0 }, +{ 1,5,0,22,12,2,36,21,10,23,86,13,28,51,9,128,48,14,32,50,7,3,96,137,54,4,202,49,37,65,208,323 }, +{ 219,98,23,127,301,51,258,308,170,910,13,165,22,105,293,616,125,242,276,401,201,395,964,115,55,284,31,374,327,206,512,900 }, +{ 64,180,80,165,5,237,2,250,34,58,297,61,197,17,22,29,186,498,231,445,247,3,752,311,95,32,483,153,27,45,115,469 }, +{ 13,77,23,33,0,2,1,64,141,51,102,10,15,3,115,40,180,6,515,128,7,22,269,202,4,217,700,5,177,117,14,165 }, +{ 15,120,51,515,13,450,23,700,202,153,196,753,260,64,128,141,730,4,326,386,21,523,33,318,5,457,95,32,403,1,77,269 }, +{ 2,1,5,29,32,45,207,263,14,425,58,72,76,21,7,408,48,46,52,186,17,292,38,6,61,89,476,50,155,720,119,3 }, +{ 15,515,700,753,4,13,11,5,1,23,33,21,3,141,32,2,40,180,117,64,269,202,102,197,0,165,120,51,341,352,153,12 }, +{ 76,5,214,129,2,123,45,710,17,249,618,460,179,32,1,257,205,519,90,207,245,184,162,61,769,209,292,106,6,29,14,128 }, +{ 1,15,23,13,120,141,51,515,202,21,700,165,0,180,137,2,5,77,128,93,753,260,269,197,326,33,110,352,82,102,318,48 }, +{ 7,2,135,14,29,87,66,52,97,172,70,112,5,58,46,337,92,16,20,43,1,38,232,155,74,294,6,461,409,151,262,32 }, +{ 574,187,384,926,860,110,258,434,269,531,141,244,160,261,253,116,699,959,940,717,533,36,219,31,902,661,871,295,201,352,10,260 }, +{ 156,354,296,1,182,586,64,379,340,937,850,698,31,48,98,44,120,18,163,23,30,658,195,125,77,284,223,291,774,481,96,39 }, +{ 250,80,34,472,17,495,176,469,33,194,64,483,4,297,141,14,161,27,53,667,56,833,73,527,585,231,106,51,84,814,2,59 }, +{ 97,7,81,140,66,92,172,192,24,298,43,6,74,69,314,426,462,14,501,16,21,508,60,189,267,232,230,104,48,20,135,330 }, +{ 31,44,116,144,268,393,492,434,367,489,127,98,918,0,384,9,22,206,948,105,93,203,1,456,332,940,299,28,137,49,293,125 }, +{ 15,128,33,3,13,51,141,1,202,64,23,2,515,120,102,0,5,82,10,700,165,197,269,153,403,110,753,137,196,318,117,12 }, +{ 31,98,127,9,0,105,22,28,44,512,293,395,299,1,242,49,685,763,320,599,125,116,109,276,284,95,870,159,23,456,36,900 }, +{ 7,24,124,1,6,97,2,69,14,18,23,92,21,67,66,16,5,484,43,20,118,65,36,22,28,0,51,140,13,71,29,150 }, +{ 1,64,442,303,284,349,202,141,622,67,154,447,260,44,652,429,9,335,237,919,197,98,167,33,682,269,547,77,863,411,340,201 }, +{ 1,15,2,141,515,0,700,13,3,180,10,753,5,64,77,33,4,6,7,197,102,269,165,23,134,11,352,341,291,349,22,120 }, +{ 99,139,12,453,196,277,775,40,475,33,23,401,215,51,11,14,77,111,313,130,38,211,37,266,129,15,339,153,719,3,369,515 }, +{ 33,77,102,4,23,128,13,141,202,64,51,0,40,59,269,115,117,137,153,1,318,11,10,177,15,134,22,90,196,2,403,32 }, +{ 7,2,14,58,70,112,16,5,87,38,46,52,6,128,135,1,32,21,155,29,66,64,0,97,92,186,172,294,13,23,20,37 }, +{ 15,13,515,1,700,2,23,0,753,5,3,4,51,10,341,115,365,180,11,33,317,77,6,7,217,12,197,165,117,9,64,102 }, +{ 2,1,14,29,75,69,67,6,52,46,38,24,103,220,83,25,70,87,262,74,96,267,50,366,26,16,226,394,357,66,108,19 }, +{ 9,105,18,39,1,0,16,557,101,272,252,890,326,49,265,21,137,100,23,938,13,310,159,5,31,24,254,51,30,128,202,132 }, +{ 80,209,45,61,667,17,6,106,5,2,151,29,483,255,454,833,27,311,112,19,738,378,1,58,113,26,25,469,119,887,32,64 }, +{ 13,23,51,15,5,1,515,0,21,2,12,141,700,165,202,115,753,32,180,4,3,197,10,120,457,9,269,128,64,341,7,33 }, +{ 99,12,453,277,139,157,369,474,339,51,38,23,37,196,188,401,775,111,11,313,328,475,153,266,4,471,79,40,33,629,102,14 }, +{ 7,92,16,232,97,140,126,14,60,107,66,35,298,387,314,104,246,462,441,150,0,38,24,2,172,357,230,330,5,633,22,289 }, +{ 13,77,23,202,318,141,33,4,51,269,102,177,115,403,137,2,40,494,90,11,342,128,31,117,21,32,7,12,64,134,14,10 }, +{ 13,2,0,23,141,1,77,3,180,33,6,64,15,10,115,51,4,5,217,197,7,165,515,102,22,11,700,269,40,352,177,14 }, +{ 15,515,700,753,4,11,1,93,13,5,180,110,82,21,120,23,2,33,10,141,3,165,197,102,901,0,32,341,117,40,153,12 }, +{ 15,515,700,753,1,13,0,2,23,4,77,51,3,5,341,291,7,33,6,115,10,9,8,217,11,177,120,180,102,165,197,365 }, +{ 20,43,198,325,173,904,104,234,66,147,77,319,416,422,97,426,5,0,7,450,861,202,712,725,2,32,639,376,38,324,945,315 }, +{ 105,0,9,28,49,301,170,1,127,159,22,16,31,98,512,623,24,109,158,395,35,68,371,65,713,55,2,242,293,21,44,18 }, +{ 213,88,689,466,230,30,321,435,699,352,217,201,795,831,144,854,1,443,96,539,530,840,418,251,855,190,93,100,669,31,957,662 }, +{ 130,453,47,196,4,57,14,59,236,711,51,153,730,77,412,381,23,202,108,128,361,13,283,117,11,719,200,46,34,78,210,2 }, +{ 1,2,5,14,0,50,36,22,38,46,65,67,12,86,114,28,103,29,208,7,10,128,21,83,218,23,96,54,194,6,133,51 }, +{ 6,26,74,19,165,453,14,730,1,125,197,50,29,51,138,357,13,2,108,391,70,719,46,457,47,500,386,262,112,23,235,52 }, +{ 9,10,376,20,43,0,49,18,30,120,2,33,325,104,501,470,77,788,725,102,523,39,858,5,904,414,174,55,137,37,342,13 }, +{ 15,515,700,753,0,1,13,23,51,77,120,202,341,82,5,4,9,260,2,137,141,128,115,351,901,8,180,10,197,21,450,33 }, +{ 105,131,272,578,9,49,371,219,159,616,286,320,224,187,716,98,28,22,0,623,127,258,910,737,385,31,239,347,254,109,424,95 }, +{ 457,51,13,23,961,12,719,99,453,15,4,515,165,401,629,3,700,11,17,14,2,37,753,41,57,569,38,45,0,33,5,32 }, +{ 202,120,5,33,318,77,450,102,1,260,403,128,494,21,165,13,269,12,326,23,342,523,402,2,817,64,15,141,125,82,457,475 }, +{ 141,269,352,217,180,64,349,137,202,160,317,15,372,515,700,752,318,753,244,13,437,291,165,864,22,237,5,82,954,21,77,418 }, +{ 70,29,2,145,74,112,26,6,75,52,19,66,632,1,87,220,5,135,163,287,307,25,226,7,58,396,294,278,113,409,69,151 }, +{ 82,351,317,15,752,180,898,352,141,901,515,341,10,700,365,1,753,498,0,217,253,115,55,854,33,5,143,32,21,160,36,197 }, +{ 39,9,310,254,0,30,101,49,252,272,100,265,105,455,159,557,190,333,286,688,18,166,1,158,709,16,625,627,31,131,327,329 }, +{ 2,58,29,5,1,151,186,52,70,45,7,549,14,75,112,400,113,155,61,46,227,163,311,315,66,6,307,27,17,220,287,74 }, +{ 141,217,13,21,352,23,269,77,180,115,317,64,202,15,349,137,5,51,165,291,318,752,372,4,0,102,33,365,197,32,341,125 }, +{ 68,35,0,9,65,101,149,124,24,154,175,16,28,7,67,1,18,189,114,398,55,14,345,39,118,133,69,2,230,429,71,283 }, +{ 66,7,29,2,112,52,20,43,97,151,74,192,135,5,173,525,337,45,145,58,415,25,14,32,644,70,544,226,222,21,6,580 }, +{ 31,125,44,22,116,299,242,55,1,170,64,36,479,870,456,685,10,599,558,0,268,506,28,740,23,903,492,164,393,206,2,86 }, +{ 188,11,79,12,99,377,94,33,542,339,40,474,111,37,4,51,102,453,139,775,13,475,23,961,277,471,134,57,431,266,115,117 }, +{ 658,698,340,98,296,303,1,31,850,363,156,919,44,774,586,385,120,77,82,10,223,30,354,291,23,914,478,87,260,163,48,13 }, +{ 15,515,700,753,82,4,1,13,901,33,197,11,5,10,23,165,2,0,180,3,21,77,51,120,365,115,217,40,117,102,32,401 }, +{ 15,515,700,753,4,11,5,13,1,141,3,180,23,202,21,2,269,64,165,33,40,32,0,318,120,128,12,197,117,352,51,17 }, +{ 91,6,233,85,370,718,81,65,25,256,63,343,42,74,235,123,138,511,397,249,26,194,650,355,64,87,544,18,90,643,66,214 }, +{ 23,13,202,51,21,120,1,5,141,128,450,64,318,403,15,137,260,33,12,48,32,31,125,494,269,102,165,515,77,2,197,14 }, +{ 180,317,365,341,752,217,115,352,901,482,372,498,1,141,15,253,515,244,2,700,0,21,13,82,23,4,579,351,753,291,269,77 }, +{ 13,115,197,341,9,352,468,237,64,498,23,165,22,509,901,546,482,180,28,569,317,51,365,873,391,95,86,217,49,837,752,706 }, +{ 13,23,51,1,141,5,165,202,21,120,64,125,180,15,2,33,197,115,128,32,260,269,12,82,4,515,137,7,318,93,0,700 }, +{ 214,289,90,174,874,6,138,280,65,81,64,85,355,751,194,233,312,348,835,91,0,32,343,636,249,29,875,288,519,104,247,74 }, +{ 15,515,700,753,4,5,11,13,1,33,23,21,2,3,102,32,141,77,180,117,31,64,0,40,134,196,120,352,12,44,197,6 }, +{ 33,15,13,515,117,23,700,217,134,753,0,51,153,77,141,2,4,64,196,1,3,180,10,115,5,102,6,11,22,202,165,7 }, +{ 15,515,700,753,33,4,77,102,1,40,13,117,11,115,134,5,21,153,23,217,3,32,2,317,120,196,180,141,51,12,59,260 }, +{ 15,515,700,753,13,0,1,23,2,217,51,3,4,5,8,317,115,9,341,10,202,180,6,365,7,82,457,22,120,901,33,291 }, +{ 7,2,135,20,97,14,66,52,337,673,192,29,43,355,353,5,16,294,107,376,147,226,331,560,64,470,222,104,415,32,4,324 }, +{ 195,132,142,167,146,77,363,271,121,354,202,120,647,178,786,212,687,0,101,878,16,522,60,5,450,411,35,55,98,639,259,318 }, +{ 202,77,20,0,318,66,104,128,102,269,177,43,33,7,216,291,494,5,2,342,74,173,97,112,450,22,337,10,234,52,64,678 }, +{ 107,362,612,356,359,97,414,43,259,20,392,7,298,147,819,683,465,173,729,660,319,14,5,779,581,595,246,35,501,92,0,230 }, +{ 6,165,14,453,13,51,19,23,386,457,74,391,308,2,26,401,47,758,603,108,719,366,1,29,309,730,324,197,133,70,115,867 }, +{ 179,72,205,180,247,245,4,490,352,59,317,152,79,498,94,217,148,76,752,864,11,216,141,405,89,452,197,111,497,188,37,21 }, +{ 107,7,298,314,14,359,32,392,232,279,172,97,60,581,387,126,121,0,534,493,356,92,441,95,13,21,35,147,22,5,16,362 }, +{ 156,271,354,586,360,132,591,195,121,18,340,1,5,13,21,48,668,446,23,463,296,658,60,55,407,698,146,70,626,51,163,24 }, +{ 13,23,51,4,0,12,457,15,11,453,2,515,5,1,99,10,115,165,700,475,401,403,3,961,40,14,37,753,719,32,64,569 }, +{ 48,125,21,165,13,221,23,763,423,508,197,5,98,92,193,16,441,386,64,314,293,457,391,140,49,60,102,693,683,51,35,867 }, +{ 202,77,120,450,5,318,1,494,0,195,18,132,523,403,326,604,354,260,121,576,203,167,234,817,682,49,35,615,21,20,13,102 }, +{ 39,9,166,30,0,101,158,68,404,190,333,274,252,310,88,100,49,28,344,35,21,22,419,131,438,1,16,65,530,694,124,10 }, +{ 15,515,700,753,110,4,1,11,165,180,93,13,82,5,2,197,33,120,0,3,10,23,21,115,901,217,341,77,317,51,32,117 }, +{ 2,29,1,14,6,52,5,46,50,26,70,19,103,58,38,67,96,262,516,309,218,133,108,27,75,17,112,114,24,487,331,83 }, +{ 120,77,15,13,1,141,260,23,515,217,110,51,137,700,317,202,165,291,180,21,753,128,0,177,326,93,450,82,64,269,197,5 }, +{ 255,59,554,297,183,56,33,444,108,358,123,196,269,122,77,153,57,177,117,730,19,467,605,130,128,50,275,4,291,475,134,133 }, +{ 13,23,51,12,153,14,117,120,165,134,99,401,38,453,15,128,197,719,64,515,475,403,37,33,196,700,40,125,5,0,54,2 }, +{ 64,33,174,348,95,108,467,554,56,0,25,306,233,6,63,511,343,120,13,85,29,561,543,707,319,180,899,355,77,49,256,18 }, +{ 120,260,51,23,77,15,202,1,93,82,141,450,13,326,515,137,21,5,64,33,110,700,128,165,318,203,269,102,351,753,197,125 }, +{ 15,515,700,753,4,13,11,1,5,21,23,2,33,64,3,180,32,141,22,102,77,0,10,93,82,352,117,40,341,31,165,6 }, +{ 15,515,700,753,341,13,23,141,33,1,0,217,4,77,180,10,82,351,51,137,5,64,9,317,21,11,102,40,260,202,854,115 }, +{ 105,272,131,22,327,286,28,239,320,9,109,578,219,49,98,224,95,159,538,371,616,127,187,64,713,55,0,170,168,258,716,623 }, +{ 16,18,68,35,24,60,71,118,92,126,0,9,101,191,7,55,154,175,212,14,167,150,302,28,375,1,107,124,346,273,21,108 }, +{ 20,147,43,470,376,142,904,178,427,798,0,595,198,325,858,319,61,202,173,97,5,422,14,22,107,259,32,49,887,77,414,392 }, +{ 13,23,51,12,33,15,99,64,128,515,453,202,117,153,37,102,700,40,134,196,120,0,2,753,141,14,38,3,82,403,77,21 }, +{ 383,17,62,136,84,119,56,440,3,504,240,80,378,129,123,548,106,128,4,11,14,555,162,32,184,361,59,64,205,5,469,57 }, +{ 70,1,48,652,5,638,846,888,21,349,269,260,340,562,767,761,163,883,774,141,125,518,591,0,23,9,87,13,371,303,622,31 }, +{ 66,135,6,97,74,278,69,7,14,324,267,172,2,140,462,1,357,38,808,550,92,841,189,29,16,25,298,87,75,204,24,335 }, +{ 51,23,33,13,102,40,12,128,64,77,10,202,0,196,117,4,14,99,134,453,65,153,11,475,139,403,22,141,86,2,21,15 }, +{ 88,100,264,166,274,435,772,1,382,921,96,478,30,438,639,909,897,521,190,466,960,410,9,144,530,418,31,329,265,691,778,93 }, +{ 62,440,136,56,84,3,504,548,555,383,4,17,129,128,507,361,123,59,119,162,14,57,152,328,161,11,202,495,184,27,80,215 }, +{ 911,617,332,959,206,141,253,244,282,384,110,120,10,260,352,143,951,811,269,373,160,417,93,531,728,203,434,940,137,55,36,717 }, +{ 120,15,260,141,77,1,515,82,700,351,33,23,450,13,110,326,64,217,269,753,203,137,102,5,165,21,51,291,93,177,373,128 }, +{ 15,515,700,753,0,1,2,23,13,51,5,9,82,901,180,8,3,4,120,6,7,141,93,12,197,341,10,33,115,730,64,125 }, +{ 7,104,97,107,356,232,66,560,298,289,14,707,38,568,359,64,20,0,65,324,22,214,92,32,192,5,387,43,712,90,172,95 }, +{ 6,1,2,66,67,14,74,24,108,29,69,83,458,7,25,38,135,103,36,150,451,114,52,594,75,65,380,18,267,602,19,278 }, +{ 13,23,51,12,115,21,202,5,457,15,4,1,64,719,0,403,2,3,453,165,99,141,401,128,32,515,10,37,523,197,120,700 }, +{ 57,59,4,11,412,381,77,53,421,291,250,368,99,14,27,369,803,283,23,108,403,19,339,210,0,401,12,444,236,40,361,736 }, +{ 15,515,700,1,0,753,2,13,23,5,51,180,3,115,6,7,457,4,9,8,12,82,197,165,141,901,120,719,33,64,21,22 }, +{ 64,95,180,247,929,146,90,126,197,32,237,60,288,165,316,92,5,13,77,7,217,955,522,22,16,314,132,4,317,10,312,86 }, +{ 15,1,120,13,23,515,0,51,700,180,141,2,5,202,21,260,753,165,137,33,77,110,197,128,326,7,450,4,102,9,269,12 }, +{ 14,2,16,46,1,7,24,69,75,35,38,50,29,220,52,140,267,67,18,54,70,309,5,60,92,189,171,87,71,163,58,0 }, +{ 31,98,127,44,9,299,0,276,293,284,116,49,935,599,105,22,456,201,28,1,39,125,242,137,371,144,131,492,159,272,51,395 }, +{ 6,27,151,53,573,445,297,113,26,73,436,19,491,250,396,315,45,112,145,58,614,881,25,34,611,200,17,80,70,5,138,631 }, +{ 32,693,81,788,90,804,403,56,494,21,84,397,202,65,18,77,64,681,214,725,523,784,526,33,102,825,240,0,115,241,817,91 }, +{ 24,7,14,2,18,16,65,0,108,149,28,69,1,71,154,36,124,35,67,140,189,429,92,68,66,22,55,118,302,150,9,6 }, +{ 0,68,9,35,65,101,189,212,114,67,124,69,1,154,149,39,230,64,252,16,88,702,103,100,18,336,28,329,520,83,30,755 }, +{ 5,2,186,29,61,45,17,1,52,48,58,171,155,227,80,209,311,21,14,46,50,106,243,513,334,502,496,38,3,6,32,592 }, +{ 15,515,700,753,13,1,2,0,3,4,5,23,341,11,10,33,6,51,165,117,153,7,180,12,365,901,77,569,197,115,64,9 }, +{ 13,15,23,515,0,51,1,700,4,2,753,10,3,5,12,77,33,961,165,457,197,11,115,9,22,102,40,403,202,21,14,59 }, +{ 15,515,700,753,13,0,1,23,2,33,102,5,4,10,9,3,51,115,77,7,6,341,12,11,217,40,457,196,180,165,8,523 }, +{ 166,39,30,274,190,100,333,438,530,310,88,252,0,9,539,265,1,656,404,101,625,131,778,254,31,455,676,329,724,158,21,23 }, +{ 734,148,94,308,431,115,37,89,111,413,79,468,197,629,341,474,569,12,13,873,179,401,11,4,180,23,205,72,59,365,134,51 }, +{ 539,228,224,219,816,190,30,258,871,840,669,93,406,530,957,187,160,531,748,137,131,88,863,36,728,839,44,213,352,116,202,466 }, +{ 393,791,125,801,730,551,386,23,31,175,93,98,51,13,144,788,126,203,21,345,116,22,949,110,575,165,326,44,0,4,60,221 }, +{ 13,23,77,141,0,4,51,2,33,115,64,1,10,3,6,15,11,102,7,217,180,40,515,22,128,177,202,9,700,269,165,5 }, +{ 2,29,7,70,52,14,1,58,112,46,75,5,171,163,87,220,307,151,186,334,38,66,155,16,69,135,278,45,262,97,6,21 }, +{ 88,321,213,100,230,435,689,466,1,382,30,352,217,699,410,96,795,36,921,752,190,141,144,180,44,831,317,83,443,31,840,251 }, +{ 363,411,101,520,354,9,195,668,132,156,447,1,905,364,18,23,765,664,146,5,360,13,121,96,98,31,252,39,100,759,264,551 }, +{ 13,23,51,730,12,719,453,457,401,475,5,21,403,2,0,1,15,4,3,899,99,32,165,11,515,308,197,115,6,961,700,523 }, +{ 72,76,89,12,37,4,308,179,38,528,90,431,54,205,148,184,401,57,152,474,23,59,51,245,428,11,32,99,405,316,257,21 }, +{ 376,20,43,147,470,173,97,595,107,319,414,142,819,5,729,178,858,7,427,32,426,104,14,0,392,362,259,61,230,77,560,246 }, +{ 202,141,269,494,318,137,51,128,403,4,217,96,77,5,64,177,291,180,15,352,102,10,33,349,2,317,0,341,120,515,21,453 }, +{ 77,202,33,128,102,318,494,269,13,0,117,23,342,291,403,15,134,51,153,141,177,515,82,137,196,700,203,64,22,351,753,4 }, +{ 253,110,951,352,499,811,10,854,180,638,244,559,642,752,564,8,141,143,417,341,901,260,206,197,922,661,93,15,498,373,165,911 }, +{ 141,13,23,180,4,217,5,1,269,317,21,0,2,202,115,51,352,77,3,197,64,341,318,15,291,9,137,93,32,165,515,33 }, +{ 9,0,18,252,16,101,68,39,24,118,35,109,158,329,28,167,60,364,333,265,49,100,22,419,553,55,1,677,71,7,212,159 }, +{ 28,109,9,39,0,158,49,22,168,35,55,175,1,65,67,185,194,159,289,95,272,114,30,105,86,584,36,169,254,2,83,24 }, +{ 15,515,13,700,1,753,2,23,0,3,4,5,33,341,11,51,6,10,197,115,901,180,77,40,102,12,365,165,141,217,7,317 }, +{ 173,693,104,422,5,18,61,32,102,0,20,13,784,560,33,66,397,526,49,207,29,25,510,707,65,6,11,344,21,263,81,77 }, +{ 23,13,386,51,308,801,719,221,401,949,21,730,165,421,102,115,125,33,341,670,468,117,770,1,120,6,197,14,403,97,67,958 }, +{ 0,49,105,16,28,24,159,9,158,320,1,68,35,239,170,18,109,7,55,65,2,95,301,124,347,14,21,154,22,127,286,31 }, +{ 2,5,1,207,45,29,32,58,76,61,6,263,292,655,72,14,17,476,7,119,52,306,70,64,21,90,186,214,106,38,3,790 }, +{ 21,6,125,49,13,64,715,66,115,95,197,33,22,32,204,165,56,278,0,408,241,120,4,808,681,350,263,85,81,571,135,509 }, +{ 612,427,325,107,202,5,376,49,64,392,403,470,21,147,31,788,494,14,362,465,858,98,20,804,518,43,845,318,125,97,725,534 }, +{ 32,21,76,72,2,1,14,5,241,449,89,38,350,221,155,48,50,292,37,46,45,90,270,54,17,179,214,12,148,430,476,413 }, +{ 24,0,28,16,7,124,35,154,14,149,65,18,9,68,55,108,175,71,2,1,22,109,92,67,484,336,118,69,302,398,570,420 }, +{ 1,5,14,2,48,50,38,67,46,21,0,54,45,270,281,12,24,32,155,96,513,103,290,83,61,58,36,17,37,72,69,181 }, +{ 13,961,569,197,37,15,23,474,515,94,148,111,12,165,629,341,700,79,901,401,51,405,753,10,134,4,115,734,873,11,89,117 }, +{ 33,23,102,51,13,40,77,128,64,202,141,15,4,12,0,1,2,117,22,11,10,403,153,515,99,318,137,269,139,196,700,134 }, +{ 0,1,24,67,9,16,18,35,28,69,103,50,5,2,65,12,83,68,7,96,14,22,21,149,75,114,13,133,23,71,218,54 }, +{ 384,617,940,332,855,911,206,959,434,282,141,10,93,253,244,110,144,268,120,36,352,137,417,203,116,31,44,269,160,201,143,951 }, +{ 30,93,473,137,31,704,450,652,190,203,800,254,166,274,326,144,269,160,127,303,120,625,88,848,110,435,77,521,349,131,340,744 }, +{ 53,27,73,26,19,250,297,200,25,630,17,6,611,122,34,42,714,235,472,65,436,14,80,684,690,106,45,113,680,108,64,4 }, +{ 15,515,1,2,700,0,753,3,5,141,180,4,13,77,33,10,217,6,7,134,11,352,197,64,165,341,317,23,12,115,102,40 }, +{ 254,530,39,613,688,221,30,31,438,190,228,960,1,44,141,21,180,406,23,166,9,202,13,96,137,48,131,829,317,269,393,51 }, +{ 9,39,28,35,30,166,158,36,0,175,101,346,364,67,49,68,168,420,88,1,194,131,100,352,55,83,190,64,137,570,86,65 }, +{ 62,56,3,548,555,507,440,161,34,4,215,136,162,514,361,527,17,14,211,130,328,11,383,123,84,183,38,57,184,152,205,494 }, +{ 92,126,107,7,356,493,97,279,359,298,16,246,35,60,14,441,362,121,43,423,5,132,392,20,508,230,199,146,232,173,150,414 }, +{ 15,82,141,515,291,922,349,700,217,260,372,120,351,93,77,753,318,352,373,854,1,326,269,21,13,102,144,202,64,23,203,137 }, +{ 141,217,352,115,180,13,269,317,752,77,23,21,341,197,5,372,244,291,9,64,51,102,4,1,365,2,165,33,3,48,237,351 }, +{ 78,47,390,19,130,453,108,27,711,813,730,444,412,283,196,690,123,14,128,26,250,389,650,236,200,65,51,4,34,183,297,73 }, +{ 34,250,297,80,472,64,495,17,311,3,148,45,667,61,176,53,243,27,90,161,469,141,483,151,62,128,29,4,58,56,5,231 }, +{ 51,23,33,13,551,77,102,326,421,21,523,120,5,899,453,692,202,153,308,615,115,958,450,401,791,68,221,93,475,18,403,4 }, +{ 98,223,393,363,411,1,478,834,664,156,284,691,447,791,914,293,354,724,697,9,807,541,759,51,18,421,48,264,948,586,195,848 }, +{ 7,14,107,232,16,92,2,60,46,5,359,121,24,526,220,620,135,1,172,21,126,314,132,77,18,75,32,278,12,23,52,38 }, +{ 32,76,2,1,21,72,241,14,5,48,292,89,476,45,720,270,179,90,17,214,148,38,50,29,129,155,350,46,290,227,123,464 }, +{ 15,515,700,753,13,23,33,77,51,4,102,0,32,202,1,11,128,82,117,141,40,5,110,8,3,90,137,21,10,318,403,165 }, +{ 66,6,69,2,1,74,14,135,278,267,380,24,29,97,67,38,103,75,7,388,324,25,52,150,87,83,189,357,335,108,204,172 }, +{ 152,4,339,59,79,471,188,11,77,94,128,33,529,377,12,111,102,202,452,402,216,99,13,542,51,40,474,37,64,291,23,961 }, +{ 15,515,700,753,1,0,196,13,33,2,77,5,23,102,3,10,9,7,217,4,6,153,117,177,14,457,115,12,40,730,11,134 }, +{ 17,209,45,106,207,5,255,119,62,2,61,3,263,742,306,655,425,378,32,56,29,136,84,80,311,58,186,240,243,383,14,21 }, +{ 120,260,450,15,1,23,817,13,515,523,326,5,700,51,82,31,202,64,21,753,318,93,32,269,98,33,351,77,102,125,457,165 }, +{ 116,492,268,93,23,206,203,0,551,918,13,51,8,22,417,940,120,10,499,31,949,791,125,523,165,473,341,730,421,959,401,391 }, +{ 15,515,700,753,165,13,0,1,197,23,4,82,120,2,180,12,260,719,8,3,386,117,5,523,901,11,341,51,10,9,141,351 }, +{ 14,24,69,7,2,66,108,1,67,6,36,398,18,267,150,97,29,38,83,149,65,74,28,0,189,71,388,16,273,124,46,22 }, +{ 330,96,523,335,367,662,141,839,1,922,372,615,244,717,269,443,418,352,403,692,217,854,752,180,36,64,498,576,349,201,98,284 }, +{ 184,90,257,205,245,229,57,152,769,17,524,5,32,497,45,432,619,2,452,266,4,106,1,21,179,59,76,3,460,292,381,128 }, +{ 7,14,16,2,46,5,70,107,87,13,58,307,92,32,38,23,202,0,172,24,18,21,60,128,77,35,20,10,9,4,171,112 }, +{ 7,66,140,16,14,92,97,69,267,172,189,24,380,2,35,60,298,451,230,135,314,74,150,71,38,357,6,330,67,423,21,443 }, +{ 121,167,354,132,18,446,147,101,212,146,407,16,55,35,647,191,20,271,199,68,60,259,463,107,9,126,363,7,195,43,14,411 }, +{ 76,90,179,32,205,21,184,460,257,288,45,245,316,5,57,152,241,2,358,1,229,72,524,148,48,769,17,4,12,38,14,720 }, +{ 147,259,178,878,427,465,581,198,786,798,142,534,325,929,20,362,35,132,107,376,43,5,279,77,49,146,70,202,590,771,33,14 }, +{ 473,93,450,778,141,30,855,466,144,203,330,530,88,523,459,372,201,617,839,704,254,321,934,326,39,36,82,717,332,213,559,403 }, +{ 523,475,51,899,730,453,23,719,403,33,457,13,421,386,4,120,117,196,102,153,15,801,450,817,515,260,202,11,700,99,165,125 }, +{ 15,1,13,515,0,2,700,5,23,753,4,3,341,317,10,115,180,11,33,64,217,77,117,165,197,7,6,365,9,141,102,134 }, +{ 19,4,119,40,33,202,27,84,102,56,77,73,504,485,26,494,757,63,862,59,23,300,25,12,128,11,5,13,342,880,469,6 }, +{ 32,20,2,13,5,21,23,6,12,38,43,29,64,7,95,51,61,207,48,147,90,178,17,182,49,0,115,202,52,362,37,22 }, +{ 339,188,11,79,4,94,377,12,99,111,542,102,37,33,474,51,471,40,453,152,77,13,59,403,342,23,117,57,475,134,128,38 }, +{ 34,128,283,176,495,231,318,432,503,275,529,527,161,53,3,202,56,291,585,469,73,17,14,412,57,27,80,245,250,381,402,51 }, +{ 15,515,13,700,1,217,141,120,23,180,753,115,365,51,317,341,77,260,0,291,110,137,202,5,21,269,64,36,349,2,4,10 }, +{ 13,15,961,515,700,753,4,12,2,457,3,11,197,51,37,569,115,23,5,0,99,10,1,134,6,111,165,33,72,40,38,79 }, +{ 15,515,700,753,13,1,0,2,23,33,5,3,10,4,9,115,7,102,6,51,12,217,77,11,40,457,569,341,117,317,14,719 }, +{ 5,76,2,32,292,214,45,1,129,519,123,179,90,710,17,29,460,72,14,207,21,249,58,205,464,263,618,48,6,245,3,257 }, +{ 72,76,32,4,21,12,38,23,99,54,89,3,14,17,51,57,11,90,13,488,179,2,59,148,45,37,5,115,401,1,10,421 }, +{ 98,223,393,1,834,264,284,791,724,293,478,772,697,909,363,682,905,447,541,821,411,51,421,9,807,48,765,31,730,96,386,410 }, +{ 341,13,509,8,23,638,165,901,762,10,569,242,391,197,873,642,506,499,629,961,15,180,116,456,206,546,417,1,338,457,515,867 }, +{ 1,2,5,50,14,38,46,114,0,36,29,22,218,65,86,96,137,21,133,285,12,10,323,181,17,58,51,23,67,7,28,6 }, +{ 481,878,202,13,5,23,182,32,269,21,1,318,77,142,557,494,141,33,640,137,70,291,2,51,260,415,929,403,120,58,4,259 }, +{ 15,515,700,753,1,4,13,0,2,5,341,3,11,180,134,12,10,317,197,365,33,21,23,165,117,6,77,7,217,37,32,498 }, +{ 25,119,19,6,26,42,27,17,4,790,45,814,2,469,483,84,122,1,0,33,32,128,76,80,611,113,73,56,5,240,202,77 }, +{ 14,2,7,1,24,0,65,6,16,69,67,22,124,28,108,5,18,36,86,10,38,46,66,398,289,168,12,83,21,23,610,13 }, +{ 51,23,128,13,15,202,12,120,33,64,141,82,10,515,0,403,700,3,1,99,117,269,153,165,753,5,318,197,102,260,2,137 }, +{ 16,35,24,0,9,18,7,1,68,69,50,71,103,65,67,189,133,23,28,13,60,537,149,335,75,21,64,5,114,2,12,14 }, +{ 754,803,133,576,880,543,2,1,657,50,14,38,46,5,29,67,218,36,58,171,52,96,24,103,775,0,114,83,181,54,65,45 }, +{ 21,32,5,3,2,17,14,72,76,1,12,23,38,51,4,54,10,0,89,13,99,137,45,36,421,115,543,11,22,128,221,48 }, +{ 434,384,268,144,855,940,617,206,332,116,93,911,959,282,203,137,141,489,44,120,10,110,244,36,98,31,269,253,367,417,160,9 }, +{ 15,2,1,0,13,515,5,700,3,23,180,217,141,10,753,4,117,6,77,33,64,7,11,197,352,317,341,134,165,115,12,9 }, +{ 2,113,6,25,1,0,29,4,7,833,5,45,32,61,128,19,77,151,74,145,64,42,14,210,655,106,59,177,27,17,21,738 }, +{ 116,268,918,203,551,31,8,692,206,791,403,499,417,93,940,421,0,23,22,120,13,523,44,51,299,473,959,1,10,475,202,125 }, +{ 107,126,132,612,362,279,20,146,259,493,199,121,590,43,660,147,35,376,939,60,941,534,683,5,0,953,16,7,49,649,595,470 }, +{ 15,515,700,753,13,1,0,23,2,33,77,4,3,51,5,102,115,10,9,341,6,7,11,342,217,12,120,180,40,317,141,8 }, +{ 53,27,17,161,469,378,73,527,19,136,383,250,495,56,862,26,62,84,80,106,200,4,34,14,440,297,3,128,585,5,129,123 }, +{ 17,45,209,106,5,207,243,454,119,255,2,263,186,290,29,3,21,62,425,61,84,32,58,56,48,408,655,136,306,14,742,227 }, +{ 4,152,59,452,128,79,216,11,339,471,529,188,94,77,202,12,291,33,318,377,99,51,23,5,402,349,32,474,102,13,205,111 }, +{ 15,515,700,753,1,0,2,13,3,5,23,4,180,51,115,9,6,12,7,8,197,33,10,961,901,77,141,752,110,22,120,341 }, +{ 951,752,638,811,351,642,180,253,10,341,197,901,110,873,8,244,15,352,165,898,143,515,564,762,499,55,365,700,82,753,141,854 }, +{ 6,262,197,350,74,26,115,509,841,583,165,38,21,13,47,50,235,19,33,324,453,4,308,196,138,99,64,903,675,1,223,130 }, +{ 125,165,391,23,386,221,21,13,558,457,51,867,197,115,401,758,77,97,308,791,7,180,48,120,963,451,743,89,603,134,403,450 }, +{ 1,14,2,5,16,46,7,38,58,24,50,0,69,48,35,67,54,18,12,75,21,45,513,155,430,37,270,9,61,163,223,32 }, +{ 23,13,51,0,12,15,4,1,115,2,515,453,10,457,5,3,202,21,165,700,403,11,37,64,77,401,9,197,753,59,475,99 }, +{ 129,84,17,56,27,495,19,548,80,123,162,378,3,504,161,469,618,73,40,53,4,26,205,184,106,183,62,6,257,128,862,12 }, +{ 28,9,22,49,109,1,67,0,39,55,168,158,83,36,35,86,420,194,185,159,95,105,69,208,272,103,50,114,2,254,169,30 }, +{ 242,391,8,456,116,13,23,492,341,165,867,51,499,457,479,638,338,509,719,10,1,642,417,762,401,93,206,268,901,569,22,197 }, +{ 211,162,248,130,57,4,41,556,507,266,183,152,305,361,11,129,62,229,38,471,514,313,157,300,377,3,440,128,123,328,339,59 }, +{ 7,92,97,16,298,140,60,126,14,35,279,314,232,246,43,230,508,173,71,107,423,24,150,779,20,189,66,18,607,21,0,653 }, +{ 15,515,700,753,1,0,2,13,23,5,3,180,51,901,6,4,7,12,9,115,8,457,165,82,120,197,10,64,141,341,22,117 }, +{ 0,18,403,25,523,74,6,24,42,91,22,102,13,51,49,193,475,681,95,85,730,64,899,397,273,750,247,673,32,805,757,288 }, +{ 56,0,18,65,33,554,84,343,64,6,90,561,22,19,899,108,27,63,289,475,240,467,370,32,233,214,24,123,95,287,28,194 }, +{ 31,98,127,9,0,44,293,105,395,299,49,242,28,22,599,116,1,284,276,125,456,685,763,159,272,623,23,935,393,144,201,137 }, +{ 1,5,2,14,38,46,50,48,21,7,58,45,270,61,155,171,0,290,69,32,29,54,67,16,24,666,663,17,37,75,502,52 }, +{ 23,51,13,453,457,12,719,4,15,99,401,2,961,3,11,730,475,515,0,1,165,115,629,700,14,17,403,40,5,33,37,64 }, +{ 968,967,966,965,964,963,962,961,960,959,958,957,956,955,954,953,952,951,950,949,948,947,946,945,944,943,942,941,940,939,938,937 }, +{ 2,1,14,29,67,103,6,46,52,75,24,133,38,218,83,309,36,108,70,114,96,5,238,74,25,26,220,236,65,50,69,87 }, +{ 7,71,16,92,24,60,14,97,150,140,35,189,149,298,18,230,43,508,2,423,69,0,38,314,66,279,399,517,251,20,232,273 }, +{ 23,1,120,51,13,202,77,141,260,21,15,5,128,82,2,450,269,165,102,318,48,32,137,515,125,64,12,115,351,180,33,7 }, +{ 77,13,33,23,64,51,4,102,141,128,40,1,2,202,0,6,177,115,137,15,59,10,11,7,269,22,515,180,318,3,700,95 }, +{ 101,9,18,363,264,520,411,604,676,682,905,271,16,821,167,0,621,364,39,100,121,118,166,781,647,252,1,848,447,265,404,60 }, +{ 144,203,326,382,166,418,93,88,96,822,1,141,859,77,744,438,110,269,921,367,521,274,100,39,494,120,403,473,217,576,13,291 }, +{ 13,21,180,125,5,23,191,32,18,16,146,199,115,24,165,118,0,225,22,1,60,197,64,901,375,241,48,12,408,71,522,818 }, +{ 15,515,700,753,13,0,23,8,1,51,82,102,2,33,4,9,180,165,5,77,10,110,12,197,120,260,18,326,351,403,22,457 }, +{ 33,77,102,64,13,23,128,51,141,202,1,40,0,2,117,10,15,4,6,318,269,134,22,515,180,115,177,153,137,196,3,700 }, +{ 174,544,104,525,74,0,151,25,6,624,29,66,2,636,81,45,204,177,64,416,7,644,5,138,222,319,355,77,22,122,789,216 }, +{ 141,304,372,352,291,947,177,269,128,954,77,349,217,202,64,318,498,437,102,864,86,13,115,180,137,5,210,197,32,950,678,7 }, +{ 161,200,53,17,714,27,34,73,472,62,585,56,440,383,136,78,527,19,4,3,106,361,14,250,80,514,377,84,322,390,862,548 }, +{ 32,76,72,21,38,14,89,54,12,37,2,241,5,428,17,1,181,221,350,45,3,4,449,90,148,179,99,292,794,770,477,46 }, +{ 33,23,128,64,141,13,77,51,102,202,2,15,1,3,40,10,5,153,269,515,165,0,117,196,180,318,6,700,137,134,120,22 }, +{ 96,137,30,0,9,39,840,202,669,406,141,530,613,1,180,88,22,160,679,576,28,403,31,219,49,228,829,100,36,15,10,856 }, +{ 180,141,352,1,15,752,115,0,217,365,2,515,13,901,341,317,23,4,197,700,269,5,3,31,753,244,21,165,253,202,51,44 }, +{ 1,2,67,0,28,50,83,65,14,46,103,114,24,38,36,9,69,5,18,7,22,133,55,218,16,124,29,54,96,160,12,480 }, +{ 180,115,352,317,365,217,752,901,141,15,341,1,515,253,700,0,753,873,2,197,31,137,165,244,4,120,160,44,98,5,202,3 }, +{ 5,32,347,49,13,21,95,713,23,1,77,33,60,64,107,4,126,928,296,850,0,241,197,102,652,195,180,534,165,153,379,10 }, +{ 341,180,365,901,317,115,15,752,515,700,217,873,753,82,0,110,197,141,951,165,1,564,13,351,253,12,10,3,2,4,308,244 }, +{ 17,45,21,3,106,5,155,38,227,32,2,209,62,54,12,243,14,181,552,587,46,540,207,794,37,48,430,119,255,221,770,29 }, +{ 16,24,35,18,7,0,50,1,9,14,75,69,2,5,12,21,60,13,67,71,23,48,10,108,223,181,189,103,46,64,92,51 }, +{ 127,13,98,165,308,23,286,293,258,51,219,395,197,115,301,401,31,391,22,105,457,170,239,276,55,338,629,116,180,479,509,569 }, +{ 539,213,748,840,957,669,30,466,88,217,144,251,863,190,137,93,230,228,679,352,317,203,617,321,258,530,160,219,96,831,816,689 }, +{ 5,48,1,21,2,14,0,36,12,38,32,54,430,181,50,270,72,99,281,45,17,10,46,22,37,218,67,3,290,76,23,51 }, +{ 13,23,0,4,33,51,2,115,141,1,77,217,180,10,9,317,3,102,11,5,15,197,7,202,22,165,40,64,515,6,341,31 }, +{ 13,15,117,515,23,12,37,134,165,700,38,54,457,753,51,64,153,197,14,10,33,82,961,0,99,89,115,719,141,3,4,1 }, +{ 5,21,2,3,1,32,14,12,48,17,0,10,51,23,38,22,4,72,13,54,36,45,137,76,99,114,86,37,11,64,540,430 }, +{ 202,128,77,318,291,33,269,102,275,141,494,342,40,678,0,177,20,210,402,7,4,5,137,6,13,450,403,32,49,120,23,22 }, +{ 1,2,24,14,67,46,69,50,38,103,16,18,75,35,83,29,52,96,5,108,0,7,54,71,149,394,236,309,70,133,220,58 }, +{ 15,515,1,700,0,2,753,13,23,5,180,3,51,4,165,457,12,197,115,6,7,21,9,141,8,901,33,82,120,77,10,110 }, +{ 0,28,65,14,67,2,124,24,1,9,7,69,55,154,36,16,46,114,175,35,83,22,429,18,109,149,68,189,108,336,251,133 }, +{ 56,162,403,3,129,775,99,161,17,40,527,33,880,4,14,128,475,12,548,23,102,202,361,117,34,184,383,200,183,196,64,53 }, +{ 151,2,29,58,112,45,186,113,5,70,52,1,311,6,315,66,61,7,74,27,631,17,80,87,287,243,209,227,14,491,19,869 }, +{ 6,1,74,2,75,29,25,66,26,70,52,138,67,324,357,42,19,220,14,85,87,108,38,451,309,103,24,69,380,135,114,65 }, +{ 15,515,700,13,23,0,1,120,753,51,180,2,260,202,5,141,77,102,9,450,115,21,197,165,7,137,110,33,12,269,901,4 }, +{ 5,45,17,2,14,46,48,38,181,50,155,3,186,54,61,29,21,227,281,80,540,106,12,400,52,1,58,32,328,171,209,487 }, +{ 16,18,265,121,158,35,60,9,39,7,329,105,252,68,24,1,132,167,159,22,0,49,286,101,21,146,23,327,120,709,5,14 }, +{ 108,467,283,56,389,650,123,412,33,177,899,475,216,453,269,349,619,65,51,730,403,670,23,196,523,128,84,13,401,789,503,543 }, +{ 514,3,11,377,328,4,361,507,57,403,14,880,130,485,176,215,236,38,152,102,211,56,62,757,54,585,300,556,34,555,40,229 }, +{ 3,555,62,266,130,99,507,139,514,12,152,229,215,305,57,40,440,33,403,471,38,56,475,14,361,313,775,328,196,548,123,23 }, +{ 120,202,318,15,77,13,1,450,33,269,515,260,5,128,494,51,23,700,102,141,40,753,326,403,817,137,523,21,177,922,342,7 }, +{ 15,1,515,23,0,13,700,2,51,753,180,5,165,21,197,12,3,120,115,4,141,6,9,7,457,33,386,202,82,8,31,341 }, +{ 15,180,515,82,351,700,10,317,753,115,217,365,141,898,33,901,13,23,110,854,752,77,1,197,4,341,143,36,64,352,102,9 }, +{ 104,289,66,707,214,90,712,64,97,173,20,0,414,194,874,43,32,7,568,560,65,38,426,312,715,192,376,74,835,5,324,147 }, +{ 84,56,0,554,63,65,453,249,123,643,18,26,847,475,511,403,416,561,524,289,370,73,9,19,45,42,719,194,27,467,33,730 }, +{ 21,346,13,350,308,826,197,101,352,68,570,0,165,23,9,841,115,100,509,694,221,230,35,217,569,88,124,749,1,777,212,154 }, +{ 16,92,7,24,60,18,35,140,126,14,50,71,46,330,2,75,246,5,121,267,571,1,230,309,220,0,9,64,146,236,54,108 }, +{ 82,15,515,898,365,700,180,33,341,753,77,901,10,115,55,351,21,5,1,4,13,102,36,217,2,165,752,120,197,117,11,317 }, +{ 16,24,35,18,69,71,140,1,103,7,189,68,0,50,9,108,2,133,60,267,230,46,149,67,167,118,92,14,75,21,191,38 }, +{ 60,71,16,18,7,20,43,118,35,68,375,28,608,0,175,566,154,92,14,149,628,33,22,13,2,10,279,23,107,356,55,117 }, +{ 187,258,871,295,201,434,219,224,489,384,268,110,261,839,44,699,93,116,36,131,141,228,144,160,940,567,244,406,137,574,98,253 }, +{ 66,7,97,172,192,712,232,324,204,74,43,448,387,426,568,20,526,107,104,135,356,729,173,0,22,5,32,95,2,64,500,560 }, +{ 15,515,700,753,1,4,0,341,13,3,134,2,5,33,11,77,12,10,23,197,365,901,7,40,217,32,21,6,51,180,961,37 }, +{ 0,28,24,9,35,65,16,124,68,55,109,154,7,39,22,149,158,14,175,1,49,252,18,71,2,168,289,419,108,420,67,101 }, +{ 7,16,14,92,2,46,140,24,220,35,38,60,75,1,50,18,87,54,5,126,29,52,278,262,314,107,71,21,172,135,330,394 }, +{ 7,92,16,14,172,126,2,60,140,35,135,314,278,46,24,38,232,107,330,66,5,18,150,246,230,97,52,1,121,563,279,21 }, +{ 6,26,235,53,297,436,27,19,25,73,113,445,90,214,65,42,64,289,250,611,624,32,45,648,614,17,85,491,34,122,200,416 }, +{ 352,141,1,217,854,752,351,180,244,36,110,661,82,258,816,160,295,219,567,224,230,269,922,144,260,268,93,201,137,116,489,202 }, +{ 16,60,35,18,126,107,68,191,92,121,7,14,598,20,493,279,167,446,118,0,28,43,463,55,24,212,375,566,9,150,575,21 }, +{ 15,1,515,2,4,13,0,700,3,5,23,753,341,77,51,115,33,11,180,10,197,141,6,165,7,901,102,40,9,202,217,12 }, +{ 23,51,13,202,21,5,1,120,15,137,128,125,32,2,12,141,33,165,64,515,403,318,700,48,180,7,6,450,115,523,475,260 }, +{ 131,716,224,371,219,187,737,616,385,254,9,98,105,924,31,258,836,39,127,578,49,916,44,761,272,137,944,159,0,242,442,22 }, +{ 15,515,700,1,753,2,5,0,4,13,3,180,11,141,197,10,341,217,33,134,165,6,77,7,317,12,352,64,365,32,102,40 }, +{ 66,74,7,173,174,29,192,2,222,20,226,43,353,52,712,6,0,138,500,204,97,145,64,104,426,673,355,90,25,5,65,87 }, +{ 5,259,786,534,590,493,279,49,13,581,465,21,929,35,941,132,147,32,23,612,362,626,107,121,178,0,146,61,48,939,10,18 }, +{ 2,14,16,7,278,69,135,140,46,24,267,35,92,38,1,189,29,52,309,60,66,75,71,172,74,357,18,87,67,6,230,5 }, +{ 165,13,308,197,391,23,401,15,51,457,180,509,115,569,3,629,961,719,34,758,317,734,14,29,46,2,17,901,38,453,5,217 }, +{ 1,22,2,14,0,28,7,168,67,49,65,24,36,95,5,105,55,35,12,46,69,16,114,159,194,50,10,9,158,83,164,109 }, +{ 34,453,3,196,130,14,322,11,47,51,377,236,361,4,730,153,514,711,57,440,62,17,161,108,176,59,485,56,162,412,202,117 }, +{ 18,16,21,23,48,13,24,35,121,5,156,60,51,1,7,132,141,221,163,115,0,271,447,340,363,202,125,71,2,781,22,698 }, +{ 165,13,457,23,197,961,629,569,341,41,12,38,401,901,54,51,115,17,15,509,421,37,62,45,719,57,32,328,117,758,157,99 }, +{ 2,1,77,141,33,64,3,102,0,23,13,5,128,10,6,15,180,202,269,40,51,515,7,165,137,117,318,4,700,153,197,352 }, +{ 68,212,0,124,101,9,154,16,562,191,21,149,65,24,35,1,118,167,818,350,520,100,722,841,264,71,13,302,478,23,375,346 }, +{ 98,23,48,598,13,293,541,21,125,121,51,807,0,31,35,259,126,7,386,1,223,783,10,107,199,20,221,144,342,963,49,64 }, +{ 21,13,5,586,1,23,167,48,33,781,647,49,165,18,51,271,77,32,761,118,0,82,391,22,146,141,459,31,197,156,115,4 }, +{ 2,1,5,61,29,7,58,45,14,6,425,32,70,52,290,738,207,21,72,112,66,76,655,17,186,46,64,263,38,0,128,87 }, +{ 39,265,9,100,1,333,363,101,18,411,447,254,166,310,31,98,264,30,639,404,156,286,16,93,593,203,272,682,0,905,44,821 }, +{ 6,2,1,19,29,51,26,108,25,74,5,23,14,114,13,386,133,103,42,66,453,70,309,138,719,324,65,38,64,96,52,75 }, +{ 20,43,356,107,49,858,595,7,414,359,0,5,392,319,97,612,422,819,14,376,173,246,22,470,147,427,230,92,197,33,683,95 }, +{ 0,9,68,35,65,67,114,101,28,1,124,175,336,69,154,103,83,24,189,133,39,16,50,7,2,149,55,251,18,345,230,36 }, +{ 23,13,51,15,0,1,515,115,165,2,5,12,700,202,4,21,141,457,753,197,10,3,180,120,32,9,318,11,453,64,6,269 }, +{ 121,195,60,16,126,107,98,271,146,407,132,35,1,167,199,223,493,191,279,20,18,5,43,7,21,92,48,393,0,362,212,467 }, +{ 31,44,299,116,393,144,492,456,268,22,105,0,367,918,384,434,127,489,98,9,963,125,242,948,1,28,206,49,36,51,93,293 }, +{ 23,13,457,51,165,401,719,758,197,453,961,629,308,14,15,12,730,3,386,569,391,29,739,515,34,828,832,901,115,514,670,341 }, +{ 105,36,131,22,180,115,341,127,169,1,9,31,64,98,44,365,317,141,272,143,160,55,219,86,197,776,239,187,0,535,13,752 }, +{ 1,6,2,14,66,25,29,5,108,67,65,114,19,38,26,52,74,7,24,18,69,86,36,388,64,51,17,83,23,46,42,75 }, +{ 51,386,23,453,719,13,730,6,457,670,758,19,401,165,2,475,47,26,899,14,108,17,1,5,197,29,894,754,236,74,27,285 }, +{ 252,18,9,101,121,16,132,0,419,167,364,60,604,35,265,363,146,271,39,158,68,109,28,329,848,24,647,907,682,159,212,55 }, +{ 283,503,128,432,26,193,63,269,789,529,102,122,389,275,678,6,25,318,445,4,342,27,573,605,177,862,643,291,216,57,235,59 }, +{ 2,1,29,75,69,52,14,6,46,74,87,7,220,226,278,38,135,66,267,70,16,262,25,24,380,324,357,140,67,394,97,222 }, +{ 97,298,69,7,66,140,189,24,16,267,172,423,60,150,14,314,92,71,81,501,43,35,74,6,517,232,149,607,83,330,18,2 }, +{ 475,421,403,899,51,805,523,958,453,817,23,615,401,801,120,326,202,670,494,730,450,386,115,629,260,576,77,365,569,0,165,13 }, +{ 7,20,14,128,77,97,112,202,2,177,16,415,269,318,275,66,107,43,141,414,135,38,307,10,58,0,6,291,32,5,4,40 }, +{ 24,14,7,0,2,1,22,28,16,65,168,124,35,67,108,109,18,49,10,149,69,158,5,95,289,12,55,6,36,71,46,21 }, +{ 26,80,27,73,122,25,19,17,6,42,684,209,445,573,667,106,45,690,4,611,255,680,297,495,65,59,128,119,483,113,64,53 }, +{ 107,259,362,376,465,20,470,147,595,534,612,683,660,43,5,49,581,0,858,35,427,246,97,786,178,356,14,21,142,878,7,279 }, +{ 131,30,228,190,856,406,224,88,219,530,863,613,778,274,944,816,187,39,100,160,258,31,44,93,1,321,539,36,871,137,435,531 }, +{ 113,6,311,25,45,491,80,611,27,26,209,667,17,73,122,42,684,396,19,85,106,5,614,4,2,255,151,29,1,64,648,61 }, +{ 15,515,700,753,0,1,23,51,120,2,13,82,5,260,9,4,341,77,180,115,141,10,7,12,450,8,202,901,197,351,165,93 }, +{ 219,127,98,258,395,421,924,293,242,201,697,105,276,51,308,23,453,272,401,944,512,137,13,31,284,567,386,365,116,131,964,125 }, +{ 15,180,352,141,515,752,217,82,1,317,854,700,351,753,115,341,110,13,260,120,21,36,33,898,23,10,5,365,4,160,901,137 }, +{ 129,123,17,257,162,184,205,249,183,769,5,80,3,4,229,130,119,45,90,99,618,106,57,497,12,128,2,84,59,152,27,40 }, +{ 33,102,23,77,64,128,51,13,0,202,10,141,40,15,1,22,117,137,2,86,4,403,269,153,515,196,65,11,700,115,99,5 }, +{ 7,14,2,16,172,107,46,92,5,135,35,202,294,87,38,232,29,97,20,21,24,1,60,220,66,43,12,0,126,52,54,70 }, +{ 403,576,615,523,475,326,805,817,494,421,51,202,120,450,137,453,23,859,260,401,402,77,33,670,0,958,15,197,386,515,165,480 }, +{ 141,352,217,137,0,180,202,349,9,269,23,51,115,291,77,372,13,317,120,752,365,351,93,22,2,341,64,10,82,854,28,18 }, +{ 1,23,13,51,202,141,5,165,21,15,120,180,64,2,197,125,33,102,12,7,137,515,48,128,269,318,93,700,0,403,9,4 }, +{ 25,151,6,145,122,29,174,45,113,74,4,665,42,138,2,614,416,287,19,348,746,0,66,26,1,7,64,243,311,396,81,624 }, +{ 30,190,254,166,100,382,731,829,88,131,264,795,9,93,625,274,438,1,578,613,716,31,44,39,530,36,616,921,265,203,160,77 }, +{ 132,5,21,13,1,23,32,195,379,687,156,121,626,296,48,70,850,146,51,82,883,771,35,49,652,407,60,4,260,0,845,33 }, +{ 9,254,0,49,272,131,39,159,688,101,105,578,518,158,286,28,327,333,68,224,252,219,344,16,22,1,716,31,30,228,24,890 }, +{ 16,7,35,60,18,20,14,68,9,0,28,118,43,92,126,55,107,2,101,154,24,71,5,202,121,109,22,252,21,97,1,621 }, +{ 15,515,700,753,13,1,341,2,0,4,3,5,11,23,10,33,117,12,901,197,6,134,77,8,165,317,21,365,217,7,17,40 }, +{ 78,19,444,47,26,390,27,453,130,813,108,730,711,65,412,122,51,680,113,235,690,196,630,283,128,236,14,64,73,53,200,445 }, +{ 2,7,29,5,61,6,45,1,66,113,112,14,52,315,738,128,32,151,74,16,20,64,70,21,592,0,25,4,425,43,491,222 }, +{ 145,112,74,66,6,29,26,70,19,396,25,87,2,287,135,151,138,222,5,226,42,122,7,307,1,644,45,58,113,651,635,632 }, +{ 92,16,7,60,126,24,140,35,14,232,18,121,246,71,46,267,172,150,107,314,132,146,230,2,278,108,330,199,236,5,38,572 }, +{ 13,115,197,538,569,341,98,55,165,127,365,762,219,286,844,23,170,206,734,638,535,901,169,253,629,0,873,509,180,10,332,258 }, +{ 58,151,74,53,287,27,29,396,6,70,2,73,5,52,112,26,651,1,297,113,17,75,19,45,334,445,145,34,315,549,436,331 }, +{ 214,289,90,874,104,751,64,65,312,835,204,249,750,194,74,81,875,32,519,288,348,0,174,247,636,715,138,192,784,6,524,280 }, +{ 9,39,28,35,30,0,166,49,1,175,439,158,64,346,36,101,67,364,86,88,274,100,168,55,23,10,420,22,190,141,505,180 }, +{ 341,901,15,515,700,753,1,365,10,0,569,180,2,197,115,31,165,3,5,4,44,22,317,13,9,951,23,253,116,143,762,93 }, +{ 120,202,77,450,260,15,128,318,102,515,494,13,817,700,269,5,403,51,1,33,23,753,82,326,141,342,291,137,21,523,351,32 }, +{ 13,115,241,64,180,32,125,197,165,4,118,22,21,23,16,247,237,28,225,191,95,141,167,5,0,341,288,35,459,18,177,24 }, +{ 16,24,35,14,1,2,7,69,18,46,60,50,267,140,71,189,108,38,75,92,0,5,9,230,67,21,309,335,54,236,394,220 }, +{ 15,515,700,753,898,180,901,341,197,638,10,165,33,1,115,4,77,365,317,13,102,217,117,0,5,2,253,3,82,569,21,752 }, +{ 193,523,18,84,56,730,233,65,4,817,90,33,643,403,91,511,453,240,59,11,214,51,719,196,153,475,32,123,64,847,102,561 }, +{ 112,29,151,2,74,6,66,7,222,145,287,45,5,624,52,25,113,416,58,122,19,70,186,204,4,87,644,549,337,884,32,0 }, +{ 13,0,23,2,1,15,33,3,77,515,141,5,4,217,10,51,64,180,700,115,6,117,11,7,753,40,102,165,197,22,317,153 }, +{ 28,0,1,67,65,9,2,114,83,69,103,50,36,22,55,24,46,14,124,109,35,7,16,38,133,160,389,323,18,12,154,5 }, +{ 121,132,18,167,271,146,101,363,621,9,411,647,16,354,520,60,212,932,1,806,55,0,195,446,68,35,31,364,777,252,407,118 }, +{ 26,6,85,396,122,624,25,19,42,445,64,648,573,416,174,680,665,214,45,348,90,65,194,145,113,881,138,289,112,436,297,544 }, +{ 16,146,18,92,24,199,60,71,121,126,35,108,156,953,271,674,132,7,32,640,360,246,649,118,21,95,5,517,14,9,1,314 }, +{ 51,13,23,453,475,730,719,15,457,403,64,115,33,95,4,523,3,12,21,6,899,102,5,128,401,202,11,141,308,515,22,125 }, +{ 151,396,6,53,27,113,58,26,73,112,74,287,45,29,297,19,145,70,138,445,315,436,34,2,17,573,5,61,549,491,1,80 }, +{ 223,1,888,774,260,98,269,385,349,202,96,141,421,622,730,863,318,697,87,453,393,418,922,834,751,5,163,335,120,291,352,30 }, +{ 16,60,92,35,126,121,7,150,246,18,107,1,598,24,167,195,14,97,71,279,98,441,191,199,517,146,356,223,298,271,230,0 }, +{ 22,1,105,28,239,170,0,55,95,31,36,301,2,320,98,127,9,49,44,64,35,67,10,86,5,12,109,23,168,13,21,312 }, +{ 2,6,5,207,292,76,1,119,45,32,17,29,61,306,790,58,240,106,14,64,214,151,476,710,7,72,84,128,4,179,70,25 }, +{ 51,23,221,254,115,13,438,530,125,48,21,39,541,960,386,49,1,613,15,840,228,308,627,131,688,401,5,326,421,158,165,83 }, +{ 1,5,2,0,12,22,21,36,10,14,48,86,23,13,32,54,3,4,28,65,51,50,137,37,208,114,9,38,17,7,281,202 }, +{ 363,23,447,182,296,340,1,93,698,478,379,156,284,144,18,269,21,98,141,70,668,411,664,658,110,914,67,937,180,691,335,291 }, +{ 17,32,45,498,41,115,180,197,106,62,54,38,546,165,13,155,468,509,341,243,241,217,542,15,57,536,428,51,117,721,292,129 }, +{ 32,95,64,246,22,92,180,13,5,652,125,241,638,237,7,49,4,126,21,115,197,296,888,316,0,165,774,23,16,392,1,534 }, +{ 15,515,700,753,33,341,13,217,4,141,77,23,180,317,1,10,102,351,82,115,40,5,854,21,137,11,352,901,365,117,197,0 }, +{ 15,120,1,82,93,217,515,260,77,141,13,110,700,351,352,23,180,753,21,854,202,317,64,349,269,51,165,137,5,128,291,36 }, +{ 13,23,51,141,77,0,33,4,115,64,2,10,102,202,217,128,1,177,269,11,7,22,6,21,32,9,180,40,15,3,165,318 }, +{ 478,264,1,520,98,724,9,682,223,664,21,759,13,772,604,100,23,363,411,48,821,5,0,905,909,447,31,265,88,101,166,39 }, +{ 20,29,7,2,77,416,6,128,33,5,0,113,104,32,43,13,491,66,23,21,102,51,74,210,202,525,64,318,10,81,174,14 }, +{ 2,1,5,14,7,58,61,29,45,290,46,38,52,21,32,270,6,592,425,0,75,155,16,48,17,50,72,70,207,24,263,663 }, +{ 80,6,17,209,106,26,483,113,19,469,255,25,378,27,495,833,45,64,161,2,61,667,76,742,32,90,445,5,814,65,887,119 }, +{ 98,223,393,1,354,834,195,791,447,697,284,293,360,541,781,156,51,807,18,664,421,411,163,668,48,31,591,765,883,386,948,23 }, +{ 679,141,816,36,93,406,876,144,228,137,1,180,669,21,332,251,5,269,116,187,96,351,202,752,317,64,203,831,574,466,855,345 }, +{ 15,515,700,13,1,753,2,0,23,341,3,5,4,10,51,11,33,165,6,7,115,197,12,64,180,153,217,77,9,569,901,317 }, +{ 13,23,202,51,5,21,403,15,120,64,1,450,128,141,12,523,33,165,494,125,2,515,269,7,48,102,318,95,260,180,453,197 }, +{ 16,18,24,60,71,92,146,246,199,35,140,7,9,118,121,108,167,230,126,132,0,640,156,14,68,133,267,360,649,271,64,55 }, +{ 269,141,678,177,202,77,128,318,33,947,40,120,291,349,102,137,64,352,210,864,461,498,13,342,196,23,275,450,954,0,205,111 }, +{ 16,24,92,18,71,60,35,7,108,191,167,246,140,14,126,21,1,68,150,118,149,388,399,9,273,0,121,796,230,48,212,517 }, +{ 2,14,1,29,46,75,52,70,69,171,38,7,58,163,16,5,24,220,67,112,223,54,50,409,155,35,267,186,151,334,394,140 }, +{ 9,252,100,265,166,39,88,404,329,0,1,520,382,812,101,593,264,274,604,676,30,118,68,553,18,664,363,23,639,865,21,411 }, +{ 16,18,35,24,0,60,158,7,22,68,14,49,109,159,55,9,28,71,2,10,5,105,1,118,329,13,344,23,92,20,21,126 }, +{ 15,13,515,700,23,0,753,1,51,2,4,10,77,5,3,197,115,165,961,202,9,457,180,12,141,22,33,120,6,11,318,31 }, +{ 160,93,251,137,317,1,180,36,120,217,345,752,617,352,332,10,96,531,498,318,365,202,141,269,816,341,901,679,143,35,83,968 }, +{ 6,25,42,128,19,59,122,4,85,26,611,27,269,233,45,0,343,91,318,80,11,177,283,73,33,614,2,77,64,138,445,216 }, +{ 95,64,74,7,32,81,51,204,0,20,237,65,56,38,91,23,207,180,347,343,29,6,511,52,49,10,25,18,554,370,14,312 }, +{ 202,120,326,260,450,817,494,318,137,403,128,77,523,553,859,5,704,1,15,23,13,576,7,16,615,51,682,291,515,0,21,234 }, +{ 20,43,107,356,362,126,595,92,359,7,422,319,493,16,858,5,392,246,414,683,60,0,35,945,441,21,259,819,49,97,279,173 }, +{ 25,42,6,77,33,102,0,122,4,690,29,483,210,27,21,19,2,300,18,648,680,119,117,59,1,10,342,12,26,153,91,684 }, +{ 31,44,299,116,125,242,456,599,22,393,0,144,492,28,268,1,9,963,301,105,367,36,127,170,384,434,206,98,918,10,13,93 }, +{ 410,521,686,367,662,88,335,321,201,96,98,772,144,1,934,921,443,435,284,274,264,551,120,897,44,100,33,225,744,418,909,960 }, +{ 142,178,878,234,132,786,195,202,77,416,147,929,146,522,167,259,687,639,450,271,626,481,590,5,198,212,771,49,0,465,315,427 }, +{ 254,39,131,9,272,0,578,716,310,224,30,49,105,827,518,829,166,333,616,228,613,846,101,219,1,31,890,98,159,938,252,100 }, +{ 230,699,854,473,450,351,831,137,855,217,352,704,800,202,251,498,160,144,206,203,317,201,253,752,418,141,1,332,82,180,443,36 }, +{ 403,202,475,453,494,23,51,77,318,402,13,33,128,102,137,141,120,342,269,0,450,4,899,576,40,421,275,117,217,177,196,64 }, +{ 23,44,98,182,291,144,116,39,110,141,96,82,905,70,367,264,125,93,77,411,120,1,658,202,100,415,107,363,197,30,447,105 }, +{ 15,515,1,13,700,23,77,120,0,753,51,180,202,141,260,5,21,115,2,137,128,9,450,197,365,269,12,326,110,102,318,7 }, +{ 0,32,18,95,207,577,193,29,61,104,64,784,715,102,693,887,81,91,583,671,403,5,52,474,397,180,138,49,37,344,38,263 }, +{ 0,101,9,68,252,16,100,39,166,364,124,24,154,265,212,88,18,35,329,419,28,118,71,30,65,158,191,55,1,694,21,676 }, +{ 16,24,191,18,35,71,167,118,149,68,212,9,0,1,21,108,101,92,60,375,302,7,589,755,124,674,350,48,562,246,13,363 }, +{ 2,14,69,24,1,67,46,16,38,103,29,267,7,35,189,135,278,71,108,18,83,309,52,6,149,388,75,236,60,0,150,66 }, +{ 13,1,23,0,4,2,51,15,180,33,3,115,5,515,141,10,77,700,11,9,197,341,202,165,217,102,22,7,753,317,365,6 }, +{ 447,1,698,411,31,363,98,5,919,4,156,125,759,691,13,64,459,354,44,21,48,293,30,914,478,225,82,120,2,922,848,839 }, +{ 854,82,351,217,141,180,352,15,515,752,1,700,317,898,753,244,10,21,922,115,77,36,4,260,64,110,372,13,5,365,120,11 }, +{ 0,1,4,13,5,2,82,33,3,120,10,23,9,11,77,260,21,102,8,31,40,6,351,51,64,450,22,117,93,110,7,457 }, +{ 51,13,403,23,12,475,1,2,21,5,453,523,115,202,817,7,0,99,3,6,450,120,494,64,22,95,49,899,10,37,32,141 }, +{ 180,752,352,141,498,864,317,217,9,0,115,237,230,39,30,197,83,1,930,64,35,365,372,13,579,88,702,36,101,901,482,21 }, +{ 16,24,0,7,22,18,28,35,14,158,71,2,109,60,1,168,49,154,124,68,10,55,92,118,159,9,5,747,95,105,65,6 }, +{ 15,515,700,753,0,23,1,13,341,51,120,77,141,4,137,33,260,82,202,9,180,5,351,2,291,10,11,901,21,115,40,128 }, +{ 24,68,35,149,18,16,0,7,9,14,189,108,69,65,67,1,71,2,118,28,140,101,114,336,230,124,175,133,46,55,251,154 }, +{ 13,308,197,115,125,9,165,237,391,23,509,569,546,28,49,629,22,338,317,254,749,180,468,159,903,386,217,352,558,39,36,734 }, +{ 64,90,32,217,77,4,141,216,172,312,128,13,86,33,597,147,352,95,5,115,875,22,59,11,102,14,182,437,97,177,874,707 }, +{ 1,22,36,0,105,28,2,67,95,49,55,5,239,12,86,9,83,170,312,64,31,21,23,10,164,50,114,159,208,13,7,320 }, +{ 9,18,16,0,159,105,101,252,49,310,24,68,35,39,265,272,7,1,60,28,455,890,329,557,118,286,55,137,327,167,5,13 }, +{ 129,123,214,249,618,17,5,257,205,184,460,76,2,162,769,245,90,106,128,45,119,1,183,4,3,12,179,64,6,229,99,209 }, +{ 51,453,719,457,23,13,730,899,475,386,4,15,11,12,670,196,515,523,961,401,153,3,700,99,753,117,403,32,120,165,57,0 }, +{ 173,66,192,204,20,74,104,636,7,43,289,426,825,712,560,214,81,750,65,97,707,0,90,414,64,348,32,500,22,861,95,6 }, +{ 13,23,1,51,5,21,141,120,202,15,165,2,515,180,12,125,0,64,82,700,197,269,32,48,260,128,115,93,9,137,33,753 }, +{ 200,34,322,78,472,390,27,714,19,14,136,161,453,176,236,444,59,3,62,128,108,57,283,862,73,53,47,17,412,813,4,56 }, +{ 33,347,66,204,426,498,56,172,97,95,5,681,546,22,10,0,135,180,4,241,19,174,6,353,263,21,7,370,42,197,27,808 }, +{ 131,224,219,187,385,371,258,442,254,737,31,98,836,127,924,944,44,871,908,716,39,827,201,574,116,137,36,1,276,242,578,616 }, +{ 1,15,2,180,4,141,13,515,0,5,3,115,700,901,341,23,217,352,753,51,197,77,317,33,365,752,165,21,6,7,269,93 }, +{ 370,91,718,74,81,510,397,66,636,240,355,84,138,511,18,278,6,681,701,289,90,5,214,582,64,104,0,643,192,65,750,32 }, +{ 15,515,700,753,13,120,1,0,165,2,197,23,260,180,4,82,51,386,8,5,12,10,3,141,351,341,326,9,450,7,64,6 }, +{ 32,2,76,5,1,292,72,45,476,214,21,241,29,14,17,48,129,90,179,460,464,123,290,148,519,205,3,263,249,38,710,89 }, +{ 13,165,115,17,197,569,23,509,457,45,32,41,106,180,62,38,659,734,155,536,341,629,961,873,587,54,431,37,391,99,405,428 }, +{ 68,24,35,16,0,101,9,124,154,71,149,65,18,175,28,118,7,55,302,108,92,14,22,346,1,39,429,252,375,364,10,67 }, +{ 0,9,1,68,230,65,35,69,83,23,101,13,141,67,217,352,21,39,16,88,28,124,212,100,115,154,51,64,30,36,10,317 }, +{ 100,265,88,909,410,382,812,593,1,213,321,30,252,230,352,264,9,166,689,39,676,98,21,466,724,639,478,217,13,48,553,101 }, +{ 113,61,198,904,43,0,5,37,899,325,20,59,33,523,204,725,817,389,470,329,222,40,174,58,22,453,690,848,122,104,788,105 }, +{ 16,24,141,18,7,0,71,140,35,269,75,352,12,9,108,217,5,330,60,64,199,70,22,13,486,246,318,133,65,50,23,498 }, +{ 271,167,121,60,18,191,146,199,16,1,446,132,575,212,463,354,126,35,598,566,727,98,107,21,608,955,640,407,5,24,223,68 }, +{ 15,515,115,217,700,13,317,753,141,180,33,23,110,120,4,341,82,10,1,260,365,36,64,854,351,21,51,352,137,77,40,0 }, +{ 173,7,97,356,43,107,20,387,729,104,426,232,560,595,359,392,414,707,885,81,5,0,66,858,612,49,861,14,22,32,819,230 }, +{ 15,515,700,753,13,23,51,82,0,33,165,120,196,4,1,2,197,453,260,351,180,12,40,8,386,110,5,326,9,141,217,457 }, +{ 9,100,120,30,77,795,137,82,202,39,264,827,578,127,0,166,373,318,18,326,141,260,1,450,731,31,33,395,217,291,341,254 }, +{ 14,2,67,1,24,69,0,28,65,7,46,18,114,108,36,83,38,398,9,16,124,133,103,154,50,55,22,267,29,160,35,547 }, +{ 14,7,69,24,66,16,2,267,189,67,71,150,140,97,18,60,172,35,6,1,38,149,388,92,83,135,108,74,462,380,29,36 }, +{ 15,515,700,753,1,13,2,0,4,341,5,3,23,365,11,117,180,10,12,33,134,115,77,197,217,165,6,7,317,102,21,9 }, +{ 1,22,0,12,5,2,36,28,21,10,86,13,23,49,128,9,95,51,55,96,208,141,48,202,4,137,37,64,105,3,50,7 }, +{ 605,630,63,123,736,650,65,108,444,368,561,389,19,25,42,619,122,194,183,27,53,33,84,26,297,813,114,73,256,235,249,216 }, +{ 127,39,9,0,31,371,98,254,1,385,395,44,30,836,187,131,100,116,284,578,299,166,28,21,737,16,276,272,23,49,137,935 }, +{ 2,61,6,29,45,151,1,655,7,207,32,5,112,425,17,76,833,4,14,64,58,106,119,25,113,128,72,52,70,21,292,790 }, +{ 2,5,17,14,3,29,23,27,13,401,46,6,51,58,1,453,45,53,34,52,133,19,236,26,181,114,99,366,151,108,218,38 }, +{ 31,22,170,1,301,44,127,98,36,28,55,105,239,0,338,116,512,299,293,125,86,10,242,395,2,13,9,64,841,23,95,685 }, +{ 1,21,2,14,5,32,48,50,38,270,46,76,290,72,45,54,17,0,155,221,263,207,37,281,430,3,89,12,181,408,36,67 }, +{ 17,106,119,378,84,240,62,80,383,136,306,3,56,790,742,5,207,504,64,440,32,128,45,2,123,209,14,4,61,57,297,667 }, +{ 13,15,1,515,23,2,0,700,4,115,3,51,5,10,753,180,33,341,217,11,165,317,365,197,6,77,40,64,22,9,7,117 }, +{ 772,335,96,744,1,367,662,686,652,897,303,264,521,31,225,410,141,520,260,116,64,44,321,98,144,88,919,966,340,269,349,284 }, +{ 1,0,5,28,36,2,12,22,83,67,65,50,24,14,9,96,21,218,18,114,48,281,54,10,7,160,181,103,37,23,133,99 }, +{ 25,6,145,42,138,81,174,348,525,544,26,74,85,280,287,648,746,91,66,0,29,396,204,64,636,90,122,194,355,104,65,233 }, +{ 16,24,0,18,28,158,7,35,49,22,68,159,55,1,14,109,105,2,9,71,65,154,124,95,424,344,60,239,118,577,21,10 }, +{ 352,854,699,230,93,689,137,144,217,160,251,36,669,202,351,120,617,855,752,203,332,82,450,180,141,748,831,30,258,201,1,816 }, +{ 15,13,515,700,1,2,0,753,23,5,4,3,51,33,10,115,11,317,217,77,180,341,117,165,6,134,197,153,64,9,102,7 }, +{ 104,20,43,173,66,319,0,77,202,7,198,5,97,580,355,74,2,204,174,52,712,234,426,155,102,192,32,4,500,337,226,904 }, +{ 13,23,15,51,1,515,0,2,5,700,141,4,753,165,115,12,3,21,457,10,180,269,32,6,197,202,9,7,120,11,77,33 }, +{ 15,515,700,753,0,1,13,2,23,115,4,317,8,3,5,51,9,341,10,217,22,365,33,457,6,180,77,901,197,120,18,7 }, +{ 15,515,700,753,1,0,2,3,4,13,5,141,23,16,82,217,457,10,365,180,9,317,51,21,269,898,64,202,11,12,318,341 }, +{ 2,5,1,14,50,38,29,17,114,46,133,3,45,21,58,171,181,36,218,12,6,52,0,48,137,65,361,23,155,4,285,51 }, +{ 0,68,9,65,101,124,35,212,16,149,154,100,24,1,114,336,67,589,252,39,71,189,69,562,18,13,30,398,118,88,265,264 }, +{ 1,2,14,22,0,7,67,65,28,36,24,46,168,5,86,69,38,16,49,12,289,10,194,50,83,114,95,6,18,23,55,158 }, +{ 13,23,15,51,515,0,700,4,1,753,2,10,115,961,457,12,33,11,3,5,197,9,165,77,102,403,453,40,64,22,37,59 }, +{ 15,515,700,753,0,1,2,13,23,5,51,901,8,9,180,3,7,82,4,120,12,10,719,341,6,31,141,457,197,22,115,93 }, +{ 100,252,88,101,0,265,9,724,48,1,21,352,213,676,410,382,321,230,30,329,593,909,39,812,553,217,23,689,520,264,166,419 }, +{ 2,5,1,58,171,14,46,50,29,52,45,38,186,155,67,54,151,281,334,61,48,96,17,181,103,400,502,227,21,223,12,69 }, +{ 23,120,13,1,202,141,51,21,165,128,260,15,5,269,137,64,33,180,82,318,93,197,77,326,515,125,110,700,450,2,32,48 }, +{ 341,197,10,901,13,15,8,638,569,515,479,23,180,873,700,165,143,642,0,961,753,951,1,115,509,499,116,12,498,242,82,206 }, +{ 15,1,23,13,515,21,120,51,2,141,202,700,5,180,165,0,753,197,12,7,33,260,352,137,269,4,82,128,48,9,110,6 }, +{ 2,29,50,58,1,6,5,52,14,262,17,46,27,53,151,34,171,74,324,26,38,309,45,113,19,96,287,396,223,67,73,583 }, +{ 13,23,141,51,4,202,0,115,77,2,33,217,5,317,180,64,10,269,3,9,15,21,1,128,102,137,318,11,352,515,22,31 }, +{ 1,67,0,24,50,5,14,18,16,69,2,9,103,35,83,12,96,28,54,7,58,223,21,46,281,48,65,181,22,38,36,108 }, +{ 13,23,141,51,77,64,202,115,33,102,128,4,0,269,10,21,217,32,180,318,9,137,2,11,22,291,7,177,16,31,165,197 }, +{ 317,115,180,365,873,498,217,341,13,752,482,197,569,352,1,901,36,23,457,468,165,346,546,143,509,134,579,876,868,2,332,21 }, +{ 184,257,205,229,152,17,57,497,266,432,452,524,5,619,381,32,4,90,2,12,313,128,45,59,245,106,3,471,129,769,339,214 }, +{ 13,23,1,0,15,2,4,515,51,3,10,33,5,700,115,180,753,77,11,365,341,217,9,6,197,7,102,165,317,40,22,64 }, +{ 626,70,771,687,379,846,767,761,518,878,82,481,31,786,49,591,178,163,407,44,87,13,845,125,590,371,195,120,98,557,937,351 }, +{ 264,1,410,909,772,897,686,521,335,478,98,96,691,639,100,44,284,382,31,321,744,88,914,724,662,765,223,9,682,363,0,367 }, +{ 13,23,1,2,0,15,51,515,5,10,4,33,115,77,180,700,3,141,217,40,6,753,317,197,64,165,7,11,102,9,341,22 }, +{ 141,77,13,64,269,23,115,21,318,217,5,202,102,33,137,2,15,291,177,51,48,180,32,4,515,352,128,7,0,10,96,11 }, +{ 13,15,23,515,51,0,700,753,1,2,4,10,33,11,961,453,115,40,457,14,12,3,9,5,165,401,197,77,22,21,64,102 }, +{ 1,22,0,36,2,31,5,12,13,105,28,9,49,86,141,21,23,95,128,55,44,115,170,10,164,98,180,4,137,239,83,51 }, +{ 100,101,88,0,252,9,265,30,21,39,759,724,213,329,321,13,419,68,562,382,676,352,694,35,553,410,1,166,909,593,230,23 }, +{ 539,88,30,190,321,530,840,144,669,435,957,748,778,100,96,418,203,213,1,131,410,228,466,274,36,382,219,863,613,83,822,352 }, +{ 7,97,92,173,298,107,43,314,232,140,16,356,20,387,729,362,126,359,246,14,230,501,426,441,0,5,560,66,104,779,35,60 }, +{ 2,14,7,1,58,5,46,16,38,70,75,45,24,155,29,0,21,52,61,163,220,50,69,270,35,48,32,171,18,6,64,54 }, +{ 447,411,363,664,647,98,621,1,354,271,223,478,18,777,781,936,360,759,167,132,121,48,21,156,9,195,118,293,23,691,13,264 }, +{ 2,1,14,65,36,67,0,7,46,22,69,5,38,24,28,6,83,29,86,114,168,50,124,208,12,18,108,10,194,484,103,16 }, +{ 421,386,51,791,730,958,165,801,23,453,697,403,615,13,221,523,24,899,401,326,551,670,576,102,18,33,125,77,566,115,203,197 }, +{ 104,319,422,945,0,81,20,43,715,32,784,693,879,7,397,74,306,207,52,681,671,2,61,173,6,636,904,95,887,5,18,192 }, +{ 2,29,1,46,14,52,70,262,6,26,50,67,75,96,309,38,103,112,58,19,5,163,145,83,74,220,223,357,24,69,331,25 }, +{ 786,929,590,771,687,626,941,178,465,259,70,5,13,21,35,534,107,518,132,49,878,48,146,121,379,279,31,767,147,195,108,125 }, +{ 5,2,186,45,17,29,48,50,14,61,46,155,400,1,227,171,52,58,38,54,430,209,80,281,3,106,536,311,181,243,21,502 }, +{ 0,32,64,95,817,494,342,403,207,202,194,389,453,365,312,180,316,5,690,237,848,577,450,61,102,523,475,289,49,241,65,482 }, +{ 23,13,51,15,202,515,1,0,2,12,115,4,700,5,165,753,77,457,21,141,128,10,3,64,403,32,197,318,9,11,33,117 }, +{ 119,2,6,76,5,17,45,292,306,240,32,1,19,84,64,61,4,209,710,80,26,0,106,27,214,25,128,129,29,179,3,113 }, +{ 15,515,700,1,753,0,13,23,180,120,51,2,5,33,165,197,9,450,7,260,115,523,4,12,202,141,82,77,21,102,8,6 }, +{ 6,138,74,280,222,85,66,226,25,42,87,204,64,337,29,135,95,174,235,26,145,65,19,32,792,294,112,52,256,2,5,22 }, +{ 39,9,0,101,333,158,49,252,310,254,272,68,16,18,159,286,344,455,30,109,627,327,24,105,419,100,364,22,35,1,329,709 }, +{ 21,5,32,14,2,1,38,72,76,54,17,3,48,221,270,0,45,46,12,181,37,89,36,50,540,290,430,10,4,741,99,23 }, +{ 120,137,202,269,141,260,318,450,922,494,77,291,82,15,5,351,128,1,515,326,64,854,700,352,342,21,753,678,349,32,523,90 }, +{ 16,24,68,35,71,18,149,118,191,167,9,0,212,124,65,246,7,67,140,189,399,101,133,60,1,108,267,114,69,92,695,154 }, +{ 28,0,9,67,1,22,109,36,55,65,194,114,39,83,49,69,2,35,103,50,158,208,86,420,168,289,505,24,7,185,5,323 }, +{ 147,325,198,427,142,178,202,798,5,376,20,318,259,43,120,450,77,234,534,904,470,465,878,725,329,14,315,0,260,858,70,61 }, +{ 28,0,65,9,109,1,55,67,35,22,24,39,289,7,175,14,114,2,158,124,420,194,68,16,336,36,49,69,168,570,154,505 }, +{ 141,180,13,115,1,23,4,269,2,202,0,317,217,51,15,5,21,352,77,318,3,752,197,10,165,365,137,341,9,515,33,64 }, +{ 15,515,700,0,753,1,2,13,23,9,51,5,4,901,33,7,8,3,12,93,180,120,197,6,82,341,10,141,22,260,457,115 }, +{ 6,74,66,1,25,75,324,380,278,26,138,85,135,500,87,42,220,841,97,350,29,19,70,226,38,21,52,606,235,889,2,14 }, +{ 13,23,51,12,4,15,453,0,457,1,403,165,115,3,11,2,64,5,401,10,515,37,202,33,40,32,99,475,197,700,308,17 }, +{ 788,180,5,83,693,319,314,4,32,21,17,11,817,3,510,498,33,12,24,104,814,120,64,117,306,804,523,450,288,160,102,43 }, +{ 15,1,515,13,2,700,23,0,4,753,3,341,5,51,33,11,10,6,77,7,115,102,180,165,141,9,197,217,901,40,12,64 }, +{ 101,18,9,167,520,16,0,118,60,212,604,364,694,24,55,252,68,917,264,35,1,121,146,363,39,100,806,5,21,166,191,28 }, +{ 13,23,0,33,51,141,77,4,64,2,115,217,9,102,7,202,21,10,180,3,15,128,5,269,6,32,11,16,165,352,22,317 }, +{ 66,135,97,74,172,6,278,7,204,324,138,174,29,85,2,87,25,140,92,192,52,38,802,69,448,500,808,620,22,1,280,232 }, +{ 2,24,69,6,97,7,1,0,14,298,423,66,67,29,150,25,189,267,124,74,607,18,36,81,172,33,83,38,52,273,71,809 }, +{ 0,9,158,39,68,49,109,16,24,333,35,344,101,22,159,254,272,30,124,65,28,18,793,154,310,252,327,105,627,419,286,55 }, +{ 15,515,700,10,753,33,77,180,4,341,1,197,13,115,365,23,901,317,5,102,11,217,165,117,141,40,2,3,253,21,134,55 }, +{ 100,166,382,478,265,264,88,39,98,1,404,274,9,593,724,921,639,438,363,682,411,31,30,812,96,447,821,905,252,0,223,435 }, +{ 19,283,436,53,297,26,813,432,27,128,42,25,390,503,122,736,73,123,605,63,389,529,630,250,690,65,381,444,6,269,108,216 }, +{ 33,202,13,128,494,0,51,141,269,1,4,2,102,180,15,137,65,95,6,450,77,40,117,59,457,36,196,817,134,86,49,515 }, +{ 18,16,60,68,101,167,191,118,35,121,9,212,55,0,126,1,24,647,199,146,520,107,628,621,363,71,21,28,346,92,806,727 }, +{ 352,230,217,531,160,93,36,669,748,854,689,258,137,871,728,699,752,251,574,202,373,351,228,120,717,260,144,219,268,82,816,1 }, +{ 15,515,700,753,13,23,0,51,8,1,4,82,165,77,110,33,10,180,5,202,11,22,120,12,9,197,115,93,403,141,40,351 }, +{ 0,9,101,217,35,88,352,100,39,175,30,68,562,752,13,317,252,115,180,197,64,1,83,141,65,213,165,230,194,36,28,265 }, +{ 32,64,5,470,288,90,21,147,0,95,356,22,20,519,835,312,819,18,247,182,11,97,13,4,387,49,43,298,316,48,107,7 }, +{ 16,14,24,1,7,2,35,0,5,50,18,69,46,12,58,75,9,67,70,163,21,54,38,48,223,502,281,37,140,60,28,10 }, +{ 18,265,9,252,39,195,354,411,1,16,132,101,121,682,167,203,5,363,146,593,35,333,21,271,60,13,100,0,156,327,7,520 }, +{ 4,13,1,115,141,23,2,180,5,0,51,3,217,202,77,15,33,269,341,318,317,21,165,515,11,10,197,365,9,137,64,352 }, +{ 7,24,16,14,71,35,18,92,140,189,108,149,68,60,69,150,2,230,97,66,0,458,67,1,65,251,38,314,388,267,36,46 }, +{ 1,22,31,36,0,2,44,5,141,105,180,170,12,64,13,98,86,55,23,21,28,164,115,127,10,125,128,4,9,239,352,197 }, +{ 66,7,97,2,192,20,52,43,135,74,560,107,104,0,750,147,414,29,580,173,324,376,226,194,77,174,204,38,356,64,16,470 }, +{ 0,101,9,68,35,124,24,65,39,16,252,100,154,166,28,364,149,694,30,88,55,346,1,419,71,439,265,289,22,21,175,158 }, +{ 39,9,100,30,127,0,737,856,31,836,827,254,98,931,166,88,93,1,44,190,131,228,120,395,625,385,863,264,219,373,110,28 }, +{ 5,17,2,3,21,45,14,155,48,32,38,1,328,181,186,46,23,51,12,61,227,29,106,54,99,133,62,832,13,37,514,543 }, +{ 131,613,30,224,228,716,274,100,827,406,219,856,39,190,31,88,1,166,9,44,829,863,931,93,0,187,625,924,127,98,137,254 }, +{ 352,217,64,141,752,269,180,864,437,372,954,115,498,177,77,349,317,318,579,291,947,197,247,0,23,717,237,304,128,457,776,678 }, +{ 141,217,180,317,352,115,15,341,1,23,13,365,515,752,2,64,0,5,498,700,372,165,51,237,753,77,244,197,137,4,21,253 }, +{ 5,2,17,14,1,45,3,38,21,29,181,58,46,48,50,133,114,171,61,155,32,6,186,281,361,12,36,54,4,13,52,514 }, +{ 6,2,119,25,790,4,45,483,655,113,1,29,76,26,32,19,887,17,128,0,292,833,59,61,106,64,77,814,14,151,84,42 }, +{ 13,23,2,0,51,4,1,115,141,3,5,180,33,217,77,9,202,11,7,15,10,6,317,64,21,197,515,165,102,128,22,269 }, +{ 13,23,51,202,21,5,1,15,141,165,120,2,115,12,32,0,515,128,318,64,125,700,4,403,197,453,180,457,3,7,10,6 }, +{ 9,39,31,30,0,127,1,44,100,131,98,187,385,276,88,442,219,908,254,116,49,166,935,28,201,36,141,827,137,299,284,21 }, +{ 22,49,28,109,9,185,105,95,1,131,159,272,36,67,86,254,39,55,35,0,505,31,83,169,208,327,286,98,168,535,312,708 }, +{ 5,1,2,21,0,12,48,22,10,14,36,3,32,17,23,54,86,38,4,51,13,37,137,50,65,281,114,45,28,99,58,202 }, +{ 141,269,352,82,217,351,180,854,372,922,752,1,15,260,317,318,515,202,64,700,120,349,954,753,77,35,67,717,898,137,365,115 }, +{ 144,203,613,418,326,406,96,669,137,679,1,228,494,822,840,317,36,83,855,160,817,859,856,816,217,831,345,93,876,77,44,251 }, +{ 15,13,515,23,700,753,51,1,33,0,202,21,2,5,180,141,120,165,217,82,12,117,4,352,269,197,115,32,3,9,134,260 }, +{ 13,23,115,1,0,51,4,77,2,33,15,141,10,5,341,180,515,3,217,202,9,365,317,64,700,102,11,165,197,22,753,7 }, +{ 531,943,373,160,728,93,206,260,261,559,964,269,717,535,332,384,365,295,110,533,141,10,180,352,244,137,120,55,959,564,36,253 }, +{ 1,31,36,170,22,55,44,10,86,64,127,0,2,98,301,164,740,338,237,143,5,125,116,13,242,141,299,180,23,169,105,12 }, +{ 13,23,141,77,51,4,64,32,33,202,115,269,102,128,21,0,177,180,318,90,40,10,7,5,137,15,217,352,9,291,59,22 }, +{ 202,120,260,318,77,15,450,269,1,82,33,23,141,13,51,515,351,128,700,5,64,326,137,21,102,110,753,494,93,523,817,165 }, +{ 523,899,102,33,730,15,23,403,719,117,153,13,515,51,475,4,700,5,453,817,196,753,494,40,202,120,1,2,450,457,17,421 }, +{ 202,403,494,450,120,817,523,475,318,453,33,402,128,77,13,51,260,576,342,102,15,23,515,4,700,5,82,753,326,210,137,615 }, +{ 2,29,112,66,7,52,70,151,58,87,135,5,74,226,307,6,14,186,1,45,549,172,644,25,113,287,46,155,334,64,294,97 }, +{ 1,77,349,291,260,120,652,102,5,39,64,269,9,33,340,342,13,98,888,698,23,296,100,318,51,202,87,137,638,128,50,850 }, +{ 1,13,15,2,0,4,23,515,5,141,180,3,700,341,115,51,753,269,77,901,197,352,217,33,21,11,365,6,165,202,7,317 }, +{ 7,14,16,2,46,5,70,58,1,38,24,35,92,163,0,75,21,18,50,54,140,12,87,220,155,69,171,23,60,9,13,307 }, +{ 2,29,66,226,135,7,87,74,52,278,6,75,222,220,294,70,97,1,145,25,172,262,324,38,69,112,331,92,5,14,140,26 }, +{ 15,515,700,753,33,77,117,4,1,102,134,40,153,11,13,196,217,21,5,51,23,115,32,3,2,202,141,137,128,291,48,177 }, +{ 15,217,82,515,351,141,317,1,13,700,260,77,110,120,115,854,23,753,180,51,21,36,137,922,5,64,365,352,291,202,93,341 }, +{ 31,190,30,373,120,110,863,88,44,127,908,856,260,318,82,98,93,187,836,717,935,39,442,131,141,254,228,219,1,968,77,116 }, +{ 23,13,308,9,165,115,51,21,401,125,49,39,197,391,159,254,217,743,28,438,773,629,558,386,341,95,32,317,876,679,109,166 }, +{ 49,5,43,165,7,0,21,104,125,22,173,422,64,13,623,102,20,18,314,95,91,141,23,31,193,51,391,900,779,558,92,232 }, +{ 1,4,13,2,15,0,23,515,77,3,341,33,5,700,115,51,202,753,141,180,11,10,102,217,6,901,40,7,197,318,317,365 }, +{ 93,843,295,120,36,160,206,261,10,137,567,110,384,141,943,268,201,332,258,55,1,180,64,116,44,144,699,203,282,31,260,373 }, +{ 81,7,192,426,43,173,172,104,879,91,5,712,715,526,6,97,568,95,448,66,33,861,560,32,49,20,0,636,232,825,2,22 }, +{ 475,403,51,453,33,102,13,23,494,202,0,196,15,77,153,18,4,117,515,450,318,22,730,128,700,421,65,753,269,402,134,817 }, +{ 141,269,260,318,202,120,352,349,82,351,1,5,854,137,64,291,15,922,180,851,32,77,515,372,21,700,7,217,13,947,33,753 }, +{ 15,515,700,753,1,13,0,2,4,23,3,5,180,115,197,12,51,165,217,10,961,9,6,141,352,21,8,7,33,77,457,120 }, +{ 39,166,9,30,0,101,274,404,252,333,190,100,158,438,310,88,68,265,656,21,1,530,329,344,49,539,625,254,13,131,48,419 }, +{ 0,1,28,9,22,12,65,83,67,36,5,2,50,55,96,109,16,13,24,23,21,238,49,18,285,160,128,39,69,114,7,323 }, +{ 15,515,700,1,0,753,23,2,13,51,5,180,115,6,3,9,197,12,457,120,7,165,901,82,4,21,8,141,31,33,719,341 }, +{ 9,39,30,0,28,166,22,49,180,1,352,35,317,158,88,141,498,131,115,345,752,128,228,217,100,83,219,930,13,251,365,36 }, +{ 5,61,45,2,80,29,311,209,6,17,58,1,151,106,454,667,243,70,52,496,287,592,255,738,64,74,483,14,27,32,112,19 }, +{ 31,125,22,44,299,456,685,242,599,116,170,28,0,1,492,393,506,144,558,10,268,301,239,23,13,36,963,367,55,206,105,95 }, +{ 187,258,926,574,839,93,228,860,406,219,871,160,137,531,224,116,120,902,669,201,36,131,44,144,843,533,318,384,442,1,434,268 }, +{ 7,107,75,16,87,9,64,177,24,18,291,77,349,141,60,232,23,0,51,269,132,14,5,21,70,32,678,112,126,121,71,947 }, +{ 15,515,700,753,1,0,2,13,5,3,23,180,4,115,901,51,6,8,961,9,7,10,12,82,197,22,141,341,33,120,365,457 }, +{ 13,23,51,1,5,202,2,12,15,21,165,141,0,115,3,4,32,515,197,10,180,318,128,120,64,700,6,7,403,269,457,137 }, +{ 1,2,0,77,64,3,141,13,33,15,23,10,6,102,5,515,180,4,117,7,700,165,11,217,269,40,753,115,128,17,197,134 }, +{ 345,531,332,269,260,317,717,752,373,351,180,352,728,82,10,365,160,533,217,143,498,251,244,93,341,901,36,1,141,898,55,864 }, +{ 16,7,33,189,92,77,388,60,140,35,102,24,14,1,230,21,150,117,733,314,18,915,71,13,108,134,5,64,69,2,98,22 }, +{ 142,202,234,178,5,786,77,49,70,0,416,450,639,878,1,48,21,929,147,259,315,455,198,120,12,481,163,113,846,329,318,22 }, +{ 81,715,192,0,173,712,681,104,636,91,74,20,750,370,7,718,95,879,22,43,825,560,422,64,207,49,172,18,397,10,426,319 }, +{ 13,23,15,51,515,0,700,753,4,1,961,2,10,115,457,11,33,453,3,5,9,40,12,197,165,77,401,475,64,102,22,569 }, +{ 64,297,5,445,95,61,250,311,80,34,17,312,45,2,86,472,58,14,180,53,22,151,869,738,247,237,29,1,128,165,21,288 }, +{ 16,24,18,71,7,35,118,92,14,154,60,68,0,149,28,302,124,150,55,175,2,9,97,1,429,20,108,273,22,65,43,126 }, +{ 195,360,156,771,132,163,626,687,591,371,883,146,121,846,70,586,379,13,293,98,407,48,761,296,354,18,31,1,55,49,21,105 }, +{ 202,13,77,23,318,33,51,0,4,141,5,21,217,32,291,102,64,128,15,10,9,494,269,137,515,403,1,31,117,700,120,317 }, +{ 7,192,97,81,172,66,426,173,43,715,712,232,861,879,104,330,568,298,74,893,885,526,387,825,92,140,91,14,636,6,5,448 }, +{ 2,1,14,6,67,7,65,69,24,36,66,124,108,83,38,29,22,86,0,18,484,5,28,46,12,10,25,302,150,16,650,74 }, +{ 33,77,13,202,102,4,0,23,128,51,141,64,318,22,403,269,137,10,15,40,494,117,32,59,11,153,1,21,177,196,515,115 }, +{ 269,141,318,77,349,291,217,202,33,15,372,304,515,22,102,177,351,700,352,120,5,137,10,317,260,753,64,851,854,403,49,21 }, +{ 6,74,66,85,138,25,87,42,135,26,226,222,280,29,75,500,220,278,792,70,19,2,1,294,204,64,32,145,853,112,52,174 }, +{ 9,0,105,39,16,18,1,101,272,31,127,98,24,518,333,252,310,28,68,737,846,371,158,916,938,49,30,7,286,35,301,455 }, +{ 2,1,14,67,24,46,83,108,69,29,38,103,114,36,6,133,18,0,28,7,65,52,236,75,50,398,5,309,135,16,278,160 }, +{ 22,1,28,105,49,95,0,2,67,55,36,239,168,159,65,35,14,170,320,164,9,7,10,5,114,12,83,64,194,109,24,301 }, +{ 7,66,172,97,92,140,232,568,298,14,192,314,16,380,135,324,2,330,74,38,357,448,126,69,35,5,107,6,387,60,204,572 }, +{ 2,29,1,14,5,6,46,133,114,50,52,26,218,108,19,13,366,236,27,45,70,17,58,23,86,51,137,65,112,38,25,12 }, +{ 6,1,74,25,2,26,29,66,42,19,75,14,388,67,108,70,52,85,103,65,38,138,357,133,114,594,324,516,603,96,309,69 }, +{ 22,9,28,1,36,49,109,105,86,95,131,31,169,39,0,141,272,159,44,55,98,180,13,30,185,115,83,128,352,137,64,208 }, +{ 203,822,326,23,77,859,403,494,576,39,473,182,33,1,691,100,18,217,13,817,411,447,363,102,93,966,96,478,291,704,310,120 }, +{ 15,515,700,753,33,77,117,4,102,134,115,153,13,1,40,217,11,196,341,2,5,3,23,317,365,0,21,291,32,51,12,569 }, +{ 15,515,700,753,13,0,23,1,8,82,51,165,197,120,180,2,9,33,4,110,5,12,10,260,351,386,141,7,457,475,93,901 }, +{ 1,23,13,15,51,0,21,2,515,5,141,180,120,165,700,202,197,4,753,12,33,9,7,82,115,93,3,352,260,6,110,48 }, +{ 15,515,700,753,0,1,2,5,3,4,8,13,180,341,10,23,7,6,9,51,77,197,961,115,165,82,120,31,22,202,457,217 }, +{ 559,661,922,564,141,533,10,317,373,110,143,269,244,260,332,261,93,642,752,295,351,876,531,843,180,206,728,384,352,1,434,120 }, +{ 15,515,1,0,700,2,13,23,753,5,51,180,3,165,12,6,197,115,4,9,7,21,719,8,457,82,141,120,33,22,901,10 }, +{ 198,234,0,325,5,77,202,416,20,147,32,43,639,315,49,61,450,455,142,21,113,230,22,318,725,342,207,13,95,904,494,10 }, +{ 447,264,363,9,411,676,682,1,156,664,821,478,166,354,812,39,100,905,382,897,98,18,759,404,31,101,724,5,265,223,88,13 }, +{ 24,14,69,16,35,18,2,7,108,189,71,67,267,149,1,46,68,83,38,140,0,236,251,9,388,60,133,103,65,28,29,50 }, +{ 16,7,35,20,14,18,109,2,43,120,107,60,1,121,326,907,553,77,13,147,23,82,68,260,0,403,5,24,202,126,265,199 }, +{ 30,131,187,276,31,44,613,442,39,9,190,228,1,839,116,935,908,219,127,88,244,224,110,137,93,201,98,141,36,567,0,856 }, +{ 98,223,1,393,812,265,100,421,593,834,697,48,51,410,791,382,21,88,31,284,9,125,96,293,230,23,213,217,656,689,541,5 }, +{ 98,51,127,219,616,258,105,293,395,421,924,512,31,308,23,201,116,44,301,272,763,276,125,13,453,170,401,295,261,944,115,567 }, +{ 253,110,951,352,811,206,332,180,141,244,282,10,854,417,642,638,559,752,143,911,260,55,93,533,499,498,661,120,351,959,564,341 }, +{ 49,9,159,254,272,158,0,131,28,39,627,105,327,286,22,518,688,578,68,347,374,101,224,424,95,35,219,24,16,364,65,344 }, +{ 105,22,131,272,98,286,327,109,374,239,28,95,320,219,9,224,55,127,187,36,578,169,64,185,538,1,159,10,371,634,49,616 }, +{ 691,478,340,1,658,914,724,363,744,698,156,772,411,296,682,447,9,284,335,98,264,303,909,21,354,410,225,13,664,686,88,919 }, +{ 16,24,35,18,71,7,140,108,189,267,92,60,14,230,68,69,9,1,149,46,246,191,388,167,2,0,118,236,133,21,674,5 }, +{ 9,0,127,31,98,371,395,39,737,49,1,44,385,272,512,28,293,242,836,761,254,299,101,16,187,22,116,158,159,131,18,21 }, +{ 16,18,68,0,60,35,9,101,252,28,118,24,419,55,7,109,604,71,39,121,22,364,14,158,191,167,925,126,329,21,92,49 }, +{ 116,268,203,93,206,692,551,31,417,940,499,8,473,44,202,523,959,0,120,137,559,22,450,403,576,10,728,299,13,326,51,1 }, +{ 225,459,744,1,919,914,691,330,622,21,141,223,5,284,934,335,88,538,340,82,385,839,363,120,478,98,48,30,64,32,686,166 }, +{ 5,1,2,0,14,36,21,281,12,48,50,67,22,28,54,83,24,218,38,10,181,9,32,18,65,58,45,114,430,17,99,37 }, +{ 137,450,202,704,120,260,326,318,968,269,851,403,291,77,23,141,182,310,494,373,351,457,82,890,349,110,60,128,817,678,105,96 }, +{ 15,515,700,753,4,33,13,23,77,5,40,11,102,93,1,21,110,51,82,117,141,2,10,8,32,64,120,31,202,3,217,115 }, +{ 15,13,1,23,515,0,51,2,700,5,753,21,180,141,165,3,12,115,197,4,7,6,457,9,352,202,33,8,719,120,77,341 }, +{ 219,127,258,98,276,201,131,395,944,293,116,284,567,31,242,105,137,935,295,44,403,860,51,224,576,456,9,371,578,475,202,512 }, +{ 16,7,18,35,60,0,14,20,118,28,68,22,2,24,1,92,158,107,5,49,154,126,109,12,43,10,55,6,677,71,21,168 }, +{ 93,728,531,160,559,373,574,120,295,860,533,269,717,260,926,902,258,318,36,201,261,434,851,137,617,141,187,352,843,384,332,251 }, +{ 28,0,1,9,22,109,83,39,49,12,36,67,55,5,96,2,128,30,158,69,21,23,160,208,35,13,65,323,50,141,194,238 }, +{ 23,13,51,1,5,15,141,21,0,2,165,515,202,700,12,197,180,120,32,115,4,753,64,9,7,269,6,3,125,386,48,453 }, +{ 5,21,13,49,14,20,7,23,43,32,1,0,652,48,713,22,38,2,16,132,955,107,12,279,24,888,197,640,70,303,18,638 }, +{ 9,0,28,39,1,30,35,101,22,67,83,141,49,175,36,68,55,88,13,251,10,69,23,158,180,115,64,100,217,65,345,166 }, +{ 260,120,82,269,5,450,351,1,202,141,854,13,77,922,32,33,137,4,23,125,291,21,15,515,165,349,177,700,318,326,180,753 }, +{ 121,16,18,35,363,101,60,20,107,14,68,259,621,55,604,43,7,252,9,364,126,0,167,191,5,407,132,28,199,419,146,10 }, +{ 13,23,51,1,15,0,2,141,5,515,12,21,700,115,165,180,4,753,3,197,202,32,9,120,7,8,6,11,37,10,457,269 }, +{ 201,144,206,443,418,203,435,96,335,459,187,1,88,332,330,321,269,934,30,372,822,521,268,326,44,523,382,141,410,264,494,473 }, +{ 31,44,276,201,116,131,284,662,567,144,9,489,98,295,268,434,0,30,137,39,93,1,187,22,219,918,110,299,141,36,224,384 }, +{ 520,478,664,1,264,604,9,167,777,759,411,0,806,724,48,21,101,68,647,936,363,223,118,682,410,18,100,16,252,98,265,13 }, +{ 23,70,21,87,60,75,120,182,163,379,92,18,7,937,71,121,446,132,24,98,931,126,107,77,795,195,115,44,411,146,51,850 }, +{ 39,9,0,737,127,31,846,98,1,827,105,310,371,30,254,100,44,18,395,242,272,101,385,916,836,16,265,131,938,93,166,557 }, +{ 279,20,43,126,107,7,92,16,356,362,60,595,246,359,598,35,0,683,939,653,121,97,125,441,399,392,150,199,48,230,14,649 }, +{ 206,417,93,959,499,728,8,559,120,473,137,141,10,564,31,260,44,450,203,341,253,244,373,116,143,638,268,180,352,110,318,940 }, +{ 259,465,147,132,590,687,534,199,581,146,941,427,107,640,279,178,121,5,195,150,522,955,198,35,786,929,798,142,1,21,325,626 }, +{ 523,15,120,450,202,515,403,51,817,700,13,753,23,457,33,899,128,64,730,102,494,342,115,719,453,196,49,99,318,421,308,5 }, +{ 141,559,10,244,365,564,661,180,253,143,752,110,55,317,533,341,901,93,373,206,535,160,82,922,260,36,531,964,352,332,261,197 }, +{ 219,258,98,127,276,964,943,137,843,535,201,935,131,860,261,295,284,567,206,44,116,31,253,492,203,332,160,615,36,93,55,692 }, +{ 0,319,422,207,945,693,577,887,32,804,95,344,104,904,61,20,5,43,7,725,113,510,306,102,49,263,153,426,33,83,22,9 }, +{ 1,22,0,5,12,2,36,21,28,86,49,105,9,10,23,13,141,95,31,55,128,37,51,4,83,202,3,64,96,7,32,44 }, +{ 15,515,13,700,1,0,753,2,23,3,4,5,51,10,115,197,6,33,12,9,165,7,8,77,11,961,180,269,141,22,120,457 }, +{ 15,1,23,120,77,13,515,51,141,202,700,180,110,137,260,753,326,5,128,102,0,21,2,165,269,33,197,450,318,217,93,115 }, +{ 15,515,1,700,0,753,13,2,23,180,51,5,120,4,9,115,197,12,7,165,21,33,6,82,3,8,523,901,31,141,457,260 }, +{ 16,18,24,7,92,35,60,75,9,13,71,14,0,108,50,21,126,121,1,140,23,5,132,146,2,12,128,10,64,141,70,87 }, +{ 180,341,901,15,515,1,365,700,0,2,197,753,115,4,10,13,752,5,3,8,165,317,141,23,143,873,44,31,569,55,93,6 }, +{ 9,0,175,35,101,28,39,67,68,1,65,83,30,69,364,336,22,114,55,124,194,158,100,289,252,166,64,345,103,36,50,88 }, +{ 64,165,180,197,115,247,217,237,21,13,32,316,22,141,352,72,288,304,95,225,76,391,386,16,468,90,49,35,365,640,372,23 }, +{ 15,515,700,13,753,1,0,2,23,4,5,3,115,51,141,197,12,10,180,961,7,9,21,33,217,6,8,165,457,11,77,341 }, +{ 132,121,199,146,60,279,493,640,407,598,126,195,534,581,955,590,107,5,150,35,522,49,259,16,18,360,156,0,147,362,21,167 }, +{ 0,1,28,9,22,5,36,12,65,24,67,96,2,83,18,50,114,55,21,16,7,10,23,14,13,160,137,51,48,218,103,69 }, +{ 93,120,957,77,30,968,459,110,137,160,613,102,202,352,373,141,31,372,217,330,190,318,269,260,203,44,28,473,228,177,863,704 }, +{ 15,515,700,0,753,1,13,23,2,51,5,9,120,82,4,7,901,197,10,8,260,180,341,12,33,6,3,523,165,102,115,141 }, +{ 206,417,8,141,499,44,244,93,31,10,137,253,559,116,728,144,120,564,269,638,203,352,143,260,341,752,268,717,951,180,160,110 }, +{ 530,254,228,1,96,21,406,39,827,31,669,840,613,829,137,679,166,98,23,51,960,438,131,93,48,224,219,317,310,36,876,190 }, +{ 15,515,700,753,13,457,0,197,719,1,165,82,23,8,120,730,2,10,12,180,134,5,9,141,260,4,351,51,115,3,341,899 }, +{ 0,16,68,9,24,28,18,35,252,109,39,419,124,158,154,55,101,71,22,118,60,7,49,65,333,14,1,10,329,364,677,346 }, +{ 1,15,13,23,515,51,120,0,700,180,2,165,5,753,141,197,21,33,202,102,260,4,9,12,7,326,137,450,115,6,82,110 }, +{ 535,253,352,564,110,365,82,180,341,10,854,533,55,898,244,901,873,141,752,143,642,559,498,317,36,951,115,964,638,282,661,197 }, +{ 31,44,125,338,116,64,242,36,1,10,55,22,456,237,180,13,299,164,506,86,23,165,558,143,0,762,492,479,844,546,93,8 }, +{ 13,23,4,1,202,2,0,51,115,77,141,180,5,15,217,3,33,11,515,317,9,10,102,21,700,341,365,318,269,64,32,128 }, +{ 9,39,0,166,68,101,28,364,30,158,562,35,175,65,333,154,49,404,706,124,21,252,274,168,190,289,100,570,16,1,310,346 }, +{ 15,515,700,753,341,13,0,23,1,33,141,4,260,82,77,51,351,180,9,5,115,137,10,217,11,120,102,40,349,269,202,854 } diff --git a/src/ktx.imageio/etcdec.h b/src/ktx.imageio/etcdec.h new file mode 100644 index 0000000000..b90ac59d3e --- /dev/null +++ b/src/ktx.imageio/etcdec.h @@ -0,0 +1,597 @@ +// clang-format off +/* etcdec.h - v0.91 + provides functions to decompress blocks of ETC/EAC compressed images + written by Sergii "iOrange" Kudlai in 2022 + + This library does not allocate memory and is trying to use as less stack as possible + + The library was never optimized specifically for speed but for the overall size + it has zero external dependencies and is not using any runtime functions + + Supported ETC formats: + ETC1 + ETC2_RGB + ETC2_RGB_A1 ("punchthrough" alpha) + ETC2_RGBA + EAC_R11 + EAC_RG11 + + ETC1/ETC2_RGB/ETC2_RGB_A1/ETC2_RGBA are expected to decompress into 4*4 RGBA blocks 8bit per component (32bit pixel) + EAC_R11/EAC_RG11 are expected to decompress into 4*4 R/RG blocks of either 32bit float or 16bit unsigned int16 per + component (32/16bit (R11) and 64/32bit (RG11) pixel) + + For more info, issues and suggestions please visit https://github.com/iOrange/etcdec + + CREDITS: + Vladimir Vondrus (@mosra) - fixes for platforms that define char as unsigned type + + LICENSE: See end of file for license information. +*/ + +#ifndef ETCDEC_HEADER_INCLUDED +#define ETCDEC_HEADER_INCLUDED + +/* if ETCDEC_STATIC causes problems, try defining ETCDECDEF to 'inline' or 'static inline' */ +#ifndef ETCDECDEF +#ifdef ETCDEC_STATIC +#define ETCDECDEF static +#else +#ifdef __cplusplus +#define ETCDECDEF extern "C" +#else +#define ETCDECDEF extern +#endif +#endif +#endif + + +/* Used information sources: + + ETC1 compression + https://registry.khronos.org/OpenGL/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt + http://www.jacobstrom.com/publications/packman_sketch.pdf + + ETC2/EAC compression + https://registry.khronos.org/OpenGL/specs/gl/glspec43.core.pdf +*/ + + +#define ETCDEC_ETC_RGB_BLOCK_SIZE 8 +#define ETCDEC_ETC_RGB_A1_BLOCK_SIZE 8 +#define ETCDEC_EAC_RGBA_BLOCK_SIZE 16 +#define ETCDEC_EAC_R11_BLOCK_SIZE 8 +#define ETCDEC_EAC_RG11_BLOCK_SIZE 16 + +#define ETCDEC_ETC_RGB_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*ETCDEC_ETC_RGB_BLOCK_SIZE) +#define ETCDEC_ETC_RGB_A1_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*ETCDEC_ETC_RGB_A1_BLOCK_SIZE) +#define ETCDEC_EAC_RGBA_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*ETCDEC_EAC_RGBA_BLOCK_SIZE) +#define ETCDEC_EAC_R11_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*ETCDEC_EAC_R11_BLOCK_SIZE) +#define ETCDEC_EAC_RG11_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*ETCDEC_EAC_RG11_BLOCK_SIZE) + +ETCDECDEF void etcdec_etc_rgb(const void* compressedBlock, void* decompressedBlock, int destinationPitch); +ETCDECDEF void etcdec_etc_rgb_a1(const void* compressedBlock, void* decompressedBlock, int destinationPitch); +ETCDECDEF void etcdec_eac_rgba(const void* compressedBlock, void* decompressedBlock, int destinationPitch); +ETCDECDEF void etcdec_eac_r11_u16(const void* compressedBlock, void* decompressedBlock, int destinationPitch); +ETCDECDEF void etcdec_eac_rg11_u16(const void* compressedBlock, void* decompressedBlock, int destinationPitch); +ETCDECDEF void etcdec_eac_r11_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned); +ETCDECDEF void etcdec_eac_rg11_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned); + + +#ifdef ETCDEC_IMPLEMENTATION + +/* http://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend */ +static int etcdec__extend_sign(int val, int bits) { + return (val << (32 - bits)) >> (32 - bits); +} + +static int etcdec__clamp_255(int value) { + return value < 0 ? 0 : (value > 255 ? 255 : value); +} +static int etcdec__clamp_2047(int value) { + return value < 0 ? 0 : (value > 2047 ? 2047 : value); +} + + +#ifndef ETCDEC_BSWAP64 +static unsigned long long etcdec__bswap(unsigned long long x) { + return (((x) & 0xFF00000000000000ull) >> 56) | + (((x) & 0x00FF000000000000ull) >> 40) | + (((x) & 0x0000FF0000000000ull) >> 24) | + (((x) & 0x000000FF00000000ull) >> 8) | + (((x) & 0x00000000FF000000ull) << 8) | + (((x) & 0x0000000000FF0000ull) << 24) | + (((x) & 0x000000000000FF00ull) << 40) | + (((x) & 0x00000000000000FFull) << 56); +} + +#define ETCDEC_BSWAP64(x) etcdec__bswap(x) +#endif /* ETCDEC_BSWAP64 */ + +static void etcdec__decompress_legacy_etc_mode(unsigned long long block, + int r0, int g0, int b0, + int r1, int g1, int b1, + unsigned char* decompressed, + int isOpaque, + int destinationPitch) { + int flipBit, codeWord0, codeWord1; + int i, j, x0, y0, x1, y1, m, idx; + const int (*modifiersTablePtr)[4]; + + /* already remapped so we can just use pixel indices "as-is" */ + static int modifierTableRemappedOpaque[8][4] = { + { 2, 8, -2, -8 }, + { 5, 17, -5, -17 }, + { 9, 29, -9, -29 }, + { 13, 42, -13, -42 }, + { 18, 60, -18, -60 }, + { 24, 80, -24, -80 }, + { 33, 106, -33, -106 }, + { 47, 183, -47, -183 } + }; + + static int modifierTableRemappedTransparent[8][4] = { + { 0, 8, 0, -8 }, + { 0, 17, 0, -17 }, + { 0, 29, 0, -29 }, + { 0, 42, 0, -42 }, + { 0, 60, 0, -60 }, + { 0, 80, 0, -80 }, + { 0, 106, 0, -106 }, + { 0, 183, 0, -183 } + }; + + flipBit = (block & 0x100000000ull) != 0; + + codeWord0 = (block >> 37) & 0x7; + codeWord1 = (block >> 34) & 0x7; + + modifiersTablePtr = isOpaque ? modifierTableRemappedOpaque : modifierTableRemappedTransparent; + + /* now decode both blocks, using proper orientation based on flipBit */ + for (i = 0; i < 2; ++i) { + for (j = 0; j < 4; ++j) { + x0 = flipBit ? i : j; + x1 = flipBit ? (i + 2) : j; + y0 = flipBit ? j : i; + y1 = flipBit ? j : (i + 2); + + /* if isOpaque == 0 and idx is "msb=1 & lsb=0" (== 2) -> pixel is completely transparent */ + + /* block A */ + m = x0 + y0 * 4; + idx = (((block >> (m + 16)) & 1) << 1) | ((block >> m) & 1); + m = (x0 * destinationPitch) + (y0 * 4); + if (isOpaque || idx != 2) { + decompressed[m + 0] = (unsigned char)etcdec__clamp_255(r0 + modifiersTablePtr[codeWord0][idx]); + decompressed[m + 1] = (unsigned char)etcdec__clamp_255(g0 + modifiersTablePtr[codeWord0][idx]); + decompressed[m + 2] = (unsigned char)etcdec__clamp_255(b0 + modifiersTablePtr[codeWord0][idx]); + decompressed[m + 3] = 0xFF; + } else { + *((unsigned int*)(decompressed + m)) = 0u; + } + + // block B + m = x1 + y1 * 4; + idx = (((block >> (m + 16)) & 1) << 1) | ((block >> m) & 1); + m = (x1 * destinationPitch) + (y1 * 4); + if (isOpaque || idx != 2) { + decompressed[m + 0] = (unsigned char)etcdec__clamp_255(r1 + modifiersTablePtr[codeWord1][idx]); + decompressed[m + 1] = (unsigned char)etcdec__clamp_255(g1 + modifiersTablePtr[codeWord1][idx]); + decompressed[m + 2] = (unsigned char)etcdec__clamp_255(b1 + modifiersTablePtr[codeWord1][idx]); + decompressed[m + 3] = 0xFF; + } else { + *((unsigned int*)(decompressed + m)) = 0u; + } + } + } +} + +static void etcdec__decompress_etc_mode_t_h(unsigned long long block, + int mode, + unsigned char* decompressed, + int isOpaque, + int destinationPitch) { + int r0, g0, b0, r1, g1, b1; + int ra, rb, ga, gb, ba, bb, da, db, dist; + int i, j, k, idx; + unsigned int paintColors[4]; /* 0xAABBGGRR */ + + static char distanceTable[8] = { 3, 6, 11, 16, 23, 32, 41, 64 }; + + if (mode == 1) { /* "T" mode */ + ra = (block >> 59) & 0x3; + rb = (block >> 56) & 0x3; + g0 = (block >> 52) & 0xF; + b0 = (block >> 48) & 0xF; + r1 = (block >> 44) & 0xF; + g1 = (block >> 40) & 0xF; + b1 = (block >> 36) & 0xF; + da = (block >> 34) & 0x3; + db = (block >> 32) & 0x1; + + r0 = (ra << 2) | rb; + } else { /* "H" mode */ + r0 = (block >> 59) & 0xF; + ga = (block >> 56) & 0x7; + gb = (block >> 52) & 0x1; + ba = (block >> 51) & 0x1; + bb = (block >> 47) & 0x7; + r1 = (block >> 43) & 0xF; + g1 = (block >> 39) & 0xF; + b1 = (block >> 35) & 0xF; + da = (block >> 34) & 0x1; + db = (block >> 32) & 0x1; + + g0 = (ga << 1) | gb; + b0 = (ba << 3) | bb; + } + + /* These four bit values are extended to RGB888 by replicating + the four higher order bits in the four lower order bits. */ + r0 = (r0 << 4) | r0; + g0 = (g0 << 4) | g0; + b0 = (b0 << 4) | b0; + r1 = (r1 << 4) | r1; + g1 = (g1 << 4) | g1; + b1 = (b1 << 4) | b1; + + if (mode == 1) { /* "T" mode */ + dist = (da << 1) | db; + dist = distanceTable[dist]; + + paintColors[0] = 0xFF000000 | (b0 << 16) | (g0 << 8) | r0; + paintColors[2] = 0xFF000000 | (b1 << 16) | (g1 << 8) | r1; + paintColors[1] = 0xFF000000 | (etcdec__clamp_255(b1 + dist) << 16) + | (etcdec__clamp_255(g1 + dist) << 8) + | etcdec__clamp_255(r1 + dist); + paintColors[3] = 0xFF000000 | (etcdec__clamp_255(b1 - dist) << 16) + | (etcdec__clamp_255(g1 - dist) << 8) + | etcdec__clamp_255(r1 - dist); + } else { /* "H" mode */ + dist = ((r0 << 16) | (g0 << 8) | b0) >= ((r1 << 16) | (g1 << 8) | b1) ? 1 : 0; + dist |= (da << 2) | (db << 1); + dist = distanceTable[dist]; + + paintColors[0] = 0xFF000000 | (etcdec__clamp_255(b0 + dist) << 16) + | (etcdec__clamp_255(g0 + dist) << 8) + | etcdec__clamp_255(r0 + dist); + paintColors[1] = 0xFF000000 | (etcdec__clamp_255(b0 - dist) << 16) + | (etcdec__clamp_255(g0 - dist) << 8) + | etcdec__clamp_255(r0 - dist); + paintColors[2] = 0xFF000000 | (etcdec__clamp_255(b1 + dist) << 16) + | (etcdec__clamp_255(g1 + dist) << 8) + | etcdec__clamp_255(r1 + dist); + paintColors[3] = 0xFF000000 | (etcdec__clamp_255(b1 - dist) << 16) + | (etcdec__clamp_255(g1 - dist) << 8) + | etcdec__clamp_255(r1 - dist); + } + + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { + k = i + j * 4; + idx = (((block >> (k + 16)) & 1) << 1) | ((block >> k) & 1); + /* if isOpaque == 0 and idx is "msb=1 & lsb=0" (== 2) -> pixel is completely transparent */ + if (isOpaque || idx != 2) { + ((unsigned int*)decompressed)[j] = paintColors[idx]; + } else { + ((unsigned int*)decompressed)[j] = 0u; + } + } + + decompressed += destinationPitch; + } +} + +static void etcdec__decompress_etc_mode_planar(unsigned long long block, unsigned char* decompressed, int destinationPitch) { + int ro, go, bo, rh, gh, bh, rv, gv, bv; + int go1, go2, bo1, bo2, bo3, rh1, rh2; + int i, j; + + ro = (block >> 57) & 0x3F; + go1 = (block >> 56) & 0x01; + go2 = (block >> 49) & 0x3F; + bo1 = (block >> 48) & 0x01; + bo2 = (block >> 43) & 0x03; + bo3 = (block >> 39) & 0x07; + rh1 = (block >> 34) & 0x1F; + rh2 = (block >> 32) & 0x01; + gh = (block >> 25) & 0x7F; + bh = (block >> 19) & 0x3F; + rv = (block >> 13) & 0x3F; + gv = (block >> 6) & 0x7F; + bv = (block >> 0) & 0x3F; + + go = (go1 << 6) | go2; + bo = (bo1 << 5) | (bo2 << 3) | bo3; + rh = (rh1 << 1) | rh2; + + ro = (ro << 2) | (ro >> 4); + rh = (rh << 2) | (rh >> 4); + rv = (rv << 2) | (rv >> 4); + go = (go << 1) | (go >> 6); + gh = (gh << 1) | (gh >> 6); + gv = (gv << 1) | (gv >> 6); + bo = (bo << 2) | (bo >> 4); + bh = (bh << 2) | (bh >> 4); + bv = (bv << 2) | (bv >> 4); + + /* With three base colors in RGB888 format, the color of each + pixel can then be determined as: + R(x, y) = x * (RH − RO) / 4.0 + y * (RV − RO) / 4.0 + RO + G(x, y) = x * (GH − GO) / 4.0 + y * (GV − GO) / 4.0 + GO + B(x, y) = x * (BH − BO) / 4.0 + y * (BV − BO) / 4.0 + BO */ + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { + decompressed[(j * 4) + 0] = (unsigned char)etcdec__clamp_255((j * (rh - ro) + i * (rv - ro) + (ro << 2) + 2) >> 2); + decompressed[(j * 4) + 1] = (unsigned char)etcdec__clamp_255((j * (gh - go) + i * (gv - go) + (go << 2) + 2) >> 2); + decompressed[(j * 4) + 2] = (unsigned char)etcdec__clamp_255((j * (bh - bo) + i * (bv - bo) + (bo << 2) + 2) >> 2); + decompressed[(j * 4) + 3] = 0xFF; + } + + decompressed += destinationPitch; + } +} + +static void etcdec__decompress_etc_block(const void* compressedBlock, void* decompressedBlock, int isPunchthrough, int destinationPitch) { + unsigned long long block; + int diffBit, newMode; + int r0, g0, b0, r1, g1, b1; + unsigned char* decompressed; + + block = ETCDEC_BSWAP64(((unsigned long long*)compressedBlock)[0]); + decompressed = (unsigned char*)decompressedBlock; + + /* if isPunchthrough == TRUE -> this is actually an 'opaque' bit */ + diffBit = (block & 0x200000000ull) != 0; + + newMode = 0; /* assume legacy mode by default */ + + if (!isPunchthrough && !diffBit) { /* "individual" mode */ + r0 = (block >> 60) & 0xF; + r1 = (block >> 56) & 0xF; + g0 = (block >> 52) & 0xF; + g1 = (block >> 48) & 0xF; + b0 = (block >> 44) & 0xF; + b1 = (block >> 40) & 0xF; + + /* These four bit values are extended to RGB888 by replicating + the four higher order bits in the four lower order bits. */ + r0 = (r0 << 4) | r0; + g0 = (g0 << 4) | g0; + b0 = (b0 << 4) | b0; + r1 = (r1 << 4) | r1; + g1 = (g1 << 4) | g1; + b1 = (b1 << 4) | b1; + } else { /* "differential" mode */ + r0 = (block >> 59) & 0x1F; + r1 = r0 + etcdec__extend_sign((block >> 56) & 0x7, 3); + g0 = (block >> 51) & 0x1F; + g1 = g0 + etcdec__extend_sign((block >> 48) & 0x7, 3); + b0 = (block >> 43) & 0x1F; + b1 = b0 + etcdec__extend_sign((block >> 40) & 0x7, 3); + + if (r1 < 0 || r1 > 31) { + /* First, R and dR are added, and if the sum is not + within the interval[0, 31], the "T" mode is selected */ + newMode = 1; + } else if (g1 < 0 || g1 > 31) { + /* Otherwise, if the sum of Gand dG is outside + the interval[0, 31], the "H" mode is selected */ + newMode = 2; + } else if (b1 < 0 || b1 > 31) { + /* Otherwise, if the sum of Band dB is outside + of the interval[0, 31], the "planar" mode is selected */ + newMode = 3; + } else { + /* Finally the "differential" mode is selected */ + + /* These five-bit codewords are extended to RGB888 by replicating + the top three highest order bits to the three lowest order bits. */ + r0 = (r0 << 3) | (r0 >> 2); + g0 = (g0 << 3) | (g0 >> 2); + b0 = (b0 << 3) | (b0 >> 2); + r1 = (r1 << 3) | (r1 >> 2); + g1 = (g1 << 3) | (g1 >> 2); + b1 = (b1 << 3) | (b1 >> 2); + } + } + + if (!newMode) { /* legacy mode: ETC1 & ETC2 */ + etcdec__decompress_legacy_etc_mode(block, r0, g0, b0, r1, g1, b1, decompressed, !isPunchthrough || diffBit, destinationPitch); + } else if (newMode < 3) { /* ETC2 "T" and "H" modes */ + etcdec__decompress_etc_mode_t_h(block, newMode, decompressed, !isPunchthrough || diffBit, destinationPitch); + } else { /* ETC2 "planar" mode */ + etcdec__decompress_etc_mode_planar(block, decompressed, destinationPitch); + } +} + +static void etcdec__decompress_eac_block(const void* compressedBlock, void* decompressedBlock, int is11Bit, int destinationPitch, int pixelSize) { + unsigned long long block; + unsigned char* decompressed; + int baseCodeword, multiplier, modifier, idx; + int i, j, k; + const signed char* modifiersPtr; + + static signed char modifierTable[16][8] = { + { -3, -6, -9, -15, 2, 5, 8, 14 }, + { -3, -7, -10, -13, 2, 6, 9, 12 }, + { -2, -5, -8, -13, 1, 4, 7, 12 }, + { -2, -4, -6, -13, 1, 3, 5, 12 }, + { -3, -6, -8, -12, 2, 5, 7, 11 }, + { -3, -7, -9, -11, 2, 6, 8, 10 }, + { -4, -7, -8, -11, 3, 6, 7, 10 }, + { -3, -5, -8, -11, 2, 4, 7, 10 }, + { -2, -6, -8, -10, 1, 5, 7, 9 }, + { -2, -5, -8, -10, 1, 4, 7, 9 }, + { -2, -4, -8, -10, 1, 3, 7, 9 }, + { -2, -5, -7, -10, 1, 4, 6, 9 }, + { -3, -4, -7, -10, 2, 3, 6, 9 }, + { -1, -2, -3, -10, 0, 1, 2, 9 }, + { -4, -6, -8, -9, 3, 5, 7, 8 }, + { -3, -5, -7, -9, 2, 4, 6, 8 } + }; + + block = ETCDEC_BSWAP64(((unsigned long long*)compressedBlock)[0]); + decompressed = (unsigned char*)decompressedBlock; + baseCodeword = (block >> 56) & 0xFF; + multiplier = (block >> 52) & 0xF; + modifiersPtr = modifierTable[(block >> 48) & 0xF]; + + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { + idx = (block >> ((15 - (j * 4 + i)) * 3)) & 0x7; + modifier = modifiersPtr[idx]; + + if (is11Bit) { + /* EAC R11/RG11 */ + /* If the multiplier value is zero, we should set the multiplier to 1.0/8.0 */ + /* so that the "multiplier * 8" will resolve to 1 */ + k = etcdec__clamp_2047((baseCodeword * 8 + 4) + (modifier * (multiplier ? multiplier * 8 : 1))); + + /* Now just extending the 11-bits value to 16-bits for convenience */ + *((unsigned short*)(decompressed + (j * pixelSize))) = (unsigned short)((k << 5) | (k >> 6)); + } else { + /* EAC ETC2 Alpha channel */ + decompressed[j * pixelSize] = (unsigned char)etcdec__clamp_255(baseCodeword + (modifier * multiplier)); + } + } + + decompressed += destinationPitch; + } +} + + +ETCDECDEF void etcdec_etc_rgb(const void* compressedBlock, void* decompressedBlock, int destinationPitch) { + etcdec__decompress_etc_block(compressedBlock, decompressedBlock, 0, destinationPitch); +} + +ETCDECDEF void etcdec_etc_rgb_a1(const void* compressedBlock, void* decompressedBlock, int destinationPitch) { + etcdec__decompress_etc_block(compressedBlock, decompressedBlock, 1, destinationPitch); +} + +ETCDECDEF void etcdec_eac_rgba(const void* compressedBlock, void* decompressedBlock, int destinationPitch) { + /* first half of the block (64 bits) is an Alpha (EAC 8 bits) compressed data */ + /* second half of the block (64 bits) is just an ETC2_RGB compressed data */ + etcdec__decompress_etc_block(((char*)compressedBlock) + 8, decompressedBlock, 0, destinationPitch); + etcdec__decompress_eac_block(compressedBlock, ((char*)decompressedBlock) + 3, 0, destinationPitch, 4); +} + +ETCDECDEF void etcdec_eac_r11_u16(const void* compressedBlock, void* decompressedBlock, int destinationPitch) { + etcdec__decompress_eac_block(compressedBlock, decompressedBlock, 1, destinationPitch, 2); +} + +ETCDECDEF void etcdec_eac_rg11_u16(const void* compressedBlock, void* decompressedBlock, int destinationPitch) { + etcdec__decompress_eac_block(compressedBlock, decompressedBlock, 1, destinationPitch, 4); + etcdec__decompress_eac_block(((char*)compressedBlock) + 8, ((char*)decompressedBlock) + 2, 1, destinationPitch, 4); +} + +ETCDECDEF void etcdec_eac_r11_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) { + unsigned short block[16]; + unsigned char* decompressed; + const unsigned short* b; + int i, j; + short s; + + etcdec_eac_r11_u16(compressedBlock, block, 4 * 2); + b = block; + decompressed = (unsigned char*)decompressedBlock; + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j, ++b) { + if (isSigned) { + s = (short)b[0]; + *((float*)(decompressed + j * 4)) = (s < 0 ? ((float)s / 32768.0f) : ((float)s / 32767.0f)); + } else { + *((float*)(decompressed + j * 4)) = (float)b[0] / 65535.0f; + } + } + decompressed += destinationPitch; + } +} + +ETCDECDEF void etcdec_eac_rg11_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) { + unsigned short block[16*2]; + unsigned char* decompressed; + const unsigned short* b; + int i, j; + short sr, sg; + + etcdec_eac_rg11_u16(compressedBlock, block, 4 * 4); + b = block; + decompressed = (unsigned char*)decompressedBlock; + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j, b += 2) { + if (isSigned) { + sr = (short)b[0]; + sg = (short)b[1]; + *((float*)(decompressed + j * 8 + 0)) = (sr < 0 ? ((float)sr / 32768.0f) : ((float)sr / 32767.0f)); + *((float*)(decompressed + j * 8 + 4)) = (sg < 0 ? ((float)sg / 32768.0f) : ((float)sg / 32767.0f)); + } + else { + *((float*)(decompressed + j * 8 + 0)) = (float)b[0] / 65535.0f; + *((float*)(decompressed + j * 8 + 4)) = (float)b[1] / 65535.0f; + } + } + decompressed += destinationPitch; + } +} + +#endif /* ETCDEC_IMPLEMENTATION */ + +#endif /* ETCDEC_HEADER_INCLUDED */ + +/* LICENSE: + +This software is available under 2 licenses -- choose whichever you prefer. + +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License + +Copyright (c) 2022 Sergii Kudlai + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +------------------------------------------------------------------------------ +ALTERNATIVE B - The Unlicense + +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to + +*/ diff --git a/src/ktx.imageio/ktx_pvt.h b/src/ktx.imageio/ktx_pvt.h new file mode 100644 index 0000000000..d5ab75cfa7 --- /dev/null +++ b/src/ktx.imageio/ktx_pvt.h @@ -0,0 +1,362 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +OIIO_PLUGIN_NAMESPACE_BEGIN + +#define BCN_BLOCK_SIZE 4 +#define ETC_BLOCK_SIZE 4 + +#define BC1_BLOCK_SIZE 8 +#define BC2_BLOCK_SIZE 16 +#define BC3_BLOCK_SIZE 16 +#define BC4_BLOCK_SIZE 8 +#define BC5_BLOCK_SIZE 16 +#define BC6H_BLOCK_SIZE 16 +#define BC7_BLOCK_SIZE 16 + +#define BC1_OUTPUT_NCHANNELS 4 +#define BC3_OUTPUT_NCHANNELS 4 +#define BC4_OUTPUT_NCHANNELS 1 +#define BC5_OUTPUT_NCHANNELS 2 +#define BC6H_OUTPUT_NCHANNELS 4 +#define BC7_OUTPUT_NCHANNELS 4 + +// ETC2_RGB decompresses to RGBA (otherwise another inner loop with memcpy) +#define ETC2_RGB_OUTPUT_NCHANNELS 4 +#define ETC2_RGBA_OUTPUT_NCHANNELS 4 +#define ETC2_RGB_A1_OUTPUT_NCHANNELS 4 + + + +// this is: "«KTX 20»\r\n\x1A\n" +static const uint8_t KTX2_IDENTIFIER[12] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, + 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; + + + +enum class TextureKind : uint32_t { + SINGLE_TEXTURE_1D, + SINGLE_TEXTURE_2D, + SINGLE_TEXTURE_3D, + CUBEMAP_TEXTURE, + ARRAY_TEXTURE_1D, + ARRAY_TEXTURE_2D, + ARRAY_TEXTURE_3D, + ARRAY_TEXTURE_CUBEMAP, +}; + + + +enum class BlockCompression : uint8_t { + NONE = 0, + BC1 = 1, ///< aka DXT1 + BC2 = 2, ///< aka DXT3 + BC3 = 3, ///< aka DXT5 + BC4 = 4, + BC5 = 5, + BC6HU = 6, + BC6HS = 7, + BC7 = 8, + ETC1 = 9, + ETC2_RGB = 10, + ETC2_RGB_A1 = 11, + ETC2_RGBA = 12, + EAC_R11 = 13, + EAC_RG11 = 14, + ASTC = 15, +}; + + + +static inline std::string +block_compression_name(BlockCompression cmp) +{ + switch (cmp) { + case BlockCompression::NONE: return "NONE"; + case BlockCompression::BC1: return "BC1"; + case BlockCompression::BC2: return "BC2"; + case BlockCompression::BC3: return "BC3"; + case BlockCompression::BC4: return "BC4"; + case BlockCompression::BC5: return "BC5"; + case BlockCompression::BC6HU: return "BC6HU"; + case BlockCompression::BC6HS: return "BC6HS"; + case BlockCompression::BC7: return "BC7"; + case BlockCompression::ETC1: return "ETC1"; + case BlockCompression::ETC2_RGB: return "ETC2_RGB"; + case BlockCompression::ETC2_RGB_A1: return "ETC2_RGB_A1"; + case BlockCompression::ETC2_RGBA: return "ETC2_RGBA"; + case BlockCompression::EAC_R11: return "EAC_R11"; + case BlockCompression::EAC_RG11: return "EAC_RG11"; + case BlockCompression::ASTC: return "ASTC"; + default: break; + } + return std::to_string(static_cast(cmp)); +} + + + +struct KTXglFormat { + uint32_t glInternalformat; + uint32_t glFormat; + uint32_t glType; +}; + + + +// TODO: VkFormat struct should be autogenerated via CMake and the Vulkan +// version +// Provided by VK_VERSION_1_0 +typedef enum VkFormat : uint32_t { + VK_FORMAT_UNDEFINED = 0, + VK_FORMAT_R4G4_UNORM_PACK8 = 1, + VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2, + VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3, + VK_FORMAT_R5G6B5_UNORM_PACK16 = 4, + VK_FORMAT_B5G6R5_UNORM_PACK16 = 5, + VK_FORMAT_R5G5B5A1_UNORM_PACK16 = 6, + VK_FORMAT_B5G5R5A1_UNORM_PACK16 = 7, + VK_FORMAT_A1R5G5B5_UNORM_PACK16 = 8, + VK_FORMAT_R8_UNORM = 9, + VK_FORMAT_R8_SNORM = 10, + VK_FORMAT_R8_USCALED = 11, + VK_FORMAT_R8_SSCALED = 12, + VK_FORMAT_R8_UINT = 13, + VK_FORMAT_R8_SINT = 14, + VK_FORMAT_R8_SRGB = 15, + VK_FORMAT_R8G8_UNORM = 16, + VK_FORMAT_R8G8_SNORM = 17, + VK_FORMAT_R8G8_USCALED = 18, + VK_FORMAT_R8G8_SSCALED = 19, + VK_FORMAT_R8G8_UINT = 20, + VK_FORMAT_R8G8_SINT = 21, + VK_FORMAT_R8G8_SRGB = 22, + VK_FORMAT_R8G8B8_UNORM = 23, + VK_FORMAT_R8G8B8_SNORM = 24, + VK_FORMAT_R8G8B8_USCALED = 25, + VK_FORMAT_R8G8B8_SSCALED = 26, + VK_FORMAT_R8G8B8_UINT = 27, + VK_FORMAT_R8G8B8_SINT = 28, + VK_FORMAT_R8G8B8_SRGB = 29, + VK_FORMAT_B8G8R8_UNORM = 30, + VK_FORMAT_B8G8R8_SNORM = 31, + VK_FORMAT_B8G8R8_USCALED = 32, + VK_FORMAT_B8G8R8_SSCALED = 33, + VK_FORMAT_B8G8R8_UINT = 34, + VK_FORMAT_B8G8R8_SINT = 35, + VK_FORMAT_B8G8R8_SRGB = 36, + VK_FORMAT_R8G8B8A8_UNORM = 37, + VK_FORMAT_R8G8B8A8_SNORM = 38, + VK_FORMAT_R8G8B8A8_USCALED = 39, + VK_FORMAT_R8G8B8A8_SSCALED = 40, + VK_FORMAT_R8G8B8A8_UINT = 41, + VK_FORMAT_R8G8B8A8_SINT = 42, + VK_FORMAT_R8G8B8A8_SRGB = 43, + VK_FORMAT_B8G8R8A8_UNORM = 44, + VK_FORMAT_B8G8R8A8_SNORM = 45, + VK_FORMAT_B8G8R8A8_USCALED = 46, + VK_FORMAT_B8G8R8A8_SSCALED = 47, + VK_FORMAT_B8G8R8A8_UINT = 48, + VK_FORMAT_B8G8R8A8_SINT = 49, + VK_FORMAT_B8G8R8A8_SRGB = 50, + VK_FORMAT_A8B8G8R8_UNORM_PACK32 = 51, + VK_FORMAT_A8B8G8R8_SNORM_PACK32 = 52, + VK_FORMAT_A8B8G8R8_USCALED_PACK32 = 53, + VK_FORMAT_A8B8G8R8_SSCALED_PACK32 = 54, + VK_FORMAT_A8B8G8R8_UINT_PACK32 = 55, + VK_FORMAT_A8B8G8R8_SINT_PACK32 = 56, + VK_FORMAT_A8B8G8R8_SRGB_PACK32 = 57, + VK_FORMAT_A2R10G10B10_UNORM_PACK32 = 58, + VK_FORMAT_A2R10G10B10_SNORM_PACK32 = 59, + VK_FORMAT_A2R10G10B10_USCALED_PACK32 = 60, + VK_FORMAT_A2R10G10B10_SSCALED_PACK32 = 61, + VK_FORMAT_A2R10G10B10_UINT_PACK32 = 62, + VK_FORMAT_A2R10G10B10_SINT_PACK32 = 63, + VK_FORMAT_A2B10G10R10_UNORM_PACK32 = 64, + VK_FORMAT_A2B10G10R10_SNORM_PACK32 = 65, + VK_FORMAT_A2B10G10R10_USCALED_PACK32 = 66, + VK_FORMAT_A2B10G10R10_SSCALED_PACK32 = 67, + VK_FORMAT_A2B10G10R10_UINT_PACK32 = 68, + VK_FORMAT_A2B10G10R10_SINT_PACK32 = 69, + VK_FORMAT_R16_UNORM = 70, + VK_FORMAT_R16_SNORM = 71, + VK_FORMAT_R16_USCALED = 72, + VK_FORMAT_R16_SSCALED = 73, + VK_FORMAT_R16_UINT = 74, + VK_FORMAT_R16_SINT = 75, + VK_FORMAT_R16_SFLOAT = 76, + VK_FORMAT_R16G16_UNORM = 77, + VK_FORMAT_R16G16_SNORM = 78, + VK_FORMAT_R16G16_USCALED = 79, + VK_FORMAT_R16G16_SSCALED = 80, + VK_FORMAT_R16G16_UINT = 81, + VK_FORMAT_R16G16_SINT = 82, + VK_FORMAT_R16G16_SFLOAT = 83, + VK_FORMAT_R16G16B16_UNORM = 84, + VK_FORMAT_R16G16B16_SNORM = 85, + VK_FORMAT_R16G16B16_USCALED = 86, + VK_FORMAT_R16G16B16_SSCALED = 87, + VK_FORMAT_R16G16B16_UINT = 88, + VK_FORMAT_R16G16B16_SINT = 89, + VK_FORMAT_R16G16B16_SFLOAT = 90, + VK_FORMAT_R16G16B16A16_UNORM = 91, + VK_FORMAT_R16G16B16A16_SNORM = 92, + VK_FORMAT_R16G16B16A16_USCALED = 93, + VK_FORMAT_R16G16B16A16_SSCALED = 94, + VK_FORMAT_R16G16B16A16_UINT = 95, + VK_FORMAT_R16G16B16A16_SINT = 96, + VK_FORMAT_R16G16B16A16_SFLOAT = 97, + VK_FORMAT_R32_UINT = 98, + VK_FORMAT_R32_SINT = 99, + VK_FORMAT_R32_SFLOAT = 100, + VK_FORMAT_R32G32_UINT = 101, + VK_FORMAT_R32G32_SINT = 102, + VK_FORMAT_R32G32_SFLOAT = 103, + VK_FORMAT_R32G32B32_UINT = 104, + VK_FORMAT_R32G32B32_SINT = 105, + VK_FORMAT_R32G32B32_SFLOAT = 106, + VK_FORMAT_R32G32B32A32_UINT = 107, + VK_FORMAT_R32G32B32A32_SINT = 108, + VK_FORMAT_R32G32B32A32_SFLOAT = 109, + VK_FORMAT_R64_UINT = 110, + VK_FORMAT_R64_SINT = 111, + VK_FORMAT_R64_SFLOAT = 112, + VK_FORMAT_R64G64_UINT = 113, + VK_FORMAT_R64G64_SINT = 114, + VK_FORMAT_R64G64_SFLOAT = 115, + VK_FORMAT_R64G64B64_UINT = 116, + VK_FORMAT_R64G64B64_SINT = 117, + VK_FORMAT_R64G64B64_SFLOAT = 118, + VK_FORMAT_R64G64B64A64_UINT = 119, + VK_FORMAT_R64G64B64A64_SINT = 120, + VK_FORMAT_R64G64B64A64_SFLOAT = 121, + VK_FORMAT_B10G11R11_UFLOAT_PACK32 = 122, + VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 = 123, + VK_FORMAT_D16_UNORM = 124, + VK_FORMAT_X8_D24_UNORM_PACK32 = 125, + VK_FORMAT_D32_SFLOAT = 126, + VK_FORMAT_S8_UINT = 127, + VK_FORMAT_D16_UNORM_S8_UINT = 128, + VK_FORMAT_D24_UNORM_S8_UINT = 129, + VK_FORMAT_D32_SFLOAT_S8_UINT = 130, + VK_FORMAT_BC1_RGB_UNORM_BLOCK = 131, + VK_FORMAT_BC1_RGB_SRGB_BLOCK = 132, + VK_FORMAT_BC1_RGBA_UNORM_BLOCK = 133, + VK_FORMAT_BC1_RGBA_SRGB_BLOCK = 134, + VK_FORMAT_BC2_UNORM_BLOCK = 135, + VK_FORMAT_BC2_SRGB_BLOCK = 136, + VK_FORMAT_BC3_UNORM_BLOCK = 137, + VK_FORMAT_BC3_SRGB_BLOCK = 138, + VK_FORMAT_BC4_UNORM_BLOCK = 139, + VK_FORMAT_BC4_SNORM_BLOCK = 140, + VK_FORMAT_BC5_UNORM_BLOCK = 141, + VK_FORMAT_BC5_SNORM_BLOCK = 142, + VK_FORMAT_BC6H_UFLOAT_BLOCK = 143, + VK_FORMAT_BC6H_SFLOAT_BLOCK = 144, + VK_FORMAT_BC7_UNORM_BLOCK = 145, + VK_FORMAT_BC7_SRGB_BLOCK = 146, + VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK = 147, + VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK = 148, + VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK = 149, + VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK = 150, + VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK = 151, + VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK = 152, + VK_FORMAT_EAC_R11_UNORM_BLOCK = 153, + VK_FORMAT_EAC_R11_SNORM_BLOCK = 154, + VK_FORMAT_EAC_R11G11_UNORM_BLOCK = 155, + VK_FORMAT_EAC_R11G11_SNORM_BLOCK = 156, + VK_FORMAT_ASTC_4x4_UNORM_BLOCK = 157, + VK_FORMAT_ASTC_4x4_SRGB_BLOCK = 158, + VK_FORMAT_ASTC_5x4_UNORM_BLOCK = 159, + VK_FORMAT_ASTC_5x4_SRGB_BLOCK = 160, + VK_FORMAT_ASTC_5x5_UNORM_BLOCK = 161, + VK_FORMAT_ASTC_5x5_SRGB_BLOCK = 162, + VK_FORMAT_ASTC_6x5_UNORM_BLOCK = 163, + VK_FORMAT_ASTC_6x5_SRGB_BLOCK = 164, + VK_FORMAT_ASTC_6x6_UNORM_BLOCK = 165, + VK_FORMAT_ASTC_6x6_SRGB_BLOCK = 166, + VK_FORMAT_ASTC_8x5_UNORM_BLOCK = 167, + VK_FORMAT_ASTC_8x5_SRGB_BLOCK = 168, + VK_FORMAT_ASTC_8x6_UNORM_BLOCK = 169, + VK_FORMAT_ASTC_8x6_SRGB_BLOCK = 170, + VK_FORMAT_ASTC_8x8_UNORM_BLOCK = 171, + VK_FORMAT_ASTC_8x8_SRGB_BLOCK = 172, + VK_FORMAT_ASTC_10x5_UNORM_BLOCK = 173, + VK_FORMAT_ASTC_10x5_SRGB_BLOCK = 174, + VK_FORMAT_ASTC_10x6_UNORM_BLOCK = 175, + VK_FORMAT_ASTC_10x6_SRGB_BLOCK = 176, + VK_FORMAT_ASTC_10x8_UNORM_BLOCK = 177, + VK_FORMAT_ASTC_10x8_SRGB_BLOCK = 178, + VK_FORMAT_ASTC_10x10_UNORM_BLOCK = 179, + VK_FORMAT_ASTC_10x10_SRGB_BLOCK = 180, + VK_FORMAT_ASTC_12x10_UNORM_BLOCK = 181, + VK_FORMAT_ASTC_12x10_SRGB_BLOCK = 182, + VK_FORMAT_ASTC_12x12_UNORM_BLOCK = 183, + VK_FORMAT_ASTC_12x12_SRGB_BLOCK = 184, +} VkFormat; + + + +struct FormatInfo { + int nbrchannels; + TypeDesc typedesc; + BlockCompression compression { BlockCompression::NONE }; + VkFormat decompressed_format { VK_FORMAT_UNDEFINED }; +}; + + + +// +// Note: +// Colorspace detection from VkFormat is simply wrong/not complete. You should +// do colorspace detection via the color model and transfer function. +// See: +// https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_data_format_descriptor +// +// TODO: there are a lot of formats to test so currently only "widely" used +// formats are supported (every format included here has to have a corresponding +// test file with the same VkFormat). +// +inline bool +extract_info_from_format(VkFormat vkformat, FormatInfo& formatinfo) +{ + // clang-format off + switch (vkformat) { + // Raw, uncompressed formats + case VK_FORMAT_R8_UNORM: formatinfo = { 1, TypeDesc::UINT8, BlockCompression::NONE }; return true; + case VK_FORMAT_R8G8_UNORM: formatinfo = { 2, TypeDesc::UINT8, BlockCompression::NONE }; return true; + case VK_FORMAT_R8G8B8_SRGB: formatinfo = { 3, TypeDesc::UINT8, BlockCompression::NONE }; return true; + case VK_FORMAT_R8G8B8A8_SRGB: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::NONE }; return true; + // ETC2 block-compressed formats + // TODO: decompress ETC2_RGB into RGB format (not RGBA). This requires some changes + case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: formatinfo = { 3, TypeDesc::UINT8, BlockCompression::ETC2_RGB, VK_FORMAT_R8G8B8A8_SRGB }; return true; + case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ETC2_RGB_A1, VK_FORMAT_R8G8B8A8_SRGB }; return true; + case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ETC2_RGBA, VK_FORMAT_R8G8B8A8_SRGB }; return true; + // BCn formats + case VK_FORMAT_BC1_RGB_SRGB_BLOCK: formatinfo = { 3, TypeDesc::UINT8, BlockCompression::BC1, VK_FORMAT_R8G8B8A8_SRGB }; return true; + case VK_FORMAT_BC3_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC3, VK_FORMAT_R8G8B8A8_SRGB }; return true; + case VK_FORMAT_BC4_UNORM_BLOCK: formatinfo = { 1, TypeDesc::UINT8, BlockCompression::BC4, VK_FORMAT_R8_UNORM }; return true; + case VK_FORMAT_BC5_UNORM_BLOCK: formatinfo = { 2, TypeDesc::UINT8, BlockCompression::BC5, VK_FORMAT_R8G8_UNORM }; return true; + case VK_FORMAT_BC7_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC7, VK_FORMAT_R8G8B8A8_SRGB }; return true; + // ASTC formats (2D blocks) + case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ASTC, VK_FORMAT_R8G8B8A8_SRGB }; return true; + default: break; + } + // clang-format on + return false; +} + + + +// TODO +inline void +gl_to_vkformat() +{ +} + +OIIO_PLUGIN_NAMESPACE_END diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp new file mode 100644 index 0000000000..f0ca2bd057 --- /dev/null +++ b/src/ktx.imageio/ktxinput.cpp @@ -0,0 +1,1485 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + +#include +#include + +// Per KTX-Software BUILDING.md: +// > When linking to the static library, make sure to +// > define `KHRONOS_STATIC` before including KTX header files. +// > This is especially important on Windows. +#ifndef BUILD_SHARED_LIBS +# define KHRONOS_STATIC 1 +#endif + +#include "ktx_pvt.h" +#include +#include + +// #include "ConvectionKernels/ConvectionKernels_BC67.h" /* for BC6HS/BC6HU decoders */ +#define ETCDEC_IMPLEMENTATION +#include "bc7enc-rdo/bc7decomp.h" /* for BC7 decoder */ +#include "bc7enc-rdo/rgbcx.h" /* for BC1-BC5 decoders */ +#include "etcdec.h" /* for ETC2 decoders */ + +OIIO_PLUGIN_NAMESPACE_BEGIN + +class KtxInput final : public ImageInput { +public: + KtxInput() {} + + ~KtxInput() override { close(); } + + const char* format_name(void) const override { return "ktx"; } + + int supports(string_view feature) const override + { + return ( + // as per the KTX1/2 specs: + // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_keyvalue_data + feature == "arbitrary_metadata" || + /* ktx supports 3D textures, cubmap textures, texture arrays, etc. */ + feature == "multiimage" || + /* ktx supports storage of mipmaps */ + feature == "mipmap"); + } + + bool valid_file(Filesystem::IOProxy* ioproxy) const override; + + bool open(const std::string& name, ImageSpec& newspec) override; + + bool open(const std::string& name, ImageSpec& newspec, + const ImageSpec& config) override; + + bool read_native_scanline(int subimage, int miplevel, int y, int z, + void* data) override; + + bool read_native_scanlines(int subimage, int miplevel, int ybegin, int yend, + int z, void* data) override; + + bool read_native_scanlines(int subimage, int miplevel, int ybegin, int yend, + span data) override; + + const std::string& filename() const { return m_filename; } + + bool close() override; + + int current_subimage(void) const override + { + lock_guard lock(*this); + return m_subimage; + } + + int current_miplevel(void) const override + { + lock_guard lock(*this); + return m_miplevel; + } + + bool seek_subimage(int subimage, int miplevel) override; + +private: + std::string m_filename; + + /// Buffer to hold the decoded GPU-block-compressed format. This is only + /// used to hold BCn or ECT decompressed data for a particular + /// miplevel/subimage. + std::vector m_buf; + + /// Non-owning pointer to KTX2 texture. The texture is managed by libktx + /// and should be destroyed via a 'ktxTexture_Destroy()' call. + ktxTexture* m_tex { nullptr }; + + /// m_tex2 reinterpret_cast'ed to KtxTexture2* for convenience. + ktxTexture2* m_tex2 { nullptr }; + + /// Non-owning pointer to first byte of the requested (miplevel, slice). + /// + /// For non-GPU-compressed formats, this points to first byte of the whole + /// texture data. + /// + /// For GPU-compressed formats: + /// - BCn: this is simply m_buf.data() + /// - ETC: this is simply m_buf.data() + /// - ASTC: this points to first byte of the whole decompressed texture + uint8_t* m_data_ptr { nullptr }; + + ktx_uint32_t m_pitch { 0 }; ///< Row pitch for current mip level. + ktx_size_t m_offset { 0 }; ///< Current offset from subimage call. + int m_subimage { -1 }; ///< What subimage are we looking at? + int m_nbrsubimages { -1 }; ///< Number of slices/faces in texture + int m_miplevel { -1 }; ///< What mip level are we looking at? + int m_nbrmiplevels { -1 }; ///< Number of mip levels + + /// GPU block compression kind (only set in case of GPU-block-compressed KTX + /// textures). + BlockCompression m_cmp = BlockCompression::NONE; + + /// Original VkFormat (i.e., before applying any decompression or transcoding). + VkFormat m_vkformat; + + std::unique_ptr m_config; ///< Saved copy of configuration spec + + /// TODO: add gl, direct3d, and metal format support + std::optional m_glFormat { std::nullopt }; + std::optional m_dxgiFormat { std::nullopt }; + std::optional m_metalFormat { std::nullopt }; + + /// Helper function: performs the actual pixel decoding. + bool internal_readimg(unsigned char* dst, int w, int h, int d); + + bool ktx_magic_cmp(const uint8_t* KTX_MAGIC, const uint8_t* sig, + size_t start) const; + + TextureKind get_texture_kind() const; + + std::string get_colorspace() const; + + inline void cpy_decoded_block(const uint8_t* pSrc, uint8_t* dst, size_t x, + size_t y, size_t width, size_t height, + size_t nchannels) const; + + template + inline bool check_bcn_spans(cspan src, span

dst, int level, + BlockCompression cmp) const; + + void decode_bc1(cspan src, span dst, int level) const; + void decode_bc3(cspan src, span dst, int level) const; + void decode_bc4(cspan src, span dst, int level) const; + void decode_bc5(cspan src, span dst, int level) const; + // void decode_bc6h(cspan src, span dst, int level, + // bool is_signed) const; + void decode_bc7(cspan src, span dst, int level) const; + + inline bool check_etc_spans(cspan src, span dst, + int level, BlockCompression cmp) const; + + void decode_etc2_rgb(cspan src, span dst, + int level) const; + void decode_etc2_rgba(cspan src, span dst, + int level) const; + void decode_etc2_rgb_a1(cspan src, span dst, + int level) const; +}; + + + +// Obligatory material to make this a recognizable imageio plugin: +OIIO_PLUGIN_EXPORTS_BEGIN + +OIIO_EXPORT int ktx_imageio_version = OIIO_PLUGIN_VERSION; + +OIIO_EXPORT const char* +ktx_imageio_library_version() +{ + return "ktx v5.0.0-rc1"; +} // hardcoded because I couldn't expose KTX_VERSION +OIIO_EXPORT ImageInput* +ktx_input_imageio_create() +{ + return new KtxInput; +} +OIIO_EXPORT const char* ktx_input_extensions[] = { "ktx2", nullptr }; + +OIIO_PLUGIN_EXPORTS_END + + + +bool +KtxInput::open(const std::string& name, ImageSpec& newspec, + const ImageSpec& config) +{ + // + // OIIO API is limited for certain KTX texture types (e.g., 3D array + // textures, cubemap array textures). Therefore we add the option to specify + // which layer to use in case these textures are used. This is ignored for + // other types of textures (e.g., 2D array textures, cubemaps, etc.) + // + // m_array_layer_idx = config.get_int_attribute("ktx:ArrayLayerIndex", + // m_array_layer_idx); + + // Check 'config' for any special requests + // if (config.get_int_attribute("oiio:UnassociatedAlpha", 0) == 1) + // m_keep_unassociated_alpha = true; + // m_linear_premult = config.get_int_attribute("png:linear_premult", + // OIIO::get_int_attribute( + // "png:linear_premult")); + ioproxy_retrieve_from_config(config); + m_config.reset(new ImageSpec(config)); // save config spec + return open(name, newspec); +} + + + +/// Opens the file with given name and seek to the first subimage in the +/// file. Various file attributes are put in `newspec` and a copy +/// is also saved internally to the `ImageInput` (retrievable via +/// `spec()`. From examining `newspec` or `spec()`, you can +/// discern the resolution, if it's tiled, number of channels, native +/// data format, and other metadata about the image. +/// +/// @param name +/// Filename to open, UTF-8 encoded. +/// +/// @param newspec +/// Reference to an ImageSpec in which to deposit a full +/// description of the contents of the first subimage of the +/// file. +/// +/// @returns +/// `true` if the file was found and opened successfully. +bool +KtxInput::open(const std::string& name, ImageSpec& newspec) +{ + m_filename = name; + + if (!ioproxy_use_or_open(name)) { + errorfmt("ioproxy_use_or_open(\"{}\") failed", name); + return false; + } + + // If an IOProxy was passed, it had better be a File or a MemReader + Filesystem::IOProxy* m_io = ioproxy(); + std::string proxytype = m_io->proxytype(); + if (proxytype != "file" && proxytype != "memreader") { + errorfmt("ktx reader can't handle proxy type {}", proxytype); + return false; + } + + // check if magic to insure that this is a KTX2 file + if (!this->valid_file(m_io)) { + // close_file(); + errorfmt("\"{}\" is not a KTX2 file, magic number doesn't match", name); + return false; + } + + // + // IMPORTANT: + // + // KTX can hold layered compressions (i.e., on top of the potential GPU- + // compatible compression like ASTC, the whole data can be furthermore + // compressed using a super compression scheme). + // + // If KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT is provided for any + // of the ktxTexture_CreateFrom* calls, then libktx will allocate an internal + // buffer large enough to hold all data inflated IF AND ONLY IF + // supercompressionScheme == KTX_SS_ZSTD or KTX_SS_ZLIB. + // + // Whithin the same call, ALL the texture data is then loaded. This is not + // ideal especially when dealing with, for instance, 3D textures, or even + // worse, 3D array textures. + // + // TODO: + // Implementing the per-subimage allocation approach requires a significant + // effort. For the moment, let's make sure this approach is working (i.e., + // all tests are passing) then let's profile and see what more experienced + // users might say about this. + // + // For under-the-hood details, see official libktx repo: + // https://github.com/KhronosGroup/KTX-Software/blob/main/lib/src/texture.c + // + if (proxytype == "file") { + auto fd = reinterpret_cast(m_io)->handle(); + auto res = ktxTexture_CreateFromStdioStream( + fd, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &m_tex); + if (KTX_SUCCESS != res) { + errorfmt("Failed to create ktx texture using " + "ktxTexture_CreateFromStdioStream"); + return false; + } + } else /* (proxytype == "memreader") */ { + OIIO_ASSERT(proxytype == "memreader"); + auto buff = reinterpret_cast(m_io)->buffer(); + auto res = ktxTexture_CreateFromMemory( + buff.data(), buff.size(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, + &m_tex); + if (KTX_SUCCESS != res) { + errorfmt( + "Failed to create ktx texture using ktxTexture_CreateFromMemory"); + return false; + } + } + + m_tex2 = reinterpret_cast(m_tex); + m_nbrmiplevels = m_tex2->numLevels; + m_nbrsubimages = m_tex2->numFaces; + + m_spec = ImageSpec(m_tex2->baseWidth, m_tex2->baseHeight, + 4 /* dummy value - will be overwritten */); + m_spec.depth = m_spec.full_depth = m_tex2->baseDepth; + std::string colorspace = get_colorspace(); + m_spec.set_colorspace(colorspace); + + // + // Make sure to save everything that is needed to recreate this exact same + // KTX texture from OIIO API (i.e., fields of `ktxTextureCreateInfo`). + // + // Note: + // KtxTexture fields may change after some libktx calls that take + // KtxTexture* argument because they may potentially modify the texture + // (e.g., in ktxTexture2_DecodeAstc supercompressionScheme is overwritten to + // none, ktxTexture2_TranscodeBasis overwrites texture format, etc.). + // + // TODO: save original supercompressionScheme BEFORE infalting the texture + m_spec.extra_attribs.attribute("ktx:supercompressionscheme", + TypeDesc::UINT32, 1, + cspan( + m_tex2->supercompressionScheme)); + + m_spec.extra_attribs.attribute("ktx:texturekind", TypeDesc::UINT32, 1, + cspan(static_cast( + get_texture_kind()))); + + // save as string + m_spec.extra_attribs.attribute("ktx:version", "2.0"); + + // Contrary to the specs' layerCount, numLayers is always >= 1 (even for + // non-array types) + m_spec.extra_attribs.attribute("ktx:nlayers", TypeDesc::UINT32, 1, + cspan(m_tex->numLayers)); + + m_spec.extra_attribs.attribute("ktx:miplevels", TypeDesc::UINT32, 1, + cspan(m_tex->numLevels)); + + m_spec.extra_attribs.attribute("ktx:generatemipmaps", TypeDesc::UINT8, 1, + cspan(m_tex->generateMipmaps)); + + // Store colormodel so that if a KTX2 is requested to be generated, we know + // if a Basis Universal scheme has to be applied. + m_spec.extra_attribs.attribute("ktx:colormodel", TypeDesc::UINT32, 1, + cspan( + ktxTexture2_GetColorModel_e(m_tex2))); + + // m_spec.extra_attribs.attribute("ktx:transferfunction", TypeDesc::UINT32, 1, + // cspan(transfer_function)); + + // TODO: do we actually need the dfd data to re-generate the same KTX2 file? + // uint32_t dfdTotalSize = *m_tex2->pDfd; + // m_spec.extra_attribs.attribute("ktx:dfd", TypeDesc::UINT8, dfdTotalSize, + // make_cspan(reinterpret_cast( + // m_tex2->pDfd), + // dfdTotalSize)); + + // + // Save arbitrary metadata. KTX allows for the storage of arbitrary + // key/value metadata pairs as per the specification here: + // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_keyvalue_data + // + // KTX2 spec. defines a predifined set of key/value metadata at + // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_predefined_keyvalue_pairs + // + // Predifined keys we care about: + // + // - KTXcubemapIncomplete: 1 byte bitfield + // - KTXorientation: null-terminated string + // + // - KTXglFormat: + // + UInt32 glInternalformat + // + UInt32 glFormat + // + UInt32 glType + // + // - KTXdxgiFormat__: UInt32 + // - KTXmetalPixelFormat: UInt32 + // + auto kventry = m_tex->kvDataHead; + if (kventry) + do { + auto status = KTX_SUCCESS; + unsigned int keylen { 0 }; + unsigned int vallen { 0 }; + char* key { nullptr }; + void* val { nullptr }; + + if ((status = ktxHashListEntry_GetKey(kventry, &keylen, &key)) + != KTX_SUCCESS) + continue; + + // "The key must be terminated by a NUL character" + // This will probably never occur, but it doesn't hurt to be safe + if (keylen <= 1) + continue; + + if ((status = ktxHashListEntry_GetValue(kventry, &vallen, &val)) + != KTX_SUCCESS) + continue; + + // vallen checks are done below depending on the attribute name + + auto attr_name = std::string(key, key + (keylen - 1)); + auto ktx_prefixed_attr_name = fmt::format("ktx:{}", attr_name); + + if (attr_name == KTX_WRITER_KEY) { + // KTXwriter identifies the program used to write this KTX file + // Should be NUL terminated. + if (vallen <= 1) + continue; + auto char_ptr = reinterpret_cast(val); + m_spec.extra_attribs.attribute( + ktx_prefixed_attr_name, + std::string(char_ptr, char_ptr + (vallen - 1))); + } else if (attr_name == "KTXcubemapIncomplete") { + OIIO_ASSERT(vallen == 1); + // TODO: handle KTXcubemapIncomplete + } else if (attr_name == KTX_ORIENTATION_KEY) { + // + // KTX may define a different orientation than the one used by OIIO. See: + // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_ktxorientation + // E.g., for KTX1 (OpenGL) without any re-orientation logic images are + // flipped over X axis (top becomes down). + // + + // TODO: set orientation functions + } else if (attr_name == "KTXglFormat") { + OIIO_ASSERT(vallen == sizeof(KTXglFormat) /* 12 bytes */); + KTXglFormat glFormat; + glFormat.glInternalformat = *reinterpret_cast(val); + glFormat.glFormat = *(reinterpret_cast(val) + 1); + glFormat.glType = *(reinterpret_cast(val) + 2); + m_glFormat = glFormat; + m_spec.extra_attribs.attribute( + ktx_prefixed_attr_name, TypeDesc::UINT32, 3, + make_cspan(reinterpret_cast(val), 3)); + } else if (attr_name == "KTXdxgiFormat__") { + OIIO_ASSERT(vallen == sizeof(uint32_t)); + m_dxgiFormat = *reinterpret_cast(val); + m_spec.extra_attribs.attribute(ktx_prefixed_attr_name, + m_dxgiFormat.value()); + } else if (attr_name == "KTXmetalPixelFormat") { + OIIO_ASSERT(vallen == sizeof(uint32_t)); + m_metalFormat = *reinterpret_cast(val); + m_spec.extra_attribs.attribute(ktx_prefixed_attr_name, + m_metalFormat.value()); + } else { + // otherwise store the arbitrary value as a byte string + m_spec.extra_attribs.attribute( + ktx_prefixed_attr_name, TypeDesc::UCHAR, vallen, + make_cspan(reinterpret_cast(val), vallen)); + } + + } while ((kventry = ktxHashList_Next(kventry))); + + // + // We only support KTX_SS_NONE, KTX_SS_ZLIB, KTX_SS_ZSTD, and + // KTX_SS_BASIS_LZ supercompression schemes. New schemes may be added to the + // spec hence why we do a strict if check. + // + if (m_tex2->supercompressionScheme != KTX_SS_NONE + && m_tex2->supercompressionScheme != KTX_SS_ZSTD + && m_tex2->supercompressionScheme != KTX_SS_ZLIB + && m_tex2->supercompressionScheme != KTX_SS_BASIS_LZ) { + // vendor-specific or newly introduced supercompression schemes (not + // supported) + errorfmt("unsuppoted supercompression scheme: {}", + static_cast(m_tex2->supercompressionScheme)); + return false; + } + + // + // Store original VkFormat (i.e., after Basic Universal transcoding and + // before potential GPU block format decompression). + // + // Important: + // Call this BEFORE (potential) ktxTexture2_TranscodeBasis call + // + m_spec.extra_attribs.attribute("ktx:vkformat", TypeDesc::UINT32, 1, + cspan(static_cast( + m_tex2->vkFormat))); + + // + // Do we need to transcode this texture (i.e., is this a Basis Universal + // texture format)? + // + // KTX2 provides transcoders that can directly target raw, uncompressed + // pixels (via the KTX_TTF_RGBA32 flag). + // + // Important: + // This modifies the KtxTexture2 (m_tex) therefore make sure to save + // essential properties for proper KTX2 regeneration. + // + if (ktxTexture2_NeedsTranscoding(m_tex2)) { + if (auto status = ktxTexture2_TranscodeBasis( + m_tex2, ktx_transcode_fmt_e::KTX_TTF_RGBA32, 0); + status != KTX_SUCCESS) { + errorfmt("failed to transcode KTX2 texture to raw pixels. " + "ktxTexture2_TranscodeBasis returned Ktx error code: {}", + static_cast(status)); + return false; + } + } + + // + // This could mean one of the following as per the specs at: + // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_use_of_vk_format_undefined + // + // 1. For custom formats that do not have any equivalent in GPU APIs. + // This is currently not supported. + // + // 2. ETC1S/UASTC supercompression scheme: makes no sense since we + // transcoded it above to uncompressed format. + // + // 3. For any formats from any GPU APIs that do not have Vulkan + // equivalents. E.g., OpenGL/Direct3D/Metal formats. + // In this case, one of the following metadata entries have to be + // present: + // - "KTXglFormat" for OpenGL + // - "KTXdxgiFormat__" for Direct3D + // - "KTXmetalPixelFormat" for Metal + // TODO + // + // 4. Compressed color models in Section 5.6 of [KDF14] or successors that + // do not have corresponding Vulkan formats. + // TODO + // + if (m_tex2->vkFormat == VK_FORMAT_UNDEFINED) { + // TODO: check case (4) - color model + + // check case (3) - non-Vulkan GPU formats (here we simply map these + // formats to VkFormat and call it a day) + if (m_glFormat.has_value()) { + // TODO: add glformat support + errorfmt("Loading KTX textures with OpenGL formats but no vkFormat " + "(i.e., VK_FORMAT_UNDEFINED) is currently not supported"); + return false; + } else if (m_dxgiFormat.has_value()) { + // TODO: add direct3d format support + errorfmt( + "Loading KTX textures with Direct3D formats but no vkFormat " + "(i.e., VK_FORMAT_UNDEFINED) is currently not supported"); + return false; + } else if (m_metalFormat.has_value()) { + // TODO: add metal format support + errorfmt("Loading KTX textures with Metal formats but no vkFormat " + "(i.e., VK_FORMAT_UNDEFINED) is currently not supported"); + return false; + } + + // error for other cases (case (2) should not occur and case (1) is + // not supported) + errorfmt( + "VkFormat of provided KTX texture is VK_FORMAT_UNDEFINED " + "which potentially means that a custom format with no equivalent " + "in GPU APIs is provided. This is not supported."); + return false; + } + + // + // In case this KTX texture is GPU block compressed, we need to map its + // vkformat to the corresponding decompressed VkFormat. + // + // E.g., VK_FORMAT_BC7_SRGB_BLOCK --> VK_FORMAT_R8G8B8A8_SRGB + // + // We do this so that we can save the correct crucial stats in the spec + // (e.g., nchannels, colorspace, typedesc, etc.) and because the internal + // state of data in OIIO is always decompressed (i.e., we never return + // block-compressed data from read_native_scanline(s) functions). + // + auto format = static_cast(m_tex2->vkFormat); + if (m_tex2->isCompressed) { + FormatInfo format_info; + if (!extract_info_from_format(static_cast(format), + format_info)) { + errorfmt( + "Failed to extract info (e.g., nchannels, typedesc, etc.) from VkFormat: {}", + static_cast(format)); + return false; + } + if (format_info.compression == BlockCompression::NONE + || format_info.decompressed_format == VK_FORMAT_UNDEFINED) { + errorfmt( + "KTX texture is GPU-block-compressed using unsuppoted format: {}", + static_cast(format)); + return false; + } + format = format_info.decompressed_format; + m_cmp = format_info.compression; + } + + // + // Important: + // Call this AFTER transcoding the basis universal scheme (i.e., after + // ktxTexture2_TranscodeBasis) and AFTER detecting which GPU block + // compression scheme is used. + // + { + FormatInfo format_info; + if (!extract_info_from_format(static_cast(format), + format_info)) { + errorfmt( + "Failed to extract info (e.g., nchannels, typedesc, etc.) from VkFormat: {}", + static_cast(format)); + return false; + } + + m_spec.set_format(format_info.typedesc); + m_spec.nchannels = format_info.nbrchannels; + } + + // TODO: verify the x, y, z limits (probably not 65535) + if (!check_open(m_spec, { 0, 65535, 0, 65535, 0, 65535, 0, 4 })) + return false; + + // Initialize BC1, BC3, BC4 and BC5 decoder library + if (m_cmp == BlockCompression::BC1 || m_cmp == BlockCompression::BC3 + || m_cmp == BlockCompression::BC4 || m_cmp == BlockCompression::BC5) + rgbcx::init(rgbcx::bc1_approx_mode::cBC1Ideal); + + if (!seek_subimage(0, 0)) + // errorfmt is set via seek_subimage + return false; + + newspec = m_spec; + return true; +} + + + +bool +KtxInput::close() +{ + // Check if already closed + if (!ioproxy_opened()) + return true; + if (m_tex) { + ktxTexture_Destroy(m_tex); + m_tex = nullptr; + } + ioproxy_clear(); + return true; +}; + + + +// +// In the context of KTX, `subimage` CAN be interpreted as (1D textures are +// considered 2D textures with height set to 1): +// 1. array layer (if texture is a 2D texture array) +// 2. depth slice (if texture is 3D) +// 3. cube map face (if texture is a cubemap) +// 4. depth slice (of first 3D texture if texture is a 3D texture array) +// 5. cube map face (of first cubemap texture if texture is a cubmap array) +// +// `miplevel` is simply interpreted as a mip level of the above `subimage`. +// +// In other cases, if subimage is > 0, it is invalid. +// +bool +KtxInput::seek_subimage(int subimage, int miplevel) +{ + lock_guard lock(*this); + + // + // Before doing any calls, check if provided subimage and mip lvl are valid. + // This is how OIIO figures out the number of subimages/miplevels. + // + if (subimage < 0 || miplevel < 0 || subimage >= m_nbrsubimages + || miplevel >= m_nbrmiplevels) + /* don't errorfmt here */ + return false; + + // if same subimage and miplevel as current => early out + if (this->current_subimage() == subimage + && this->current_miplevel() == miplevel) + return true; + + m_subimage = subimage; + m_miplevel = miplevel; + + // cast to ktx_uint32_t to stop the compiler/clangd from complaining + auto _subimage = static_cast(subimage); + + ktx_uint32_t arr_layer { 0 }; // array layer + ktx_uint32_t face_slice { 0 }; // 3d texture slice or cubemap face + + // is this a cubemap? (i.e., subimage means cubemap face) + if (m_tex->isCubemap) + face_slice = _subimage; + + // is this an array texture? (i.e., subimage means array layer) + if (m_tex->isArray) + arr_layer = _subimage; + + // is this a 3D texture? (i.e., subimage means face slice) + if (m_tex->numDimensions == 3) + face_slice = _subimage; + + // + // According to official libktx source code, this is how they compute + // dimensions of a miplevel. See: + // https://github.com/KhronosGroup/KTX-Software/lib/src/texture.c + // + const size_t width = std::max(m_tex2->baseWidth >> miplevel, 1u); + const size_t height = std::max(m_tex2->baseHeight >> miplevel, 1u); + const size_t depth = std::max(m_tex2->baseDepth >> miplevel, 1u); + + m_spec.width = width; + m_spec.height = height; + m_spec.depth = depth; + + // + // Decode GPU-compression if any. Supported formats: + // + // ASTC: libktx provides decoders for ASTC block compression via the + // ktxTexture2_DecodeAstc call. + // This currently decodes the whole texture (all miplevels, all + // slices, etc.) into memory. + // TODO: implement decode_astc for per-miplvl/subimage decoding. + // + // BCn: we implement decode_bcn using 3rd party BCn decoder + // via bcdec (at https://github.com/iOrange/bcdec). This allows us + // to do a per-miplvl and per-subimage decode hence why we don't + // decode here but rather in seek_subimage. + // + // ETC2: we implement decode_etc using 3rd party ETC decoder + // via etcdec (https://github.com/iOrange/etcdec). This allows us + // to do a per-miplvl and per-subimage decode hence why we don't + // decode here but rather in seek_subimage. + // + // PVRTC: TODO + // + if (m_tex2->isCompressed /* i.e., is GPU block compressed? */) { + ktx_size_t offset; + if (auto status = ktxTexture2_GetImageOffset(m_tex2, miplevel, + arr_layer, face_slice, + &offset); + status != KTX_SUCCESS) { + return status; + } + // TODO: Are pointer indices [offset, offset + size[ safe? + // Encoded blocks + cspan src_span(m_tex2->pData + offset, + ktxTexture_GetImageSize(ktxTexture(m_tex), + miplevel)); + + switch (m_cmp) { + /* BCn formats */ + case BlockCompression::BC1: { + // TODO: is std::vector<>::resize() a nop when the vector already has that exact size? + m_buf.resize(width * height * BC1_OUTPUT_NCHANNELS); + span dst_span(m_buf.data(), m_buf.size()); + if (!check_bcn_spans(src_span, dst_span, miplevel, m_cmp)) + return false; + decode_bc1(src_span, dst_span, miplevel); + break; + } + + case BlockCompression::BC3: { + m_buf.resize(width * height * BC3_OUTPUT_NCHANNELS); + span dst_span(m_buf.data(), m_buf.size()); + if (!check_bcn_spans(src_span, dst_span, miplevel, m_cmp)) + return false; + decode_bc3(src_span, dst_span, miplevel); + break; + } + + case BlockCompression::BC4: { + m_buf.resize(width * height * BC4_OUTPUT_NCHANNELS); + span dst_span(m_buf.data(), m_buf.size()); + if (!check_bcn_spans(src_span, dst_span, miplevel, m_cmp)) + return false; + decode_bc4(src_span, dst_span, miplevel); + break; + } + + case BlockCompression::BC5: { + m_buf.resize(width * height * BC5_OUTPUT_NCHANNELS); + span dst_span(m_buf.data(), m_buf.size()); + if (!check_bcn_spans(src_span, dst_span, miplevel, m_cmp)) + return false; + decode_bc5(src_span, dst_span, miplevel); + break; + } + + /* HDR format */ + // case BlockCompression::BC6HS: { + // m_buf.resize(width * height * BC6H_OUTPUT_NCHANNELS * 2); + // span dst_span(reinterpret_cast(m_buf.data()), + // m_buf.size() / 2); + // if (!check_bcn_spans(src_span, dst_span, miplevel, m_cmp)) + // return false; + // decode_bc6h(src_span, dst_span, miplevel, /* is_signed */ true); + // break; + // } + + case BlockCompression::BC7: { + m_buf.resize(width * height * BC7_OUTPUT_NCHANNELS); + span dst_span(m_buf.data(), m_buf.size()); + if (!check_bcn_spans(src_span, dst_span, miplevel, m_cmp)) + return false; + decode_bc7(src_span, dst_span, miplevel); + break; + } + + /* ETC formats */ + case BlockCompression::ETC2_RGB: { + m_buf.resize(width * height * ETC2_RGB_OUTPUT_NCHANNELS); + span dst_span(m_buf.data(), m_buf.size()); + if (!check_etc_spans(src_span, dst_span, miplevel, m_cmp)) + return false; + decode_etc2_rgb(src_span, dst_span, miplevel); + break; + } + + case BlockCompression::ETC2_RGB_A1: { + m_buf.resize(width * height * ETC2_RGB_A1_OUTPUT_NCHANNELS); + span dst_span(m_buf.data(), m_buf.size()); + if (!check_etc_spans(src_span, dst_span, miplevel, m_cmp)) + return false; + decode_etc2_rgb_a1(src_span, dst_span, miplevel); + break; + } + + case BlockCompression::ETC2_RGBA: { + m_buf.resize(width * height * ETC2_RGBA_OUTPUT_NCHANNELS); + span dst_span(m_buf.data(), m_buf.size()); + if (!check_etc_spans(src_span, dst_span, miplevel, m_cmp)) + return false; + decode_etc2_rgba(src_span, dst_span, miplevel); + break; + } + + /* ASTC formats */ + case BlockCompression::ASTC: + // + // Note: + // ktxTexture2_DecodeAstc internally creates a new ktxTexture2 texture + // and populates it with decoded data from the originally provided + // texture. At the end, it moves the decoded data to m_tex and + // destroys the temporarily created texture. + // + // This operation is very expensive (both in memory and CPU cycles). + // After this, m_tex2->isCompressed will be false => this will only + // be called once. + // + if (auto status = ktxTexture2_DecodeAstc(m_tex2); + status != KTX_SUCCESS) { + errorfmt("failed to decode ASTC-compressed texture. " + "ktxTexture2_DecodeAstc returned Ktx error code: {}", + static_cast(status)); + return false; + } + break; + + default: + errorfmt("Unknown/unsupported GPU block compression kind: {}", + static_cast(m_cmp)); + return false; + } + + m_pitch = width * m_spec.nchannels + * m_spec.format.size() /* 1 for LDR, 2 for HDR formats */; + m_data_ptr = m_buf.data(); + } + + // Do NOT change this to `else` statement because this handles the ASTC case + // above (which, again, sets m_tex2->isCompressed to `false`) + if (!m_tex2->isCompressed) { + // + // GetImageOffset implements internal checks depending on texture kind (e.g., + // 3D, cubemap, etc.) and incase of invalid input, KTX_INVALID_OPERATION is + // returned. + // + ktx_size_t offset; + if (auto status = ktxTexture_GetImageOffset(m_tex, miplevel, arr_layer, + face_slice, &offset); + status != KTX_SUCCESS) { + errorfmt("ktxTexture_GetImageOffset failed with exit code: {}", + static_cast(status)); + return false; + } + m_pitch = ktxTexture_GetRowPitch(m_tex, miplevel); + m_data_ptr = m_tex2->pData + offset; + } + return true; +} + + + +bool +KtxInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, + void* data) +{ + lock_guard lock(*this); + return read_native_scanlines(subimage, miplevel, y, y + 1, + as_writable_bytes(data, m_spec.scanline_bytes( + true))); +} + + + +bool +KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, + int yend, int /* z */, void* data) +{ + lock_guard lock(*this); + + if (ybegin >= yend) { + errorfmt("Invalid scanline range requested: {}-{}", ybegin, yend); + return false; + } + + // avoid calling seek_subimage because this will NOT be thread-safe and + // we have to introduce a lock which will make this slower (read note above + // about how libktx inflates all data in open()). + if (!seek_subimage(subimage, miplevel)) + return false; + + size_t size = m_spec.scanline_bytes(true) * size_t(yend - ybegin); + return read_native_scanlines(subimage, miplevel, ybegin, yend, + as_writable_bytes(data, size)); +} + + +bool +KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, + int yend, span data) +{ + lock_guard lock(*this); + // is provided [ybegin, yend[ valid? + if (ybegin < 0 || ybegin >= yend || yend > m_spec.height) { + // out of range scanlines + errorfmt("KTX read_native_scanlines: Out of valid range scanline indices " + "(b={} e={}).", + ybegin, yend); + return false; + } + + // can the provided span hold the requested scanlines? + if (!valid_raw_span_size(data, m_spec, 0, m_spec.width, ybegin, yend)) + // errorfmt is set within valid_raw_span_size + return false; + + // since miplevel is valid => get number of bytes in a row for this mip + memcpy(data.data(), m_data_ptr, m_pitch * (yend - ybegin)); + // std::cout << "read_native_scanlines(" << subimage << ", " << miplevel + // << ", " << ybegin << ", " << yend << ")" << '\n'; + return true; +} + + +bool +OpenImageIO::KtxInput::valid_file(Filesystem::IOProxy* ioproxy) const +{ + // Check magic number to assure this is a KTX2 file + if (!ioproxy || ioproxy->mode() != Filesystem::IOProxy::Read) + return false; + + // per KTX2 specs: the first 12 bytes of a KTX2 file are used to identify it + uint8_t magic[12] {}; + const size_t numRead = ioproxy->pread(magic, sizeof(magic), 0); + + return (numRead == sizeof(magic)) + && this->ktx_magic_cmp(KTX2_IDENTIFIER, magic, 0); +} + + +bool +KtxInput::ktx_magic_cmp(const uint8_t* KTX_MAGIC, const uint8_t* sig, + size_t start) const +{ + for (size_t i = start; (i - start) < sizeof(KTX_MAGIC); ++i) + if (sig[i] != KTX_MAGIC[i]) + return false; + return true; +} + + +TextureKind +KtxInput::get_texture_kind() const +{ + switch (m_tex->numDimensions) { + case 1: + if (m_tex->isArray) + return TextureKind::ARRAY_TEXTURE_1D; + return TextureKind::SINGLE_TEXTURE_1D; + case 2: + if (m_tex->isArray && m_tex->isCubemap) + return TextureKind::ARRAY_TEXTURE_CUBEMAP; + else if (m_tex->isArray) + return TextureKind::ARRAY_TEXTURE_2D; + return TextureKind::SINGLE_TEXTURE_2D; + case 3: + if (m_tex->isArray) + return TextureKind::ARRAY_TEXTURE_3D; + return TextureKind::SINGLE_TEXTURE_3D; + default: return TextureKind::SINGLE_TEXTURE_2D; + } +} + + +std::string +KtxInput::get_colorspace() const +{ + // for set of, see: + // https://github.com/KhronosGroup/KTX-Software/blob/main/external/dfdutils/KHR/khr_df.h + // for OIIO colorspaces, see: + // https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/src/libOpenImageIO/color_ocio.cpp + khr_df_transfer_e transfer_function = ktxTexture2_GetTransferFunction_e( + m_tex2); + khr_df_primaries_e primaries = ktxTexture2_GetPrimaries_e(m_tex2); + // std::cout << "tf: " << transfer_function << "; primaries: " << primaries + // << '\n'; + switch (transfer_function) { + case KHR_DF_TRANSFER_SRGB: + switch (primaries) { + case KHR_DF_PRIMARIES_BT709: return "srgb_rec709_scene"; + default: break; + } + break; + case KHR_DF_TRANSFER_LINEAR: + switch (primaries) { + case KHR_DF_PRIMARIES_BT709: return "lin_rec709_scene"; + default: break; + } + break; + // case KHR_DF_TRANSFER_DCIP3: colorspace = "lin_rec709_scene"; return true; + default: break; + } + // TODO: need to generate test files before adding support for any other + // colorspaces + return "unknown"; +} + +// +// Copies a decoded block (e.g., a decoded BC7 block) from provided pSrc to +// provided pDst. For each row of the decoded block, performs a memcpy to the +// destination block while accounting for potential non-multiple-of-block-size +// destination dimensions. +// +// Source: +// +// <-------------- block size -------------> +// +---------------------------------------+ +// | pSrc + 0 | ... | pSrc + src_pitch - 1 | <-- row: 0 +// | | +// | | ... | | +// | | +// | | ... | | +// | | +// | | ... | | +// | | +// | | ... | | +// | | +// | | ... | | <-- row: block_size - 1 +// +---------------------------------------+ +// +// +// Destination: +// +// <-------------------- width -----------------> +// +--------------------------------------------+ +// | pDst | ... | pDst + dst_pitch - 1 | <-- row: 0 +// | | +// | | +// | pDst + y * dst_pitch * nchannels * x -> +--|---+ <- destination block +// | | |xxx| +// | | |xxx| +// | +--|---+ +// | | +// | | +// | | +// | | <-- row: height - 1 +// +--------------------------------------------+ +// +// Source and destination SHOULD have the same stride (i.e., nchannels). +// +inline void +KtxInput::cpy_decoded_block(const uint8_t* pSrc, uint8_t* dst, size_t x, + size_t y, size_t width, size_t height, + size_t nchannels /* stride */) const +{ + // TODO: expose this as param + constexpr size_t kBlockSize { 4 }; + const size_t src_pitch = kBlockSize * nchannels; + const size_t dst_pitch = width * nchannels; + int cols = std::min(kBlockSize, width - x); + uint8_t* pDst = dst + y * dst_pitch + nchannels * x; + for (size_t py { 0 }; py < kBlockSize && y + py < height; ++py) { + memcpy(pDst, pSrc, cols * nchannels); + pSrc += src_pitch; + pDst += dst_pitch; + } +} + + +// +// Makes sure that provided source BCn blocks span and target span (where blocks +// will be decoded into) are of sufficient sizes. +// You should call this before any decode_bcn() functions. +// +template +inline bool +KtxInput::check_bcn_spans(cspan src, span

dst, int level, + BlockCompression cmp) const +{ + const size_t nchannels { static_cast(m_spec.nchannels) }; + const size_t width = std::max(m_tex->baseWidth >> level, 1u); + const size_t height = std::max(m_tex->baseHeight >> level, 1u); + size_t expected_nchannels; + size_t expected_dst_size; + size_t expected_src_size; + const int nblocks_x { static_cast( + std::ceil(width / (float)BCN_BLOCK_SIZE)) }; + const int nblocks_y { static_cast( + std::ceil(height / (float)BCN_BLOCK_SIZE)) }; + + switch (cmp) { + case BlockCompression::BC1: + expected_nchannels = BC1_OUTPUT_NCHANNELS; + expected_dst_size = width * height * BC1_OUTPUT_NCHANNELS; + expected_src_size = nblocks_x * nblocks_y * BC1_BLOCK_SIZE; + break; + + case BlockCompression::BC3: + expected_nchannels = BC3_OUTPUT_NCHANNELS; + expected_dst_size = width * height * BC3_OUTPUT_NCHANNELS; + expected_src_size = nblocks_x * nblocks_y * BC3_BLOCK_SIZE; + break; + + case BlockCompression::BC4: + expected_nchannels = BC4_OUTPUT_NCHANNELS; + expected_dst_size = width * height * BC4_OUTPUT_NCHANNELS; + expected_src_size = nblocks_x * nblocks_y * BC4_BLOCK_SIZE; + break; + + case BlockCompression::BC5: + expected_nchannels = BC5_OUTPUT_NCHANNELS; + expected_dst_size = width * height * BC5_OUTPUT_NCHANNELS; + expected_src_size = nblocks_x * nblocks_y * BC5_BLOCK_SIZE; + break; + + // case KHR_DF_MODEL_BC6H: + // expected_nchannels = BC6H_OUTPUT_NCHANNELS; + // expected_dst_size = width * height * BC6H_OUTPUT_NCHANNELS; + // expected_src_size = nblocks_x * nblocks_y * BC6H_BLOCK_SIZE; + // break; + + case BlockCompression::BC7: + expected_nchannels = BC7_OUTPUT_NCHANNELS; + expected_dst_size = width * height * BC7_OUTPUT_NCHANNELS; + expected_src_size = nblocks_x * nblocks_y * BC7_BLOCK_SIZE; + break; + + default: return false; + } + + if (nchannels != expected_nchannels) { + errorfmt("Current BCn scheme is expected to decode into " + "{}-channel-images but target image got: {} channels.", + expected_nchannels, nchannels); + return false; + } + + if (src.size() < expected_src_size) { + errorfmt("The source data buffer's size is smaller than expected. " + "Expected {} bytes but provided buffer only has {} bytes.", + expected_src_size, src.size()); + return false; + } + + if (dst.size() < expected_dst_size) { + errorfmt( + "The size of the destination buffer to hold decoded BCn " + "blocks is smaller than expected. Expected {} bytes but provided " + "buffer only has {} bytes.", + expected_dst_size, dst.size()); + return false; + } + return true; +} + + +void +KtxInput::decode_bc1(cspan src, span dst, int level) const +{ + const size_t width = std::max(m_tex2->baseWidth >> level, 1u); + const size_t height = std::max(m_tex2->baseHeight >> level, 1u); + + const size_t rgba_pitch = BCN_BLOCK_SIZE * BC1_OUTPUT_NCHANNELS; + uint8_t rgba[BCN_BLOCK_SIZE * rgba_pitch]; /* 64 bytes */ + const uint8_t* src_blocks = src.data(); + + for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { + // BC1: 8 bytes -> 4 x 4 x 4 = 64 bytes + rgbcx::unpack_bc1(src_blocks, rgba, true); + src_blocks += BC1_BLOCK_SIZE; + cpy_decoded_block(rgba, dst.data(), x, y, width, height, + BC1_OUTPUT_NCHANNELS); + } + } +} + + +void +KtxInput::decode_bc3(cspan src, span dst, int level) const +{ + const size_t width = std::max(m_tex2->baseWidth >> level, 1u); + const size_t height = std::max(m_tex2->baseHeight >> level, 1u); + + const size_t rgba_pitch = BCN_BLOCK_SIZE * BC3_OUTPUT_NCHANNELS; + uint8_t rgba[BCN_BLOCK_SIZE * rgba_pitch]; /* 64 bytes */ + const uint8_t* src_blocks = src.data(); + + for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { + // BC3: 16 bytes -> 4 x 4 x 4 = 64 bytes + rgbcx::unpack_bc3(src_blocks, rgba); + src_blocks += BC3_BLOCK_SIZE; + cpy_decoded_block(rgba, dst.data(), x, y, width, height, + BC3_OUTPUT_NCHANNELS); + } + } +} + + +void +KtxInput::decode_bc4(cspan src, span dst, int level) const +{ + const size_t width = std::max(m_tex2->baseWidth >> level, 1u); + const size_t height = std::max(m_tex2->baseHeight >> level, 1u); + + const size_t r_pitch = BCN_BLOCK_SIZE * BC4_OUTPUT_NCHANNELS; + uint8_t r[BCN_BLOCK_SIZE * r_pitch]; /* 16 bytes */ + const uint8_t* src_blocks = src.data(); + + for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { + // BC4: 8 bytes -> 4 x 4 x 1 = 16 bytes + rgbcx::unpack_bc4(src_blocks, r, /* stride */ BC4_OUTPUT_NCHANNELS); + src_blocks += BC4_BLOCK_SIZE; + cpy_decoded_block(r, dst.data(), x, y, width, height, + BC4_OUTPUT_NCHANNELS); + } + } +} + + +void +KtxInput::decode_bc5(cspan src, span dst, int level) const +{ + const size_t width = std::max(m_tex2->baseWidth >> level, 1u); + const size_t height = std::max(m_tex2->baseHeight >> level, 1u); + + const size_t rg_pitch = BCN_BLOCK_SIZE * BC5_OUTPUT_NCHANNELS; + uint8_t rg[BCN_BLOCK_SIZE * rg_pitch]; /* 32 bytes */ + const uint8_t* src_blocks = src.data(); + + for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { + // BC5: 16 bytes -> 4 x 4 x 2 = 32 bytes + rgbcx::unpack_bc5(src_blocks, rg, 0, 1, + /* stride */ BC5_OUTPUT_NCHANNELS); + src_blocks += BC5_BLOCK_SIZE; + cpy_decoded_block(rg, dst.data(), x, y, width, height, + BC5_OUTPUT_NCHANNELS); + } + } +} + +// TODO: this is not yet tested because apparently I can't (or ktx can't) +// generate a BC6HS/BC6HU KTX file... +// void +// KtxInput::decode_bc6h(cspan src, span dst, int level, +// bool is_signed) const +// { +// constexpr size_t kBlockSize { 4 }; +// constexpr size_t nchannels { BC6H_OUTPUT_NCHANNELS }; +// const size_t width = std::max(m_tex2->baseWidth >> level, 1u); +// const size_t height = std::max(m_tex2->baseHeight >> level, 1u); +// const size_t pitch = width * nchannels; +// +// cvtt::PixelBlockF16 rgbx; /* int16_t m_pixels[16][4]; */ +// constexpr int rgbx_pitch { kBlockSize * 4 }; +// const uint8_t* src_blocks = src.data(); +// +// for (size_t y { 0 }; y < height; y += kBlockSize) { +// for (size_t x { 0 }; x < width; x += kBlockSize) { +// // BC6: 16 bytes -> 4 x 4 x 3 x 2 = 32 bytes +// +// cvtt::Internal::BC6HComputer::UnpackOne(rgbx, src_blocks, +// is_signed); +// src_blocks += BC6H_BLOCK_SIZE; +// +// // copy HDR block into destination +// const int16_t* pSrc = rgbx.m_pixels[0]; +// int16_t* pDst = dst.data() + y * pitch + x * nchannels; +// const int cols = std::min(kBlockSize, width - x); +// for (size_t py { 0 }; py < kBlockSize && y + py < height; ++py) { +// memcpy(pDst, pSrc, cols * nchannels * 2); +// pSrc += rgbx_pitch; +// pDst += pitch; +// } +// } +// } +// } + + +void +KtxInput::decode_bc7(cspan src, span dst, int level) const +{ + const size_t width = std::max(m_tex2->baseWidth >> level, 1u); + const size_t height = std::max(m_tex2->baseHeight >> level, 1u); + + const size_t rgba_pitch = BCN_BLOCK_SIZE * BC7_OUTPUT_NCHANNELS; + uint8_t rgba[BCN_BLOCK_SIZE * rgba_pitch]; /* 64 bytes */ + const uint8_t* src_blocks = src.data(); + + for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { + // BC7: 16 bytes -> 4 x 4 x 4 = 64 bytes + bc7decomp::unpack_bc7(src_blocks, + reinterpret_cast( + rgba)); + src_blocks += BC7_BLOCK_SIZE; + cpy_decoded_block(rgba, dst.data(), x, y, width, height, + BC7_OUTPUT_NCHANNELS); + } + } +} + + +// +// Makes sure that provided source ETC blocks span and target span (where blocks +// will be decoded into) are of sufficient sizes. +// You should call this before any decode_etc() functions. +// +inline bool +KtxInput::check_etc_spans(cspan src, span dst, int level, + BlockCompression cmp) const +{ + const size_t nchannels { static_cast(m_spec.nchannels) }; + const size_t width = std::max(m_tex->baseWidth >> level, 1u); + const size_t height = std::max(m_tex->baseHeight >> level, 1u); + size_t expected_nchannels; + size_t expected_dst_size; + size_t expected_src_size; + const int nblocks_x { static_cast( + std::ceil(width / (float)ETC_BLOCK_SIZE)) }; + const int nblocks_y { static_cast( + std::ceil(height / (float)ETC_BLOCK_SIZE)) }; + + switch (cmp) { + case BlockCompression::ETC2_RGB: + expected_nchannels = ETC2_RGB_OUTPUT_NCHANNELS; + expected_dst_size = width * height * ETC2_RGB_OUTPUT_NCHANNELS; + expected_src_size = nblocks_x * nblocks_y * ETCDEC_ETC_RGB_BLOCK_SIZE; + break; + + case BlockCompression::ETC2_RGB_A1: + expected_nchannels = ETC2_RGB_A1_OUTPUT_NCHANNELS; + expected_dst_size = width * height * ETC2_RGB_A1_OUTPUT_NCHANNELS; + expected_src_size = nblocks_x * nblocks_y + * ETCDEC_ETC_RGB_A1_BLOCK_SIZE; + break; + + case BlockCompression::ETC2_RGBA: + expected_nchannels = ETC2_RGBA_OUTPUT_NCHANNELS; + expected_dst_size = width * height * ETC2_RGBA_OUTPUT_NCHANNELS; + expected_src_size = nblocks_x * nblocks_y * ETCDEC_EAC_RGBA_BLOCK_SIZE; + break; + + default: + errorfmt("Unsupported ETCO compression format: {}", + static_cast(cmp)); + return false; + } + + if (nchannels != expected_nchannels) { + errorfmt("Current ETC scheme is expected to decode into " + "{}-channel-images but target image got: {} channels.", + expected_nchannels, nchannels); + return false; + } + + if (src.size() < expected_src_size) { + errorfmt("The source data buffer's size is smaller than expected. " + "Expected {} bytes but provided buffer only has {} bytes.", + expected_src_size, src.size()); + return false; + } + + if (dst.size() < expected_dst_size) { + errorfmt( + "The size of the destination buffer to hold decoded ETC " + "blocks is smaller than expected. Expected {} bytes but provided " + "buffer only has {} bytes.", + expected_dst_size, dst.size()); + return false; + } + return true; +} + + +void +KtxInput::decode_etc2_rgb(cspan src, span dst, + int level) const +{ + const size_t width = std::max(m_tex2->baseWidth >> level, 1u); + const size_t height = std::max(m_tex2->baseHeight >> level, 1u); + + const size_t rgbx_pitch = ETC_BLOCK_SIZE * ETC2_RGB_OUTPUT_NCHANNELS; + uint8_t rgbx[ETC_BLOCK_SIZE * rgbx_pitch]; /* 64 bytes */ + const uint8_t* src_blocks = src.data(); + + for (size_t y { 0 }; y < height; y += ETC_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += ETC_BLOCK_SIZE) { + // ETC2_RGB: 8 bytes -> 4 x 4 x (3 + 1) = 64 bytes (alpha ignored) + etcdec_etc_rgb(src_blocks, rgbx, + ETC_BLOCK_SIZE * ETC2_RGB_OUTPUT_NCHANNELS); + src_blocks += ETCDEC_ETC_RGB_BLOCK_SIZE; + cpy_decoded_block(rgbx, dst.data(), x, y, width, height, + ETC2_RGB_OUTPUT_NCHANNELS); + } + } +} + + +void +KtxInput::decode_etc2_rgba(cspan src, span dst, + int level) const +{ + const size_t width = std::max(m_tex2->baseWidth >> level, 1u); + const size_t height = std::max(m_tex2->baseHeight >> level, 1u); + + const size_t rgba_pitch = ETC_BLOCK_SIZE * ETC2_RGBA_OUTPUT_NCHANNELS; + uint8_t rgba[ETC_BLOCK_SIZE * rgba_pitch]; /* 64 bytes */ + const uint8_t* src_blocks = src.data(); + + for (size_t y { 0 }; y < height; y += ETC_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += ETC_BLOCK_SIZE) { + // ETC2_RGBA: 16 bytes -> 4 x 4 x 4 = 64 bytes + etcdec_eac_rgba(src_blocks, rgba, + ETC_BLOCK_SIZE * ETC2_RGBA_OUTPUT_NCHANNELS); + src_blocks += ETCDEC_EAC_RGBA_BLOCK_SIZE; + cpy_decoded_block(rgba, dst.data(), x, y, width, height, + ETC2_RGBA_OUTPUT_NCHANNELS); + } + } +} + + +void +KtxInput::decode_etc2_rgb_a1(cspan src, span dst, + int level) const +{ + const size_t width = std::max(m_tex2->baseWidth >> level, 1u); + const size_t height = std::max(m_tex2->baseHeight >> level, 1u); + + const size_t rgba_pitch = ETC_BLOCK_SIZE * ETC2_RGB_A1_OUTPUT_NCHANNELS; + uint8_t rgba[ETC_BLOCK_SIZE * rgba_pitch]; /* 64 bytes */ + const uint8_t* src_blocks = src.data(); + + for (size_t y { 0 }; y < height; y += ETC_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += ETC_BLOCK_SIZE) { + // ETC2_RGB_A1: 8 bytes -> 4 x 4 x 4 = 64 bytes + etcdec_etc_rgb_a1(src_blocks, rgba, + ETC_BLOCK_SIZE * ETC2_RGB_A1_OUTPUT_NCHANNELS); + src_blocks += ETCDEC_ETC_RGB_A1_BLOCK_SIZE; + cpy_decoded_block(rgba, dst.data(), x, y, width, height, + ETC2_RGB_A1_OUTPUT_NCHANNELS); + } + } +} + +OIIO_PLUGIN_NAMESPACE_END diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp new file mode 100644 index 0000000000..df7554f219 --- /dev/null +++ b/src/ktx.imageio/ktxoutput.cpp @@ -0,0 +1,963 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + +// TODO: only set this if libktx is statically built/linked against +// Per KTX-Software BUILDING.md: +// > When linking to the static library, make sure to +// > define `KHRONOS_STATIC` before including KTX header files. +// > This is especially important on Windows. +#ifndef BUILD_SHARED_LIBS +# define KHRONOS_STATIC 1 +#endif + +#include "ktx_pvt.h" +#include +#include + +#include "bc7enc-rdo/bc7enc.h" /* for BC7 encoder */ +#include "bc7enc-rdo/rgbcx.h" /* for BC1-BC5 encoders */ +// TODO: ETC encoders +// TODO: BC6HS/BC6HU encoders + +OIIO_PLUGIN_NAMESPACE_BEGIN + +class KtxOutput final : public ImageOutput { +public: + KtxOutput() {} + + ~KtxOutput() override { close(); } + + const char* format_name(void) const override { return "ktx"; } + + int supports(string_view feature) const override + { + return ( + // as per the KTX1/2 specs: + // registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_keyvalue_data + feature == "arbitrary_metadata" || + /* ktx supports 3D textures, cubmap textures, texture arrays, ... */ + feature == "multiimage" || + /* not sure ... this naming is confusing */ + feature == "mipmap"); + } + + bool open(const std::string& name, const ImageSpec& spec, + OpenMode mode = Create) override; + + bool write_scanline(int y, int z, TypeDesc format, const void* data, + stride_t xstride) override; + + bool write_scanlines(int ybegin, int yend, int z, TypeDesc format, + const void* data, stride_t xstride = AutoStride, + stride_t ystride = AutoStride) override; + + bool close() override; + +private: + std::string m_filename; + + ktxTexture2* m_tex { nullptr }; + + uint32_t m_nlayers { 1 }; + + uint32_t m_miplevels { 1 }; + + ktxSupercmpScheme m_superCmp { KTX_SS_NONE }; + + khr_df_model_e m_colormodel { KHR_DF_MODEL_UNSPECIFIED }; + + BlockCompression m_cmp { BlockCompression::NONE }; + + /// libktx only supports writing whole images (i.e., (miplevel, layer, face_slice/depth) + /// hence why we keep a large std::vector at all times. This is also needed because we + /// apply compression upon file closure and not each time on write_scanline(s). + std::vector m_img; + + // TODO: what about volumetric textures? do we keep an array of these very + // large vectors? For the moment, these are just not supported. + + void init(); + + VkFormat figure_vkformat_from_spec() const; + + bool basisu_basislz_compress(); + + bool basisu_uastc_compress(); + + bool write_ktx2(); + + inline bool check_bcn_spans(cspan src, span dst, + size_t width, size_t height, + BlockCompression cmp) const; + + void encode_bc1(cspan src, span dst, size_t width, + size_t height); + + void encode_bc3(cspan src, span dst, size_t width, + size_t height); + + void encode_bc4(cspan src, span dst, size_t width, + size_t height); + + void encode_bc5(cspan src, span dst, size_t width, + size_t height); + + void encode_bc7(cspan src, span dst, size_t width, + size_t height, const bc7enc_compress_block_params* params); + + // void generate_mip_levels(const image_span& base_lvl_image, + // ImageInput& inputFile, uint32_t numMipLevels, + // uint32_t layerIndex, uint32_t faceIndex, + // uint32_t depthSliceIndex); +}; + + + +OIIO_PLUGIN_EXPORTS_BEGIN + +OIIO_EXPORT ImageOutput* +ktx_output_imageio_create() +{ + return new KtxOutput; +} +OIIO_EXPORT const char* ktx_output_extensions[] = { "ktx2", nullptr }; + +OIIO_PLUGIN_EXPORTS_END + + + +bool +KtxOutput::open(const std::string& name, const ImageSpec& newspec, + OpenMode mode) +{ + // TODO: verify the x, y, z limits (probably not 65535) + if (!check_open(mode, newspec, { 0, 65535, 0, 65535, 0, 65535, 0, 4 })) + return false; + + // Save name and spec for later use + m_filename = name; + + ioproxy_retrieve_from_config(m_spec); + if (!ioproxy_use_or_open(m_filename)) + return false; + + // TODO: get_int_attribute causes a segfault and I have no idea why ... + // Weirdly, calling find_attribute directly (and checking the resulting + // pointer) works, but not get_int_attribute ... + + // keep this commented in case we need it + /* + cspan dfd; + ParamValue* dfdQ = m_spec.find_attribute("ktx:dfd", TypeDesc::UINT8); + if (dfdQ) { + dfd = dfdQ->as_cspan(); + std::cout << "[ktxoutput] found dfd with len: " << dfdQ->nvalues() + << '\n'; + } + + // Copy dfd data from const span because ktxTextureCreateInfo does not take + // a const uint8_t ptr. + ktx_uint32_t* pDfd { nullptr }; + std::vector dfd_copy; + if (!dfd.empty()) { + dfd_copy.assign(dfd.begin(), dfd.end()); + pDfd = reinterpret_cast(dfd_copy.data()); + std::cout << "pDfd is set to != nullptr" << '\n'; + } + */ + + // + // Use sensible default in case the input data did not originate from a KTX2 + // file and the user did not provide a supercompression scheme. KTX2 usually + // uses Basis LZ supercompression scheme to benefit from both: smaller + // disk filesizes and on-the-fly transcoding to a supported native GPU + // format. + // + ParamValue* superCmpSchemQ + = m_spec.find_attribute("ktx:supercompressionscheme", TypeDesc::UINT32); + if (superCmpSchemQ) { + m_superCmp = static_cast( + *reinterpret_cast(superCmpSchemQ->data())); + // std::cout << "[ktxoutput] found supercompression scheme: " << m_superCmp + // << '\n'; + } + + + // Get transfer function (for color space conversions) + // ParamValue* tfQ = m_spec.find_attribute("ktx:transferfunction", + // TypeDesc::UINT32); + // if (tfQ) { + // m_tf = static_cast( + // *reinterpret_cast(tfQ->data())); + // std::cout << "[ktxoutput] found tf: " << m_tf << '\n'; + // } + + // Get color model (to detect GPU compression, Basis Universal format, etc.) + ParamValue* colorModelQ = m_spec.find_attribute("ktx:colormodel", + TypeDesc::UINT32); + if (colorModelQ) { + m_colormodel = static_cast( + *reinterpret_cast(colorModelQ->data())); + // std::cout << "[ktxoutput] found color model: " << m_colormodel << '\n'; + } + + // Do an early check on supported supercompressionscheme values + if (m_superCmp != KTX_SS_BASIS_LZ && m_superCmp != KTX_SS_NONE) { + // doing an `errorfmt()` then `close()` causes a seg fault... + close(); + errorfmt("unsupported super compression scheme: {}", + static_cast(m_superCmp)); + return false; + } + + // Get original VkFormat (i.e., before potential decompression/transcoding) + // or the vkformat explicitly set via the "ktx:vkformat" attribute. + auto vkFormat = VkFormat::VK_FORMAT_UNDEFINED; + ParamValue* vkFormatQ = m_spec.find_attribute("ktx:vkformat", + TypeDesc::UINT32); + if (vkFormatQ) { + vkFormat = static_cast( + *reinterpret_cast(vkFormatQ->data())); + // Get GPU-block-compression from provided VkFormat + if (vkFormat != VK_FORMAT_UNDEFINED) { + FormatInfo format_info; + if (!extract_info_from_format(vkFormat, format_info)) { + close(); + errorfmt("Could not extract format info from provided " + "VkFormat: {}. This format is probably unsupported.", + static_cast(vkFormat)); + return false; + } + m_cmp = format_info.compression; + } + // std::cout << "[ktxoutput] found vkformat: " << vkFormat << '\n'; + } + + // + // Since we are using libktx's ktxTexture2_CompressAstc, the format has to + // be set to an uncompressed VkFormat otherwise we get KTX_INVALID_OPERATION + // error code. + // + if (m_cmp == BlockCompression::ASTC) + vkFormat = VK_FORMAT_R8G8B8A8_SRGB; + + // Id a basis universal format compression is not requested and + // "ktx:vkformat" is VK_FORMAT_UNDEFINED, then we error out. The user has to + // set the vkformat so that we know in which format we write the texture to. + if ((m_colormodel != KHR_DF_MODEL_ETC1S + && m_colormodel != KHR_DF_MODEL_UASTC) + && vkFormat == VK_FORMAT_UNDEFINED) { + // TODO: maybe don't error out and set the format depending on the nchannels? + // (e.g., 4 + srgb_rec709_scene colorspace => VK_FORMAT_R8G8B8A8_SRGB) + close(); + errorfmt( + "VkFormat is set to VK_FORMAT_UNDEFINED even though the " + "supercompressionscheme is not Basis LZ. You have to set the " + "target VkFormat by setting the ImageSpec's attribute 'ktx:vkformat'."); + return false; + } + + // + // If we intend to compress to BasisLZ/ETC1S or UASTC then we need to figure + // the VkFormat so that ktxTexture_SetImageFromMemory does not segfault. + // (makes sense, since we are creating a KTX texture and telling it to + // allocate storage, how would it know the size of a given subimage if we + // provide it with VK_FORMAT_UNDEFINED?) + // + if ((m_colormodel == KHR_DF_MODEL_ETC1S + || m_colormodel == KHR_DF_MODEL_UASTC) + && vkFormat == VK_FORMAT_UNDEFINED) { + vkFormat = figure_vkformat_from_spec(); + } else if (m_colormodel == KHR_DF_MODEL_ETC1S + || m_colormodel == KHR_DF_MODEL_UASTC) { + // TODO: It could be that the user explicitly provided a vkformat - in which + // case we have to make sure it aligns with the spec. + // if (!is_vkformat_aligned_with_spec()) ... + close(); + errorfmt("Expected vkformat to be VK_FORMAT_UNDEFINED for Basis " + "Universal (UASTC or ETC1S) target KTX textures."); + return false; + } + + // get number of mip levels + m_miplevels = 1; + ParamValue* miplevelsQ = m_spec.find_attribute("ktx:miplevels", + TypeDesc::UINT32); + if (miplevelsQ) { + m_miplevels = *reinterpret_cast(miplevelsQ->data()); + } + + if (m_miplevels > 1) { + close(); + errorfmt("Cannot re-generate mip levels because there is no way to " + "know the original filter that was used to generate them."); + return false; + } + + // + // TODO: sanity checks on provided attributes (e.g., certain + // supercompression schemes cannot be applied to certain basisu formats, + // etc.) + // + + // get number of layers + m_nlayers = 1; + ParamValue* nlayersQ = m_spec.find_attribute("ktx:nlayers", + TypeDesc::UINT32); + if (nlayersQ) { + m_nlayers = *reinterpret_cast(nlayersQ->data()); + } + + // std::cout << "vkformat: " << vkFormat << '\n'; + // std::cout << "mip levels: " << m_miplevels << '\n'; + // std::cout << "nlayers: " << m_nlayers << '\n'; + // std::cout << "[width, height, depth]: [" << m_spec.width << ", " + // << m_spec.height << ", " << m_spec.depth << "] \n"; + + // TODO: avoid some of these static casts into ktx_uint*_t types by storing + // uint*_t as unsigned integers and not as integers. + OIIO_ASSERT(vkFormat != VK_FORMAT_UNDEFINED); // otherwise segfault + ktxTextureCreateInfo create_info; + create_info.glInternalformat = 0; // Ignored as we'll create a KTX2 texture + create_info.vkFormat = vkFormat; + create_info.pDfd = nullptr; + create_info.baseWidth = static_cast(m_spec.width); + create_info.baseHeight = static_cast(m_spec.height); + create_info.baseDepth = static_cast(m_spec.depth); + create_info.numDimensions = 2; // TODO: this is currently hardcoded + create_info.numLevels = 1; // static_cast(m_miplevels) + create_info.numLayers = 1; // static_cast(nlayers) + create_info.numFaces = 1; // TODO: this is currently hardcoded + create_info.isArray = KTX_FALSE; + create_info.generateMipmaps = KTX_FALSE; + + if (auto status = ktxTexture2_Create(&create_info, + KTX_TEXTURE_CREATE_ALLOC_STORAGE, + &m_tex); + status != KTX_SUCCESS) { + close(); + errorfmt("ktxTexture_Create return KTX exit error code: {}", + static_cast(status)); + return false; + } + + // Reserve space for base level mipmap + if (!m_tex->isCompressed) { + m_img.resize(ktxTexture_GetImageSize(ktxTexture(m_tex), 0)); + } else { + // TODO: + // Not compressed => make sure that vector's size matches the expected + // size from the set raw VkFormat: + // (e.g., VK_FORMAT_R8G8_SRGB => width * height * 3 ) + m_img.resize(m_spec.scanline_bytes() * m_spec.height); + rgbcx::init(rgbcx::bc1_approx_mode::cBC1Ideal); + } + + return true; +} + + + +bool +KtxOutput::write_scanline(int y, int z, TypeDesc format, const void* data, + stride_t xstride) +{ + return write_scanlines(y, y + 1, z, format, data, xstride); +} + + + +// TODO: use the span alternative. Apparently, there isn't one that takes a +// depth parameter (i.e., z). +bool +KtxOutput::write_scanlines(int ybegin, int yend, + int _ /* slice or face or layer */, TypeDesc format, + const void* data, stride_t xstride, stride_t ystride) +{ + // std::cout << "write_scanlines called with: ybegin=" << ybegin + // << "; yend=" << yend << "; z=" << z << "; format=" << format + // << "; xstride=" << xstride << '\n'; + + m_spec.auto_stride(xstride, format, spec().nchannels); + // const void* origdata = data; + if (format == TypeUnknown) + format = m_spec.format; + + const size_t pitch = m_spec.scanline_bytes(); + auto pSrc = reinterpret_cast(data); + size_t offset = ybegin * pitch; + size_t datalen = (yend - ybegin) * pitch; + + memcpy(m_img.data() + offset, pSrc, datalen); + // std::cout << "write_scanlines success" << '\n'; + return true; +} + + + +bool +KtxOutput::close() +{ + // Check if already closed => if so, then the KTX2 file is already saved + if (!ioproxy_opened()) { + init(); + return true; + } + + bool result = true; + if (m_tex) { + // Apparently we can't do (or I don't know yet how to) partial writes + // using libktx. We can only write whole ktxTextures all together. + if (result) + result = write_ktx2(); // TODO: can this throw? (prob not) + ktxTexture_Destroy(ktxTexture(m_tex)); + } + init(); + return result; +} + + + +void +KtxOutput::init() +{ + // TODO: calling open() after close() on this hasn't been testes yet ... + m_tex = nullptr; + m_superCmp = KTX_SS_NONE; + m_colormodel = KHR_DF_MODEL_UNSPECIFIED; + // TODO: other stuff... + ioproxy_clear(); +} + + + +VkFormat +KtxOutput::figure_vkformat_from_spec() const +{ + // TODO: check colorspace and return VkFormat accordingly + // TODO: check format (TypeDesc) and return VkFormat accordingly + switch (m_spec.nchannels) { + case 1: return VK_FORMAT_R8_SRGB; + case 2: return VK_FORMAT_R8G8_SRGB; + case 3: return VK_FORMAT_R8G8B8_SRGB; + case 4: return VK_FORMAT_R8G8B8A8_SRGB; + } + return VK_FORMAT_R8G8B8A8_SRGB; +} + + + +// +// Applies BasisLZ/ETC1S supercompression to this KTX2 texture. The ImageSpec is +// queried (searched) for attribute that determine the BasisLZ/ETC1S compression +// params (see ktxBasisParams struct in libktx). +// +bool +KtxOutput::basisu_basislz_compress() +{ + // TODO: retrieve BasisLZ/ETC1S compression params. `ktx info` prints some + // Basis Supercompression Global Data that might be useful in figuring out + // what params the original data was compressed with so that we can reproduce + // it. + // TODO: expose as "ktx:" attribute(s) + ktxBasisParams params; + params.structSize = sizeof(ktxBasisParams); + params.codec = ktx_basis_codec_e::KTX_BASIS_CODEC_ETC1S; + params.threadCount = 1; + params.etc1sCompressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL; + if (auto status = ktxTexture2_CompressBasisEx(m_tex, ¶ms); + status != KTX_SUCCESS) { + errorfmt("ktxTexture2_CompressBasisEx returned error code: ", + static_cast(status)); + return false; + } + return true; +} + + + +// +// Applies UASTC basis universal 'compression' to this KTX2 texture. +// The ImageSpec is queried (searched) for attribute that determine the UASTC +// compression params (see ktxBasisParams struct in libktx). +// +bool +KtxOutput::basisu_uastc_compress() +{ + // TODO: expose parameters + ktxBasisParams params; + params.structSize = sizeof(ktxBasisParams); + params.codec = ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_LDR_4x4; + params.threadCount = 1; + // .uastcFlags = KTX_PACK_UASTC_LEVEL_DEFAULT, + // TODO: set uastcRDONoMultithreading for testing + if (auto status = ktxTexture2_CompressBasisEx(m_tex, ¶ms); + status != KTX_SUCCESS) { + errorfmt("ktxTexture2_CompressBasisEx returned error code: ", + static_cast(status)); + return false; + } + return true; +} + + + +bool +KtxOutput::write_ktx2() +{ + // TODO: this attribute should be ignored in testing. + // Add/overwrite the KTXwriter metadata entry. The specs encourages us to do + // so. + char writer[100]; + snprintf(writer, sizeof(writer), "oiio version %d - plugin version %d", + OPENIMAGEIO_VERSION, OIIO_PLUGIN_VERSION); + ktxHashList_AddKVPair(&m_tex->kvDataHead, KTX_WRITER_KEY, + (ktx_uint32_t)strlen(writer) + 1, writer); + // std::cout << "KTXwrite: " << writer << '\n'; + + // dimensions for current mip level + const size_t width = std::max(m_tex->baseWidth >> 0, 1u); + const size_t height = std::max(m_tex->baseHeight >> 0, 1u); + + // + // In case data was read from an input KTX2 file with mipmaps, we have to + // write the base level then generate mipmaps up to the specified level + // (via the "ktx:miplevels" attribute). KTX-Software (not necessarily + // libktx), surely has a function somewhere that generates these mipmaps. + // Ideally, we should follow the exact same implementation used in + // KTX-Software to generate the mipmaps. + // + // Note 1: + // You may notice the `generateMipmaps` flag in the ktxTexture struct, it + // is just used to instruct Vulkan or OpenGL to generate mipmaps for the + // texture to be uploaded NOT for mipmap generation on the CPU. + // + // Note 2: + // There is apparently no metadata to know which filter (+ params) that + // was used to generate the mipmaps. + // + // Important: + // If the VkFormat related to the KTX texture creation is wrongly set, this + // will cause a segfault! + // + if (!m_tex->isCompressed) { + if (auto status = ktxTexture_SetImageFromMemory(ktxTexture(m_tex), 0, 0, + 0, m_img.data(), + m_img.size()); + status != KTX_SUCCESS) { + has_error(); + errorfmt( + "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", + static_cast(status)); + return false; + } + } else if (m_cmp == BlockCompression::BC1 || m_cmp == BlockCompression::BC3 + || m_cmp == BlockCompression::BC4 + || m_cmp == BlockCompression::BC5 + || m_cmp == BlockCompression::BC7) { + // + // Is this a BCn GPU block compressed texture? If so, we need to encode + // to target block compression. To do so, we create another intermediate + // std::vector (is there a better alternative?) + // + std::vector img_compressed; + img_compressed.resize(ktxTexture_GetImageSize(ktxTexture(m_tex), 0)); + switch (m_cmp) { + case BlockCompression::BC1: + if (!check_bcn_spans(m_img, img_compressed, width, height, m_cmp)) + return false; + encode_bc1(m_img, img_compressed, width, height); + break; + + case BlockCompression::BC3: + if (!check_bcn_spans(m_img, img_compressed, width, height, m_cmp)) + return false; + encode_bc3(m_img, img_compressed, width, height); + break; + + case BlockCompression::BC4: + if (!check_bcn_spans(m_img, img_compressed, width, height, m_cmp)) + return false; + encode_bc4(m_img, img_compressed, width, height); + break; + + case BlockCompression::BC5: + if (!check_bcn_spans(m_img, img_compressed, width, height, m_cmp)) + return false; + encode_bc5(m_img, img_compressed, width, height); + break; + + case BlockCompression::BC7: { + // MUST be called before calling bc7enc_compress_block() (or you'll get artifacts). + bc7enc_compress_block_init(); + + // Make sure to init BC7 block compression params to default. There is + // simply no way to figure out the originally used params from KTX2 file so + // we use defaults. + bc7enc_compress_block_params cmp_params; + bc7enc_compress_block_params_init(&cmp_params); + if (!check_bcn_spans(m_img, img_compressed, width, height, m_cmp)) + return false; + encode_bc7(m_img, img_compressed, width, height, &cmp_params); + break; + } + default: return false; + } + + if (auto status = ktxTexture_SetImageFromMemory(ktxTexture(m_tex), 0, 0, + 0, + img_compressed.data(), + img_compressed.size()); + status != KTX_SUCCESS) { + errorfmt( + "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", + static_cast(status)); + return false; + } + } else if (m_cmp == BlockCompression::ASTC) { + // First set uncompressed images + if (auto status = ktxTexture_SetImageFromMemory(ktxTexture(m_tex), 0, 0, + 0, m_img.data(), + m_img.size()); + status != KTX_SUCCESS) { + has_error(); + errorfmt( + "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", + static_cast(status)); + return false; + } + + // Then compress the whole texture to ASTC format + // TODO: expose ASTC compression quality parameter as spec attribute + if (auto status = ktxTexture2_CompressAstc(m_tex, 0); + status != KTX_SUCCESS) { + errorfmt("ktxTexture2_CompressAstc returned KTX exit error code: {}", + static_cast(status)); + return false; + } + } + + // If basis universal compression is requested (i.e., to BasisLZ/ETC1S + // or UASTC), compress the texture before writing. + if (m_colormodel == KHR_DF_MODEL_ETC1S) { + if (!basisu_basislz_compress()) + return false; + } else if (m_colormodel == KHR_DF_MODEL_UASTC) { + if (!basisu_uastc_compress()) + return false; + } + + // Finally, apply the supercompression scheme (if any) + if (m_superCmp == KTX_SS_ZLIB) { + if (auto status = ktxTexture2_DeflateZLIB(m_tex, 0); + status != KTX_SUCCESS) { + errorfmt("ktxTexture2_DeflateZLIB returned KTX exit error code: {}", + static_cast(status)); + return false; + } + + } else if (m_superCmp == KTX_SS_ZSTD) { + if (auto status = ktxTexture2_DeflateZstd(m_tex, 0); + status != KTX_SUCCESS) { + errorfmt("ktxTexture2_DeflateZstd returned KTX exit error code: {}", + static_cast(status)); + return false; + } + } + + Filesystem::IOProxy* m_io = ioproxy(); + if (!strcmp(m_io->proxytype(), "file")) { + auto fd = reinterpret_cast(m_io)->handle(); + if (auto status = ktxTexture2_WriteToStdioStream(m_tex, fd); + status != KTX_SUCCESS) { + errorfmt( + "ktxTexture2_WriteToStdioStream returned KTX exit error code: {}", + static_cast(status)); + return false; + } + return true; + } + + // TODO: this hasn't been tested yet + if (!strcmp(m_io->proxytype(), "vecoutput")) { + auto proxy = reinterpret_cast(m_io); + ktx_uint8_t* buff; + ktx_size_t buff_size; + if (auto status = ktxTexture2_WriteToMemory(m_tex, &buff, &buff_size); + status != KTX_SUCCESS) { + errorfmt( + "ktxTexture2_WriteToMemory returned KTX exit error code: {}", + static_cast(status)); + return false; + } + /* Cleanup when we go out of scope or on exception */ + auto _ = std::unique_ptr(buff); + proxy->write(buff, buff_size); + return true; + } + + // OIIO should guarantee that this never happens + errorfmt("unexpected IOProxy type: {}", m_io->proxytype()); + return false; +} + + + +// +// Makes sure that provided source BCn blocks span and target span (where blocks +// will be decoded into) are of sufficient sizes. +// This should be called before any decode_bcn() functions. +// +inline bool +KtxOutput::check_bcn_spans(cspan src, span dst, size_t width, + size_t height, BlockCompression cmp) const +{ + const size_t nchannels { static_cast(m_spec.nchannels) }; + const int nblocks_x { static_cast( + std::ceil(width / (float)BCN_BLOCK_SIZE)) }; + const int nblocks_y { static_cast( + std::ceil(height / (float)BCN_BLOCK_SIZE)) }; + size_t expected_nchannels; + size_t expected_dst_size; + + /* Safety checks */ + if ((width % BCN_BLOCK_SIZE != 0) || (height % BCN_BLOCK_SIZE != 0)) { + errorfmt("To use BCn block compression, the image's dimensions have " + "to be a multiple of: {}", + BCN_BLOCK_SIZE); + return false; + } + + switch (cmp) { + case BlockCompression::BC1: + expected_nchannels = BC1_OUTPUT_NCHANNELS; + expected_dst_size = nblocks_x * nblocks_y * BC1_BLOCK_SIZE; + break; + + case BlockCompression::BC3: + expected_nchannels = BC3_OUTPUT_NCHANNELS; + expected_dst_size = nblocks_x * nblocks_y * BC3_BLOCK_SIZE; + break; + + case BlockCompression::BC4: + expected_nchannels = BC4_OUTPUT_NCHANNELS; + expected_dst_size = nblocks_x * nblocks_y * BC4_BLOCK_SIZE; + break; + + case BlockCompression::BC5: + expected_nchannels = BC5_OUTPUT_NCHANNELS; + expected_dst_size = nblocks_x * nblocks_y * BC5_BLOCK_SIZE; + break; + + case BlockCompression::BC7: + expected_nchannels = BC7_OUTPUT_NCHANNELS; + expected_dst_size = nblocks_x * nblocks_y * BC7_BLOCK_SIZE; + break; + + default: + errorfmt("Unsupported block compression format: {}", + static_cast(m_cmp)); + return false; + } + + if (nchannels != expected_nchannels) { + errorfmt("Current BCn scheme is expected to encode {}-channel-images " + "but provided image got: {} channels.", + nchannels); + return false; + } + + const size_t expected_src_size { width * height * expected_nchannels }; + if (src.size() < expected_src_size) { + errorfmt("The source data buffer's size is smaller than expected. " + "Expected {} bytes but provided buffer only has {} bytes.", + expected_src_size, src.size()); + return false; + } + + if (dst.size() < expected_dst_size) { + errorfmt("The size of the destination buffer to hold current BCn " + "scheme's compressed blocks is smaller than expected. " + "Expected {} bytes but provided buffer only has {} bytes.", + expected_dst_size, dst.size()); + return false; + } + return true; +} + + + +void +KtxOutput::encode_bc1(cspan src, span dst, size_t width, + size_t height) +{ + uint8_t* dst_ptr { dst.data() }; + const size_t pixels_pitch { BCN_BLOCK_SIZE + * BC1_OUTPUT_NCHANNELS }; // 4 x 4 + uint8_t pPixels[BCN_BLOCK_SIZE * pixels_pitch]; // 4 x 4 x 4 + + // Row-major loop over blocks + for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { + // extract/copy source block + for (size_t i { 0 }; i < BCN_BLOCK_SIZE; ++i) { + // copy 4 pixels (32bpp) to pPixels + memcpy(pPixels + i * pixels_pitch, + src.data() + (y + i) * width * BC1_OUTPUT_NCHANNELS + + x * BC1_OUTPUT_NCHANNELS, + pixels_pitch); + } + // BC1: 8 bytes -> 4 x 4 x 4 = 64 bytes + rgbcx::encode_bc1(10, dst_ptr, + reinterpret_cast(pPixels), true, + false); + dst_ptr += BC1_BLOCK_SIZE; + } + } +} + + + +void +KtxOutput::encode_bc3(cspan src, span dst, size_t width, + size_t height) +{ + uint8_t* dst_ptr { dst.data() }; + const size_t pixels_pitch { BCN_BLOCK_SIZE + * BC3_OUTPUT_NCHANNELS }; // 4 x 4 + uint8_t pPixels[BCN_BLOCK_SIZE * pixels_pitch]; // 4 x 4 x 4 + + for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { + for (size_t i { 0 }; i < BCN_BLOCK_SIZE; ++i) + memcpy(pPixels + i * pixels_pitch, + src.data() + (y + i) * width * BC3_OUTPUT_NCHANNELS + + x * BC3_OUTPUT_NCHANNELS, + pixels_pitch); + // BC3: 16 bytes -> 4 x 4 x 4 = 64 bytes + rgbcx::encode_bc3(10, dst_ptr, + reinterpret_cast(pPixels)); + dst_ptr += BC3_BLOCK_SIZE; + } + } +} + + + +void +KtxOutput::encode_bc4(cspan src, span dst, size_t width, + size_t height) +{ + uint8_t* dst_ptr { dst.data() }; + const size_t pixels_pitch { BCN_BLOCK_SIZE + * BC4_OUTPUT_NCHANNELS }; // 4 x 1 + uint8_t pPixels[BCN_BLOCK_SIZE * pixels_pitch]; // 4 x 4 x 1 + + for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { + for (size_t i { 0 }; i < BCN_BLOCK_SIZE; ++i) + memcpy(pPixels + i * pixels_pitch, + src.data() + (y + i) * width * BC4_OUTPUT_NCHANNELS + + x * BC4_OUTPUT_NCHANNELS, + pixels_pitch); + // BC4: 8 bytes -> 4 x 4 x 1 = 16 bytes + rgbcx::encode_bc4(dst_ptr, + reinterpret_cast(pPixels), + /* stride */ BC4_OUTPUT_NCHANNELS); + dst_ptr += BC4_BLOCK_SIZE; + } + } +} + + + +void +KtxOutput::encode_bc5(cspan src, span dst, size_t width, + size_t height) +{ + uint8_t* dst_ptr { dst.data() }; + const size_t pixels_pitch { BCN_BLOCK_SIZE + * BC5_OUTPUT_NCHANNELS }; // 4 x 2 + uint8_t pPixels[BCN_BLOCK_SIZE * pixels_pitch]; // 4 x 4 x 2 + + for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { + for (size_t i { 0 }; i < BCN_BLOCK_SIZE; ++i) + memcpy(pPixels + i * pixels_pitch, + src.data() + (y + i) * width * BC5_OUTPUT_NCHANNELS + + x * BC5_OUTPUT_NCHANNELS, + pixels_pitch); + // BC5: 16 bytes -> 4 x 4 x 2 = 32 bytes + rgbcx::encode_bc5(dst_ptr, + reinterpret_cast(pPixels), 0, 1, + /* stride */ BC5_OUTPUT_NCHANNELS); + dst_ptr += BC5_BLOCK_SIZE; + } + } +} + + + +void +KtxOutput::encode_bc7(cspan src, span dst, size_t width, + size_t height, const bc7enc_compress_block_params* params) +{ + uint8_t* dst_ptr { dst.data() }; + const size_t pixels_pitch { BCN_BLOCK_SIZE + * BC7_OUTPUT_NCHANNELS }; // 4 x 4 + uint8_t pPixels[BCN_BLOCK_SIZE * pixels_pitch]; // 4 x 4 x 4 + + for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { + for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { + for (size_t i { 0 }; i < BCN_BLOCK_SIZE; ++i) + memcpy(pPixels + i * pixels_pitch, + src.data() + (y + i) * width * BC7_OUTPUT_NCHANNELS + + x * BC7_OUTPUT_NCHANNELS, + pixels_pitch); + // BC7: 16 bytes -> 4 x 4 x 4 = 64 bytes + bc7enc_compress_block(dst_ptr, + reinterpret_cast(pPixels), + params); + dst_ptr += BC7_BLOCK_SIZE; + } + } +} + + + +// void +// KtxOutput::generate_mip_levels(const image_span& base_lvl_image, +// ImageInput& inputFile, uint32_t numMipLevels, +// uint32_t layerIndex, uint32_t faceIndex, +// uint32_t depthSliceIndex) +// { +// //if (isFormatINT(static_cast(texture->vkFormat))) +// // fatal(rc::NOT_SUPPORTED, "Mipmap generation for SINT or UINT format {} is not supported.", +// // toString(static_cast(texture->vkFormat))); +// +// for (uint32_t mipLevelIndex = 1; mipLevelIndex < numMipLevels; +// ++mipLevelIndex) { +// const auto mipImageWidth = std::max(1u, m_tex->baseWidth +// >> (mipLevelIndex)); +// const auto mipImageHeight = std::max(1u, m_tex->baseHeight +// >> (mipLevelIndex)); +// +// ROI roi(0, mipImageHeight, 0, mipImageWidth, 0, 1, /*chans:*/ 0, 4); +// ImageBuf dst = ImageBufAlgo::resample(Src, true, roi); +// // if (options.normalize) +// // image->normalize(); +// +// // const auto imageData = convert(levelImage, options.vkFormat, inputFile, +// // true); +// +// const auto ret = ktxTexture_SetImageFromMemory( +// m_tex, mipLevelIndex, layerIndex, +// faceIndex +// + depthSliceIndex, // Faces and Depths are mutually exclusive, Addition is acceptable +// NULL, 0); +// // (ret == KTX_SUCCESS && "Internal error"); +// } +// } + +OIIO_PLUGIN_NAMESPACE_END diff --git a/src/libOpenImageIO/imageioplugin.cpp b/src/libOpenImageIO/imageioplugin.cpp index 0ea86da08a..a3f19d229c 100644 --- a/src/libOpenImageIO/imageioplugin.cpp +++ b/src/libOpenImageIO/imageioplugin.cpp @@ -305,6 +305,7 @@ PLUGENTRY(tiff); PLUGENTRY(targa); PLUGENTRY(webp); PLUGENTRY(zfile); +PLUGENTRY(ktx); #endif // defined(EMBED_PLUGINS) @@ -439,6 +440,9 @@ catalog_builtin_plugins() #if !defined(DISABLE_ZFILE) DECLAREPLUG (zfile); #endif +#if !defined(DISABLE_KTX) + DECLAREPLUG (ktx); +#endif #endif } // clang-format on diff --git a/testsuite/ktx/ref/out.txt b/testsuite/ktx/ref/out.txt new file mode 100644 index 0000000000..201a5c8a95 --- /dev/null +++ b/testsuite/ktx/ref/out.txt @@ -0,0 +1,180 @@ +Reading ../oiio-images/ktx2/2d_rgb8.ktx2 +../oiio-images/ktx2/2d_rgb8.ktx2 : 40 x 40, 3 channel, uint8 ktx + MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 + SHA-1: 5BFA8296353869F0D950DC016A7E31C08D513EE8 + channel list: R, G, B + ktx:colormodel: 1 + ktx:generatemipmaps: 0 + ktx:KTXwriter: "ktx create v4.3.1~1 / libktx v4.3.0~1" + ktx:miplevels: 6 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:texturekind: 1 + ktx:version: "2.0" + ktx:vkformat: 29 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/2d_rgba8.ktx2 +../oiio-images/ktx2/2d_rgba8.ktx2 : 40 x 40, 4 channel, uint8 ktx + MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 + SHA-1: 9B40384A9B89EE338F24212AB876C14F9BABDBC0 + channel list: R, G, B, A + ktx:colormodel: 1 + ktx:generatemipmaps: 0 + ktx:KTXwriter: "ktx create v4.3.1~1 / libktx v4.3.0~1" + ktx:miplevels: 6 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:texturekind: 1 + ktx:version: "2.0" + ktx:vkformat: 43 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/2d_uastc.ktx2 +../oiio-images/ktx2/2d_uastc.ktx2 : 40 x 40, 4 channel, uint8 ktx + MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 + SHA-1: A41EE02136B25739FB99520244FF5D0C69A0353C + channel list: R, G, B, A + ktx:colormodel: 166 + ktx:generatemipmaps: 0 + ktx:KTXwriter: "ktx create v4.3.1~1 / libktx v4.3.0~1" + ktx:miplevels: 6 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:texturekind: 1 + ktx:version: "2.0" + ktx:vkformat: 0 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/2d_etc1s.ktx2 +../oiio-images/ktx2/2d_etc1s.ktx2 : 40 x 40, 4 channel, uint8 ktx + MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 + SHA-1: CD540B1FE2CE1F85BE11F7EC21446A8405844029 + channel list: R, G, B, A + ktx:colormodel: 163 + ktx:generatemipmaps: 0 + ktx:KTXwriter: "ktx create v4.3.1~1 / libktx v4.3.0~1" + ktx:miplevels: 6 + ktx:nlayers: 1 + ktx:supercompressionscheme: 1 + ktx:texturekind: 1 + ktx:version: "2.0" + ktx:vkformat: 0 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/2d_astc4x4.ktx2 +../oiio-images/ktx2/2d_astc4x4.ktx2 : 40 x 40, 4 channel, uint8 ktx + MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 + SHA-1: 0F24F23031B85B09E74A4E35D7EC519A9A9259CC + channel list: R, G, B, A + ktx:colormodel: 162 + ktx:generatemipmaps: 0 + ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" + ktx:miplevels: 6 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:texturekind: 1 + ktx:version: "2.0" + ktx:vkformat: 158 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/2d_bc1.ktx2 +../oiio-images/ktx2/2d_bc1.ktx2 : 40 x 40, 4 channel, uint8 ktx + MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 + SHA-1: 0E7F8EBBCA431855C12D2BF5A638AC46922582CC + channel list: R, G, B, A + ktx:colormodel: 128 + ktx:generatemipmaps: 0 + ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" + ktx:miplevels: 6 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:texturekind: 1 + ktx:version: "2.0" + ktx:vkformat: 132 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/2d_bc3.ktx2 +../oiio-images/ktx2/2d_bc3.ktx2 : 40 x 40, 4 channel, uint8 ktx + MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 + SHA-1: B88FED8C339C75154B4D79FEBFADA05CAA06B4E4 + channel list: R, G, B, A + ktx:colormodel: 130 + ktx:generatemipmaps: 0 + ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" + ktx:miplevels: 6 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:texturekind: 1 + ktx:version: "2.0" + ktx:vkformat: 138 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/2d_bc4.ktx2 +../oiio-images/ktx2/2d_bc4.ktx2 : 40 x 40, 1 channel, uint8 ktx + MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 + SHA-1: E0352908B50C8CEC4593866CE8704532EE42A600 + channel list: R + ktx:colormodel: 131 + ktx:generatemipmaps: 0 + ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" + ktx:miplevels: 6 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:texturekind: 1 + ktx:version: "2.0" + ktx:vkformat: 139 + oiio:ColorSpace: "lin_rec709_scene" +Reading ../oiio-images/ktx2/2d_bc5.ktx2 +../oiio-images/ktx2/2d_bc5.ktx2 : 40 x 40, 2 channel, uint8 ktx + MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 + SHA-1: F136624C2A6714A137A0155DAF47C86A31D69ECD + channel list: R, G + ktx:colormodel: 132 + ktx:generatemipmaps: 0 + ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" + ktx:miplevels: 6 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:texturekind: 1 + ktx:version: "2.0" + ktx:vkformat: 141 + oiio:ColorSpace: "lin_rec709_scene" +Reading ../oiio-images/ktx2/2d_bc7.ktx2 +../oiio-images/ktx2/2d_bc7.ktx2 : 40 x 40, 4 channel, uint8 ktx + MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 + SHA-1: 1A8C2DBD9F198A97E15348E26D39B81B565D447C + channel list: R, G, B, A + ktx:colormodel: 134 + ktx:generatemipmaps: 0 + ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" + ktx:miplevels: 6 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:texturekind: 1 + ktx:version: "2.0" + ktx:vkformat: 146 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/2d_etc1.ktx2 +../oiio-images/ktx2/2d_etc1.ktx2 : 40 x 40, 4 channel, uint8 ktx + MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 + SHA-1: 365A5301637FA43F7D7787874A440AA40C9C4E3E + channel list: R, G, B, A + ktx:colormodel: 161 + ktx:generatemipmaps: 0 + ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" + ktx:miplevels: 6 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:texturekind: 1 + ktx:version: "2.0" + ktx:vkformat: 148 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/2d_etc2.ktx2 +../oiio-images/ktx2/2d_etc2.ktx2 : 40 x 40, 4 channel, uint8 ktx + MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 + SHA-1: 6E71EEF6D68C31DE010657E64F9A2030048BCC0F + channel list: R, G, B, A + ktx:colormodel: 161 + ktx:generatemipmaps: 0 + ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" + ktx:miplevels: 6 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:texturekind: 1 + ktx:version: "2.0" + ktx:vkformat: 152 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/ktx/run.py b/testsuite/ktx/run.py new file mode 100644 index 0000000000..3a97ceddfa --- /dev/null +++ b/testsuite/ktx/run.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: BSD-3-Clause and Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + +# save the error output +redirect = ' >> out.txt 2>&1 ' +files = [ + # Raw, uncompressed formats (sRGB) + "2d_rgb8.ktx2", + "2d_rgba8.ktx2", + # Basis Universal formats + "2d_uastc.ktx2", + "2d_etc1s.ktx2", + # GPU-block-compressed formats + "2d_astc4x4.ktx2", + "2d_bc1.ktx2", + "2d_bc3.ktx2", + "2d_bc4.ktx2", + "2d_bc5.ktx2", + "2d_bc7.ktx2", + "2d_etc1.ktx2", + "2d_etc2.ktx2", +] + +for f in files: + command += info_command (OIIO_TESTSUITE_IMAGEDIR + "/" + f) From 289bc1425a600dfd0a83a3b179b90eec201dfa31 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Mon, 22 Jun 2026 13:27:22 +0200 Subject: [PATCH 02/42] Remove BCn and ETC encoders/decoders dependencies *Significantly simplify KTX support by relying on libktx to decode/encode GPU-compressed formats such as BCn, ASTC, and ETC. Consequently, BCn and ETC dependencies are removed. All future required encoders/decoders should be implemented in libktx and not here to keep this is as simple, maintainable, and short as possible. *Remove ETC sub-dependency from local libktx build. *Update ktx README.md documentation to reflect that only libktx dependency is and should be used (i.e., no additional dependencies should be added). Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 6 +- src/ktx.imageio/CMakeLists.txt | 6 +- src/ktx.imageio/README.md | 195 +- src/ktx.imageio/bc7enc-rdo/bc7decomp.cpp | 680 ---- src/ktx.imageio/bc7enc-rdo/bc7decomp.h | 180 - src/ktx.imageio/bc7enc-rdo/bc7enc.cpp | 2581 -------------- src/ktx.imageio/bc7enc-rdo/bc7enc.h | 124 - src/ktx.imageio/bc7enc-rdo/rgbcx.cpp | 3081 ----------------- src/ktx.imageio/bc7enc-rdo/rgbcx.h | 475 --- .../bc7enc-rdo/rgbcx_table4_small.h | 972 ------ src/ktx.imageio/etcdec.h | 597 ---- src/ktx.imageio/ktx_pvt.h | 25 - src/ktx.imageio/ktxinput.cpp | 640 +--- src/ktx.imageio/ktxoutput.cpp | 331 +- 14 files changed, 157 insertions(+), 9736 deletions(-) delete mode 100644 src/ktx.imageio/bc7enc-rdo/bc7decomp.cpp delete mode 100644 src/ktx.imageio/bc7enc-rdo/bc7decomp.h delete mode 100644 src/ktx.imageio/bc7enc-rdo/bc7enc.cpp delete mode 100644 src/ktx.imageio/bc7enc-rdo/bc7enc.h delete mode 100644 src/ktx.imageio/bc7enc-rdo/rgbcx.cpp delete mode 100644 src/ktx.imageio/bc7enc-rdo/rgbcx.h delete mode 100644 src/ktx.imageio/bc7enc-rdo/rgbcx_table4_small.h delete mode 100644 src/ktx.imageio/etcdec.h diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index 1f12d4f1b9..70d8735c7c 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -1,6 +1,6 @@ # Copyright Contributors to the OpenImageIO project. # SPDX-License-Identifier: Apache-2.0 -# https://github.com/Academ SoftwareFoundation/OpenImageIO +# https://github.com/AcademySoftwareFoundation/OpenImageIO set_cache (Ktx_BUILD_VERSION v5.0.0-rc1 "Ktx version for local builds") set (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") @@ -29,11 +29,11 @@ build_dependency_with_cmake(Ktx -D CMAKE_POSITION_INDEPENDENT_CODE=ON -D LIBKTX_VERSION_READ_ONLY=OFF -D LIBKTX_VERSION_FULL=ON - -D LIBKTX_FEATURE_KTX1=OFF + -D LIBKTX_FEATURE_KTX1=ON # Setting this to OFF causes linker issues -D LIBKTX_FEATURE_KTX2=ON -D LIBKTX_FEATURE_VK_UPLOAD=OFF -D LIBKTX_FEATURE_GL_UPLOAD=OFF - -D LIBKTX_FEATURE_ETC_UNPACK=ON + -D LIBKTX_FEATURE_ETC_UNPACK=OFF # This has some weird licensing and I don't feel comfortable including it ... # as per KTX-Software: # > Intel Macs have support for SSE, but if you're building universal # > binaries, you have to disable SSE or the build will fail. diff --git a/src/ktx.imageio/CMakeLists.txt b/src/ktx.imageio/CMakeLists.txt index b80dff5866..a035da5584 100644 --- a/src/ktx.imageio/CMakeLists.txt +++ b/src/ktx.imageio/CMakeLists.txt @@ -3,11 +3,7 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO if (Ktx_FOUND) - add_oiio_plugin ( ktxinput.cpp ktxoutput.cpp - # TODO: add certain warning ignores - # bc7enc_rdo dependency (for BC1, BC2, BC3, BC4, BC5 and BC7 decoding/encoding) - bc7enc-rdo/rgbcx.cpp bc7enc-rdo/bc7decomp.cpp bc7enc-rdo/bc7enc.cpp - LINK_LIBRARIES KTX::ktx) + add_oiio_plugin (ktxinput.cpp ktxoutput.cpp LINK_LIBRARIES KTX::ktx) else () message (WARNING "KTX plugin will not be built, no libktx") endif () diff --git a/src/ktx.imageio/README.md b/src/ktx.imageio/README.md index 17caf5ae09..adfd9e3df4 100644 --- a/src/ktx.imageio/README.md +++ b/src/ktx.imageio/README.md @@ -1,16 +1,27 @@ # About -This KTX plugin support obviously nulifies the benefits of using KTX in the -first place. That being said, this plugin is still useful so that end users -don't have to convert back and forth between KTX <-> supported format (e.g., PNG). -It is also useful to convert to and from KTX2 format. +This KTX plugin support obviously nullifies the benefits of using KTX in the +first place (i.e., to reduce upload time to GPUs or totally eliminate the need for +transcoding to GPU-conformant format before uploading). That being said, this +plugin is still useful so that end users don't have to convert back and forth +between KTX <-> supported format (e.g., PNG). It is also useful to convert to +and from KTX2 format. -An example usecase would be Blender and its glTf import/export plugin. +An important note about DDS -> KTX conversion: -Ideally, at some point in the future, OIIO may introduce a new API to accomodate -texture formats that are mainly used for fast texture uploads to GPUs. + - [KTX-Software][libktx] will provide tools for lossless DDS to KTX conversion + without having to decode then encode to KTX format. A PR is currently being + worked on. + - If you use OIIO for this conversion, then the quality will almost certainly + degrade. -Below you will find a set of notes about why this plugin is implemented the way +An example use-case would be Blender and its glTf import/export plugin. + +Ideally, at some point in the future, OIIO may introduce a new API to +accommodate texture formats that are mainly used for fast texture uploads tow +GPUs. This is outside the scope of this basic format support addition. + +Below you will find a set of notes on why this plugin is implemented the way it is. It took me some time to understand how libktx works and what it provides (and why). Some terminology is also defined here. @@ -18,13 +29,14 @@ it is. It took me some time to understand how libktx works and what it provides KTX2 (the 2 here is to distinguish it from deprecated KTX/KTX1) is a binary container format that is intended for usage for fast loading of textures to the -GPU. KTX2 contains GPU-native formats (e.g., block compressed format BC7) with -an optional additional layer of compression (hereafter refered to as +GPU. KTX2 contains GPU-native formats (e.g., block-compressed format BC7) with +an optional additional layer of compression (hereafter referred to as *supercompression*). -As per the specs, KTX formats may store downsampled texture data for each mip +As per the specs, KTX2 formats may store downsampled texture data for each mip level (not necessarily the whole pyramid). This introduces problems for the -KTX2 writer (at `ktxoutput.cpp`) because +KTX2 writer (at `ktxoutput.cpp`) because the spec doesn't force the mention of +which filter/downsampler was used to create the mip levels. ## GPU Block Compression Formats @@ -42,45 +54,50 @@ requirements: All BCn formats encode a 4x4 block of pixels (could be 1 channel, or 2, or 3, or 4 depending on the particular format) into a fixed-size data (i.e., no -variable-rate encoding). - -#### BC1/DXT1 +variable-rate encoding). BC7 is the go-to format on desktop hardware for LDR +textures. BC6HU/BC6HS is the go-to format on desktop hardware for HDR formats. -64 bytes (4x4 block) => 8 bytes -alpha channed is encoded using 1 bit +Microsoft has some fairly well-written explanation of each format. From the +perspective of OIIO, we just don't care since the work to decode/encode these +formats is offloaded to libktx. ### ASTC -TODO -Luckily libktx provides ASTC encoders/decoders and we don't have to deal with -ASTC's extreme complexity (e.g., there are many different block sizes). +libktx provides ASTC encoders/decoders and we don't have to deal with ASTC's +extreme complexity (e.g., there are many different block sizes). + +### ETC2 + +libktx provides ETC2 encoders/decoders (have to double verify) but the +dependency has some weird licensing (afraid non-permissive as Mark @KTX-Software +pointed out). ETC formats are therefore not supported. ## KTX Supercompression **supercompression**: a compression on top of another compression (i.e., layered compression) for better disk storage/network transmission. Unlike GPU block -compression, supercompression has the flexibilty to employ variable-rate +compression, supercompression has the flexibility to employ variable-rate encoding. In this context, supercompression is employed on top of fixed-rate, -endpoint-compressed formats (like BCn, ETC2, etc.) that have hardware-decoding +endpoint-compressed formats (like BCn, ASTC, etc.) that have hardware-decoding support in commodity GPUs (of course, depends on GPU - mobile vs desktop, etc.). -Depending on the used Basis Universal codec (if any), -supercompression may be applied. **For ETC1S, supercompression must be used -(usually BasisLZ)**. This is the reason why you constantly see the notation -"BasisLZ/ETC1S" which *probably (have to verify)* reads: *BasisLZ over ETC1S*. +Depending on the used Basis Universal codec (if any), supercompression may be +applied. **For ETC1S, supercompression must be used (usually BasisLZ)**. This is +the reason why you constantly see the notation "BasisLZ/ETC1S" which +*probably (have to verify)* reads: *BasisLZ over ETC1S*. For UASTC, we *may* apply Zstandard supercompression (i.e., `KTX_SS_ZSTD`). ### KTS\_SS\_BASIS\_LZ (BasisLZ) -This is intended to be used to supercompress Basis Universal ETC1S format. +This is intended to be used to super-compress Basis Universal ETC1S format. The expected workflow is as follows: ``` Basis LZ -> transcode to GPU format (e.g., block-compressed BC7) ``` -For OIIO usecase, we can directly use libktx to transcode into raw bytes: +For OIIO use-case, we can directly use libktx to transcode into raw bytes: ``` Basis LZ → transcode (using ktxTexture2_TranscodeBasis) → raw RGBA @@ -89,7 +106,8 @@ Basis LZ → transcode (using ktxTexture2_TranscodeBasis) → raw RGBA The `ktxTexture2_TranscodeBasis` function provided by libktx can transcode directly into raw RGBA values which is very handy. It however doesn't provide/expose the functionality to just decode a single miplevel/subimage -(maybe this is simply not doable with Basis LZ - have to verify). +(maybe this is simply not doable with Basis LZ - have to verify). Either way, +I might open a PR to provide single image/texture decoders. ## Supported Encoders/Decoders @@ -103,47 +121,23 @@ provide/expose the functionality to just decode a single miplevel/subimage - [ ] `ARRAY_TEXTURE_3D` (not planned) - [ ] `ARRAY_TEXTURE_CUBEMAP` (not planned) -- Supported/Tested VkFormats: +- Supported/Tested raw VkFormats (decoder + encoder): - [X] `VK_FORMAT_R8_UNORM` - [X] `VK_FORMAT_R8G8_SRGB` - [X] `VK_FORMAT_R8G8B8_SRGB` - [X] `VK_FORMAT_R8G8B8A8_SRGB` - - [X] `VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK` - - [X] `VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK` - - [X] `VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK` - - [X] `VK_FORMAT_BC1_RGB_SRGB_BLOCK` - - [X] `VK_FORMAT_BC3_SRGB_BLOCK` - - [X] `VK_FORMAT_BC4_UNORM_BLOCK` - - [X] `VK_FORMAT_BC5_UNORM_BLOCK` - - [X] `VK_FORMAT_BC7_SRGB_BLOCK` - - [X] `VK_FORMAT_ASTC_4x4_SRGB_BLOCK` - -- BCn GPU block-compressed formats: - - [X] BC1 encoder/decoder - - [ ] BC2 encoder/decoder (not implemented) - - [X] BC3 encoder/decoder - - [X] BC4 encoder/decoder - - [X] BC5 encoder/decoder - - [ ] BC6HS/BC6HU encoder/decoder (partially implemented but not tested) - - [X] BC7 encoder/decoder - -- ETC2 GPU block-compressed formats: - - [X] `ETC2_RGB` (aka ETC1) decoder - - [X] `ETC2_RGB_A1` decoder - - [X] `ETC2_RGBA` decoder - - [ ] ETC2 encoder (there are a few ETC2 decoders - including ConvectionKernels and etcpack) - -- ASTC GPU block-compressed formats: - - [X] `ASTC` decoder (using libktx's `ktxTexture2_DecodeAstc`) - - [X] `ASTC` encoder (using libktx's `ktxTexture2_CompressAstc`) - -- Basis Universal schemes: - - [X] `UASTC` encoder/decoder - - [X] `ETC1S` encoder/decoder - -- Supercompression schemes: - - [X] `ZLIB` decompressor/compressor (using libktx's `ktxTexture2_DeflateZLIB`) - - [X] `ZSTD` decompressor/compressor (using libktx's `ktxTexture2_DeflateZstd`) + +- Block-compressed formats (decoder + encoder): + - [X] ASTC + - [ ] BCn (waiting on libktx BCn support PR merge) + +- Basis Universal schemes (encoder + decoder): + - [X] `UASTC` + - [X] `ETC1S` + +- Supercompression schemes (decompressor + compressor): + - [X] `ZLIB` + - [X] `ZSTD` ## Limitations @@ -154,14 +148,14 @@ if `get_int_attribute("ktx:miplevels") > 1`. - KTX2 supports many GPU-block-compression encoders and each one may have many different parameters that change the encoding quality (as usual, quality-speed -tradeoff). There is simply no way to regenerate the exact same input texture -without knowing these parameters and nor the KTX2 specs nor libktx nor -KTX-Software tooling stores any (or sufficient) information about these params -in the metadata. +trade-off). To regenerate same input KTX2 format, we rely on the heuristic that +whatever created the original KTX2 input also supplied `KTXwriterScParams` +metadata field which should provide all non-default arguments provided to +`ktx create/encode` to create the texture. - As stated in the comments in `ktxinput.cpp`, if given ktx texture is supercompressed then it has to be all decompressed (i.e., NOT the decompression -of the underlying GPU texture format but rather the supercompression). This +of the underlying GPU texture format but rather just the supercompression). This means that if you just need a particular subimage/miplevel, you pay the memory price of loading the whole KTX texture (which might be very large for 3D textures and texture arrays). @@ -180,69 +174,34 @@ textures and texture arrays). to close). libktx does not provide a way to append or write subimages (is this problematic or contrary to the way OIIO expects us to write files?). -- KTX1 format is not yet supported. Adding support for it after finishing KTX2 +- KTX1 format is not yet supported. Adding support for it after finishing KTX2 *should be* relatively straightforward (Note: KTX1 is officially deprecated and -KTX-Software provides tools to convert from KTX1 to KTX2). +KTX-Software provides tools to convert from KTX1 to KTX2). => support is not +planned for the moment. - Only LDR formats (to be more precise, only TypeDesc::UINT8). Adding support for HDR is straightforward (conversions for large number of enum values from VkFormat have to be written). -- bc7enc\_rdo dependency for encoding/decoding *BC1-7* formats does not ( -contrary to what the repo description suggests) support BC6HS/BC6HU HDR formats. -See below on what we can use for BC6HU/BC6HS encoding/decoding. - ## Dependencies -[libktx][libktx]: for general KTX@ format support (loading of KTX2 files, transcoding -support, supercompression decompression support, etc.). +We only depend on libktx and nothing else. If CPU decoding/encoding of a format +is not supported by libktx, open a PR there that adds support to it. I tried the +approach of implementing formats here (e.g., BCn) and this results in extremely +harder to maintain and much more complex code here (see first commit with +12 000 changed lines). - - Commit hash: see `OpenImageIO/src/cmake/build_Ktx.cmake` - - License: Many subresources. TODO: `lib/etcdec.cxx`'s license is not open - source but libktx exposes a function to decode ETC formats, do we use it? - -[bc7enc\_rdo][bc7enc]: for BC1, BC2, BC3, BC4, BC5 and BC7 decoding/encoding. - - - Commit hash: - ``` - dbe416d28a5530b4e8cc45b14bf034dc6b96bbde - ``` - - License: MIT License - - Note: I am working on a PR to push `ktxTexture2_DecodeBcn` function - to KTX-Software (libktx) so that we no longer have to include these - dependencies here. - -[etcdec][etcdec]: ETC2/EAC decoding. +[libktx][libktx]: for general KTX@ format support (loading of KTX2 files, +transcoding support, supercompression decompression support, etc.). - - Commit hash: - ``` - 972875d403ed8ac27e0f35c2f29d819e710a688a - ``` - - License: MIT LICENSE - - Note: libktx has etcunpack included by default. Maybe we can use it and - remove this dependency? - -Note: for BC6HS/BC6HU and ETC encoding support, we can use -[ConvectionKernels][ConvectionKernels]. It is better to have this dependency -built seperately (i.e., not copied into source ktx.imageio directory). - -Personally, for ETC encoding, I would prefer to use ETCPACK since libktx already -uses its decoder and is the more *standard* choice (i.e., seems more official). - -For BC6HU/BC6HS decoding, we can use the same dependency used by the DDS format -(bcdec.h). For encoding, we extract the needed function from DirectX Texture -Library (MIT license) or I write it myself. + - Commit hash: see `OpenImageIO/src/cmake/build_Ktx.cmake` + - License: Many subresources. ## Resources - [KTX2 Specs](https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html) - [Official Implementation (KTX-Software)](https://github.com/KhronosGroup/KTX-Software) - [Basis Universal Supercompression Implementation (used by libktx)](https://github.com/BinomialLLC/basis_universal) -- [BC1-7 encoders/decoders with RDO](https://github.com/richgel999/bc7enc_rdo) - [Comparing-BCn-texture-decoders](https://aras-p.info/blog/2022/06/23/Comparing-BCn-texture-decoders/) -- [ConvectionKernels][ConvectionKernels] [libktx]: https://github.com/KhronosGroup/KTX-Software.git -[bc7enc]: https://github.com/richgel999/bc7enc_rdo.git -[etcdec]: https://github.com/iOrange/etcdec.git -[ConvectionKernels]: https://github.com/elasota/ConvectionKernels.git diff --git a/src/ktx.imageio/bc7enc-rdo/bc7decomp.cpp b/src/ktx.imageio/bc7enc-rdo/bc7decomp.cpp deleted file mode 100644 index 62747ca854..0000000000 --- a/src/ktx.imageio/bc7enc-rdo/bc7decomp.cpp +++ /dev/null @@ -1,680 +0,0 @@ -// clang-format off -// File: bc7decomp.c - Richard Geldreich, Jr. 3/31/2020 - MIT license or public domain (see end of file) -#include "bc7decomp.h" -#include - -#if (defined(_M_AMD64) || defined(_M_X64) || defined(__SSE2__)) -# define BC7DECOMP_USE_SSE2 -#endif - -#ifdef BC7DECOMP_USE_SSE2 -#include -#include -#endif - -namespace bc7decomp -{ - -#ifdef BC7DECOMP_USE_SSE2 - const __m128i g_bc7_weights4_sse2[8] = - { - _mm_set_epi16(4, 4, 4, 4, 0, 0, 0, 0), - _mm_set_epi16(13, 13, 13, 13, 9, 9, 9, 9), - _mm_set_epi16(21, 21, 21, 21, 17, 17, 17, 17), - _mm_set_epi16(30, 30, 30, 30, 26, 26, 26, 26), - _mm_set_epi16(38, 38, 38, 38, 34, 34, 34, 34), - _mm_set_epi16(47, 47, 47, 47, 43, 43, 43, 43), - _mm_set_epi16(55, 55, 55, 55, 51, 51, 51, 51), - _mm_set_epi16(64, 64, 64, 64, 60, 60, 60, 60), - }; -#endif - -const uint32_t g_bc7_weights2[4] = { 0, 21, 43, 64 }; -const uint32_t g_bc7_weights3[8] = { 0, 9, 18, 27, 37, 46, 55, 64 }; -const uint32_t g_bc7_weights4[16] = { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 }; - -const uint8_t g_bc7_partition2[64 * 16] = -{ - 0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1, 0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1, 0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1, 0,0,0,1,0,0,1,1,0,0,1,1,0,1,1,1, 0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,1,0,1,1,1,1,1,1,1, 0,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,1,0,0,1,1,0,1,1,1, - 0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1, 0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1, 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1, - 0,0,0,0,1,0,0,0,1,1,1,0,1,1,1,1, 0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,0, 0,1,1,1,0,0,1,1,0,0,0,1,0,0,0,0, 0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,1,0,0,0,1,1,0,0,1,1,1,0, 0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,0, 0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1, - 0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0, 0,0,0,0,1,0,0,0,1,0,0,0,1,1,0,0, 0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0, 0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0, 0,0,0,1,0,1,1,1,1,1,1,0,1,0,0,0, 0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0, 0,1,1,1,0,0,0,1,1,0,0,0,1,1,1,0, 0,0,1,1,1,0,0,1,1,0,0,1,1,1,0,0, - 0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1, 0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1, 0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0, 0,0,1,1,0,0,1,1,1,1,0,0,1,1,0,0, 0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0, 0,1,0,1,0,1,0,1,1,0,1,0,1,0,1,0, 0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1, 0,1,0,1,1,0,1,0,1,0,1,0,0,1,0,1, - 0,1,1,1,0,0,1,1,1,1,0,0,1,1,1,0, 0,0,0,1,0,0,1,1,1,1,0,0,1,0,0,0, 0,0,1,1,0,0,1,0,0,1,0,0,1,1,0,0, 0,0,1,1,1,0,1,1,1,1,0,1,1,1,0,0, 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0, 0,0,1,1,1,1,0,0,1,1,0,0,0,0,1,1, 0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1, 0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0, - 0,1,0,0,1,1,1,0,0,1,0,0,0,0,0,0, 0,0,1,0,0,1,1,1,0,0,1,0,0,0,0,0, 0,0,0,0,0,0,1,0,0,1,1,1,0,0,1,0, 0,0,0,0,0,1,0,0,1,1,1,0,0,1,0,0, 0,1,1,0,1,1,0,0,1,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,1, 0,1,1,0,0,0,1,1,1,0,0,1,1,1,0,0, 0,0,1,1,1,0,0,1,1,1,0,0,0,1,1,0, - 0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,1, 0,1,1,0,0,0,1,1,0,0,1,1,1,0,0,1, 0,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1, 0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,1, 0,0,0,0,1,1,1,1,0,0,1,1,0,0,1,1, 0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,0, 0,0,1,0,0,0,1,0,1,1,1,0,1,1,1,0, 0,1,0,0,0,1,0,0,0,1,1,1,0,1,1,1 -}; - -const uint8_t g_bc7_partition3[64 * 16] = -{ - 0,0,1,1,0,0,1,1,0,2,2,1,2,2,2,2, 0,0,0,1,0,0,1,1,2,2,1,1,2,2,2,1, 0,0,0,0,2,0,0,1,2,2,1,1,2,2,1,1, 0,2,2,2,0,0,2,2,0,0,1,1,0,1,1,1, 0,0,0,0,0,0,0,0,1,1,2,2,1,1,2,2, 0,0,1,1,0,0,1,1,0,0,2,2,0,0,2,2, 0,0,2,2,0,0,2,2,1,1,1,1,1,1,1,1, 0,0,1,1,0,0,1,1,2,2,1,1,2,2,1,1, - 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2, 0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2, 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2, 0,0,1,2,0,0,1,2,0,0,1,2,0,0,1,2, 0,1,1,2,0,1,1,2,0,1,1,2,0,1,1,2, 0,1,2,2,0,1,2,2,0,1,2,2,0,1,2,2, 0,0,1,1,0,1,1,2,1,1,2,2,1,2,2,2, 0,0,1,1,2,0,0,1,2,2,0,0,2,2,2,0, - 0,0,0,1,0,0,1,1,0,1,1,2,1,1,2,2, 0,1,1,1,0,0,1,1,2,0,0,1,2,2,0,0, 0,0,0,0,1,1,2,2,1,1,2,2,1,1,2,2, 0,0,2,2,0,0,2,2,0,0,2,2,1,1,1,1, 0,1,1,1,0,1,1,1,0,2,2,2,0,2,2,2, 0,0,0,1,0,0,0,1,2,2,2,1,2,2,2,1, 0,0,0,0,0,0,1,1,0,1,2,2,0,1,2,2, 0,0,0,0,1,1,0,0,2,2,1,0,2,2,1,0, - 0,1,2,2,0,1,2,2,0,0,1,1,0,0,0,0, 0,0,1,2,0,0,1,2,1,1,2,2,2,2,2,2, 0,1,1,0,1,2,2,1,1,2,2,1,0,1,1,0, 0,0,0,0,0,1,1,0,1,2,2,1,1,2,2,1, 0,0,2,2,1,1,0,2,1,1,0,2,0,0,2,2, 0,1,1,0,0,1,1,0,2,0,0,2,2,2,2,2, 0,0,1,1,0,1,2,2,0,1,2,2,0,0,1,1, 0,0,0,0,2,0,0,0,2,2,1,1,2,2,2,1, - 0,0,0,0,0,0,0,2,1,1,2,2,1,2,2,2, 0,2,2,2,0,0,2,2,0,0,1,2,0,0,1,1, 0,0,1,1,0,0,1,2,0,0,2,2,0,2,2,2, 0,1,2,0,0,1,2,0,0,1,2,0,0,1,2,0, 0,0,0,0,1,1,1,1,2,2,2,2,0,0,0,0, 0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0, 0,1,2,0,2,0,1,2,1,2,0,1,0,1,2,0, 0,0,1,1,2,2,0,0,1,1,2,2,0,0,1,1, - 0,0,1,1,1,1,2,2,2,2,0,0,0,0,1,1, 0,1,0,1,0,1,0,1,2,2,2,2,2,2,2,2, 0,0,0,0,0,0,0,0,2,1,2,1,2,1,2,1, 0,0,2,2,1,1,2,2,0,0,2,2,1,1,2,2, 0,0,2,2,0,0,1,1,0,0,2,2,0,0,1,1, 0,2,2,0,1,2,2,1,0,2,2,0,1,2,2,1, 0,1,0,1,2,2,2,2,2,2,2,2,0,1,0,1, 0,0,0,0,2,1,2,1,2,1,2,1,2,1,2,1, - 0,1,0,1,0,1,0,1,0,1,0,1,2,2,2,2, 0,2,2,2,0,1,1,1,0,2,2,2,0,1,1,1, 0,0,0,2,1,1,1,2,0,0,0,2,1,1,1,2, 0,0,0,0,2,1,1,2,2,1,1,2,2,1,1,2, 0,2,2,2,0,1,1,1,0,1,1,1,0,2,2,2, 0,0,0,2,1,1,1,2,1,1,1,2,0,0,0,2, 0,1,1,0,0,1,1,0,0,1,1,0,2,2,2,2, 0,0,0,0,0,0,0,0,2,1,1,2,2,1,1,2, - 0,1,1,0,0,1,1,0,2,2,2,2,2,2,2,2, 0,0,2,2,0,0,1,1,0,0,1,1,0,0,2,2, 0,0,2,2,1,1,2,2,1,1,2,2,0,0,2,2, 0,0,0,0,0,0,0,0,0,0,0,0,2,1,1,2, 0,0,0,2,0,0,0,1,0,0,0,2,0,0,0,1, 0,2,2,2,1,2,2,2,0,2,2,2,1,2,2,2, 0,1,0,1,2,2,2,2,2,2,2,2,2,2,2,2, 0,1,1,1,2,0,1,1,2,2,0,1,2,2,2,0, -}; - -const uint8_t g_bc7_table_anchor_index_second_subset[64] = { 15,15,15,15,15,15,15,15, 15,15,15,15,15,15,15,15, 15, 2, 8, 2, 2, 8, 8,15, 2, 8, 2, 2, 8, 8, 2, 2, 15,15, 6, 8, 2, 8,15,15, 2, 8, 2, 2, 2,15,15, 6, 6, 2, 6, 8,15,15, 2, 2, 15,15,15,15,15, 2, 2,15 }; - -const uint8_t g_bc7_table_anchor_index_third_subset_1[64] = -{ - 3, 3,15,15, 8, 3,15,15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8,15, 3, 3, 6,10, 5, 8, 8, 6, 8, 5,15,15, 8,15, 3, 5, 6,10, 8,15, 15, 3,15, 5,15,15,15,15, 3,15, 5, 5, 5, 8, 5,10, 5,10, 8,13,15,12, 3, 3 -}; - -const uint8_t g_bc7_table_anchor_index_third_subset_2[64] = -{ - 15, 8, 8, 3,15,15, 3, 8, 15,15,15,15,15,15,15, 8, 15, 8,15, 3,15, 8,15, 8, 3,15, 6,10,15,15,10, 8, 15, 3,15,10,10, 8, 9,10, 6,15, 8,15, 3, 6, 6, 8, 15, 3,15,15,15,15,15,15, 15,15,15,15, 3,15,15, 8 -}; - -const uint8_t g_bc7_first_byte_to_mode[256] = -{ - 8, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, - 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, -}; - -inline void insert_weight_zero(uint64_t& index_bits, uint32_t bits_per_index, uint32_t offset) -{ - uint64_t LOW_BIT_MASK = (static_cast(1) << ((bits_per_index * (offset + 1)) - 1)) - 1; - uint64_t HIGH_BIT_MASK = ~LOW_BIT_MASK; - - index_bits = ((index_bits & HIGH_BIT_MASK) << 1) | (index_bits & LOW_BIT_MASK); -} - -// BC7 mode 0-7 decompression. -// Instead of one monster routine to unpack all the BC7 modes, we're lumping the 3 subset, 2 subset, 1 subset, and dual plane modes together into simple shared routines. - -static inline uint32_t bc7_dequant(uint32_t val, uint32_t pbit, uint32_t val_bits) { assert(val < (1U << val_bits)); assert(pbit < 2); assert(val_bits >= 4 && val_bits <= 8); const uint32_t total_bits = val_bits + 1; val = (val << 1) | pbit; val <<= (8 - total_bits); val |= (val >> total_bits); assert(val <= 255); return val; } -static inline uint32_t bc7_dequant(uint32_t val, uint32_t val_bits) { assert(val < (1U << val_bits)); assert(val_bits >= 4 && val_bits <= 8); val <<= (8 - val_bits); val |= (val >> val_bits); assert(val <= 255); return val; } - -static inline uint32_t bc7_interp2(uint32_t l, uint32_t h, uint32_t w) { assert(w < 4); return (l * (64 - g_bc7_weights2[w]) + h * g_bc7_weights2[w] + 32) >> 6; } -static inline uint32_t bc7_interp3(uint32_t l, uint32_t h, uint32_t w) { assert(w < 8); return (l * (64 - g_bc7_weights3[w]) + h * g_bc7_weights3[w] + 32) >> 6; } -static inline uint32_t bc7_interp4(uint32_t l, uint32_t h, uint32_t w) { assert(w < 16); return (l * (64 - g_bc7_weights4[w]) + h * g_bc7_weights4[w] + 32) >> 6; } -static inline uint32_t bc7_interp(uint32_t l, uint32_t h, uint32_t w, uint32_t bits) -{ - assert(l <= 255 && h <= 255); - switch (bits) - { - case 2: return bc7_interp2(l, h, w); - case 3: return bc7_interp3(l, h, w); - case 4: return bc7_interp4(l, h, w); - default: - break; - } - return 0; -} - - -#ifdef BC7DECOMP_USE_SSE2 -static inline __m128i bc7_interp_sse2(__m128i l, __m128i h, __m128i w, __m128i iw) -{ - return _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(l, iw), _mm_mullo_epi16(h, w)), _mm_set1_epi16(32)), 6); -} - -static inline void bc7_interp2_sse2(const color_rgba* endpoint_pair, color_rgba* out_colors) -{ - __m128i endpoints = _mm_loadu_si64(endpoint_pair); - __m128i endpoints_16 = _mm_unpacklo_epi8(endpoints, _mm_setzero_si128()); - - __m128i endpoints_16_swapped = _mm_shuffle_epi32(endpoints_16, _MM_SHUFFLE(1, 0, 3, 2)); - - // Interpolated colors will be color 1 and 2 - __m128i interpolated_colors = bc7_interp_sse2(endpoints_16, endpoints_16_swapped, _mm_set1_epi16(21), _mm_set1_epi16(43)); - - // all_colors will be 1, 2, 0, 3 - __m128i all_colors = _mm_packus_epi16(interpolated_colors, endpoints_16); - - all_colors = _mm_shuffle_epi32(all_colors, _MM_SHUFFLE(3, 1, 0, 2)); - - _mm_storeu_si128(reinterpret_cast<__m128i*>(out_colors), all_colors); -} - -static inline void bc7_interp3_sse2(const color_rgba* endpoint_pair, color_rgba* out_colors) -{ - __m128i endpoints = _mm_loadu_si64(endpoint_pair); - __m128i endpoints_16bit = _mm_unpacklo_epi8(endpoints, _mm_setzero_si128()); - __m128i endpoints_16bit_swapped = _mm_shuffle_epi32(endpoints_16bit, _MM_SHUFFLE(1, 0, 3, 2)); - - __m128i interpolated_16 = bc7_interp_sse2(endpoints_16bit, endpoints_16bit_swapped, _mm_set1_epi16(9), _mm_set1_epi16(55)); - __m128i interpolated_23 = bc7_interp_sse2(endpoints_16bit, endpoints_16bit_swapped, _mm_set_epi16(37, 37, 37, 37, 18, 18, 18, 18), _mm_set_epi16(27, 27, 27, 27, 46, 46, 46, 46)); - __m128i interpolated_45 = bc7_interp_sse2(endpoints_16bit, endpoints_16bit_swapped, _mm_set_epi16(18, 18, 18, 18, 37, 37, 37, 37), _mm_set_epi16(46, 46, 46, 46, 27, 27, 27, 27)); - - __m128i interpolated_01 = _mm_unpacklo_epi64(endpoints_16bit, interpolated_16); - __m128i interpolated_67 = _mm_unpackhi_epi64(interpolated_16, endpoints_16bit); - - __m128i all_colors_0 = _mm_packus_epi16(interpolated_01, interpolated_23); - __m128i all_colors_1 = _mm_packus_epi16(interpolated_45, interpolated_67); - - _mm_storeu_si128(reinterpret_cast<__m128i*>(out_colors), all_colors_0); - _mm_storeu_si128(reinterpret_cast<__m128i*>(out_colors + 4), all_colors_1); -} -#endif - -bool unpack_bc7_mode0_2(uint32_t mode, const uint64_t* data_chunks, color_rgba* pPixels) -{ - //const uint32_t SUBSETS = 3; - const uint32_t ENDPOINTS = 6; - const uint32_t COMPS = 3; - const uint32_t WEIGHT_BITS = (mode == 0) ? 3 : 2; - const uint32_t WEIGHT_MASK = (1 << WEIGHT_BITS) - 1; - const uint32_t ENDPOINT_BITS = (mode == 0) ? 4 : 5; - const uint32_t ENDPOINT_MASK = (1 << ENDPOINT_BITS) - 1; - const uint32_t PBITS = (mode == 0) ? 6 : 0; - [[maybe_unused]] const uint32_t WEIGHT_VALS = 1 << WEIGHT_BITS; - const uint32_t PART_BITS = (mode == 0) ? 4 : 6; - const uint32_t PART_MASK = (1 << PART_BITS) - 1; - - const uint64_t low_chunk = data_chunks[0]; - const uint64_t high_chunk = data_chunks[1]; - - const uint32_t part = (low_chunk >> (mode + 1)) & PART_MASK; - - uint64_t channel_read_chunks[3] = { 0, 0, 0 }; - - if (mode == 0) - { - channel_read_chunks[0] = low_chunk >> 5; - channel_read_chunks[1] = low_chunk >> 29; - channel_read_chunks[2] = ((low_chunk >> 53) | (high_chunk << 11)); - } - else - { - channel_read_chunks[0] = low_chunk >> 9; - channel_read_chunks[1] = ((low_chunk >> 39) | (high_chunk << 25)); - channel_read_chunks[2] = high_chunk >> 5; - } - - color_rgba endpoints[ENDPOINTS]; - for (uint32_t c = 0; c < COMPS; c++) - { - uint64_t channel_read_chunk = channel_read_chunks[c]; - for (uint32_t e = 0; e < ENDPOINTS; e++) - { - endpoints[e][c] = static_cast(channel_read_chunk & ENDPOINT_MASK); - channel_read_chunk >>= ENDPOINT_BITS; - } - } - - uint32_t pbits[6]; - if (mode == 0) - { - uint8_t p_bits_chunk = static_cast((high_chunk >> 13) & 0xff); - - for (uint32_t p = 0; p < PBITS; p++) - pbits[p] = (p_bits_chunk >> p) & 1; - } - - uint64_t weights_read_chunk = high_chunk >> (67 - 16 * WEIGHT_BITS); - insert_weight_zero(weights_read_chunk, WEIGHT_BITS, 0); - insert_weight_zero(weights_read_chunk, WEIGHT_BITS, std::min(g_bc7_table_anchor_index_third_subset_1[part], g_bc7_table_anchor_index_third_subset_2[part])); - insert_weight_zero(weights_read_chunk, WEIGHT_BITS, std::max(g_bc7_table_anchor_index_third_subset_1[part], g_bc7_table_anchor_index_third_subset_2[part])); - - uint32_t weights[16]; - for (uint32_t i = 0; i < 16; i++) - { - weights[i] = static_cast(weights_read_chunk & WEIGHT_MASK); - weights_read_chunk >>= WEIGHT_BITS; - } - - for (uint32_t e = 0; e < ENDPOINTS; e++) - for (uint32_t c = 0; c < 4; c++) - endpoints[e][c] = static_cast((c == 3) ? 255 : (PBITS ? bc7_dequant(endpoints[e][c], pbits[e], ENDPOINT_BITS) : bc7_dequant(endpoints[e][c], ENDPOINT_BITS))); - - color_rgba block_colors[3][8]; - -#ifdef BC7DECOMP_USE_SSE2 - for (uint32_t s = 0; s < 3; s++) - { - if (WEIGHT_BITS == 2) - bc7_interp2_sse2(endpoints + s * 2, block_colors[s]); - else - bc7_interp3_sse2(endpoints + s * 2, block_colors[s]); - } -#else - for (uint32_t s = 0; s < 3; s++) - for (uint32_t i = 0; i < WEIGHT_VALS; i++) - { - for (uint32_t c = 0; c < 3; c++) - block_colors[s][i][c] = static_cast(bc7_interp(endpoints[s * 2 + 0][c], endpoints[s * 2 + 1][c], i, WEIGHT_BITS)); - block_colors[s][i][3] = 255; - } -#endif - - for (uint32_t i = 0; i < 16; i++) - pPixels[i] = block_colors[g_bc7_partition3[part * 16 + i]][weights[i]]; - - return true; -} - -bool unpack_bc7_mode1_3_7(uint32_t mode, const uint64_t* data_chunks, color_rgba* pPixels) -{ - //const uint32_t SUBSETS = 2; - const uint32_t ENDPOINTS = 4; - const uint32_t COMPS = (mode == 7) ? 4 : 3; - const uint32_t WEIGHT_BITS = (mode == 1) ? 3 : 2; - const uint32_t WEIGHT_MASK = (1 << WEIGHT_BITS) - 1; - const uint32_t ENDPOINT_BITS = (mode == 7) ? 5 : ((mode == 1) ? 6 : 7); - const uint32_t ENDPOINT_MASK = (1 << ENDPOINT_BITS) - 1; - const uint32_t PBITS = (mode == 1) ? 2 : 4; - const uint32_t SHARED_PBITS = (mode == 1) ? true : false; - [[maybe_unused]] const uint32_t WEIGHT_VALS = 1 << WEIGHT_BITS; - - const uint64_t low_chunk = data_chunks[0]; - const uint64_t high_chunk = data_chunks[1]; - - const uint32_t part = ((low_chunk >> (mode + 1)) & 0x3f); - - color_rgba endpoints[ENDPOINTS]; - - uint64_t channel_read_chunks[4] = { 0, 0, 0, 0 }; - uint64_t p_read_chunk = 0; - channel_read_chunks[0] = (low_chunk >> (mode + 7)); - uint64_t weight_read_chunk; - - switch (mode) - { - case 1: - channel_read_chunks[1] = (low_chunk >> 32); - channel_read_chunks[2] = ((low_chunk >> 56) | (high_chunk << 8)); - p_read_chunk = high_chunk >> 16; - weight_read_chunk = high_chunk >> 18; - break; - case 3: - channel_read_chunks[1] = ((low_chunk >> 38) | (high_chunk << 26)); - channel_read_chunks[2] = high_chunk >> 2; - p_read_chunk = high_chunk >> 30; - weight_read_chunk = high_chunk >> 34; - break; - case 7: - channel_read_chunks[1] = low_chunk >> 34; - channel_read_chunks[2] = ((low_chunk >> 54) | (high_chunk << 10)); - channel_read_chunks[3] = high_chunk >> 10; - p_read_chunk = (high_chunk >> 30); - weight_read_chunk = (high_chunk >> 34); - break; - default: - return false; - }; - - for (uint32_t c = 0; c < COMPS; c++) - { - uint64_t channel_read_chunk = channel_read_chunks[c]; - for (uint32_t e = 0; e < ENDPOINTS; e++) - { - endpoints[e][c] = static_cast(channel_read_chunk & ENDPOINT_MASK); - channel_read_chunk >>= ENDPOINT_BITS; - } - } - - uint32_t pbits[4]; - for (uint32_t p = 0; p < PBITS; p++) - pbits[p] = (p_read_chunk >> p) & 1; - - insert_weight_zero(weight_read_chunk, WEIGHT_BITS, 0); - insert_weight_zero(weight_read_chunk, WEIGHT_BITS, g_bc7_table_anchor_index_second_subset[part]); - - uint32_t weights[16]; - for (uint32_t i = 0; i < 16; i++) - { - weights[i] = static_cast(weight_read_chunk & WEIGHT_MASK); - weight_read_chunk >>= WEIGHT_BITS; - } - - for (uint32_t e = 0; e < ENDPOINTS; e++) - for (uint32_t c = 0; c < 4; c++) - endpoints[e][c] = static_cast((mode != 7U && c == 3U) ? 255 : bc7_dequant(endpoints[e][c], pbits[SHARED_PBITS ? (e >> 1) : e], ENDPOINT_BITS)); - - color_rgba block_colors[2][8]; -#ifdef BC7DECOMP_USE_SSE2 - for (uint32_t s = 0; s < 2; s++) - { - if (WEIGHT_BITS == 2) - bc7_interp2_sse2(endpoints + s * 2, block_colors[s]); - else - bc7_interp3_sse2(endpoints + s * 2, block_colors[s]); - } -#else - for (uint32_t s = 0; s < 2; s++) - for (uint32_t i = 0; i < WEIGHT_VALS; i++) - { - for (uint32_t c = 0; c < COMPS; c++) - block_colors[s][i][c] = static_cast(bc7_interp(endpoints[s * 2 + 0][c], endpoints[s * 2 + 1][c], i, WEIGHT_BITS)); - block_colors[s][i][3] = (COMPS == 3) ? 255 : block_colors[s][i][3]; - } -#endif - - for (uint32_t i = 0; i < 16; i++) - pPixels[i] = block_colors[g_bc7_partition2[part * 16 + i]][weights[i]]; - - return true; -} - -bool unpack_bc7_mode4_5(uint32_t mode, const uint64_t* data_chunks, color_rgba* pPixels) -{ - const uint32_t ENDPOINTS = 2; - //const uint32_t COMPS = 4; - const uint32_t WEIGHT_BITS = 2; - const uint32_t WEIGHT_MASK = (1 << WEIGHT_BITS) - 1; - const uint32_t A_WEIGHT_BITS = (mode == 4) ? 3 : 2; - const uint32_t A_WEIGHT_MASK = (1 << A_WEIGHT_BITS) - 1; - const uint32_t ENDPOINT_BITS = (mode == 4) ? 5 : 7; - const uint32_t ENDPOINT_MASK = (1 << ENDPOINT_BITS) - 1; - const uint32_t A_ENDPOINT_BITS = (mode == 4) ? 6 : 8; - const uint32_t A_ENDPOINT_MASK = (1 << A_ENDPOINT_BITS) - 1; - //const uint32_t WEIGHT_VALS = 1 << WEIGHT_BITS; - //const uint32_t A_WEIGHT_VALS = 1 << A_WEIGHT_BITS; - - const uint64_t low_chunk = data_chunks[0]; - const uint64_t high_chunk = data_chunks[1]; - - const uint32_t comp_rot = (low_chunk >> (mode + 1)) & 0x3; - const uint32_t index_mode = (mode == 4) ? static_cast((low_chunk >> 7) & 1) : 0; - - uint64_t color_read_bits = low_chunk >> 8; - - color_rgba endpoints[ENDPOINTS]; - for (uint32_t c = 0; c < 3; c++) - { - for (uint32_t e = 0; e < ENDPOINTS; e++) - { - endpoints[e][c] = static_cast(color_read_bits & ENDPOINT_MASK); - color_read_bits >>= ENDPOINT_BITS; - } - } - - endpoints[0][3] = static_cast(color_read_bits & ENDPOINT_MASK); - - uint64_t rgb_weights_chunk; - uint64_t a_weights_chunk; - if (mode == 4) - { - endpoints[0][3] = static_cast(color_read_bits & A_ENDPOINT_MASK); - endpoints[1][3] = static_cast((color_read_bits >> A_ENDPOINT_BITS) & A_ENDPOINT_MASK); - rgb_weights_chunk = ((low_chunk >> 50) | (high_chunk << 14)); - a_weights_chunk = high_chunk >> 17; - } - else if (mode == 5) - { - endpoints[0][3] = static_cast(color_read_bits & A_ENDPOINT_MASK); - endpoints[1][3] = static_cast(((low_chunk >> 58) | (high_chunk << 6)) & A_ENDPOINT_MASK); - rgb_weights_chunk = high_chunk >> 2; - a_weights_chunk = high_chunk >> 33; - } - else - return false; - - insert_weight_zero(rgb_weights_chunk, WEIGHT_BITS, 0); - insert_weight_zero(a_weights_chunk, A_WEIGHT_BITS, 0); - - const uint32_t weight_bits[2] = { index_mode ? A_WEIGHT_BITS : WEIGHT_BITS, index_mode ? WEIGHT_BITS : A_WEIGHT_BITS }; - const uint32_t weight_mask[2] = { index_mode ? A_WEIGHT_MASK : WEIGHT_MASK, index_mode ? WEIGHT_MASK : A_WEIGHT_MASK }; - - uint32_t weights[16], a_weights[16]; - - if (index_mode) - std::swap(rgb_weights_chunk, a_weights_chunk); - - for (uint32_t i = 0; i < 16; i++) - { - weights[i] = (rgb_weights_chunk & weight_mask[0]); - rgb_weights_chunk >>= weight_bits[0]; - } - - for (uint32_t i = 0; i < 16; i++) - { - a_weights[i] = (a_weights_chunk & weight_mask[1]); - a_weights_chunk >>= weight_bits[1]; - } - - for (uint32_t e = 0; e < ENDPOINTS; e++) - for (uint32_t c = 0; c < 4; c++) - endpoints[e][c] = static_cast(bc7_dequant(endpoints[e][c], (c == 3) ? A_ENDPOINT_BITS : ENDPOINT_BITS)); - - color_rgba block_colors[8]; -#ifdef BC7DECOMP_USE_SSE2 - if (weight_bits[0] == 3) - bc7_interp3_sse2(endpoints, block_colors); - else - bc7_interp2_sse2(endpoints, block_colors); -#else - for (uint32_t i = 0; i < (1U << weight_bits[0]); i++) - for (uint32_t c = 0; c < 3; c++) - block_colors[i][c] = static_cast(bc7_interp(endpoints[0][c], endpoints[1][c], i, weight_bits[0])); -#endif - - for (uint32_t i = 0; i < (1U << weight_bits[1]); i++) - block_colors[i][3] = static_cast(bc7_interp(endpoints[0][3], endpoints[1][3], i, weight_bits[1])); - - for (uint32_t i = 0; i < 16; i++) - { - pPixels[i] = block_colors[weights[i]]; - pPixels[i].a = block_colors[a_weights[i]].a; - if (comp_rot >= 1) - std::swap(pPixels[i].a, pPixels[i].m_comps[comp_rot - 1]); - } - - return true; -} - -struct bc7_mode_6 -{ - struct - { - uint64_t m_mode : 7; - uint64_t m_r0 : 7; - uint64_t m_r1 : 7; - uint64_t m_g0 : 7; - uint64_t m_g1 : 7; - uint64_t m_b0 : 7; - uint64_t m_b1 : 7; - uint64_t m_a0 : 7; - uint64_t m_a1 : 7; - uint64_t m_p0 : 1; - } m_lo; - - union - { - struct - { - uint64_t m_p1 : 1; - uint64_t m_s00 : 3; - uint64_t m_s10 : 4; - uint64_t m_s20 : 4; - uint64_t m_s30 : 4; - - uint64_t m_s01 : 4; - uint64_t m_s11 : 4; - uint64_t m_s21 : 4; - uint64_t m_s31 : 4; - - uint64_t m_s02 : 4; - uint64_t m_s12 : 4; - uint64_t m_s22 : 4; - uint64_t m_s32 : 4; - - uint64_t m_s03 : 4; - uint64_t m_s13 : 4; - uint64_t m_s23 : 4; - uint64_t m_s33 : 4; - - } m_hi; - - uint64_t m_hi_bits; - }; -}; - -bool unpack_bc7_mode6(const void *pBlock_bits, color_rgba *pPixels) -{ - static_assert(sizeof(bc7_mode_6) == 16, "sizeof(bc7_mode_6) == 16"); - - const bc7_mode_6 &block = *static_cast(pBlock_bits); - - if (block.m_lo.m_mode != (1 << 6)) - return false; - - const uint32_t r0 = static_cast((block.m_lo.m_r0 << 1) | block.m_lo.m_p0); - const uint32_t g0 = static_cast((block.m_lo.m_g0 << 1) | block.m_lo.m_p0); - const uint32_t b0 = static_cast((block.m_lo.m_b0 << 1) | block.m_lo.m_p0); - const uint32_t a0 = static_cast((block.m_lo.m_a0 << 1) | block.m_lo.m_p0); - const uint32_t r1 = static_cast((block.m_lo.m_r1 << 1) | block.m_hi.m_p1); - const uint32_t g1 = static_cast((block.m_lo.m_g1 << 1) | block.m_hi.m_p1); - const uint32_t b1 = static_cast((block.m_lo.m_b1 << 1) | block.m_hi.m_p1); - const uint32_t a1 = static_cast((block.m_lo.m_a1 << 1) | block.m_hi.m_p1); - - color_rgba vals[16]; -#ifdef BC7DECOMP_USE_SSE2 - __m128i vep0 = _mm_set_epi16((short)a0, (short)b0, (short)g0, (short)r0, (short)a0, (short)b0, (short)g0, (short)r0); - __m128i vep1 = _mm_set_epi16((short)a1, (short)b1, (short)g1, (short)r1, (short)a1, (short)b1, (short)g1, (short)r1); - - for (uint32_t i = 0; i < 16; i += 4) - { - const __m128i w0 = g_bc7_weights4_sse2[i / 4 * 2 + 0]; - const __m128i w1 = g_bc7_weights4_sse2[i / 4 * 2 + 1]; - - const __m128i iw0 = _mm_sub_epi16(_mm_set1_epi16(64), w0); - const __m128i iw1 = _mm_sub_epi16(_mm_set1_epi16(64), w1); - - __m128i first_half = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(vep0, iw0), _mm_mullo_epi16(vep1, w0)), _mm_set1_epi16(32)), 6); - __m128i second_half = _mm_srli_epi16(_mm_add_epi16(_mm_add_epi16(_mm_mullo_epi16(vep0, iw1), _mm_mullo_epi16(vep1, w1)), _mm_set1_epi16(32)), 6); - __m128i combined = _mm_packus_epi16(first_half, second_half); - - _mm_storeu_si128(reinterpret_cast<__m128i*>(vals + i), combined); - } -#else - for (uint32_t i = 0; i < 16; i++) - { - const uint32_t w = g_bc7_weights4[i]; - const uint32_t iw = 64 - w; - vals[i].set_noclamp_rgba( - (r0 * iw + r1 * w + 32) >> 6, - (g0 * iw + g1 * w + 32) >> 6, - (b0 * iw + b1 * w + 32) >> 6, - (a0 * iw + a1 * w + 32) >> 6); - } -#endif - - pPixels[0] = vals[block.m_hi.m_s00]; - pPixels[1] = vals[block.m_hi.m_s10]; - pPixels[2] = vals[block.m_hi.m_s20]; - pPixels[3] = vals[block.m_hi.m_s30]; - - pPixels[4] = vals[block.m_hi.m_s01]; - pPixels[5] = vals[block.m_hi.m_s11]; - pPixels[6] = vals[block.m_hi.m_s21]; - pPixels[7] = vals[block.m_hi.m_s31]; - - pPixels[8] = vals[block.m_hi.m_s02]; - pPixels[9] = vals[block.m_hi.m_s12]; - pPixels[10] = vals[block.m_hi.m_s22]; - pPixels[11] = vals[block.m_hi.m_s32]; - - pPixels[12] = vals[block.m_hi.m_s03]; - pPixels[13] = vals[block.m_hi.m_s13]; - pPixels[14] = vals[block.m_hi.m_s23]; - pPixels[15] = vals[block.m_hi.m_s33]; - - return true; -} - -bool unpack_bc7(const void *pBlock, color_rgba *pPixels) -{ - const uint8_t *block_bytes = static_cast(pBlock); - uint8_t mode = g_bc7_first_byte_to_mode[block_bytes[0]]; - - uint64_t data_chunks[2]; - - uint64_t endian_check = 1; - if (*reinterpret_cast(&endian_check) == 1) - memcpy(data_chunks, pBlock, 16); - else - { - data_chunks[0] = data_chunks[1] = 0; - for (int chunk_index = 0; chunk_index < 2; chunk_index++) - { - for (int byte_index = 0; byte_index < 8; byte_index++) - data_chunks[chunk_index] |= static_cast(block_bytes[chunk_index * 8 + byte_index]) << (byte_index * 8); - } - } - - switch (mode) - { - case 0: - case 2: - return unpack_bc7_mode0_2(mode, data_chunks, pPixels); - case 1: - case 3: - case 7: - return unpack_bc7_mode1_3_7(mode, data_chunks, pPixels); - case 4: - case 5: - return unpack_bc7_mode4_5(mode, data_chunks, pPixels); - case 6: - return unpack_bc7_mode6(data_chunks, pPixels); - default: - // reinterpret_cast to void* to silence the -Wclass-memaccess warning - memset(reinterpret_cast(pPixels), 0, sizeof(color_rgba) * 16); - break; - } - - return false; -} - -} // namespace bc7decomp - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright(c) 2020 Richard Geldreich, Jr. -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files(the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions : -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain(www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non - commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain.We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors.We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/src/ktx.imageio/bc7enc-rdo/bc7decomp.h b/src/ktx.imageio/bc7enc-rdo/bc7decomp.h deleted file mode 100644 index a9dcbccf59..0000000000 --- a/src/ktx.imageio/bc7enc-rdo/bc7decomp.h +++ /dev/null @@ -1,180 +0,0 @@ -// clang-format off -#pragma once - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union -#endif - -#include -#include -#include -#include -#include - -namespace bc7decomp -{ - -enum eNoClamp { cNoClamp }; - -template inline S clamp(S value, S low, S high) { return (value < low) ? low : ((value > high) ? high : value); } - -class color_rgba -{ -public: - union - { - uint8_t m_comps[4]; - - struct - { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; - }; - }; - - inline color_rgba() - { - static_assert(sizeof(*this) == 4, "sizeof(*this) != 4"); - } - - inline color_rgba(int y) - { - set(y); - } - - inline color_rgba(int y, int na) - { - set(y, na); - } - - inline color_rgba(int sr, int sg, int sb, int sa) - { - set(sr, sg, sb, sa); - } - - inline color_rgba(eNoClamp, int sr, int sg, int sb, int sa) - { - set_noclamp_rgba((uint8_t)sr, (uint8_t)sg, (uint8_t)sb, (uint8_t)sa); - } - - inline color_rgba& set_noclamp_y(int y) - { - m_comps[0] = (uint8_t)y; - m_comps[1] = (uint8_t)y; - m_comps[2] = (uint8_t)y; - m_comps[3] = (uint8_t)255; - return *this; - } - - inline color_rgba &set_noclamp_rgba(int sr, int sg, int sb, int sa) - { - m_comps[0] = (uint8_t)sr; - m_comps[1] = (uint8_t)sg; - m_comps[2] = (uint8_t)sb; - m_comps[3] = (uint8_t)sa; - return *this; - } - - inline color_rgba &set(int y) - { - m_comps[0] = static_cast(clamp(y, 0, 255)); - m_comps[1] = m_comps[0]; - m_comps[2] = m_comps[0]; - m_comps[3] = 255; - return *this; - } - - inline color_rgba &set(int y, int na) - { - m_comps[0] = static_cast(clamp(y, 0, 255)); - m_comps[1] = m_comps[0]; - m_comps[2] = m_comps[0]; - m_comps[3] = static_cast(clamp(na, 0, 255)); - return *this; - } - - inline color_rgba &set(int sr, int sg, int sb, int sa) - { - m_comps[0] = static_cast(clamp(sr, 0, 255)); - m_comps[1] = static_cast(clamp(sg, 0, 255)); - m_comps[2] = static_cast(clamp(sb, 0, 255)); - m_comps[3] = static_cast(clamp(sa, 0, 255)); - return *this; - } - - inline color_rgba &set_rgb(int sr, int sg, int sb) - { - m_comps[0] = static_cast(clamp(sr, 0, 255)); - m_comps[1] = static_cast(clamp(sg, 0, 255)); - m_comps[2] = static_cast(clamp(sb, 0, 255)); - return *this; - } - - inline color_rgba &set_rgb(const color_rgba &other) - { - r = other.r; - g = other.g; - b = other.b; - return *this; - } - - inline const uint8_t &operator[] (uint32_t index) const { assert(index < 4); return m_comps[index]; } - inline uint8_t &operator[] (uint32_t index) { assert(index < 4); return m_comps[index]; } - - inline void clear() - { - m_comps[0] = 0; - m_comps[1] = 0; - m_comps[2] = 0; - m_comps[3] = 0; - } - - inline bool operator== (const color_rgba &rhs) const - { - if (m_comps[0] != rhs.m_comps[0]) return false; - if (m_comps[1] != rhs.m_comps[1]) return false; - if (m_comps[2] != rhs.m_comps[2]) return false; - if (m_comps[3] != rhs.m_comps[3]) return false; - return true; - } - - inline bool operator!= (const color_rgba &rhs) const - { - return !(*this == rhs); - } - - inline bool operator<(const color_rgba &rhs) const - { - for (int i = 0; i < 4; i++) - { - if (m_comps[i] < rhs.m_comps[i]) - return true; - else if (m_comps[i] != rhs.m_comps[i]) - return false; - } - return false; - } - - inline int get_601_luma() const { return (19595U * m_comps[0] + 38470U * m_comps[1] + 7471U * m_comps[2] + 32768U) >> 16U; } - inline int get_709_luma() const { return (13938U * m_comps[0] + 46869U * m_comps[1] + 4729U * m_comps[2] + 32768U) >> 16U; } - inline int get_luma(bool luma_601) const { return luma_601 ? get_601_luma() : get_709_luma(); } - - static color_rgba comp_min(const color_rgba& a, const color_rgba& b) { return color_rgba(std::min(a[0], b[0]), std::min(a[1], b[1]), std::min(a[2], b[2]), std::min(a[3], b[3])); } - static color_rgba comp_max(const color_rgba& a, const color_rgba& b) { return color_rgba(std::max(a[0], b[0]), std::max(a[1], b[1]), std::max(a[2], b[2]), std::max(a[3], b[3])); } -}; - -bool unpack_bc7(const void *pBlock, color_rgba *pPixels); - -} // namespace bc7decomp - -namespace bc7decomp_ref -{ - bool unpack_bc7(const void* pBlock, bc7decomp::color_rgba* pPixels); -} // namespace bc7decomp_ref - -#ifdef _MSC_VER -#pragma warning(pop) -#endif diff --git a/src/ktx.imageio/bc7enc-rdo/bc7enc.cpp b/src/ktx.imageio/bc7enc-rdo/bc7enc.cpp deleted file mode 100644 index 2cc00d2ebf..0000000000 --- a/src/ktx.imageio/bc7enc-rdo/bc7enc.cpp +++ /dev/null @@ -1,2581 +0,0 @@ -// clang-format off -// File: bc7enc.c - Richard Geldreich, Jr. 3/31/2020 - MIT license or public domain (see end of file) -// Currently supports modes 1, 6 for RGB blocks, and modes 5, 6, 7 for RGBA blocks. -#include "bc7enc.h" -#include -#include -#include -#include -#include - -// Helpers -static inline int32_t clampi(int32_t value, int32_t low, int32_t high) { if (value < low) value = low; else if (value > high) value = high; return value; } -static inline float clampf(float value, float low, float high) { if (value < low) value = low; else if (value > high) value = high; return value; } -static inline float saturate(float value) { return clampf(value, 0, 1.0f); } -static inline uint8_t minimumub(uint8_t a, uint8_t b) { return (a < b) ? a : b; } -static inline int32_t minimumi(int32_t a, int32_t b) { return (a < b) ? a : b; } -static inline uint32_t minimumu(uint32_t a, uint32_t b) { return (a < b) ? a : b; } -static inline float minimumf(float a, float b) { return (a < b) ? a : b; } -static inline uint8_t maximumub(uint8_t a, uint8_t b) { return (a > b) ? a : b; } -static inline uint32_t maximumu(uint32_t a, uint32_t b) { return (a > b) ? a : b; } -static inline int32_t maximumi(int32_t a, int32_t b) { return (a > b) ? a : b; } -static inline float maximumf(float a, float b) { return (a > b) ? a : b; } -static inline int squarei(int i) { return i * i; } -static inline float squaref(float i) { return i * i; } -template inline T0 lerp(T0 a, T0 b, T1 c) { return a + (b - a) * c; } - -static inline int32_t iabs32(int32_t v) { uint32_t msk = v >> 31; return (v ^ msk) - msk; } -static inline void swapub(uint8_t* a, uint8_t* b) { uint8_t t = *a; *a = *b; *b = t; } -static inline void swapu(uint32_t* a, uint32_t* b) { uint32_t t = *a; *a = *b; *b = t; } -static inline void swapf(float* a, float* b) { float t = *a; *a = *b; *b = t; } - -struct vec4F { float m_c[4]; }; - -static inline color_rgba *color_quad_u8_set_clamped(color_rgba *pRes, int32_t r, int32_t g, int32_t b, int32_t a) { pRes->m_c[0] = (uint8_t)clampi(r, 0, 255); pRes->m_c[1] = (uint8_t)clampi(g, 0, 255); pRes->m_c[2] = (uint8_t)clampi(b, 0, 255); pRes->m_c[3] = (uint8_t)clampi(a, 0, 255); return pRes; } -static inline color_rgba *color_quad_u8_set(color_rgba *pRes, int32_t r, int32_t g, int32_t b, int32_t a) { assert((uint32_t)(r | g | b | a) <= 255); pRes->m_c[0] = (uint8_t)r; pRes->m_c[1] = (uint8_t)g; pRes->m_c[2] = (uint8_t)b; pRes->m_c[3] = (uint8_t)a; return pRes; } -static inline bool color_quad_u8_notequals(const color_rgba *pLHS, const color_rgba *pRHS) { return (pLHS->m_c[0] != pRHS->m_c[0]) || (pLHS->m_c[1] != pRHS->m_c[1]) || (pLHS->m_c[2] != pRHS->m_c[2]) || (pLHS->m_c[3] != pRHS->m_c[3]); } -static inline vec4F *vec4F_set_scalar(vec4F *pV, float x) { pV->m_c[0] = x; pV->m_c[1] = x; pV->m_c[2] = x; pV->m_c[3] = x; return pV; } -static inline vec4F *vec4F_set(vec4F *pV, float x, float y, float z, float w) { pV->m_c[0] = x; pV->m_c[1] = y; pV->m_c[2] = z; pV->m_c[3] = w; return pV; } -static inline vec4F *vec4F_saturate_in_place(vec4F *pV) { pV->m_c[0] = saturate(pV->m_c[0]); pV->m_c[1] = saturate(pV->m_c[1]); pV->m_c[2] = saturate(pV->m_c[2]); pV->m_c[3] = saturate(pV->m_c[3]); return pV; } -static inline vec4F vec4F_saturate(const vec4F *pV) { vec4F res; res.m_c[0] = saturate(pV->m_c[0]); res.m_c[1] = saturate(pV->m_c[1]); res.m_c[2] = saturate(pV->m_c[2]); res.m_c[3] = saturate(pV->m_c[3]); return res; } -static inline vec4F vec4F_from_color(const color_rgba *pC) { vec4F res; vec4F_set(&res, pC->m_c[0], pC->m_c[1], pC->m_c[2], pC->m_c[3]); return res; } -static inline vec4F vec4F_add(const vec4F *pLHS, const vec4F *pRHS) { vec4F res; vec4F_set(&res, pLHS->m_c[0] + pRHS->m_c[0], pLHS->m_c[1] + pRHS->m_c[1], pLHS->m_c[2] + pRHS->m_c[2], pLHS->m_c[3] + pRHS->m_c[3]); return res; } -static inline vec4F vec4F_sub(const vec4F *pLHS, const vec4F *pRHS) { vec4F res; vec4F_set(&res, pLHS->m_c[0] - pRHS->m_c[0], pLHS->m_c[1] - pRHS->m_c[1], pLHS->m_c[2] - pRHS->m_c[2], pLHS->m_c[3] - pRHS->m_c[3]); return res; } -static inline float vec4F_dot(const vec4F *pLHS, const vec4F *pRHS) { return pLHS->m_c[0] * pRHS->m_c[0] + pLHS->m_c[1] * pRHS->m_c[1] + pLHS->m_c[2] * pRHS->m_c[2] + pLHS->m_c[3] * pRHS->m_c[3]; } -static inline vec4F vec4F_mul(const vec4F *pLHS, float s) { vec4F res; vec4F_set(&res, pLHS->m_c[0] * s, pLHS->m_c[1] * s, pLHS->m_c[2] * s, pLHS->m_c[3] * s); return res; } -static inline vec4F *vec4F_normalize_in_place(vec4F *pV) { float s = pV->m_c[0] * pV->m_c[0] + pV->m_c[1] * pV->m_c[1] + pV->m_c[2] * pV->m_c[2] + pV->m_c[3] * pV->m_c[3]; if (s != 0.0f) { s = 1.0f / sqrtf(s); pV->m_c[0] *= s; pV->m_c[1] *= s; pV->m_c[2] *= s; pV->m_c[3] *= s; } return pV; } - -// Various BC7 tables -static const uint32_t g_bc7_weights2[4] = { 0, 21, 43, 64 }; -static const uint32_t g_bc7_weights3[8] = { 0, 9, 18, 27, 37, 46, 55, 64 }; -static const uint32_t g_bc7_weights4[16] = { 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 }; -// Precomputed weight constants used during least fit determination. For each entry in g_bc7_weights[]: w * w, (1.0f - w) * w, (1.0f - w) * (1.0f - w), w -static const float g_bc7_weights2x[4 * 4] = { 0.000000f, 0.000000f, 1.000000f, 0.000000f, 0.107666f, 0.220459f, 0.451416f, 0.328125f, 0.451416f, 0.220459f, 0.107666f, 0.671875f, 1.000000f, 0.000000f, 0.000000f, 1.000000f }; -static const float g_bc7_weights3x[8 * 4] = { 0.000000f, 0.000000f, 1.000000f, 0.000000f, 0.019775f, 0.120850f, 0.738525f, 0.140625f, 0.079102f, 0.202148f, 0.516602f, 0.281250f, 0.177979f, 0.243896f, 0.334229f, 0.421875f, 0.334229f, 0.243896f, 0.177979f, 0.578125f, 0.516602f, 0.202148f, - 0.079102f, 0.718750f, 0.738525f, 0.120850f, 0.019775f, 0.859375f, 1.000000f, 0.000000f, 0.000000f, 1.000000f }; -static const float g_bc7_weights4x[16 * 4] = { 0.000000f, 0.000000f, 1.000000f, 0.000000f, 0.003906f, 0.058594f, 0.878906f, 0.062500f, 0.019775f, 0.120850f, 0.738525f, 0.140625f, 0.041260f, 0.161865f, 0.635010f, 0.203125f, 0.070557f, 0.195068f, 0.539307f, 0.265625f, 0.107666f, 0.220459f, - 0.451416f, 0.328125f, 0.165039f, 0.241211f, 0.352539f, 0.406250f, 0.219727f, 0.249023f, 0.282227f, 0.468750f, 0.282227f, 0.249023f, 0.219727f, 0.531250f, 0.352539f, 0.241211f, 0.165039f, 0.593750f, 0.451416f, 0.220459f, 0.107666f, 0.671875f, 0.539307f, 0.195068f, 0.070557f, 0.734375f, - 0.635010f, 0.161865f, 0.041260f, 0.796875f, 0.738525f, 0.120850f, 0.019775f, 0.859375f, 0.878906f, 0.058594f, 0.003906f, 0.937500f, 1.000000f, 0.000000f, 0.000000f, 1.000000f }; - -static const uint8_t g_bc7_partition1[16] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; -static const uint8_t g_bc7_partition2[64 * 16] = -{ - 0,0,1,1,0,0,1,1,0,0,1,1,0,0,1,1, 0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1, 0,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1, 0,0,0,1,0,0,1,1,0,0,1,1,0,1,1,1, 0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,1,0,1,1,1,1,1,1,1, 0,0,0,1,0,0,1,1,0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,1,0,0,1,1,0,1,1,1, - 0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,1,0,1,1,1, 0,0,0,1,0,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1, 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1, 0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1, - 0,0,0,0,1,0,0,0,1,1,1,0,1,1,1,1, 0,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,1,0,0,0,1,1,1,0, 0,1,1,1,0,0,1,1,0,0,0,1,0,0,0,0, 0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0, 0,0,0,0,1,0,0,0,1,1,0,0,1,1,1,0, 0,0,0,0,0,0,0,0,1,0,0,0,1,1,0,0, 0,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1, - 0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0, 0,0,0,0,1,0,0,0,1,0,0,0,1,1,0,0, 0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0, 0,0,1,1,0,1,1,0,0,1,1,0,1,1,0,0, 0,0,0,1,0,1,1,1,1,1,1,0,1,0,0,0, 0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0, 0,1,1,1,0,0,0,1,1,0,0,0,1,1,1,0, 0,0,1,1,1,0,0,1,1,0,0,1,1,1,0,0, - 0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1, 0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1, 0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,0, 0,0,1,1,0,0,1,1,1,1,0,0,1,1,0,0, 0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0, 0,1,0,1,0,1,0,1,1,0,1,0,1,0,1,0, 0,1,1,0,1,0,0,1,0,1,1,0,1,0,0,1, 0,1,0,1,1,0,1,0,1,0,1,0,0,1,0,1, - 0,1,1,1,0,0,1,1,1,1,0,0,1,1,1,0, 0,0,0,1,0,0,1,1,1,1,0,0,1,0,0,0, 0,0,1,1,0,0,1,0,0,1,0,0,1,1,0,0, 0,0,1,1,1,0,1,1,1,1,0,1,1,1,0,0, 0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0, 0,0,1,1,1,1,0,0,1,1,0,0,0,0,1,1, 0,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1, 0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0, - 0,1,0,0,1,1,1,0,0,1,0,0,0,0,0,0, 0,0,1,0,0,1,1,1,0,0,1,0,0,0,0,0, 0,0,0,0,0,0,1,0,0,1,1,1,0,0,1,0, 0,0,0,0,0,1,0,0,1,1,1,0,0,1,0,0, 0,1,1,0,1,1,0,0,1,0,0,1,0,0,1,1, 0,0,1,1,0,1,1,0,1,1,0,0,1,0,0,1, 0,1,1,0,0,0,1,1,1,0,0,1,1,1,0,0, 0,0,1,1,1,0,0,1,1,1,0,0,0,1,1,0, - 0,1,1,0,1,1,0,0,1,1,0,0,1,0,0,1, 0,1,1,0,0,0,1,1,0,0,1,1,1,0,0,1, 0,1,1,1,1,1,1,0,1,0,0,0,0,0,0,1, 0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,1, 0,0,0,0,1,1,1,1,0,0,1,1,0,0,1,1, 0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,0, 0,0,1,0,0,0,1,0,1,1,1,0,1,1,1,0, 0,1,0,0,0,1,0,0,0,1,1,1,0,1,1,1 -}; - -static const uint8_t g_bc7_partition3[64 * 16] = -{ - 0,0,1,1,0,0,1,1,0,2,2,1,2,2,2,2, 0,0,0,1,0,0,1,1,2,2,1,1,2,2,2,1, 0,0,0,0,2,0,0,1,2,2,1,1,2,2,1,1, 0,2,2,2,0,0,2,2,0,0,1,1,0,1,1,1, 0,0,0,0,0,0,0,0,1,1,2,2,1,1,2,2, 0,0,1,1,0,0,1,1,0,0,2,2,0,0,2,2, 0,0,2,2,0,0,2,2,1,1,1,1,1,1,1,1, 0,0,1,1,0,0,1,1,2,2,1,1,2,2,1,1, - 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2, 0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2, 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2, 0,0,1,2,0,0,1,2,0,0,1,2,0,0,1,2, 0,1,1,2,0,1,1,2,0,1,1,2,0,1,1,2, 0,1,2,2,0,1,2,2,0,1,2,2,0,1,2,2, 0,0,1,1,0,1,1,2,1,1,2,2,1,2,2,2, 0,0,1,1,2,0,0,1,2,2,0,0,2,2,2,0, - 0,0,0,1,0,0,1,1,0,1,1,2,1,1,2,2, 0,1,1,1,0,0,1,1,2,0,0,1,2,2,0,0, 0,0,0,0,1,1,2,2,1,1,2,2,1,1,2,2, 0,0,2,2,0,0,2,2,0,0,2,2,1,1,1,1, 0,1,1,1,0,1,1,1,0,2,2,2,0,2,2,2, 0,0,0,1,0,0,0,1,2,2,2,1,2,2,2,1, 0,0,0,0,0,0,1,1,0,1,2,2,0,1,2,2, 0,0,0,0,1,1,0,0,2,2,1,0,2,2,1,0, - 0,1,2,2,0,1,2,2,0,0,1,1,0,0,0,0, 0,0,1,2,0,0,1,2,1,1,2,2,2,2,2,2, 0,1,1,0,1,2,2,1,1,2,2,1,0,1,1,0, 0,0,0,0,0,1,1,0,1,2,2,1,1,2,2,1, 0,0,2,2,1,1,0,2,1,1,0,2,0,0,2,2, 0,1,1,0,0,1,1,0,2,0,0,2,2,2,2,2, 0,0,1,1,0,1,2,2,0,1,2,2,0,0,1,1, 0,0,0,0,2,0,0,0,2,2,1,1,2,2,2,1, - 0,0,0,0,0,0,0,2,1,1,2,2,1,2,2,2, 0,2,2,2,0,0,2,2,0,0,1,2,0,0,1,1, 0,0,1,1,0,0,1,2,0,0,2,2,0,2,2,2, 0,1,2,0,0,1,2,0,0,1,2,0,0,1,2,0, 0,0,0,0,1,1,1,1,2,2,2,2,0,0,0,0, 0,1,2,0,1,2,0,1,2,0,1,2,0,1,2,0, 0,1,2,0,2,0,1,2,1,2,0,1,0,1,2,0, 0,0,1,1,2,2,0,0,1,1,2,2,0,0,1,1, - 0,0,1,1,1,1,2,2,2,2,0,0,0,0,1,1, 0,1,0,1,0,1,0,1,2,2,2,2,2,2,2,2, 0,0,0,0,0,0,0,0,2,1,2,1,2,1,2,1, 0,0,2,2,1,1,2,2,0,0,2,2,1,1,2,2, 0,0,2,2,0,0,1,1,0,0,2,2,0,0,1,1, 0,2,2,0,1,2,2,1,0,2,2,0,1,2,2,1, 0,1,0,1,2,2,2,2,2,2,2,2,0,1,0,1, 0,0,0,0,2,1,2,1,2,1,2,1,2,1,2,1, - 0,1,0,1,0,1,0,1,0,1,0,1,2,2,2,2, 0,2,2,2,0,1,1,1,0,2,2,2,0,1,1,1, 0,0,0,2,1,1,1,2,0,0,0,2,1,1,1,2, 0,0,0,0,2,1,1,2,2,1,1,2,2,1,1,2, 0,2,2,2,0,1,1,1,0,1,1,1,0,2,2,2, 0,0,0,2,1,1,1,2,1,1,1,2,0,0,0,2, 0,1,1,0,0,1,1,0,0,1,1,0,2,2,2,2, 0,0,0,0,0,0,0,0,2,1,1,2,2,1,1,2, - 0,1,1,0,0,1,1,0,2,2,2,2,2,2,2,2, 0,0,2,2,0,0,1,1,0,0,1,1,0,0,2,2, 0,0,2,2,1,1,2,2,1,1,2,2,0,0,2,2, 0,0,0,0,0,0,0,0,0,0,0,0,2,1,1,2, 0,0,0,2,0,0,0,1,0,0,0,2,0,0,0,1, 0,2,2,2,1,2,2,2,0,2,2,2,1,2,2,2, 0,1,0,1,2,2,2,2,2,2,2,2,2,2,2,2, 0,1,1,1,2,0,1,1,2,2,0,1,2,2,2,0, -}; - -static const uint8_t g_bc7_table_anchor_index_third_subset_1[64] = -{ - 3, 3,15,15, 8, 3,15,15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8,15, 3, 3, 6,10, 5, 8, 8, 6, 8, 5,15,15, 8,15, 3, 5, 6,10, 8,15, 15, 3,15, 5,15,15,15,15, 3,15, 5, 5, 5, 8, 5,10, 5,10, 8,13,15,12, 3, 3 -}; - -static const uint8_t g_bc7_table_anchor_index_third_subset_2[64] = -{ - 15, 8, 8, 3,15,15, 3, 8, 15,15,15,15,15,15,15, 8, 15, 8,15, 3,15, 8,15, 8, 3,15, 6,10,15,15,10, 8, 15, 3,15,10,10, 8, 9,10, 6,15, 8,15, 3, 6, 6, 8, 15, 3,15,15,15,15,15,15, 15,15,15,15, 3,15,15, 8 -}; - -static const uint8_t g_bc7_table_anchor_index_second_subset[64] = { 15,15,15,15,15,15,15,15, 15,15,15,15,15,15,15,15, 15, 2, 8, 2, 2, 8, 8,15, 2, 8, 2, 2, 8, 8, 2, 2, 15,15, 6, 8, 2, 8,15,15, 2, 8, 2, 2, 2,15,15, 6, 6, 2, 6, 8,15,15, 2, 2, 15,15,15,15,15, 2, 2,15 }; -static const uint8_t g_bc7_num_subsets[8] = { 3, 2, 3, 2, 1, 1, 1, 2 }; -static const uint8_t g_bc7_partition_bits[8] = { 4, 6, 6, 6, 0, 0, 0, 6 }; -static const uint8_t g_bc7_color_index_bitcount[8] = { 3, 3, 2, 2, 2, 2, 4, 2 }; -static int get_bc7_color_index_size(int mode, int index_selection_bit) { return g_bc7_color_index_bitcount[mode] + index_selection_bit; } -static uint8_t g_bc7_alpha_index_bitcount[8] = { 0, 0, 0, 0, 3, 2, 4, 2 }; -static int get_bc7_alpha_index_size(int mode, int index_selection_bit) { return g_bc7_alpha_index_bitcount[mode] - index_selection_bit; } -static const uint8_t g_bc7_mode_has_p_bits[8] = { 1, 1, 0, 1, 0, 0, 1, 1 }; -static const uint8_t g_bc7_mode_has_shared_p_bits[8] = { 0, 1, 0, 0, 0, 0, 0, 0 }; -static const uint8_t g_bc7_color_precision_table[8] = { 4, 6, 5, 7, 5, 7, 7, 5 }; -static const int8_t g_bc7_alpha_precision_table[8] = { 0, 0, 0, 0, 6, 8, 7, 5 }; -static bool get_bc7_mode_has_seperate_alpha_selectors(int mode) { return (mode == 4) || (mode == 5); } - -typedef struct { uint16_t m_error; uint8_t m_lo; uint8_t m_hi; } endpoint_err; - -static endpoint_err g_bc7_mode_1_optimal_endpoints[256][2]; // [c][pbit] -static const uint32_t BC7ENC_MODE_1_OPTIMAL_INDEX = 2; - -static endpoint_err g_bc7_mode_7_optimal_endpoints[256][2][2]; // [c][pbit][hp][lp] -const uint32_t BC7E_MODE_7_OPTIMAL_INDEX = 1; - -static float g_mode1_rgba_midpoints[64][2]; -static float g_mode5_rgba_midpoints[128]; -static float g_mode7_rgba_midpoints[32][2]; - -static uint8_t g_mode6_reduced_quant[2048][2]; - -static bool g_initialized; - -// Initialize the lookup table used for optimal single color compression in mode 1/7. Must be called before encoding. -void bc7enc_compress_block_init() -{ - if (g_initialized) - return; - - // Mode 7 endpoint midpoints - for (uint32_t p = 0; p < 2; p++) - { - for (uint32_t i = 0; i < 32; i++) - { - uint32_t vl = ((i << 1) | p) << 2; - vl |= (vl >> 6); - float lo = vl / 255.0f; - - uint32_t vh = ((minimumi(31, (i + 1)) << 1) | p) << 2; - vh |= (vh >> 6); - float hi = vh / 255.0f; - - //g_mode7_quant_values[i][p] = lo; - if (i == 31) - g_mode7_rgba_midpoints[i][p] = 1.0f; - else - g_mode7_rgba_midpoints[i][p] = (lo + hi) / 2.0f; - } - } - - // Mode 1 endpoint midpoints - for (uint32_t p = 0; p < 2; p++) - { - for (uint32_t i = 0; i < 64; i++) - { - uint32_t vl = ((i << 1) | p) << 1; - vl |= (vl >> 7); - float lo = vl / 255.0f; - - uint32_t vh = ((minimumi(63, (i + 1)) << 1) | p) << 1; - vh |= (vh >> 7); - float hi = vh / 255.0f; - - //g_mode1_quant_values[i][p] = lo; - if (i == 63) - g_mode1_rgba_midpoints[i][p] = 1.0f; - else - g_mode1_rgba_midpoints[i][p] = (lo + hi) / 2.0f; - } - } - - // Mode 5 endpoint midpoints - for (uint32_t i = 0; i < 128; i++) - { - uint32_t vl = (i << 1); - vl |= (vl >> 7); - float lo = vl / 255.0f; - - uint32_t vh = minimumi(127, i + 1) << 1; - vh |= (vh >> 7); - float hi = vh / 255.0f; - - if (i == 127) - g_mode5_rgba_midpoints[i] = 1.0f; - else - g_mode5_rgba_midpoints[i] = (lo + hi) / 2.0f; - } - - for (uint32_t p = 0; p < 2; p++) - { - for (uint32_t i = 0; i < 2048; i++) - { - float f = i / 2047.0f; - - float best_err = 1e+9f; - int best_index = 0; - for (int j = 0; j < 64; j++) - { - int ik = (j * 127 + 31) / 63; - float k = ((ik << 1) + p) / 255.0f; - - float e = fabsf(k - f); - if (e < best_err) - { - best_err = e; - best_index = ik; - } - } - - g_mode6_reduced_quant[i][p] = (uint8_t)best_index; - } - } // p - - // Mode 1 - for (int c = 0; c < 256; c++) - { - for (uint32_t lp = 0; lp < 2; lp++) - { - endpoint_err best; - best.m_error = (uint16_t)UINT16_MAX; - for (uint32_t l = 0; l < 64; l++) - { - uint32_t low = ((l << 1) | lp) << 1; - low |= (low >> 7); - for (uint32_t h = 0; h < 64; h++) - { - uint32_t high = ((h << 1) | lp) << 1; - high |= (high >> 7); - const int k = (low * (64 - g_bc7_weights3[BC7ENC_MODE_1_OPTIMAL_INDEX]) + high * g_bc7_weights3[BC7ENC_MODE_1_OPTIMAL_INDEX] + 32) >> 6; - const int err = (k - c) * (k - c); - if (err < best.m_error) - { - best.m_error = (uint16_t)err; - best.m_lo = (uint8_t)l; - best.m_hi = (uint8_t)h; - } - } // h - } // l - g_bc7_mode_1_optimal_endpoints[c][lp] = best; - } // lp - } // c - - // Mode 7: 555.1 2-bit indices - for (int c = 0; c < 256; c++) - { - for (uint32_t hp = 0; hp < 2; hp++) - { - for (uint32_t lp = 0; lp < 2; lp++) - { - endpoint_err best; - best.m_error = (uint16_t)UINT16_MAX; - best.m_lo = 0; - best.m_hi = 0; - - for (uint32_t l = 0; l < 32; l++) - { - uint32_t low = ((l << 1) | lp) << 2; - low |= (low >> 6); - - for (uint32_t h = 0; h < 32; h++) - { - uint32_t high = ((h << 1) | hp) << 2; - high |= (high >> 6); - - const int k = (low * (64 - g_bc7_weights2[BC7E_MODE_7_OPTIMAL_INDEX]) + high * g_bc7_weights2[BC7E_MODE_7_OPTIMAL_INDEX] + 32) >> 6; - - const int err = (k - c) * (k - c); - if (err < best.m_error) - { - best.m_error = (uint16_t)err; - best.m_lo = (uint8_t)l; - best.m_hi = (uint8_t)h; - } - } // h - } // l - - g_bc7_mode_7_optimal_endpoints[c][hp][lp] = best; - - } // hp - - } // lp - - } // c - - g_initialized = true; -} - -static void compute_least_squares_endpoints_rgba(uint32_t N, const uint8_t *pSelectors, const vec4F *pSelector_weights, vec4F *pXl, vec4F *pXh, const color_rgba *pColors) -{ - // Least squares using normal equations: http://www.cs.cornell.edu/~bindel/class/cs3220-s12/notes/lec10.pdf - // I did this in matrix form first, expanded out all the ops, then optimized it a bit. - float z00 = 0.0f, z01 = 0.0f, z10 = 0.0f, z11 = 0.0f; - float q00_r = 0.0f, q10_r = 0.0f, t_r = 0.0f; - float q00_g = 0.0f, q10_g = 0.0f, t_g = 0.0f; - float q00_b = 0.0f, q10_b = 0.0f, t_b = 0.0f; - float q00_a = 0.0f, q10_a = 0.0f, t_a = 0.0f; - for (uint32_t i = 0; i < N; i++) - { - const uint32_t sel = pSelectors[i]; - z00 += pSelector_weights[sel].m_c[0]; - z10 += pSelector_weights[sel].m_c[1]; - z11 += pSelector_weights[sel].m_c[2]; - float w = pSelector_weights[sel].m_c[3]; - q00_r += w * pColors[i].m_c[0]; t_r += pColors[i].m_c[0]; - q00_g += w * pColors[i].m_c[1]; t_g += pColors[i].m_c[1]; - q00_b += w * pColors[i].m_c[2]; t_b += pColors[i].m_c[2]; - q00_a += w * pColors[i].m_c[3]; t_a += pColors[i].m_c[3]; - } - - q10_r = t_r - q00_r; - q10_g = t_g - q00_g; - q10_b = t_b - q00_b; - q10_a = t_a - q00_a; - - z01 = z10; - - float det = z00 * z11 - z01 * z10; - if (det != 0.0f) - det = 1.0f / det; - - float iz00, iz01, iz10, iz11; - iz00 = z11 * det; - iz01 = -z01 * det; - iz10 = -z10 * det; - iz11 = z00 * det; - - pXl->m_c[0] = (float)(iz00 * q00_r + iz01 * q10_r); pXh->m_c[0] = (float)(iz10 * q00_r + iz11 * q10_r); - pXl->m_c[1] = (float)(iz00 * q00_g + iz01 * q10_g); pXh->m_c[1] = (float)(iz10 * q00_g + iz11 * q10_g); - pXl->m_c[2] = (float)(iz00 * q00_b + iz01 * q10_b); pXh->m_c[2] = (float)(iz10 * q00_b + iz11 * q10_b); - pXl->m_c[3] = (float)(iz00 * q00_a + iz01 * q10_a); pXh->m_c[3] = (float)(iz10 * q00_a + iz11 * q10_a); - - for (uint32_t c = 0; c < 4; c++) - { - if ((pXl->m_c[c] < 0.0f) || (pXh->m_c[c] > 255.0f)) - { - uint32_t lo_v = UINT32_MAX, hi_v = 0; - for (uint32_t i = 0; i < N; i++) - { - lo_v = minimumu(lo_v, pColors[i].m_c[c]); - hi_v = maximumu(hi_v, pColors[i].m_c[c]); - } - - if (lo_v == hi_v) - { - pXl->m_c[c] = (float)lo_v; - pXh->m_c[c] = (float)hi_v; - } - } - } -} - -static void compute_least_squares_endpoints_rgb(uint32_t N, const uint8_t *pSelectors, const vec4F *pSelector_weights, vec4F *pXl, vec4F *pXh, const color_rgba*pColors) -{ - float z00 = 0.0f, z01 = 0.0f, z10 = 0.0f, z11 = 0.0f; - float q00_r = 0.0f, q10_r = 0.0f, t_r = 0.0f; - float q00_g = 0.0f, q10_g = 0.0f, t_g = 0.0f; - float q00_b = 0.0f, q10_b = 0.0f, t_b = 0.0f; - for (uint32_t i = 0; i < N; i++) - { - const uint32_t sel = pSelectors[i]; - z00 += pSelector_weights[sel].m_c[0]; - z10 += pSelector_weights[sel].m_c[1]; - z11 += pSelector_weights[sel].m_c[2]; - float w = pSelector_weights[sel].m_c[3]; - q00_r += w * pColors[i].m_c[0]; t_r += pColors[i].m_c[0]; - q00_g += w * pColors[i].m_c[1]; t_g += pColors[i].m_c[1]; - q00_b += w * pColors[i].m_c[2]; t_b += pColors[i].m_c[2]; - } - - q10_r = t_r - q00_r; - q10_g = t_g - q00_g; - q10_b = t_b - q00_b; - - z01 = z10; - - float det = z00 * z11 - z01 * z10; - if (det != 0.0f) - det = 1.0f / det; - - float iz00, iz01, iz10, iz11; - iz00 = z11 * det; - iz01 = -z01 * det; - iz10 = -z10 * det; - iz11 = z00 * det; - - pXl->m_c[0] = (float)(iz00 * q00_r + iz01 * q10_r); pXh->m_c[0] = (float)(iz10 * q00_r + iz11 * q10_r); - pXl->m_c[1] = (float)(iz00 * q00_g + iz01 * q10_g); pXh->m_c[1] = (float)(iz10 * q00_g + iz11 * q10_g); - pXl->m_c[2] = (float)(iz00 * q00_b + iz01 * q10_b); pXh->m_c[2] = (float)(iz10 * q00_b + iz11 * q10_b); - pXl->m_c[3] = 255.0f; pXh->m_c[3] = 255.0f; - - for (uint32_t c = 0; c < 3; c++) - { - if ((pXl->m_c[c] < 0.0f) || (pXh->m_c[c] > 255.0f)) - { - uint32_t lo_v = UINT32_MAX, hi_v = 0; - for (uint32_t i = 0; i < N; i++) - { - lo_v = minimumu(lo_v, pColors[i].m_c[c]); - hi_v = maximumu(hi_v, pColors[i].m_c[c]); - } - - if (lo_v == hi_v) - { - pXl->m_c[c] = (float)lo_v; - pXh->m_c[c] = (float)hi_v; - } - } - } -} - -static void compute_least_squares_endpoints_a(uint32_t N, const uint8_t* pSelectors, const vec4F* pSelector_weights, float* pXl, float* pXh, const color_rgba *pColors) -{ - // Least squares using normal equations: http://www.cs.cornell.edu/~bindel/class/cs3220-s12/notes/lec10.pdf - // I did this in matrix form first, expanded out all the ops, then optimized it a bit. - float z00 = 0.0f, z01 = 0.0f, z10 = 0.0f, z11 = 0.0f; - float q00_a = 0.0f, q10_a = 0.0f, t_a = 0.0f; - for (uint32_t i = 0; i < N; i++) - { - const uint32_t sel = pSelectors[i]; - - z00 += pSelector_weights[sel].m_c[0]; - z10 += pSelector_weights[sel].m_c[1]; - z11 += pSelector_weights[sel].m_c[2]; - - float w = pSelector_weights[sel].m_c[3]; - - q00_a += w * pColors[i].m_c[3]; t_a += pColors[i].m_c[3]; - } - - q10_a = t_a - q00_a; - - z01 = z10; - - float det = z00 * z11 - z01 * z10; - if (det != 0.0f) - det = 1.0f / det; - - float iz00, iz01, iz10, iz11; - iz00 = z11 * det; - iz01 = -z01 * det; - iz10 = -z10 * det; - iz11 = z00 * det; - - *pXl = (float)(iz00 * q00_a + iz01 * q10_a); *pXh = (float)(iz10 * q00_a + iz11 * q10_a); - - if ((*pXl < 0.0f) || (*pXh > 255.0f)) - { - uint32_t lo_v = UINT32_MAX, hi_v = 0; - for (uint32_t i = 0; i < N; i++) - { - lo_v = minimumu(lo_v, pColors[i].m_c[3]); - hi_v = maximumu(hi_v, pColors[i].m_c[3]); - } - - if (lo_v == hi_v) - { - *pXl = (float)lo_v; - *pXh = (float)hi_v; - } - } -} - -struct color_cell_compressor_params -{ - uint32_t m_num_pixels; - const color_rgba *m_pPixels; - uint32_t m_num_selector_weights; - const uint32_t *m_pSelector_weights; - const vec4F *m_pSelector_weightsx; - uint32_t m_comp_bits; - uint32_t m_weights[4]; - bool m_has_alpha; - bool m_has_pbits; - bool m_endpoints_share_pbit; - bool m_perceptual; -}; - -struct color_cell_compressor_results -{ - uint64_t m_best_overall_err; - color_rgba m_low_endpoint; - color_rgba m_high_endpoint; - uint32_t m_pbits[2]; - uint8_t *m_pSelectors; - uint8_t *m_pSelectors_temp; -}; - -static inline color_rgba scale_color(const color_rgba *pC, const color_cell_compressor_params *pParams) -{ - color_rgba results; - - const uint32_t n = pParams->m_comp_bits + (pParams->m_has_pbits ? 1 : 0); - assert((n >= 4) && (n <= 8)); - - for (uint32_t i = 0; i < 4; i++) - { - uint32_t v = pC->m_c[i] << (8 - n); - v |= (v >> n); - assert(v <= 255); - results.m_c[i] = (uint8_t)(v); - } - - return results; -} - -static inline uint64_t compute_color_distance_rgb(const color_rgba *pE1, const color_rgba *pE2, bool perceptual, const uint32_t weights[4]) -{ - int dr, dg, db; - - if (perceptual) - { - const int l1 = pE1->m_c[0] * 109 + pE1->m_c[1] * 366 + pE1->m_c[2] * 37; - const int cr1 = ((int)pE1->m_c[0] << 9) - l1; - const int cb1 = ((int)pE1->m_c[2] << 9) - l1; - const int l2 = pE2->m_c[0] * 109 + pE2->m_c[1] * 366 + pE2->m_c[2] * 37; - const int cr2 = ((int)pE2->m_c[0] << 9) - l2; - const int cb2 = ((int)pE2->m_c[2] << 9) - l2; - dr = (l1 - l2) >> 8; - dg = (cr1 - cr2) >> 8; - db = (cb1 - cb2) >> 8; - } - else - { - dr = (int)pE1->m_c[0] - (int)pE2->m_c[0]; - dg = (int)pE1->m_c[1] - (int)pE2->m_c[1]; - db = (int)pE1->m_c[2] - (int)pE2->m_c[2]; - } - - return weights[0] * (uint32_t)(dr * dr) + weights[1] * (uint32_t)(dg * dg) + weights[2] * (uint32_t)(db * db); -} - -static inline uint64_t compute_color_distance_rgba(const color_rgba *pE1, const color_rgba *pE2, bool perceptual, const uint32_t weights[4]) -{ - int da = (int)pE1->m_c[3] - (int)pE2->m_c[3]; - return compute_color_distance_rgb(pE1, pE2, perceptual, weights) + (weights[3] * (uint32_t)(da * da)); -} - -static uint64_t pack_mode1_to_one_color(const color_cell_compressor_params *pParams, color_cell_compressor_results *pResults, uint32_t r, uint32_t g, uint32_t b, uint8_t *pSelectors) -{ - uint32_t best_err = UINT_MAX; - uint32_t best_p = 0; - - for (uint32_t p = 0; p < 2; p++) - { - uint32_t err = g_bc7_mode_1_optimal_endpoints[r][p].m_error + g_bc7_mode_1_optimal_endpoints[g][p].m_error + g_bc7_mode_1_optimal_endpoints[b][p].m_error; - if (err < best_err) - { - best_err = err; - best_p = p; - if (!best_err) - break; - } - } - - const endpoint_err *pEr = &g_bc7_mode_1_optimal_endpoints[r][best_p]; - const endpoint_err *pEg = &g_bc7_mode_1_optimal_endpoints[g][best_p]; - const endpoint_err *pEb = &g_bc7_mode_1_optimal_endpoints[b][best_p]; - - color_quad_u8_set(&pResults->m_low_endpoint, pEr->m_lo, pEg->m_lo, pEb->m_lo, 0); - color_quad_u8_set(&pResults->m_high_endpoint, pEr->m_hi, pEg->m_hi, pEb->m_hi, 0); - pResults->m_pbits[0] = best_p; - pResults->m_pbits[1] = 0; - - memset(pSelectors, BC7ENC_MODE_1_OPTIMAL_INDEX, pParams->m_num_pixels); - - color_rgba p; - for (uint32_t i = 0; i < 3; i++) - { - uint32_t low = ((pResults->m_low_endpoint.m_c[i] << 1) | pResults->m_pbits[0]) << 1; - low |= (low >> 7); - - uint32_t high = ((pResults->m_high_endpoint.m_c[i] << 1) | pResults->m_pbits[0]) << 1; - high |= (high >> 7); - - p.m_c[i] = (uint8_t)((low * (64 - g_bc7_weights3[BC7ENC_MODE_1_OPTIMAL_INDEX]) + high * g_bc7_weights3[BC7ENC_MODE_1_OPTIMAL_INDEX] + 32) >> 6); - } - p.m_c[3] = 255; - - uint64_t total_err = 0; - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - total_err += compute_color_distance_rgb(&p, &pParams->m_pPixels[i], pParams->m_perceptual, pParams->m_weights); - - pResults->m_best_overall_err = total_err; - - return total_err; -} - -static uint64_t pack_mode7_to_one_color(const color_cell_compressor_params* pParams, color_cell_compressor_results* pResults, uint32_t r, uint32_t g, uint32_t b, uint32_t a, - uint8_t* pSelectors, uint32_t num_pixels, const color_rgba *pPixels) -{ - uint32_t best_err = UINT_MAX; - uint32_t best_p = 0; - - for (uint32_t p = 0; p < 4; p++) - { - uint32_t hi_p = p >> 1; - uint32_t lo_p = p & 1; - uint32_t err = g_bc7_mode_7_optimal_endpoints[r][hi_p][lo_p].m_error + g_bc7_mode_7_optimal_endpoints[g][hi_p][lo_p].m_error + g_bc7_mode_7_optimal_endpoints[b][hi_p][lo_p].m_error + g_bc7_mode_7_optimal_endpoints[a][hi_p][lo_p].m_error; - if (err < best_err) - { - best_err = err; - best_p = p; - if (!best_err) - break; - } - } - - uint32_t best_hi_p = best_p >> 1; - uint32_t best_lo_p = best_p & 1; - - const endpoint_err* pEr = &g_bc7_mode_7_optimal_endpoints[r][best_hi_p][best_lo_p]; - const endpoint_err* pEg = &g_bc7_mode_7_optimal_endpoints[g][best_hi_p][best_lo_p]; - const endpoint_err* pEb = &g_bc7_mode_7_optimal_endpoints[b][best_hi_p][best_lo_p]; - const endpoint_err* pEa = &g_bc7_mode_7_optimal_endpoints[a][best_hi_p][best_lo_p]; - - color_quad_u8_set(&pResults->m_low_endpoint, pEr->m_lo, pEg->m_lo, pEb->m_lo, pEa->m_lo); - color_quad_u8_set(&pResults->m_high_endpoint, pEr->m_hi, pEg->m_hi, pEb->m_hi, pEa->m_hi); - pResults->m_pbits[0] = best_lo_p; - pResults->m_pbits[1] = best_hi_p; - - for (uint32_t i = 0; i < num_pixels; i++) - pSelectors[i] = (uint8_t)BC7E_MODE_7_OPTIMAL_INDEX; - - color_rgba p; - - for (uint32_t i = 0; i < 4; i++) - { - uint32_t low = (pResults->m_low_endpoint.m_c[i] << 1) | pResults->m_pbits[0]; - uint32_t high = (pResults->m_high_endpoint.m_c[i] << 1) | pResults->m_pbits[1]; - - low = (low << 2) | (low >> 6); - high = (high << 2) | (high >> 6); - - p.m_c[i] = (uint8_t)((low * (64 - g_bc7_weights2[BC7E_MODE_7_OPTIMAL_INDEX]) + high * g_bc7_weights2[BC7E_MODE_7_OPTIMAL_INDEX] + 32) >> 6); - } - - uint64_t total_err = 0; - for (uint32_t i = 0; i < num_pixels; i++) - total_err += compute_color_distance_rgba(&p, &pPixels[i], pParams->m_perceptual, pParams->m_weights); - - pResults->m_best_overall_err = total_err; - - return total_err; -} - -static uint64_t evaluate_solution(const color_rgba *pLow, const color_rgba *pHigh, const uint32_t pbits[2], const color_cell_compressor_params *pParams, color_cell_compressor_results *pResults, - const bc7enc_compress_block_params* pComp_params) -{ - color_rgba quantMinColor = *pLow; - color_rgba quantMaxColor = *pHigh; - - if (pParams->m_has_pbits) - { - uint32_t minPBit, maxPBit; - - if (pParams->m_endpoints_share_pbit) - maxPBit = minPBit = pbits[0]; - else - { - minPBit = pbits[0]; - maxPBit = pbits[1]; - } - - quantMinColor.m_c[0] = (uint8_t)((pLow->m_c[0] << 1) | minPBit); - quantMinColor.m_c[1] = (uint8_t)((pLow->m_c[1] << 1) | minPBit); - quantMinColor.m_c[2] = (uint8_t)((pLow->m_c[2] << 1) | minPBit); - quantMinColor.m_c[3] = (uint8_t)((pLow->m_c[3] << 1) | minPBit); - - quantMaxColor.m_c[0] = (uint8_t)((pHigh->m_c[0] << 1) | maxPBit); - quantMaxColor.m_c[1] = (uint8_t)((pHigh->m_c[1] << 1) | maxPBit); - quantMaxColor.m_c[2] = (uint8_t)((pHigh->m_c[2] << 1) | maxPBit); - quantMaxColor.m_c[3] = (uint8_t)((pHigh->m_c[3] << 1) | maxPBit); - } - - color_rgba actualMinColor = scale_color(&quantMinColor, pParams); - color_rgba actualMaxColor = scale_color(&quantMaxColor, pParams); - - const uint32_t N = pParams->m_num_selector_weights; - - color_rgba weightedColors[16]; - weightedColors[0] = actualMinColor; - weightedColors[N - 1] = actualMaxColor; - - const uint32_t nc = pParams->m_has_alpha ? 4 : 3; - for (uint32_t i = 1; i < (N - 1); i++) - for (uint32_t j = 0; j < nc; j++) - weightedColors[i].m_c[j] = (uint8_t)((actualMinColor.m_c[j] * (64 - pParams->m_pSelector_weights[i]) + actualMaxColor.m_c[j] * pParams->m_pSelector_weights[i] + 32) >> 6); - - const int lr = actualMinColor.m_c[0]; - const int lg = actualMinColor.m_c[1]; - const int lb = actualMinColor.m_c[2]; - const int dr = actualMaxColor.m_c[0] - lr; - const int dg = actualMaxColor.m_c[1] - lg; - const int db = actualMaxColor.m_c[2] - lb; - - uint64_t total_err = 0; - - if (pComp_params->m_force_selectors) - { - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - { - const uint32_t best_sel = pComp_params->m_selectors[i]; - - uint64_t best_err; - if (pParams->m_has_alpha) - best_err = compute_color_distance_rgba(&weightedColors[best_sel], &pParams->m_pPixels[i], pParams->m_perceptual, pParams->m_weights); - else - best_err = compute_color_distance_rgb(&weightedColors[best_sel], &pParams->m_pPixels[i], pParams->m_perceptual, pParams->m_weights); - - total_err += best_err; - - pResults->m_pSelectors_temp[i] = (uint8_t)best_sel; - } - } - else if (!pParams->m_perceptual) - { - if (pParams->m_has_alpha) - { - const int la = actualMinColor.m_c[3]; - const int da = actualMaxColor.m_c[3] - la; - - const float f = N / (float)(squarei(dr) + squarei(dg) + squarei(db) + squarei(da) + .00000125f); - - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - { - const color_rgba *pC = &pParams->m_pPixels[i]; - int r = pC->m_c[0]; - int g = pC->m_c[1]; - int b = pC->m_c[2]; - int a = pC->m_c[3]; - - int best_sel = (int)((float)((r - lr) * dr + (g - lg) * dg + (b - lb) * db + (a - la) * da) * f + .5f); - best_sel = clampi(best_sel, 1, N - 1); - - uint64_t err0 = compute_color_distance_rgba(&weightedColors[best_sel - 1], pC, false, pParams->m_weights); - uint64_t err1 = compute_color_distance_rgba(&weightedColors[best_sel], pC, false, pParams->m_weights); - - if (err1 > err0) - { - err1 = err0; - --best_sel; - } - total_err += err1; - - pResults->m_pSelectors_temp[i] = (uint8_t)best_sel; - } - } - else - { - const float f = N / (float)(squarei(dr) + squarei(dg) + squarei(db) + .00000125f); - - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - { - const color_rgba *pC = &pParams->m_pPixels[i]; - int r = pC->m_c[0]; - int g = pC->m_c[1]; - int b = pC->m_c[2]; - - int sel = (int)((float)((r - lr) * dr + (g - lg) * dg + (b - lb) * db) * f + .5f); - sel = clampi(sel, 1, N - 1); - - uint64_t err0 = compute_color_distance_rgb(&weightedColors[sel - 1], pC, false, pParams->m_weights); - uint64_t err1 = compute_color_distance_rgb(&weightedColors[sel], pC, false, pParams->m_weights); - - int best_sel = sel; - uint64_t best_err = err1; - if (err0 < best_err) - { - best_err = err0; - best_sel = sel - 1; - } - - total_err += best_err; - - pResults->m_pSelectors_temp[i] = (uint8_t)best_sel; - } - } - } - else - { - // TODO: This could be improved. - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - { - uint64_t best_err = UINT64_MAX; - uint32_t best_sel = 0; - - if (pParams->m_has_alpha) - { - for (uint32_t j = 0; j < N; j++) - { - uint64_t err = compute_color_distance_rgba(&weightedColors[j], &pParams->m_pPixels[i], true, pParams->m_weights); - if (err < best_err) - { - best_err = err; - best_sel = j; - } - } - } - else - { - for (uint32_t j = 0; j < N; j++) - { - uint64_t err = compute_color_distance_rgb(&weightedColors[j], &pParams->m_pPixels[i], true, pParams->m_weights); - if (err < best_err) - { - best_err = err; - best_sel = j; - } - } - } - - total_err += best_err; - - pResults->m_pSelectors_temp[i] = (uint8_t)best_sel; - } - } - - if (total_err < pResults->m_best_overall_err) - { - pResults->m_best_overall_err = total_err; - - pResults->m_low_endpoint = *pLow; - pResults->m_high_endpoint = *pHigh; - - pResults->m_pbits[0] = pbits[0]; - pResults->m_pbits[1] = pbits[1]; - - memcpy(pResults->m_pSelectors, pResults->m_pSelectors_temp, sizeof(pResults->m_pSelectors[0]) * pParams->m_num_pixels); - } - - return total_err; -} - -static void fixDegenerateEndpoints(uint32_t mode, color_rgba *pTrialMinColor, color_rgba *pTrialMaxColor, const vec4F *pXl, const vec4F *pXh, uint32_t iscale, - const bc7enc_compress_block_params* pComp_params) -{ - //if ((mode == 1) || (mode == 7)) - //if (mode == 1) - if ( (mode == 1) || ((mode == 6) && (pComp_params->m_quant_mode6_endpoints)) ) - { - // fix degenerate case where the input collapses to a single colorspace voxel, and we loose all freedom (test with grayscale ramps) - for (uint32_t i = 0; i < 3; i++) - { - if (pTrialMinColor->m_c[i] == pTrialMaxColor->m_c[i]) - { - if (fabs(pXl->m_c[i] - pXh->m_c[i]) > 0.0f) - { - if (pTrialMinColor->m_c[i] > (iscale >> 1)) - { - if (pTrialMinColor->m_c[i] > 0) - pTrialMinColor->m_c[i]--; - else - if (pTrialMaxColor->m_c[i] < iscale) - pTrialMaxColor->m_c[i]++; - } - else - { - if (pTrialMaxColor->m_c[i] < iscale) - pTrialMaxColor->m_c[i]++; - else if (pTrialMinColor->m_c[i] > 0) - pTrialMinColor->m_c[i]--; - } - } - } - } - } -} - -static uint64_t find_optimal_solution(uint32_t mode, vec4F xl, vec4F xh, const color_cell_compressor_params *pParams, color_cell_compressor_results *pResults, - const bc7enc_compress_block_params* pComp_params) -{ - vec4F_saturate_in_place(&xl); vec4F_saturate_in_place(&xh); - - if (pParams->m_has_pbits) - { - const int iscalep = (1 << (pParams->m_comp_bits + 1)) - 1; - const float scalep = (float)iscalep; - - const int32_t totalComps = pParams->m_has_alpha ? 4 : 3; - - uint32_t best_pbits[2]; - color_rgba bestMinColor, bestMaxColor; - - if (!pParams->m_endpoints_share_pbit) - { - if ((pParams->m_comp_bits == 7) && (pComp_params->m_quant_mode6_endpoints)) - { - best_pbits[0] = 0; - bestMinColor.m_c[0] = g_mode6_reduced_quant[(int)((xl.m_c[0] * 2047.0f) + .5f)][0]; - bestMinColor.m_c[1] = g_mode6_reduced_quant[(int)((xl.m_c[1] * 2047.0f) + .5f)][0]; - bestMinColor.m_c[2] = g_mode6_reduced_quant[(int)((xl.m_c[2] * 2047.0f) + .5f)][0]; - bestMinColor.m_c[3] = g_mode6_reduced_quant[(int)((xl.m_c[3] * 2047.0f) + .5f)][0]; - - best_pbits[1] = 1; - bestMaxColor.m_c[0] = g_mode6_reduced_quant[(int)((xh.m_c[0] * 2047.0f) + .5f)][1]; - bestMaxColor.m_c[1] = g_mode6_reduced_quant[(int)((xh.m_c[1] * 2047.0f) + .5f)][1]; - bestMaxColor.m_c[2] = g_mode6_reduced_quant[(int)((xh.m_c[2] * 2047.0f) + .5f)][1]; - bestMaxColor.m_c[3] = g_mode6_reduced_quant[(int)((xh.m_c[3] * 2047.0f) + .5f)][1]; - } - else - { - float best_err0 = 1e+9; - float best_err1 = 1e+9; - - for (int p = 0; p < 2; p++) - { - color_rgba xMinColor, xMaxColor; - - // Notes: The pbit controls which quantization intervals are selected. - // total_levels=2^(comp_bits+1), where comp_bits=4 for mode 0, etc. - // pbit 0: v=(b*2)/(total_levels-1), pbit 1: v=(b*2+1)/(total_levels-1) where b is the component bin from [0,total_levels/2-1] and v is the [0,1] component value - // rearranging you get for pbit 0: b=floor(v*(total_levels-1)/2+.5) - // rearranging you get for pbit 1: b=floor((v*(total_levels-1)-1)/2+.5) - if (pParams->m_comp_bits == 5) - { - for (uint32_t c = 0; c < 4; c++) - { - int vl = (int)(xl.m_c[c] * 31.0f); - vl += (xl.m_c[c] > g_mode7_rgba_midpoints[vl][p]); - xMinColor.m_c[c] = (uint8_t)clampi(vl * 2 + p, p, 63 - 1 + p); - - int vh = (int)(xh.m_c[c] * 31.0f); - vh += (xh.m_c[c] > g_mode7_rgba_midpoints[vh][p]); - xMaxColor.m_c[c] = (uint8_t)clampi(vh * 2 + p, p, 63 - 1 + p); - } - } - else - { - for (uint32_t c = 0; c < 4; c++) - { - xMinColor.m_c[c] = (uint8_t)(clampi(((int)((xl.m_c[c] * scalep - p) / 2.0f + .5f)) * 2 + p, p, iscalep - 1 + p)); - xMaxColor.m_c[c] = (uint8_t)(clampi(((int)((xh.m_c[c] * scalep - p) / 2.0f + .5f)) * 2 + p, p, iscalep - 1 + p)); - } - } - - color_rgba scaledLow = scale_color(&xMinColor, pParams); - color_rgba scaledHigh = scale_color(&xMaxColor, pParams); - - float err0 = 0, err1 = 0; - for (int i = 0; i < totalComps; i++) - { - err0 += squaref(scaledLow.m_c[i] - xl.m_c[i] * 255.0f); - err1 += squaref(scaledHigh.m_c[i] - xh.m_c[i] * 255.0f); - } - - if (p == 1) - { - err0 *= pComp_params->m_pbit1_weight; - err1 *= pComp_params->m_pbit1_weight; - } - - if (err0 < best_err0) - { - best_err0 = err0; - best_pbits[0] = p; - - bestMinColor.m_c[0] = xMinColor.m_c[0] >> 1; - bestMinColor.m_c[1] = xMinColor.m_c[1] >> 1; - bestMinColor.m_c[2] = xMinColor.m_c[2] >> 1; - bestMinColor.m_c[3] = xMinColor.m_c[3] >> 1; - } - - if (err1 < best_err1) - { - best_err1 = err1; - best_pbits[1] = p; - - bestMaxColor.m_c[0] = xMaxColor.m_c[0] >> 1; - bestMaxColor.m_c[1] = xMaxColor.m_c[1] >> 1; - bestMaxColor.m_c[2] = xMaxColor.m_c[2] >> 1; - bestMaxColor.m_c[3] = xMaxColor.m_c[3] >> 1; - } - } - } - } - else - { - if ((mode == 1) && (pComp_params->m_bias_mode1_pbits)) - { - float x = 0.0f; - for (uint32_t c = 0; c < 3; c++) - x = std::max(std::max(x, xl.m_c[c]), xh.m_c[c]); - - int p = 0; - if (x > (253.0f / 255.0f)) - p = 1; - - color_rgba xMinColor, xMaxColor; - for (uint32_t c = 0; c < 4; c++) - { - int vl = (int)(xl.m_c[c] * 63.0f); - vl += (xl.m_c[c] > g_mode1_rgba_midpoints[vl][p]); - xMinColor.m_c[c] = (uint8_t)clampi(vl * 2 + p, p, 127 - 1 + p); - - int vh = (int)(xh.m_c[c] * 63.0f); - vh += (xh.m_c[c] > g_mode1_rgba_midpoints[vh][p]); - xMaxColor.m_c[c] = (uint8_t)clampi(vh * 2 + p, p, 127 - 1 + p); - } - - best_pbits[0] = p; - best_pbits[1] = p; - for (uint32_t j = 0; j < 4; j++) - { - bestMinColor.m_c[j] = xMinColor.m_c[j] >> 1; - bestMaxColor.m_c[j] = xMaxColor.m_c[j] >> 1; - } - } - else - { - // Endpoints share pbits - float best_err = 1e+9; - - for (int p = 0; p < 2; p++) - { - color_rgba xMinColor, xMaxColor; - if (pParams->m_comp_bits == 6) - { - for (uint32_t c = 0; c < 4; c++) - { - int vl = (int)(xl.m_c[c] * 63.0f); - vl += (xl.m_c[c] > g_mode1_rgba_midpoints[vl][p]); - xMinColor.m_c[c] = (uint8_t)clampi(vl * 2 + p, p, 127 - 1 + p); - - int vh = (int)(xh.m_c[c] * 63.0f); - vh += (xh.m_c[c] > g_mode1_rgba_midpoints[vh][p]); - xMaxColor.m_c[c] = (uint8_t)clampi(vh * 2 + p, p, 127 - 1 + p); - } - } - else - { - for (uint32_t c = 0; c < 4; c++) - { - xMinColor.m_c[c] = (uint8_t)(clampi(((int)((xl.m_c[c] * scalep - p) / 2.0f + .5f)) * 2 + p, p, iscalep - 1 + p)); - xMaxColor.m_c[c] = (uint8_t)(clampi(((int)((xh.m_c[c] * scalep - p) / 2.0f + .5f)) * 2 + p, p, iscalep - 1 + p)); - } - } - - color_rgba scaledLow = scale_color(&xMinColor, pParams); - color_rgba scaledHigh = scale_color(&xMaxColor, pParams); - - float err = 0; - for (int i = 0; i < totalComps; i++) - err += squaref((scaledLow.m_c[i] / 255.0f) - xl.m_c[i]) + squaref((scaledHigh.m_c[i] / 255.0f) - xh.m_c[i]); - - if (p == 1) - err *= pComp_params->m_pbit1_weight; - - if (err < best_err) - { - best_err = err; - best_pbits[0] = p; - best_pbits[1] = p; - for (uint32_t j = 0; j < 4; j++) - { - bestMinColor.m_c[j] = xMinColor.m_c[j] >> 1; - bestMaxColor.m_c[j] = xMaxColor.m_c[j] >> 1; - } - } - } - } - } - - fixDegenerateEndpoints(mode, &bestMinColor, &bestMaxColor, &xl, &xh, iscalep >> 1, pComp_params); - - if ((pResults->m_best_overall_err == UINT64_MAX) || color_quad_u8_notequals(&bestMinColor, &pResults->m_low_endpoint) || color_quad_u8_notequals(&bestMaxColor, &pResults->m_high_endpoint) || (best_pbits[0] != pResults->m_pbits[0]) || (best_pbits[1] != pResults->m_pbits[1])) - evaluate_solution(&bestMinColor, &bestMaxColor, best_pbits, pParams, pResults, pComp_params); - } - else - { - const int iscale = (1 << pParams->m_comp_bits) - 1; - const float scale = (float)iscale; - - color_rgba trialMinColor, trialMaxColor; - if (pParams->m_comp_bits == 7) - { - for (uint32_t c = 0; c < 4; c++) - { - int vl = (int)(xl.m_c[c] * 127.0f); - vl += (xl.m_c[c] > g_mode5_rgba_midpoints[vl]); - trialMinColor.m_c[c] = (uint8_t)clampi(vl, 0, 127); - - int vh = (int)(xh.m_c[c] * 127.0f); - vh += (xh.m_c[c] > g_mode5_rgba_midpoints[vh]); - trialMaxColor.m_c[c] = (uint8_t)clampi(vh, 0, 127); - } - } - else - { - color_quad_u8_set_clamped(&trialMinColor, (int)(xl.m_c[0] * scale + .5f), (int)(xl.m_c[1] * scale + .5f), (int)(xl.m_c[2] * scale + .5f), (int)(xl.m_c[3] * scale + .5f)); - color_quad_u8_set_clamped(&trialMaxColor, (int)(xh.m_c[0] * scale + .5f), (int)(xh.m_c[1] * scale + .5f), (int)(xh.m_c[2] * scale + .5f), (int)(xh.m_c[3] * scale + .5f)); - } - - fixDegenerateEndpoints(mode, &trialMinColor, &trialMaxColor, &xl, &xh, iscale, pComp_params); - - if ((pResults->m_best_overall_err == UINT64_MAX) || color_quad_u8_notequals(&trialMinColor, &pResults->m_low_endpoint) || color_quad_u8_notequals(&trialMaxColor, &pResults->m_high_endpoint)) - evaluate_solution(&trialMinColor, &trialMaxColor, pResults->m_pbits, pParams, pResults, pComp_params); - } - - return pResults->m_best_overall_err; -} - -static uint64_t color_cell_compression(uint32_t mode, const color_cell_compressor_params *pParams, color_cell_compressor_results *pResults, const bc7enc_compress_block_params *pComp_params) -{ - assert((mode == 6) || (mode == 7) || (!pParams->m_has_alpha)); - - pResults->m_best_overall_err = UINT64_MAX; - - // If the partition's colors are all the same in mode 1, then just pack them as a single color. - if (mode == 1) - { - const uint32_t cr = pParams->m_pPixels[0].m_c[0], cg = pParams->m_pPixels[0].m_c[1], cb = pParams->m_pPixels[0].m_c[2]; - - bool allSame = true; - for (uint32_t i = 1; i < pParams->m_num_pixels; i++) - { - if ((cr != pParams->m_pPixels[i].m_c[0]) || (cg != pParams->m_pPixels[i].m_c[1]) || (cb != pParams->m_pPixels[i].m_c[2])) - { - allSame = false; - break; - } - } - - if (allSame) - return pack_mode1_to_one_color(pParams, pResults, cr, cg, cb, pResults->m_pSelectors); - } - else if (mode == 7) - { - const uint32_t cr = pParams->m_pPixels[0].m_c[0], cg = pParams->m_pPixels[0].m_c[1], cb = pParams->m_pPixels[0].m_c[2], ca = pParams->m_pPixels[0].m_c[3]; - - bool allSame = true; - for (uint32_t i = 1; i < pParams->m_num_pixels; i++) - { - if ((cr != pParams->m_pPixels[i].m_c[0]) || (cg != pParams->m_pPixels[i].m_c[1]) || (cb != pParams->m_pPixels[i].m_c[2]) || (ca != pParams->m_pPixels[i].m_c[3])) - { - allSame = false; - break; - } - } - - if (allSame) - return pack_mode7_to_one_color(pParams, pResults, cr, cg, cb, ca, pResults->m_pSelectors, pParams->m_num_pixels, pParams->m_pPixels); - } - - // Compute partition's mean color and principle axis. - vec4F meanColor, axis; - vec4F_set_scalar(&meanColor, 0.0f); - - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - { - vec4F color = vec4F_from_color(&pParams->m_pPixels[i]); - meanColor = vec4F_add(&meanColor, &color); - } - - vec4F meanColorScaled = vec4F_mul(&meanColor, 1.0f / (float)(pParams->m_num_pixels)); - - meanColor = vec4F_mul(&meanColor, 1.0f / (float)(pParams->m_num_pixels * 255.0f)); - vec4F_saturate_in_place(&meanColor); - - if (pParams->m_has_alpha) - { - // Use incremental PCA for RGBA PCA, because it's simple. - vec4F_set_scalar(&axis, 0.0f); - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - { - vec4F color = vec4F_from_color(&pParams->m_pPixels[i]); - color = vec4F_sub(&color, &meanColorScaled); - vec4F a = vec4F_mul(&color, color.m_c[0]); - vec4F b = vec4F_mul(&color, color.m_c[1]); - vec4F c = vec4F_mul(&color, color.m_c[2]); - vec4F d = vec4F_mul(&color, color.m_c[3]); - vec4F n = i ? axis : color; - vec4F_normalize_in_place(&n); - axis.m_c[0] += vec4F_dot(&a, &n); - axis.m_c[1] += vec4F_dot(&b, &n); - axis.m_c[2] += vec4F_dot(&c, &n); - axis.m_c[3] += vec4F_dot(&d, &n); - } - vec4F_normalize_in_place(&axis); - } - else - { - // Use covar technique for RGB PCA, because it doesn't require per-pixel normalization. - float cov[6] = { 0, 0, 0, 0, 0, 0 }; - - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - { - const color_rgba *pV = &pParams->m_pPixels[i]; - float r = pV->m_c[0] - meanColorScaled.m_c[0]; - float g = pV->m_c[1] - meanColorScaled.m_c[1]; - float b = pV->m_c[2] - meanColorScaled.m_c[2]; - cov[0] += r*r; cov[1] += r*g; cov[2] += r*b; cov[3] += g*g; cov[4] += g*b; cov[5] += b*b; - } - - float vfr = .9f, vfg = 1.0f, vfb = .7f; - for (uint32_t iter = 0; iter < 3; iter++) - { - float r = vfr*cov[0] + vfg*cov[1] + vfb*cov[2]; - float g = vfr*cov[1] + vfg*cov[3] + vfb*cov[4]; - float b = vfr*cov[2] + vfg*cov[4] + vfb*cov[5]; - - float m = maximumf(maximumf(fabsf(r), fabsf(g)), fabsf(b)); - if (m > 1e-10f) - { - m = 1.0f / m; - r *= m; g *= m; b *= m; - } - - vfr = r; vfg = g; vfb = b; - } - - float len = vfr*vfr + vfg*vfg + vfb*vfb; - if (len < 1e-10f) - vec4F_set_scalar(&axis, 0.0f); - else - { - len = 1.0f / sqrtf(len); - vfr *= len; vfg *= len; vfb *= len; - vec4F_set(&axis, vfr, vfg, vfb, 0); - } - } - - // TODO: Try picking the 2 colors with the largest projection onto the axis, instead of computing new colors along the axis. - - if (vec4F_dot(&axis, &axis) < .5f) - { - if (pParams->m_perceptual) - vec4F_set(&axis, .213f, .715f, .072f, pParams->m_has_alpha ? .715f : 0); - else - vec4F_set(&axis, 1.0f, 1.0f, 1.0f, pParams->m_has_alpha ? 1.0f : 0); - vec4F_normalize_in_place(&axis); - } - - float l = 1e+9f, h = -1e+9f; - - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - { - vec4F color = vec4F_from_color(&pParams->m_pPixels[i]); - - vec4F q = vec4F_sub(&color, &meanColorScaled); - float d = vec4F_dot(&q, &axis); - - l = minimumf(l, d); - h = maximumf(h, d); - } - - l *= (1.0f / 255.0f); - h *= (1.0f / 255.0f); - - vec4F b0 = vec4F_mul(&axis, l); - vec4F b1 = vec4F_mul(&axis, h); - vec4F c0 = vec4F_add(&meanColor, &b0); - vec4F c1 = vec4F_add(&meanColor, &b1); - vec4F minColor = vec4F_saturate(&c0); - vec4F maxColor = vec4F_saturate(&c1); - - vec4F whiteVec; - vec4F_set_scalar(&whiteVec, 1.0f); - - if (vec4F_dot(&minColor, &whiteVec) > vec4F_dot(&maxColor, &whiteVec)) - { -#if 0 - // Don't compile correctly with VC 2019 in release. - vec4F temp = minColor; - minColor = maxColor; - maxColor = temp; -#else - float a = minColor.m_c[0], b = minColor.m_c[1], c = minColor.m_c[2], d = minColor.m_c[3]; - minColor.m_c[0] = maxColor.m_c[0]; - minColor.m_c[1] = maxColor.m_c[1]; - minColor.m_c[2] = maxColor.m_c[2]; - minColor.m_c[3] = maxColor.m_c[3]; - maxColor.m_c[0] = a; - maxColor.m_c[1] = b; - maxColor.m_c[2] = c; - maxColor.m_c[3] = d; -#endif - } - - // First find a solution using the block's PCA. - if (!find_optimal_solution(mode, minColor, maxColor, pParams, pResults, pComp_params)) - return 0; - - if (pComp_params->m_try_least_squares) - { - // Now try to refine the solution using least squares by computing the optimal endpoints from the current selectors. - vec4F xl, xh; - vec4F_set_scalar(&xl, 0.0f); - vec4F_set_scalar(&xh, 0.0f); - if (pParams->m_has_alpha) - compute_least_squares_endpoints_rgba(pParams->m_num_pixels, pResults->m_pSelectors, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); - else - compute_least_squares_endpoints_rgb(pParams->m_num_pixels, pResults->m_pSelectors, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); - - xl = vec4F_mul(&xl, (1.0f / 255.0f)); - xh = vec4F_mul(&xh, (1.0f / 255.0f)); - - if (!find_optimal_solution(mode, xl, xh, pParams, pResults, pComp_params)) - return 0; - } - - if (pComp_params->m_uber_level > 0) - { - // In uber level 1, try varying the selectors a little, somewhat like cluster fit would. First try incrementing the minimum selectors, - // then try decrementing the selectrors, then try both. - uint8_t selectors_temp[16], selectors_temp1[16]; - memcpy(selectors_temp, pResults->m_pSelectors, pParams->m_num_pixels); - - const int max_selector = pParams->m_num_selector_weights - 1; - - uint32_t min_sel = 16; - uint32_t max_sel = 0; - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - { - uint32_t sel = selectors_temp[i]; - min_sel = minimumu(min_sel, sel); - max_sel = maximumu(max_sel, sel); - } - - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - { - uint32_t sel = selectors_temp[i]; - if ((sel == min_sel) && (sel < (pParams->m_num_selector_weights - 1))) - sel++; - selectors_temp1[i] = (uint8_t)sel; - } - - vec4F xl, xh; - vec4F_set_scalar(&xl, 0.0f); - vec4F_set_scalar(&xh, 0.0f); - if (pParams->m_has_alpha) - compute_least_squares_endpoints_rgba(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); - else - compute_least_squares_endpoints_rgb(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); - - xl = vec4F_mul(&xl, (1.0f / 255.0f)); - xh = vec4F_mul(&xh, (1.0f / 255.0f)); - - if (!find_optimal_solution(mode, xl, xh, pParams, pResults, pComp_params)) - return 0; - - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - { - uint32_t sel = selectors_temp[i]; - if ((sel == max_sel) && (sel > 0)) - sel--; - selectors_temp1[i] = (uint8_t)sel; - } - - if (pParams->m_has_alpha) - compute_least_squares_endpoints_rgba(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); - else - compute_least_squares_endpoints_rgb(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); - - xl = vec4F_mul(&xl, (1.0f / 255.0f)); - xh = vec4F_mul(&xh, (1.0f / 255.0f)); - - if (!find_optimal_solution(mode, xl, xh, pParams, pResults, pComp_params)) - return 0; - - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - { - uint32_t sel = selectors_temp[i]; - if ((sel == min_sel) && (sel < (pParams->m_num_selector_weights - 1))) - sel++; - else if ((sel == max_sel) && (sel > 0)) - sel--; - selectors_temp1[i] = (uint8_t)sel; - } - - if (pParams->m_has_alpha) - compute_least_squares_endpoints_rgba(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); - else - compute_least_squares_endpoints_rgb(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); - - xl = vec4F_mul(&xl, (1.0f / 255.0f)); - xh = vec4F_mul(&xh, (1.0f / 255.0f)); - - if (!find_optimal_solution(mode, xl, xh, pParams, pResults, pComp_params)) - return 0; - - // In uber levels 2+, try taking more advantage of endpoint extrapolation by scaling the selectors in one direction or another. - const uint32_t uber_err_thresh = (pParams->m_num_pixels * 56) >> 4; - if ((pComp_params->m_uber_level >= 2) && (pResults->m_best_overall_err > uber_err_thresh)) - { - const int Q = (pComp_params->m_uber_level >= 4) ? (pComp_params->m_uber_level - 2) : 1; - for (int ly = -Q; ly <= 1; ly++) - { - for (int hy = max_selector - 1; hy <= (max_selector + Q); hy++) - { - if ((ly == 0) && (hy == max_selector)) - continue; - - for (uint32_t i = 0; i < pParams->m_num_pixels; i++) - selectors_temp1[i] = (uint8_t)clampf(floorf((float)max_selector * ((float)selectors_temp[i] - (float)ly) / ((float)hy - (float)ly) + .5f), 0, (float)max_selector); - - //vec4F xl, xh; - vec4F_set_scalar(&xl, 0.0f); - vec4F_set_scalar(&xh, 0.0f); - if (pParams->m_has_alpha) - compute_least_squares_endpoints_rgba(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); - else - compute_least_squares_endpoints_rgb(pParams->m_num_pixels, selectors_temp1, pParams->m_pSelector_weightsx, &xl, &xh, pParams->m_pPixels); - - xl = vec4F_mul(&xl, (1.0f / 255.0f)); - xh = vec4F_mul(&xh, (1.0f / 255.0f)); - - if (!find_optimal_solution(mode, xl, xh, pParams, pResults, pComp_params)) - return 0; - } - } - } - } - - if (mode == 1) - { - // Try encoding the partition as a single color by using the optimal singe colors tables to encode the block to its mean. - color_cell_compressor_results avg_results = *pResults; - const uint32_t r = (int)(.5f + meanColor.m_c[0] * 255.0f), g = (int)(.5f + meanColor.m_c[1] * 255.0f), b = (int)(.5f + meanColor.m_c[2] * 255.0f); - uint64_t avg_err = pack_mode1_to_one_color(pParams, &avg_results, r, g, b, pResults->m_pSelectors_temp); - if (avg_err < pResults->m_best_overall_err) - { - *pResults = avg_results; - memcpy(pResults->m_pSelectors, pResults->m_pSelectors_temp, sizeof(pResults->m_pSelectors[0]) * pParams->m_num_pixels); - pResults->m_best_overall_err = avg_err; - } - } - else if (mode == 7) - { - // Try encoding the partition as a single color by using the optimal singe colors tables to encode the block to its mean. - color_cell_compressor_results avg_results = *pResults; - const uint32_t r = (int)(.5f + meanColor.m_c[0] * 255.0f), g = (int)(.5f + meanColor.m_c[1] * 255.0f), b = (int)(.5f + meanColor.m_c[2] * 255.0f), a = (int)(.5f + meanColor.m_c[3] * 255.0f); - uint64_t avg_err = pack_mode7_to_one_color(pParams, &avg_results, r, g, b, a, pResults->m_pSelectors_temp, pParams->m_num_pixels, pParams->m_pPixels); - if (avg_err < pResults->m_best_overall_err) - { - *pResults = avg_results; - memcpy(pResults->m_pSelectors, pResults->m_pSelectors_temp, sizeof(pResults->m_pSelectors[0]) * pParams->m_num_pixels); - pResults->m_best_overall_err = avg_err; - } - } - - return pResults->m_best_overall_err; -} - -static uint64_t color_cell_compression_est_mode1(uint32_t num_pixels, const color_rgba *pPixels, bool perceptual, uint32_t pweights[4], uint64_t best_err_so_far) -{ - // Find RGB bounds as an approximation of the block's principle axis - uint32_t lr = 255, lg = 255, lb = 255; - uint32_t hr = 0, hg = 0, hb = 0; - for (uint32_t i = 0; i < num_pixels; i++) - { - const color_rgba *pC = &pPixels[i]; - if (pC->m_c[0] < lr) lr = pC->m_c[0]; - if (pC->m_c[1] < lg) lg = pC->m_c[1]; - if (pC->m_c[2] < lb) lb = pC->m_c[2]; - if (pC->m_c[0] > hr) hr = pC->m_c[0]; - if (pC->m_c[1] > hg) hg = pC->m_c[1]; - if (pC->m_c[2] > hb) hb = pC->m_c[2]; - } - - color_rgba lowColor; color_quad_u8_set(&lowColor, lr, lg, lb, 0); - color_rgba highColor; color_quad_u8_set(&highColor, hr, hg, hb, 0); - - // Place endpoints at bbox diagonals and compute interpolated colors - const uint32_t N = 8; - color_rgba weightedColors[8]; - - weightedColors[0] = lowColor; - weightedColors[N - 1] = highColor; - for (uint32_t i = 1; i < (N - 1); i++) - { - weightedColors[i].m_c[0] = (uint8_t)((lowColor.m_c[0] * (64 - g_bc7_weights3[i]) + highColor.m_c[0] * g_bc7_weights3[i] + 32) >> 6); - weightedColors[i].m_c[1] = (uint8_t)((lowColor.m_c[1] * (64 - g_bc7_weights3[i]) + highColor.m_c[1] * g_bc7_weights3[i] + 32) >> 6); - weightedColors[i].m_c[2] = (uint8_t)((lowColor.m_c[2] * (64 - g_bc7_weights3[i]) + highColor.m_c[2] * g_bc7_weights3[i] + 32) >> 6); - } - - // Compute dots and thresholds - const int ar = highColor.m_c[0] - lowColor.m_c[0]; - const int ag = highColor.m_c[1] - lowColor.m_c[1]; - const int ab = highColor.m_c[2] - lowColor.m_c[2]; - - int dots[8]; - for (uint32_t i = 0; i < N; i++) - dots[i] = weightedColors[i].m_c[0] * ar + weightedColors[i].m_c[1] * ag + weightedColors[i].m_c[2] * ab; - - int thresh[8 - 1]; - for (uint32_t i = 0; i < (N - 1); i++) - thresh[i] = (dots[i] + dots[i + 1] + 1) >> 1; - - uint64_t total_err = 0; - if (perceptual) - { - // Transform block's interpolated colors to YCbCr - int l1[8], cr1[8], cb1[8]; - for (int j = 0; j < 8; j++) - { - const color_rgba *pE1 = &weightedColors[j]; - l1[j] = pE1->m_c[0] * 109 + pE1->m_c[1] * 366 + pE1->m_c[2] * 37; - cr1[j] = ((int)pE1->m_c[0] << 9) - l1[j]; - cb1[j] = ((int)pE1->m_c[2] << 9) - l1[j]; - } - - for (uint32_t i = 0; i < num_pixels; i++) - { - const color_rgba *pC = &pPixels[i]; - - int d = ar * pC->m_c[0] + ag * pC->m_c[1] + ab * pC->m_c[2]; - - // Find approximate selector - uint32_t s = 0; - if (d >= thresh[6]) - s = 7; - else if (d >= thresh[5]) - s = 6; - else if (d >= thresh[4]) - s = 5; - else if (d >= thresh[3]) - s = 4; - else if (d >= thresh[2]) - s = 3; - else if (d >= thresh[1]) - s = 2; - else if (d >= thresh[0]) - s = 1; - - // Compute error - const int l2 = pC->m_c[0] * 109 + pC->m_c[1] * 366 + pC->m_c[2] * 37; - const int cr2 = ((int)pC->m_c[0] << 9) - l2; - const int cb2 = ((int)pC->m_c[2] << 9) - l2; - - const int dl = (l1[s] - l2) >> 8; - const int dcr = (cr1[s] - cr2) >> 8; - const int dcb = (cb1[s] - cb2) >> 8; - - int ie = (pweights[0] * dl * dl) + (pweights[1] * dcr * dcr) + (pweights[2] * dcb * dcb); - - total_err += ie; - if (total_err > best_err_so_far) - break; - } - } - else - { - for (uint32_t i = 0; i < num_pixels; i++) - { - const color_rgba *pC = &pPixels[i]; - - int d = ar * pC->m_c[0] + ag * pC->m_c[1] + ab * pC->m_c[2]; - - // Find approximate selector - uint32_t s = 0; - if (d >= thresh[6]) - s = 7; - else if (d >= thresh[5]) - s = 6; - else if (d >= thresh[4]) - s = 5; - else if (d >= thresh[3]) - s = 4; - else if (d >= thresh[2]) - s = 3; - else if (d >= thresh[1]) - s = 2; - else if (d >= thresh[0]) - s = 1; - - // Compute error - const color_rgba *pE1 = &weightedColors[s]; - - int dr = (int)pE1->m_c[0] - (int)pC->m_c[0]; - int dg = (int)pE1->m_c[1] - (int)pC->m_c[1]; - int db = (int)pE1->m_c[2] - (int)pC->m_c[2]; - - total_err += pweights[0] * (dr * dr) + pweights[1] * (dg * dg) + pweights[2] * (db * db); - if (total_err > best_err_so_far) - break; - } - } - - return total_err; -} - -static uint64_t color_cell_compression_est_mode7(uint32_t num_pixels, const color_rgba * pPixels, bool perceptual, uint32_t pweights[4], uint64_t best_err_so_far) -{ - // Find RGB bounds as an approximation of the block's principle axis - uint32_t lr = 255, lg = 255, lb = 255, la = 255; - uint32_t hr = 0, hg = 0, hb = 0, ha = 0; - for (uint32_t i = 0; i < num_pixels; i++) - { - const color_rgba* pC = &pPixels[i]; - if (pC->m_c[0] < lr) lr = pC->m_c[0]; - if (pC->m_c[1] < lg) lg = pC->m_c[1]; - if (pC->m_c[2] < lb) lb = pC->m_c[2]; - if (pC->m_c[3] < la) la = pC->m_c[3]; - - if (pC->m_c[0] > hr) hr = pC->m_c[0]; - if (pC->m_c[1] > hg) hg = pC->m_c[1]; - if (pC->m_c[2] > hb) hb = pC->m_c[2]; - if (pC->m_c[3] > ha) ha = pC->m_c[3]; - } - - color_rgba lowColor; color_quad_u8_set(&lowColor, lr, lg, lb, la); - color_rgba highColor; color_quad_u8_set(&highColor, hr, hg, hb, ha); - - // Place endpoints at bbox diagonals and compute interpolated colors - const uint32_t N = 4; - color_rgba weightedColors[4]; - - weightedColors[0] = lowColor; - weightedColors[N - 1] = highColor; - for (uint32_t i = 1; i < (N - 1); i++) - { - weightedColors[i].m_c[0] = (uint8_t)((lowColor.m_c[0] * (64 - g_bc7_weights2[i]) + highColor.m_c[0] * g_bc7_weights2[i] + 32) >> 6); - weightedColors[i].m_c[1] = (uint8_t)((lowColor.m_c[1] * (64 - g_bc7_weights2[i]) + highColor.m_c[1] * g_bc7_weights2[i] + 32) >> 6); - weightedColors[i].m_c[2] = (uint8_t)((lowColor.m_c[2] * (64 - g_bc7_weights2[i]) + highColor.m_c[2] * g_bc7_weights2[i] + 32) >> 6); - weightedColors[i].m_c[3] = (uint8_t)((lowColor.m_c[3] * (64 - g_bc7_weights2[i]) + highColor.m_c[3] * g_bc7_weights2[i] + 32) >> 6); - } - - // Compute dots and thresholds - const int ar = highColor.m_c[0] - lowColor.m_c[0]; - const int ag = highColor.m_c[1] - lowColor.m_c[1]; - const int ab = highColor.m_c[2] - lowColor.m_c[2]; - const int aa = highColor.m_c[3] - lowColor.m_c[3]; - - int dots[4]; - for (uint32_t i = 0; i < N; i++) - dots[i] = weightedColors[i].m_c[0] * ar + weightedColors[i].m_c[1] * ag + weightedColors[i].m_c[2] * ab + weightedColors[i].m_c[3] * aa; - - int thresh[4 - 1]; - for (uint32_t i = 0; i < (N - 1); i++) - thresh[i] = (dots[i] + dots[i + 1] + 1) >> 1; - - uint64_t total_err = 0; - if (perceptual) - { - // Transform block's interpolated colors to YCbCr - int l1[4], cr1[4], cb1[4]; - for (int j = 0; j < 4; j++) - { - const color_rgba* pE1 = &weightedColors[j]; - l1[j] = pE1->m_c[0] * 109 + pE1->m_c[1] * 366 + pE1->m_c[2] * 37; - cr1[j] = ((int)pE1->m_c[0] << 9) - l1[j]; - cb1[j] = ((int)pE1->m_c[2] << 9) - l1[j]; - } - - for (uint32_t i = 0; i < num_pixels; i++) - { - const color_rgba* pC = &pPixels[i]; - - int d = ar * pC->m_c[0] + ag * pC->m_c[1] + ab * pC->m_c[2] + aa * pC->m_c[3]; - - // Find approximate selector - uint32_t s = 0; - if (d >= thresh[2]) - s = 3; - else if (d >= thresh[1]) - s = 2; - else if (d >= thresh[0]) - s = 1; - - // Compute error - const int l2 = pC->m_c[0] * 109 + pC->m_c[1] * 366 + pC->m_c[2] * 37; - const int cr2 = ((int)pC->m_c[0] << 9) - l2; - const int cb2 = ((int)pC->m_c[2] << 9) - l2; - - const int dl = (l1[s] - l2) >> 8; - const int dcr = (cr1[s] - cr2) >> 8; - const int dcb = (cb1[s] - cb2) >> 8; - - const int dca = (int)pC->m_c[3] - (int)weightedColors[s].m_c[3]; - - int ie = (pweights[0] * dl * dl) + (pweights[1] * dcr * dcr) + (pweights[2] * dcb * dcb) + (pweights[3] * dca * dca); - - total_err += ie; - if (total_err > best_err_so_far) - break; - } - } - else - { - for (uint32_t i = 0; i < num_pixels; i++) - { - const color_rgba* pC = &pPixels[i]; - - int d = ar * pC->m_c[0] + ag * pC->m_c[1] + ab * pC->m_c[2] + aa * pC->m_c[3]; - - // Find approximate selector - uint32_t s = 0; - if (d >= thresh[2]) - s = 3; - else if (d >= thresh[1]) - s = 2; - else if (d >= thresh[0]) - s = 1; - - // Compute error - const color_rgba* pE1 = &weightedColors[s]; - - int dr = (int)pE1->m_c[0] - (int)pC->m_c[0]; - int dg = (int)pE1->m_c[1] - (int)pC->m_c[1]; - int db = (int)pE1->m_c[2] - (int)pC->m_c[2]; - int da = (int)pE1->m_c[3] - (int)pC->m_c[3]; - - total_err += pweights[0] * (dr * dr) + pweights[1] * (dg * dg) + pweights[2] * (db * db) + pweights[3] * (da * da); - if (total_err > best_err_so_far) - break; - } - } - - return total_err; -} - -// This table contains bitmasks indicating which "key" partitions must be best ranked before this partition is worth evaluating. -// We first rank the best/most used 14 partitions (sorted by usefulness), record the best one found as the key partition, then use -// that to control the other partitions to evaluate. The quality loss is ~.08 dB RGB PSNR, the perf gain is up to ~11% (at uber level 0). -static const uint32_t g_partition_predictors[35] = -{ - UINT32_MAX, - UINT32_MAX, - UINT32_MAX, - UINT32_MAX, - UINT32_MAX, - (1 << 1) | (1 << 2) | (1 << 8), - (1 << 1) | (1 << 3) | (1 << 7), - UINT32_MAX, - UINT32_MAX, - (1 << 2) | (1 << 8) | (1 << 16), - (1 << 7) | (1 << 3) | (1 << 15), - UINT32_MAX, - (1 << 8) | (1 << 14) | (1 << 16), - (1 << 7) | (1 << 14) | (1 << 15), - UINT32_MAX, - UINT32_MAX, - UINT32_MAX, - UINT32_MAX, - (1 << 14) | (1 << 15), - (1 << 16) | (1 << 22) | (1 << 14), - (1 << 17) | (1 << 24) | (1 << 14), - (1 << 2) | (1 << 14) | (1 << 15) | (1 << 1), - UINT32_MAX, - (1 << 1) | (1 << 3) | (1 << 14) | (1 << 16) | (1 << 22), - UINT32_MAX, - (1 << 1) | (1 << 2) | (1 << 15) | (1 << 17) | (1 << 24), - (1 << 1) | (1 << 3) | (1 << 22), - UINT32_MAX, - UINT32_MAX, - UINT32_MAX, - (1 << 14) | (1 << 15) | (1 << 16) | (1 << 17), - UINT32_MAX, - UINT32_MAX, - (1 << 1) | (1 << 2) | (1 << 3) | (1 << 27) | (1 << 4) | (1 << 24), - (1 << 14) | (1 << 15) | (1 << 16) | (1 << 11) | (1 << 17) | (1 << 27) -}; - -// Estimate the partition used by modes 1/7. This scans through each partition and computes an approximate error for each. -static uint32_t estimate_partition(const color_rgba *pPixels, const bc7enc_compress_block_params *pComp_params, uint32_t pweights[4], uint32_t mode) -{ - const uint32_t total_partitions = minimumu(pComp_params->m_max_partitions, BC7ENC_MAX_PARTITIONS); - if (total_partitions <= 1) - return 0; - - uint64_t best_err = UINT64_MAX; - uint32_t best_partition = 0; - - // Partition order sorted by usage frequency across a large test corpus. Pattern 34 (checkerboard) must appear in slot 34. - // Using a sorted order allows the user to decrease the # of partitions to scan with minimal loss in quality. - static const uint8_t s_sorted_partition_order[64] = - { - 1 - 1, 14 - 1, 2 - 1, 3 - 1, 16 - 1, 15 - 1, 11 - 1, 17 - 1, - 4 - 1, 24 - 1, 27 - 1, 7 - 1, 8 - 1, 22 - 1, 20 - 1, 30 - 1, - 9 - 1, 5 - 1, 10 - 1, 21 - 1, 6 - 1, 32 - 1, 23 - 1, 18 - 1, - 19 - 1, 12 - 1, 13 - 1, 31 - 1, 25 - 1, 26 - 1, 29 - 1, 28 - 1, - 33 - 1, 34 - 1, 35 - 1, 46 - 1, 47 - 1, 52 - 1, 50 - 1, 51 - 1, - 49 - 1, 39 - 1, 40 - 1, 38 - 1, 54 - 1, 53 - 1, 55 - 1, 37 - 1, - 58 - 1, 59 - 1, 56 - 1, 42 - 1, 41 - 1, 43 - 1, 44 - 1, 60 - 1, - 45 - 1, 57 - 1, 48 - 1, 36 - 1, 61 - 1, 64 - 1, 63 - 1, 62 - 1 - }; - - assert(s_sorted_partition_order[34] == 34); - - int best_key_partition = 0; - - for (uint32_t partition_iter = 0; (partition_iter < total_partitions) && (best_err > 0); partition_iter++) - { - const uint32_t partition = s_sorted_partition_order[partition_iter]; - - // Check to see if we should bother evaluating this partition at all, depending on the best partition found from the first 14. - if (pComp_params->m_mode17_partition_estimation_filterbank) - { - if ((partition_iter >= 14) && (partition_iter <= 34)) - { - const uint32_t best_key_partition_bitmask = 1 << (best_key_partition + 1); - if ((g_partition_predictors[partition] & best_key_partition_bitmask) == 0) - { - if (partition_iter == 34) - break; - - continue; - } - } - } - - const uint8_t *pPartition = &g_bc7_partition2[partition * 16]; - - color_rgba subset_colors[2][16]; - uint32_t subset_total_colors[2] = { 0, 0 }; - for (uint32_t index = 0; index < 16; index++) - subset_colors[pPartition[index]][subset_total_colors[pPartition[index]]++] = pPixels[index]; - - uint64_t total_subset_err = 0; - for (uint32_t subset = 0; (subset < 2) && (total_subset_err < best_err); subset++) - { - if (mode == 7) - total_subset_err += color_cell_compression_est_mode7(subset_total_colors[subset], &subset_colors[subset][0], pComp_params->m_perceptual, pweights, best_err); - else - total_subset_err += color_cell_compression_est_mode1(subset_total_colors[subset], &subset_colors[subset][0], pComp_params->m_perceptual, pweights, best_err); - } - - if (partition < 16) - { - total_subset_err = (uint64_t)((double)total_subset_err * pComp_params->m_low_frequency_partition_weight + .5f); - } - - if (total_subset_err < best_err) - { - best_err = total_subset_err; - best_partition = partition; - } - - // If the checkerboard pattern doesn't get the highest ranking vs. the previous (lower frequency) patterns, then just stop now because statistically the subsequent patterns won't do well either. - if ((partition == 34) && (best_partition != 34)) - break; - - if (partition_iter == 13) - best_key_partition = best_partition; - - } // partition - - return best_partition; -} - -static void set_block_bits(uint8_t *pBytes, uint32_t val, uint32_t num_bits, uint32_t *pCur_ofs) -{ - assert((num_bits <= 32) && (val < (1ULL << num_bits))); - while (num_bits) - { - const uint32_t n = minimumu(8 - (*pCur_ofs & 7), num_bits); - pBytes[*pCur_ofs >> 3] |= (uint8_t)(val << (*pCur_ofs & 7)); - val >>= n; - num_bits -= n; - *pCur_ofs += n; - } - assert(*pCur_ofs <= 128); -} - -struct bc7_optimization_results -{ - uint32_t m_mode; - uint32_t m_partition; - uint8_t m_selectors[16]; - uint8_t m_alpha_selectors[16]; - color_rgba m_low[3]; - color_rgba m_high[3]; - uint32_t m_pbits[3][2]; - uint32_t m_rotation; - uint32_t m_index_selector; -}; - -void encode_bc7_block(void* pBlock, const bc7_optimization_results* pResults) -{ - assert(pResults->m_index_selector <= 1); - assert(pResults->m_rotation <= 3); - - const uint32_t best_mode = pResults->m_mode; - - const uint32_t total_subsets = g_bc7_num_subsets[best_mode]; - const uint32_t total_partitions = 1 << g_bc7_partition_bits[best_mode]; - //const uint32_t num_rotations = 1 << g_bc7_rotation_bits[best_mode]; - //const uint32_t num_index_selectors = (best_mode == 4) ? 2 : 1; - - const uint8_t* pPartition; - if (total_subsets == 1) - pPartition = &g_bc7_partition1[0]; - else if (total_subsets == 2) - pPartition = &g_bc7_partition2[pResults->m_partition * 16]; - else - pPartition = &g_bc7_partition3[pResults->m_partition * 16]; - - uint8_t color_selectors[16]; - memcpy(color_selectors, pResults->m_selectors, 16); - - uint8_t alpha_selectors[16]; - memcpy(alpha_selectors, pResults->m_alpha_selectors, 16); - - color_rgba low[3], high[3]; - memcpy(low, pResults->m_low, sizeof(low)); - memcpy(high, pResults->m_high, sizeof(high)); - - uint32_t pbits[3][2]; - memcpy(pbits, pResults->m_pbits, sizeof(pbits)); - - int anchor[3] = { -1, -1, -1 }; - - for (uint32_t k = 0; k < total_subsets; k++) - { - uint32_t anchor_index = 0; - if (k) - { - if ((total_subsets == 3) && (k == 1)) - anchor_index = g_bc7_table_anchor_index_third_subset_1[pResults->m_partition]; - else if ((total_subsets == 3) && (k == 2)) - anchor_index = g_bc7_table_anchor_index_third_subset_2[pResults->m_partition]; - else - anchor_index = g_bc7_table_anchor_index_second_subset[pResults->m_partition]; - } - - anchor[k] = anchor_index; - - const uint32_t color_index_bits = get_bc7_color_index_size(best_mode, pResults->m_index_selector); - const uint32_t num_color_indices = 1 << color_index_bits; - - if (color_selectors[anchor_index] & (num_color_indices >> 1)) - { - for (uint32_t i = 0; i < 16; i++) - if (pPartition[i] == k) - color_selectors[i] = (uint8_t)((num_color_indices - 1) - color_selectors[i]); - - if (get_bc7_mode_has_seperate_alpha_selectors(best_mode)) - { - for (uint32_t q = 0; q < 3; q++) - { - uint8_t t = low[k].m_c[q]; - low[k].m_c[q] = high[k].m_c[q]; - high[k].m_c[q] = t; - } - } - else - { - color_rgba tmp = low[k]; - low[k] = high[k]; - high[k] = tmp; - } - - if (!g_bc7_mode_has_shared_p_bits[best_mode]) - { - uint32_t t = pbits[k][0]; - pbits[k][0] = pbits[k][1]; - pbits[k][1] = t; - } - } - - if (get_bc7_mode_has_seperate_alpha_selectors(best_mode)) - { - const uint32_t alpha_index_bits = get_bc7_alpha_index_size(best_mode, pResults->m_index_selector); - const uint32_t num_alpha_indices = 1 << alpha_index_bits; - - if (alpha_selectors[anchor_index] & (num_alpha_indices >> 1)) - { - for (uint32_t i = 0; i < 16; i++) - if (pPartition[i] == k) - alpha_selectors[i] = (uint8_t)((num_alpha_indices - 1) - alpha_selectors[i]); - - uint8_t t = low[k].m_c[3]; - low[k].m_c[3] = high[k].m_c[3]; - high[k].m_c[3] = t; - } - } - } - - uint8_t* pBlock_bytes = (uint8_t*)(pBlock); - memset(pBlock_bytes, 0, BC7ENC_BLOCK_SIZE); - - uint32_t cur_bit_ofs = 0; - set_block_bits(pBlock_bytes, 1 << best_mode, best_mode + 1, &cur_bit_ofs); - - if ((best_mode == 4) || (best_mode == 5)) - set_block_bits(pBlock_bytes, pResults->m_rotation, 2, &cur_bit_ofs); - - if (best_mode == 4) - set_block_bits(pBlock_bytes, pResults->m_index_selector, 1, &cur_bit_ofs); - - if (total_partitions > 1) - set_block_bits(pBlock_bytes, pResults->m_partition, (total_partitions == 64) ? 6 : 4, &cur_bit_ofs); - - const uint32_t total_comps = (best_mode >= 4) ? 4 : 3; - for (uint32_t comp = 0; comp < total_comps; comp++) - { - for (uint32_t subset = 0; subset < total_subsets; subset++) - { - set_block_bits(pBlock_bytes, low[subset].m_c[comp], (comp == 3) ? g_bc7_alpha_precision_table[best_mode] : g_bc7_color_precision_table[best_mode], &cur_bit_ofs); - set_block_bits(pBlock_bytes, high[subset].m_c[comp], (comp == 3) ? g_bc7_alpha_precision_table[best_mode] : g_bc7_color_precision_table[best_mode], &cur_bit_ofs); - } - } - - if (g_bc7_mode_has_p_bits[best_mode]) - { - for (uint32_t subset = 0; subset < total_subsets; subset++) - { - set_block_bits(pBlock_bytes, pbits[subset][0], 1, &cur_bit_ofs); - if (!g_bc7_mode_has_shared_p_bits[best_mode]) - set_block_bits(pBlock_bytes, pbits[subset][1], 1, &cur_bit_ofs); - } - } - - for (uint32_t y = 0; y < 4; y++) - { - for (uint32_t x = 0; x < 4; x++) - { - int idx = x + y * 4; - - uint32_t n = pResults->m_index_selector ? get_bc7_alpha_index_size(best_mode, pResults->m_index_selector) : get_bc7_color_index_size(best_mode, pResults->m_index_selector); - - if ((idx == anchor[0]) || (idx == anchor[1]) || (idx == anchor[2])) - n--; - - set_block_bits(pBlock_bytes, pResults->m_index_selector ? alpha_selectors[idx] : color_selectors[idx], n, &cur_bit_ofs); - } - } - - if (get_bc7_mode_has_seperate_alpha_selectors(best_mode)) - { - for (uint32_t y = 0; y < 4; y++) - { - for (uint32_t x = 0; x < 4; x++) - { - int idx = x + y * 4; - - uint32_t n = pResults->m_index_selector ? get_bc7_color_index_size(best_mode, pResults->m_index_selector) : get_bc7_alpha_index_size(best_mode, pResults->m_index_selector); - - if ((idx == anchor[0]) || (idx == anchor[1]) || (idx == anchor[2])) - n--; - - set_block_bits(pBlock_bytes, pResults->m_index_selector ? color_selectors[idx] : alpha_selectors[idx], n, &cur_bit_ofs); - } - } - } - - assert(cur_bit_ofs == 128); -} - -static void handle_alpha_block_mode5(const color_rgba* pPixels, const bc7enc_compress_block_params* pComp_params, color_cell_compressor_params* pParams, uint32_t lo_a, uint32_t hi_a, bc7_optimization_results* pOpt_results5, uint64_t* pMode5_err, uint64_t* pMode5_alpha_err) -{ - pParams->m_pSelector_weights = g_bc7_weights2; - pParams->m_pSelector_weightsx = (const vec4F*)g_bc7_weights2x; - pParams->m_num_selector_weights = 4; - - pParams->m_comp_bits = 7; - pParams->m_has_pbits = false; - pParams->m_endpoints_share_pbit = false; - pParams->m_has_alpha = false; - - pParams->m_perceptual = pComp_params->m_perceptual; - - pParams->m_num_pixels = 16; - pParams->m_pPixels = pPixels; - - color_cell_compressor_results results5; - results5.m_pSelectors = pOpt_results5->m_selectors; - - uint8_t selectors_temp[16]; - results5.m_pSelectors_temp = selectors_temp; - - *pMode5_err = color_cell_compression(5, pParams, &results5, pComp_params); - assert(*pMode5_err == results5.m_best_overall_err); - - pOpt_results5->m_low[0] = results5.m_low_endpoint; - pOpt_results5->m_high[0] = results5.m_high_endpoint; - - if (lo_a == hi_a) - { - *pMode5_alpha_err = 0; - pOpt_results5->m_low[0].m_c[3] = (uint8_t)lo_a; - pOpt_results5->m_high[0].m_c[3] = (uint8_t)hi_a; - memset(pOpt_results5->m_alpha_selectors, 0, sizeof(pOpt_results5->m_alpha_selectors)); - } - else - { - *pMode5_alpha_err = UINT64_MAX; - - const uint32_t total_passes = (pComp_params->m_uber_level >= 1) ? 3 : 2; - for (uint32_t pass = 0; pass < total_passes; pass++) - { - int32_t vals[4]; - vals[0] = lo_a; - vals[3] = hi_a; - - const int32_t w_s1 = 21, w_s2 = 43; - vals[1] = (vals[0] * (64 - w_s1) + vals[3] * w_s1 + 32) >> 6; - vals[2] = (vals[0] * (64 - w_s2) + vals[3] * w_s2 + 32) >> 6; - - uint8_t trial_alpha_selectors[16]; - - uint64_t trial_alpha_err = 0; - for (uint32_t i = 0; i < 16; i++) - { - const int32_t a = pParams->m_pPixels[i].m_c[3]; - - int s = 0; - int32_t be = iabs32(a - vals[0]); - int e = iabs32(a - vals[1]); if (e < be) { be = e; s = 1; } - e = iabs32(a - vals[2]); if (e < be) { be = e; s = 2; } - e = iabs32(a - vals[3]); if (e < be) { be = e; s = 3; } - - trial_alpha_selectors[i] = (uint8_t)s; - - uint32_t a_err = (uint32_t)(be * be) * pParams->m_weights[3]; - - trial_alpha_err += a_err; - } - - if (trial_alpha_err < *pMode5_alpha_err) - { - *pMode5_alpha_err = trial_alpha_err; - pOpt_results5->m_low[0].m_c[3] = (uint8_t)lo_a; - pOpt_results5->m_high[0].m_c[3] = (uint8_t)hi_a; - memcpy(pOpt_results5->m_alpha_selectors, trial_alpha_selectors, sizeof(pOpt_results5->m_alpha_selectors)); - } - - if (pass != (total_passes - 1U)) - { - float xl, xh; - compute_least_squares_endpoints_a(16, trial_alpha_selectors, (const vec4F*)g_bc7_weights2x, &xl, &xh, pParams->m_pPixels); - - uint32_t new_lo_a = clampi((int)floor(xl + .5f), 0, 255); - uint32_t new_hi_a = clampi((int)floor(xh + .5f), 0, 255); - if (new_lo_a > new_hi_a) - swapu(&new_lo_a, &new_hi_a); - - if ((new_lo_a == lo_a) && (new_hi_a == hi_a)) - break; - - lo_a = new_lo_a; - hi_a = new_hi_a; - } - } - - *pMode5_err += *pMode5_alpha_err; - } -} - -static void handle_alpha_block(void *pBlock, const color_rgba *pPixels, const bc7enc_compress_block_params *pComp_params, color_cell_compressor_params *pParams) -{ - assert((pComp_params->m_mode_mask & (1 << 6)) || (pComp_params->m_mode_mask & (1 << 5)) || (pComp_params->m_mode_mask & (1 << 7))); - - pParams->m_pSelector_weights = g_bc7_weights4; - pParams->m_pSelector_weightsx = (const vec4F *)g_bc7_weights4x; - pParams->m_num_selector_weights = 16; - pParams->m_comp_bits = 7; - pParams->m_has_pbits = true; - pParams->m_endpoints_share_pbit = false; - pParams->m_has_alpha = true; - pParams->m_perceptual = pComp_params->m_perceptual; - pParams->m_num_pixels = 16; - pParams->m_pPixels = pPixels; - - bc7_optimization_results opt_results6, opt_results5, opt_results7; - color_cell_compressor_results results6; - memset(&results6, 0, sizeof(results6)); - - uint64_t best_err = UINT64_MAX; - uint32_t best_mode = 0; - uint8_t selectors_temp[16]; - - if (pComp_params->m_mode_mask & (1 << 6)) - { - results6.m_pSelectors = opt_results6.m_selectors; - results6.m_pSelectors_temp = selectors_temp; - - best_err = (uint64_t)(color_cell_compression(6, pParams, &results6, pComp_params) * pComp_params->m_mode6_error_weight + .5f); - best_mode = 6; - } - - if ((best_err > 0) && (pComp_params->m_mode_mask & (1 << 5))) - { - uint32_t lo_a = 255, hi_a = 0; - for (uint32_t i = 0; i < 16; i++) - { - uint32_t a = pPixels[i].m_c[3]; - lo_a = minimumu(lo_a, a); - hi_a = maximumu(hi_a, a); - } - - uint64_t mode5_err, mode5_alpha_err; - handle_alpha_block_mode5(pPixels, pComp_params, pParams, lo_a, hi_a, &opt_results5, &mode5_err, &mode5_alpha_err); - - mode5_err = (uint64_t)(mode5_err * pComp_params->m_mode5_error_weight + .5f); - - if (mode5_err < best_err) - { - best_err = mode5_err; - best_mode = 5; - } - } - - if ((best_err > 0) && (pComp_params->m_mode_mask & (1 << 7))) - { - const uint32_t trial_partition = estimate_partition(pPixels, pComp_params, pParams->m_weights, 7); - - pParams->m_pSelector_weights = g_bc7_weights2; - pParams->m_pSelector_weightsx = (const vec4F*)g_bc7_weights2x; - pParams->m_num_selector_weights = 4; - pParams->m_comp_bits = 5; - pParams->m_has_pbits = true; - pParams->m_endpoints_share_pbit = false; - pParams->m_has_alpha = true; - - const uint8_t* pPartition = &g_bc7_partition2[trial_partition * 16]; - - color_rgba subset_colors[2][16]; - - uint32_t subset_total_colors7[2] = { 0, 0 }; - - uint8_t subset_pixel_index7[2][16]; - uint8_t subset_selectors7[2][16]; - color_cell_compressor_results subset_results7[2]; - - for (uint32_t idx = 0; idx < 16; idx++) - { - const uint32_t p = pPartition[idx]; - subset_colors[p][subset_total_colors7[p]] = pPixels[idx]; - subset_pixel_index7[p][subset_total_colors7[p]] = (uint8_t)idx; - subset_total_colors7[p]++; - } - - uint64_t trial_err = 0; - for (uint32_t subset = 0; subset < 2; subset++) - { - pParams->m_num_pixels = subset_total_colors7[subset]; - pParams->m_pPixels = &subset_colors[subset][0]; - - color_cell_compressor_results* pResults = &subset_results7[subset]; - pResults->m_pSelectors = &subset_selectors7[subset][0]; - pResults->m_pSelectors_temp = selectors_temp; - uint64_t err = color_cell_compression(7, pParams, pResults, pComp_params); - trial_err += err; - if ((uint64_t)(trial_err * pComp_params->m_mode7_error_weight + .5f) > best_err) - break; - - } // subset - - const uint64_t mode7_trial_err = (uint64_t)(trial_err * pComp_params->m_mode7_error_weight + .5f); - - if (mode7_trial_err < best_err) - { - best_err = mode7_trial_err; - best_mode = 7; - opt_results7.m_mode = 7; - opt_results7.m_partition = trial_partition; - opt_results7.m_index_selector = 0; - opt_results7.m_rotation = 0; - for (uint32_t subset = 0; subset < 2; subset++) - { - for (uint32_t i = 0; i < subset_total_colors7[subset]; i++) - opt_results7.m_selectors[subset_pixel_index7[subset][i]] = subset_selectors7[subset][i]; - opt_results7.m_low[subset] = subset_results7[subset].m_low_endpoint; - opt_results7.m_high[subset] = subset_results7[subset].m_high_endpoint; - opt_results7.m_pbits[subset][0] = subset_results7[subset].m_pbits[0]; - opt_results7.m_pbits[subset][1] = subset_results7[subset].m_pbits[1]; - } - } - } - - if (best_mode == 7) - { - encode_bc7_block(pBlock, &opt_results7); - } - else if (best_mode == 5) - { - opt_results5.m_mode = 5; - opt_results5.m_partition = 0; - opt_results5.m_rotation = 0; - opt_results5.m_index_selector = 0; - - encode_bc7_block(pBlock, &opt_results5); - } - else if (best_mode == 6) - { - opt_results6.m_mode = 6; - opt_results6.m_partition = 0; - opt_results6.m_low[0] = results6.m_low_endpoint; - opt_results6.m_high[0] = results6.m_high_endpoint; - opt_results6.m_pbits[0][0] = results6.m_pbits[0]; - opt_results6.m_pbits[0][1] = results6.m_pbits[1]; - opt_results6.m_rotation = 0; - opt_results6.m_index_selector = 0; - - encode_bc7_block(pBlock, &opt_results6); - } - else - { - assert(0); - } -} - -static void handle_opaque_block(void *pBlock, const color_rgba *pPixels, const bc7enc_compress_block_params *pComp_params, color_cell_compressor_params *pParams) -{ - assert((pComp_params->m_mode_mask & (1 << 6)) || (pComp_params->m_mode_mask & (1 << 1))); - - uint8_t selectors_temp[16]; - - bc7_optimization_results opt_results; - - uint64_t best_err = UINT64_MAX; - - pParams->m_perceptual = pComp_params->m_perceptual; - pParams->m_num_pixels = 16; - pParams->m_pPixels = pPixels; - pParams->m_has_alpha = false; - - opt_results.m_partition = 0; - opt_results.m_index_selector = 0; - opt_results.m_rotation = 0; - - // Mode 6 - if (pComp_params->m_mode_mask & (1 << 6)) - { - pParams->m_pSelector_weights = g_bc7_weights4; - pParams->m_pSelector_weightsx = (const vec4F*)g_bc7_weights4x; - pParams->m_num_selector_weights = 16; - pParams->m_comp_bits = 7; - pParams->m_has_pbits = true; - pParams->m_endpoints_share_pbit = false; - - color_cell_compressor_results results6; - results6.m_pSelectors = opt_results.m_selectors; - results6.m_pSelectors_temp = selectors_temp; - - best_err = (uint64_t)(color_cell_compression(6, pParams, &results6, pComp_params) * pComp_params->m_mode6_error_weight + .5f); - - opt_results.m_mode = 6; - opt_results.m_low[0] = results6.m_low_endpoint; - opt_results.m_high[0] = results6.m_high_endpoint; - opt_results.m_pbits[0][0] = results6.m_pbits[0]; - opt_results.m_pbits[0][1] = results6.m_pbits[1]; - } - - // Mode 1 - if ((best_err > 0) && (pComp_params->m_max_partitions > 0) && (pComp_params->m_mode_mask & (1 << 1))) - { - const uint32_t trial_partition = estimate_partition(pPixels, pComp_params, pParams->m_weights, 1); - - pParams->m_pSelector_weights = g_bc7_weights3; - pParams->m_pSelector_weightsx = (const vec4F *)g_bc7_weights3x; - pParams->m_num_selector_weights = 8; - pParams->m_comp_bits = 6; - pParams->m_has_pbits = true; - pParams->m_endpoints_share_pbit = true; - - const uint8_t *pPartition = &g_bc7_partition2[trial_partition * 16]; - - color_rgba subset_colors[2][16]; - - uint32_t subset_total_colors1[2] = { 0, 0 }; - - uint8_t subset_pixel_index1[2][16]; - uint8_t subset_selectors1[2][16]; - color_cell_compressor_results subset_results1[2]; - - for (uint32_t idx = 0; idx < 16; idx++) - { - const uint32_t p = pPartition[idx]; - subset_colors[p][subset_total_colors1[p]] = pPixels[idx]; - subset_pixel_index1[p][subset_total_colors1[p]] = (uint8_t)idx; - subset_total_colors1[p]++; - } - - uint64_t trial_err = 0; - for (uint32_t subset = 0; subset < 2; subset++) - { - pParams->m_num_pixels = subset_total_colors1[subset]; - pParams->m_pPixels = &subset_colors[subset][0]; - - color_cell_compressor_results *pResults = &subset_results1[subset]; - pResults->m_pSelectors = &subset_selectors1[subset][0]; - pResults->m_pSelectors_temp = selectors_temp; - uint64_t err = color_cell_compression(1, pParams, pResults, pComp_params); - - trial_err += err; - if ((uint64_t)(trial_err * pComp_params->m_mode1_error_weight + .5f) > best_err) - break; - - } // subset - - const uint64_t mode1_trial_err = (uint64_t)(trial_err * pComp_params->m_mode1_error_weight + .5f); - if (mode1_trial_err < best_err) - { - best_err = mode1_trial_err; - opt_results.m_mode = 1; - opt_results.m_partition = trial_partition; - for (uint32_t subset = 0; subset < 2; subset++) - { - for (uint32_t i = 0; i < subset_total_colors1[subset]; i++) - opt_results.m_selectors[subset_pixel_index1[subset][i]] = subset_selectors1[subset][i]; - opt_results.m_low[subset] = subset_results1[subset].m_low_endpoint; - opt_results.m_high[subset] = subset_results1[subset].m_high_endpoint; - opt_results.m_pbits[subset][0] = subset_results1[subset].m_pbits[0]; - } - } - } - - encode_bc7_block(pBlock, &opt_results); -} - -bool bc7enc_compress_block(void *pBlock, const void *pPixelsRGBA, const bc7enc_compress_block_params *pComp_params) -{ - assert(g_bc7_mode_1_optimal_endpoints[255][0].m_hi != 0); - - const color_rgba *pPixels = (const color_rgba *)(pPixelsRGBA); - - color_cell_compressor_params params; - if (pComp_params->m_perceptual) - { - // https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.709_conversion - const float pr_weight = (.5f / (1.0f - .2126f)) * (.5f / (1.0f - .2126f)); - const float pb_weight = (.5f / (1.0f - .0722f)) * (.5f / (1.0f - .0722f)); - params.m_weights[0] = (int)(pComp_params->m_weights[0] * 4.0f); - params.m_weights[1] = (int)(pComp_params->m_weights[1] * 4.0f * pr_weight); - params.m_weights[2] = (int)(pComp_params->m_weights[2] * 4.0f * pb_weight); - params.m_weights[3] = pComp_params->m_weights[3] * 4; - } - else - memcpy(params.m_weights, pComp_params->m_weights, sizeof(params.m_weights)); - - if (pComp_params->m_force_alpha) - { - handle_alpha_block(pBlock, pPixels, pComp_params, ¶ms); - return true; - } - - for (uint32_t i = 0; i < 16; i++) - { - if (pPixels[i].m_c[3] < 255) - { - handle_alpha_block(pBlock, pPixels, pComp_params, ¶ms); - return true; - } - } - handle_opaque_block(pBlock, pPixels, pComp_params, ¶ms); - return false; -} - -static const uint8_t g_tdefl_small_dist_extra[512] = -{ - 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7 -}; - -static const uint8_t g_tdefl_large_dist_extra[128] = -{ - 0, 0, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13 -}; - -static inline uint32_t compute_match_cost_estimate(uint32_t dist, uint32_t match_len_in_bytes) -{ - assert(match_len_in_bytes <= 258); - - uint32_t len_cost = 6; - if (match_len_in_bytes >= 12) - len_cost = 9; - else if (match_len_in_bytes >= 8) - len_cost = 8; - else if (match_len_in_bytes >= 6) - len_cost = 7; - - uint32_t dist_cost = 5; - if (dist < 512) - dist_cost += g_tdefl_small_dist_extra[dist & 511]; - else - { - dist_cost += g_tdefl_large_dist_extra[std::min(dist, 32767) >> 8]; - while (dist >= 32768) - { - dist_cost++; - dist >>= 1; - } - } - return len_cost + dist_cost; -} - -class tracked_stat -{ -public: - tracked_stat() { clear(); } - - void clear() { m_num = 0; m_total = 0; m_total2 = 0; } - - void update(uint32_t val) { m_num++; m_total += val; m_total2 += val * val; } - - tracked_stat& operator += (uint32_t val) { update(val); return *this; } - - uint32_t get_number_of_values() { return m_num; } - uint64_t get_total() const { return m_total; } - uint64_t get_total2() const { return m_total2; } - - float get_average() const { return m_num ? (float)m_total / m_num : 0.0f; }; - float get_std_dev() const { return m_num ? sqrtf((float)(m_num * m_total2 - m_total * m_total)) / m_num : 0.0f; } - float get_variance() const { float s = get_std_dev(); return s * s; } - -private: - uint32_t m_num; - uint64_t m_total; - uint64_t m_total2; -}; - -static inline float compute_block_max_std_dev(const color_rgba* pPixels) -{ - tracked_stat r_stats, g_stats, b_stats, a_stats; - - for (uint32_t i = 0; i < 16; i++) - { - r_stats.update(pPixels[i].m_c[0]); - g_stats.update(pPixels[i].m_c[1]); - b_stats.update(pPixels[i].m_c[2]); - a_stats.update(pPixels[i].m_c[3]); - } - - return std::max(std::max(std::max(r_stats.get_std_dev(), g_stats.get_std_dev()), b_stats.get_std_dev()), a_stats.get_std_dev()); -} - -struct bc7_block -{ - uint8_t m_bytes[16]; - - uint32_t get_mode() const - { - uint32_t bc7_mode = 0; - while (((m_bytes[0] & (1 << bc7_mode)) == 0) && (bc7_mode < 8)) - bc7_mode++; - return bc7_mode; - } -}; - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. -If you use this software in a product, attribution / credits is requested but not required. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright(c) 2020-2021 Richard Geldreich, Jr. -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files(the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions : -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain(www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non - commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain.We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors.We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/src/ktx.imageio/bc7enc-rdo/bc7enc.h b/src/ktx.imageio/bc7enc-rdo/bc7enc.h deleted file mode 100644 index 3f410ec3af..0000000000 --- a/src/ktx.imageio/bc7enc-rdo/bc7enc.h +++ /dev/null @@ -1,124 +0,0 @@ -// clang-format off -#pragma once - -// File: bc7enc.h - Richard Geldreich, Jr. - MIT license or public domain (see end of bc7enc.c) -// If you use this software in a product, attribution / credits is requested but not required. -#include -#include -#include -#include - -#define BC7ENC_BLOCK_SIZE (16) -#define BC7ENC_MAX_PARTITIONS (64) -#define BC7ENC_MAX_UBER_LEVEL (4) - -struct color_rgba { uint8_t m_c[4]; }; - -struct bc7enc_compress_block_params -{ - uint32_t m_mode_mask; - - // m_max_partitions may range from 0 (disables mode 1) to BC7ENC_MAX_PARTITIONS. The higher this value, the slower the compressor, but the higher the quality. - uint32_t m_max_partitions; - - // Relative RGBA or YCbCrA weights. - uint32_t m_weights[4]; - - // m_uber_level may range from 0 to BC7ENC_MAX_UBER_LEVEL. The higher this value, the slower the compressor, but the higher the quality. - uint32_t m_uber_level; - - // If m_perceptual is true, colorspace error is computed in YCbCr space, otherwise RGB. - bool m_perceptual; - - // Set m_try_least_squares to false for slightly faster/lower quality compression. - bool m_try_least_squares; - - // When m_mode17_partition_estimation_filterbank, the mode1 partition estimator skips lesser used partition patterns unless they are strongly predicted to be potentially useful. - // There's a slight loss in quality with this enabled (around .08 dB RGB PSNR or .05 dB Y PSNR), but up to a 11% gain in speed depending on the other settings. - bool m_mode17_partition_estimation_filterbank; - - bool m_force_alpha; - - bool m_force_selectors; - uint8_t m_selectors[16]; - - bool m_quant_mode6_endpoints; - bool m_bias_mode1_pbits; - - float m_pbit1_weight; - - float m_mode1_error_weight; - float m_mode5_error_weight; - float m_mode6_error_weight; - float m_mode7_error_weight; - - float m_low_frequency_partition_weight; - - void clear() - { - memset(this, 0, sizeof(*this)); - } - - void print() - { - printf("Mode mask: 0x%X\n", m_mode_mask); - printf("Max partitions: %u\n", m_max_partitions); - printf("Weights: %u %u %u %u\n", m_weights[0], m_weights[1], m_weights[2], m_weights[3]); - printf("Uber level: %u\n", m_uber_level); - printf("Perceptual: %u\n", m_perceptual); - printf("Try least squares: %u\n", m_try_least_squares); - printf("Mode 1/7 partition estimation filterbank: %u\n", m_mode17_partition_estimation_filterbank); - printf("Force alpha: %u\n", m_force_alpha); - printf("Quant mode 6 endpoints: %u\n", m_quant_mode6_endpoints); - printf("Bias mode 1 p-bits: %u\n", m_bias_mode1_pbits); - printf("p-bit 1 weight: %f\n", m_pbit1_weight); - printf("Mode error weights: %f %f %f %f\n", m_mode1_error_weight, m_mode5_error_weight, m_mode6_error_weight, m_mode7_error_weight); - printf("Low frequency partition weight: %f\n", m_low_frequency_partition_weight); - } -}; - -inline void bc7enc_compress_block_params_init_linear_weights(bc7enc_compress_block_params *p) -{ - p->m_perceptual = false; - p->m_weights[0] = 1; - p->m_weights[1] = 1; - p->m_weights[2] = 1; - p->m_weights[3] = 1; -} - -inline void bc7enc_compress_block_params_init_perceptual_weights(bc7enc_compress_block_params *p) -{ - p->m_perceptual = true; - p->m_weights[0] = 128; - p->m_weights[1] = 64; - p->m_weights[2] = 16; - p->m_weights[3] = 32; -} - -inline void bc7enc_compress_block_params_init(bc7enc_compress_block_params *p) -{ - p->m_mode_mask = UINT32_MAX; - p->m_max_partitions = BC7ENC_MAX_PARTITIONS; - p->m_try_least_squares = true; - p->m_mode17_partition_estimation_filterbank = true; - p->m_uber_level = 0; - p->m_force_selectors = false; - p->m_force_alpha = false; - p->m_quant_mode6_endpoints = false; - p->m_bias_mode1_pbits = false; - p->m_pbit1_weight = 1.0f; - p->m_mode1_error_weight = 1.0f; - p->m_mode5_error_weight = 1.0f; - p->m_mode6_error_weight = 1.0f; - p->m_mode7_error_weight = 1.0f; - p->m_low_frequency_partition_weight = 1.0f; - bc7enc_compress_block_params_init_perceptual_weights(p); -} - -// bc7enc_compress_block_init() MUST be called before calling bc7enc_compress_block() (or you'll get artifacts). -void bc7enc_compress_block_init(); - -// Packs a single block of 16x16 RGBA pixels (R first in memory) to 128-bit BC7 block pBlock, using either mode 1 and/or 6. -// Alpha blocks will always use mode 6, and by default opaque blocks will use either modes 1 or 6. -// Returns true if the block had any pixels with alpha < 255, otherwise it return false. (This is not an error code - a block is always encoded.) -bool bc7enc_compress_block(void *pBlock, const void *pPixelsRGBA, const bc7enc_compress_block_params *pComp_params); diff --git a/src/ktx.imageio/bc7enc-rdo/rgbcx.cpp b/src/ktx.imageio/bc7enc-rdo/rgbcx.cpp deleted file mode 100644 index 06aa368128..0000000000 --- a/src/ktx.imageio/bc7enc-rdo/rgbcx.cpp +++ /dev/null @@ -1,3081 +0,0 @@ -// clang-format off -// rgbcx.cpp - see license at end of rgbcx.h -#include "rgbcx.h" -#include -#include -#include - -namespace rgbcx -{ - [[maybe_unused]] const uint8_t g_bc1_to_linear[4] = { 0, 3, 1, 2 }; - - const uint32_t NUM_UNIQUE_TOTAL_ORDERINGS4 = 969; - -#ifdef _MSC_VER -#pragma region -#endif - // All total orderings for 16 pixels 2-bit selectors. - // BC1 selector order 0, 2, 3, 1 (i.e. the selectors are reordered into linear order). - static uint8_t g_unique_total_orders4[NUM_UNIQUE_TOTAL_ORDERINGS4][4] = - { - {0,8,2,6},{4,3,9,0},{4,8,1,3},{12,0,3,1},{11,3,2,0},{6,4,6,0},{7,5,0,4},{6,0,8,2},{1,0,0,15},{3,0,8,5},{1,1,13,1},{13,1,2,0},{0,14,1,1},{0,15,1,0},{0,13,0,3},{16,0,0,0},{4,3,4,5},{8,6,0,2},{0,10,0,6},{10,0,4,2},{7,2,1,6},{4,7,5,0},{1,4,7,4},{0,14,2,0},{2,7,2,5},{9,0,5,2},{9,2,2,3},{10,0,5,1},{2,3,7,4},{4,9,0,3},{1,5,0,10},{1,1,6,8}, - {6,6,4,0},{11,5,0,0},{11,2,0,3},{4,0,10,2},{2,3,10,1},{1,13,1,1},{0,14,0,2},{2,3,3,8},{12,3,1,0},{14,0,0,2},{9,1,3,3},{6,4,0,6},{1,1,5,9},{5,9,0,2},{2,10,1,3},{12,0,0,4},{4,6,6,0},{0,6,4,6},{3,7,4,2},{0,13,3,0},{3,10,0,3},{10,2,1,3},{1,12,1,2},{2,0,13,1},{11,0,5,0},{12,1,3,0},{6,4,5,1},{10,4,2,0},{3,6,1,6},{7,3,6,0},{10,4,0,2},{10,0,2,4}, - {0,5,9,2},{0,9,3,4},{6,4,2,4},{3,4,7,2},{3,3,5,5},{4,2,9,1},{6,2,8,0},{3,5,3,5},{4,10,1,1},{10,1,3,2},{5,7,0,4},{5,3,7,1},{6,8,1,1},{8,8,0,0},{11,1,0,4},{14,1,0,1},{9,3,2,2},{8,2,1,5},{0,0,2,14},{3,3,9,1},{10,1,5,0},{8,3,1,4},{1,5,8,2},{6,1,9,0},{3,2,1,10},{3,11,1,1},{7,6,3,0},{9,0,3,4},{5,2,5,4},{0,2,3,11},{15,0,0,1},{0,6,6,4}, - {3,4,9,0},{4,7,0,5},{0,4,4,8},{0,13,2,1},{2,4,1,9},{3,2,5,6},{10,6,0,0},{3,5,6,2},{8,0,4,4},{1,3,6,6},{7,7,0,2},{6,1,4,5},{0,11,1,4},{2,2,8,4},{0,1,2,13},{15,0,1,0},{7,2,6,1},{8,1,7,0},{1,8,4,3},{2,13,1,0},{1,0,7,8},{14,2,0,0},{1,8,1,6},{9,3,3,1},{0,0,7,9},{4,4,1,7},{9,0,6,1},{10,2,4,0},{1,7,3,5},{0,3,8,5},{5,2,4,5},{1,2,5,8}, - {0,8,7,1},{10,3,2,1},{12,0,4,0},{2,1,4,9},{5,2,2,7},{1,9,3,3},{15,1,0,0},{6,3,4,3},{9,5,0,2},{1,6,9,0},{6,6,0,4},{13,2,1,0},{5,1,8,2},{0,5,11,0},{7,1,0,8},{1,2,12,1},{0,3,3,10},{7,4,2,3},{5,1,4,6},{7,0,3,6},{3,12,0,1},{3,4,5,4},{1,10,0,5},{7,4,3,2},{10,5,0,1},{13,3,0,0},{2,5,4,5},{3,10,1,2},{5,1,2,8},{14,0,1,1},{1,5,4,6},{1,4,5,6}, - {2,3,11,0},{11,0,4,1},{11,2,2,1},{5,3,8,0},{1,3,10,2},{0,1,13,2},{3,1,4,8},{4,2,4,6},{1,5,6,4},{2,1,11,2},{1,2,9,4},{4,7,3,2},{6,2,5,3},{7,2,2,5},{8,1,4,3},{3,2,8,3},{12,1,0,3},{7,8,1,0},{7,0,2,7},{5,10,0,1},{0,2,14,0},{2,9,3,2},{7,0,0,9},{11,1,4,0},{10,4,1,1},{2,2,9,3},{5,7,2,2},{1,3,1,11},{13,2,0,1},{4,2,8,2},{2,3,1,10},{4,2,5,5}, - {7,0,7,2},{10,0,0,6},{0,8,5,3},{4,4,0,8},{12,4,0,0},{0,1,14,1},{8,0,1,7},{5,1,5,5},{11,0,3,2},{0,4,1,11},{0,8,8,0},{0,2,5,9},{7,3,2,4},{7,8,0,1},{1,0,3,12},{7,4,5,0},{1,6,7,2},{7,6,1,2},{9,6,1,0},{12,2,0,2},{4,1,6,5},{4,0,1,11},{8,4,4,0},{13,0,1,2},{8,6,2,0},{4,12,0,0},{2,7,5,2},{2,0,5,9},{5,4,5,2},{3,8,5,0},{7,3,3,3},{4,4,8,0}, - {2,1,3,10},{5,0,1,10},{6,4,3,3},{4,9,1,2},{1,4,0,11},{11,3,1,1},{4,0,12,0},{13,0,0,3},{6,1,6,3},{9,0,4,3},{8,0,0,8},{8,4,0,4},{0,12,1,3},{0,4,10,2},{3,4,8,1},{1,3,8,4},{9,2,5,0},{5,7,4,0},{1,0,11,4},{4,10,0,2},{1,3,12,0},{6,9,0,1},{5,0,9,2},{5,9,2,0},{13,1,0,2},{9,3,4,0},{9,4,0,3},{3,1,12,0},{2,4,3,7},{1,2,13,0},{2,2,4,8},{6,8,0,2}, - {9,2,1,4},{9,5,1,1},{2,0,4,10},{5,4,0,7},{0,0,6,10},{1,2,0,13},{4,7,2,3},{6,5,5,0},{3,3,1,9},{1,6,1,8},{12,2,1,1},{4,4,5,3},{1,0,6,9},{0,6,10,0},{4,8,3,1},{4,3,2,7},{2,1,7,6},{1,9,1,5},{3,1,3,9},{8,7,1,0},{1,2,3,10},{14,1,1,0},{5,4,4,3},{3,7,0,6},{7,4,1,4},{3,7,5,1},{1,1,0,14},{0,10,3,3},{0,4,3,9},{1,7,7,1},{2,0,10,4},{5,8,0,3}, - {6,7,3,0},{0,8,4,4},{5,7,3,1},{7,9,0,0},{7,6,2,1},{0,4,5,7},{6,3,5,2},{1,2,1,12},{5,2,0,9},{8,5,0,3},{4,6,1,5},{1,1,7,7},{10,5,1,0},{1,2,8,5},{1,8,2,5},{5,1,0,10},{6,9,1,0},{13,0,2,1},{8,3,5,0},{6,3,6,1},{2,11,3,0},{3,7,3,3},{1,5,2,8},{7,5,2,2},{0,6,7,3},{13,1,1,1},{5,3,4,4},{7,2,7,0},{5,8,3,0},{3,13,0,0},{0,7,9,0},{8,0,3,5}, - {1,3,7,5},{4,0,2,10},{12,0,1,3},{1,7,6,2},{3,9,0,4},{7,2,0,7},{0,1,7,8},{2,1,8,5},{0,13,1,2},{0,8,1,7},{5,0,11,0},{5,6,2,3},{0,3,0,13},{2,3,4,7},{5,6,3,2},{4,2,10,0},{3,3,7,3},{7,2,5,2},{1,1,11,3},{12,3,0,1},{5,1,1,9},{1,15,0,0},{9,7,0,0},{9,1,2,4},{0,7,3,6},{3,0,13,0},{3,0,11,2},{0,6,5,5},{8,2,2,4},{6,10,0,0},{4,8,4,0},{0,0,3,13}, - {0,4,12,0},{7,1,6,2},{3,5,0,8},{8,0,6,2},{6,2,3,5},{2,10,0,4},{4,11,0,1},{6,1,5,4},{5,1,3,7},{0,11,3,2},{4,6,0,6},{2,6,0,8},{3,1,7,5},{2,14,0,0},{2,9,2,3},{0,3,4,9},{11,0,1,4},{13,0,3,0},{8,3,0,5},{0,5,3,8},{5,11,0,0},{0,1,4,11},{2,1,9,4},{3,4,4,5},{7,1,2,6},{12,2,2,0},{9,4,1,2},{6,0,2,8},{4,6,2,4},{11,2,3,0},{3,2,2,9},{10,3,1,2}, - {1,1,2,12},{0,5,2,9},{0,1,11,4},{6,2,4,4},{2,8,2,4},{0,9,4,3},{11,0,2,3},{0,2,11,3},{6,0,7,3},{0,3,6,7},{4,5,5,2},{1,2,6,7},{7,5,1,3},{9,0,2,5},{2,6,4,4},{4,1,9,2},{4,8,2,2},{1,12,3,0},{0,9,6,1},{0,10,6,0},{3,1,5,7},{2,13,0,1},{2,2,1,11},{3,6,0,7},{5,6,5,0},{5,5,4,2},{4,0,3,9},{3,4,1,8},{0,11,2,3},{2,12,1,1},{7,1,3,5},{7,0,9,0}, - {8,0,8,0},{1,0,2,13},{3,3,10,0},{2,4,4,6},{2,3,8,3},{1,10,5,0},{7,3,0,6},{2,9,0,5},{1,4,6,5},{6,6,3,1},{5,6,0,5},{6,3,0,7},{3,10,2,1},{2,5,5,4},{3,8,4,1},{1,14,0,1},{10,3,3,0},{3,5,7,1},{1,1,3,11},{2,4,0,10},{9,3,1,3},{5,10,1,0},{3,0,6,7},{3,1,9,3},{11,2,1,2},{5,3,3,5},{0,5,1,10},{4,1,11,0},{10,2,0,4},{7,6,0,3},{2,7,0,7},{4,2,2,8}, - {6,1,7,2},{4,9,2,1},{0,0,8,8},{3,7,2,4},{9,6,0,1},{0,12,4,0},{6,7,1,2},{0,7,2,7},{1,0,10,5},{0,0,14,2},{2,7,3,4},{5,0,0,11},{7,7,1,1},{6,2,7,1},{4,5,3,4},{3,5,1,7},{5,9,1,1},{6,2,1,7},{3,2,0,11},{0,11,0,5},{3,11,2,0},{10,1,4,1},{7,0,4,5},{11,4,0,1},{10,3,0,3},{0,2,4,10},{0,15,0,1},{0,11,5,0},{6,7,2,1},{1,12,2,1},{4,1,3,8},{1,0,13,2}, - {1,8,5,2},{7,0,1,8},{3,12,1,0},{9,2,4,1},{1,7,4,4},{11,4,1,0},{4,3,8,1},{2,8,4,2},{1,11,3,1},{1,1,4,10},{4,10,2,0},{8,2,5,1},{1,0,9,6},{5,3,2,6},{0,9,7,0},{10,2,2,2},{5,8,1,2},{8,7,0,1},{0,3,12,1},{1,0,1,14},{4,8,0,4},{3,8,0,5},{4,6,5,1},{0,9,5,2},{10,2,3,1},{2,3,9,2},{1,0,12,3},{11,3,0,2},{4,5,2,5},{0,2,12,2},{9,1,0,6},{9,2,0,5}, - {1,2,7,6},{4,7,4,1},{0,12,2,2},{0,0,0,16},{2,8,3,3},{3,6,2,5},{0,6,3,7},{7,5,4,0},{3,3,3,7},{3,3,0,10},{5,0,6,5},{0,0,10,6},{8,5,3,0},{8,1,5,2},{6,0,9,1},{11,1,2,2},{2,11,2,1},{9,5,2,0},{3,0,4,9},{2,2,12,0},{2,6,6,2},{2,1,13,0},{6,0,5,5},{2,0,14,0},{2,11,1,2},{4,4,7,1},{2,0,11,3},{3,1,1,11},{2,9,4,1},{3,7,6,0},{14,0,2,0},{1,10,4,1}, - {8,0,7,1},{3,6,5,2},{0,3,11,2},{2,5,6,3},{11,1,3,1},{6,5,3,2},{3,8,1,4},{0,2,7,7},{2,10,2,2},{1,6,2,7},{11,0,0,5},{12,1,1,2},{12,1,2,1},{0,7,1,8},{0,3,9,4},{0,2,1,13},{7,1,4,4},{10,1,0,5},{4,0,8,4},{5,2,7,2},{0,2,0,14},{4,3,7,2},{2,7,1,6},{1,2,2,11},{6,3,3,4},{1,14,1,0},{2,4,6,4},{5,3,6,2},{5,3,5,3},{8,4,1,3},{1,3,0,12},{3,5,2,6}, - {1,8,7,0},{0,7,4,5},{2,1,6,7},{4,11,1,0},{7,2,4,3},{6,1,3,6},{4,5,4,3},{2,11,0,3},{1,5,7,3},{12,0,2,2},{5,0,4,7},{1,13,0,2},{7,7,2,0},{4,1,7,4},{4,5,0,7},{5,0,5,6},{6,5,4,1},{2,4,2,8},{1,10,1,4},{6,3,1,6},{3,3,8,2},{0,7,7,2},{4,4,2,6},{1,1,8,6},{1,12,0,3},{2,1,12,1},{1,9,2,4},{1,11,0,4},{2,5,2,7},{10,0,3,3},{4,6,3,3},{3,7,1,5}, - {1,9,0,6},{7,1,7,1},{1,6,5,4},{9,2,3,2},{6,2,2,6},{2,2,2,10},{8,3,3,2},{0,1,8,7},{2,0,8,6},{0,3,1,12},{9,4,2,1},{9,4,3,0},{6,2,6,2},{1,8,0,7},{5,1,10,0},{0,5,5,6},{8,2,4,2},{2,3,2,9},{6,0,3,7},{2,2,6,6},{2,6,2,6},{1,13,2,0},{9,3,0,4},{7,3,5,1},{6,5,2,3},{5,2,6,3},{2,0,12,2},{5,7,1,3},{8,1,3,4},{3,1,10,2},{1,0,15,0},{0,8,0,8}, - {5,0,7,4},{4,4,6,2},{0,1,0,15},{10,0,1,5},{7,3,4,2},{4,9,3,0},{2,5,7,2},{3,4,2,7},{8,3,2,3},{5,1,6,4},{0,10,2,4},{6,6,1,3},{6,0,0,10},{4,4,3,5},{1,3,9,3},{7,5,3,1},{3,0,7,6},{1,8,6,1},{4,3,0,9},{3,11,0,2},{6,0,6,4},{0,1,3,12},{0,4,2,10},{5,5,6,0},{4,1,4,7},{8,1,6,1},{5,6,4,1},{8,4,2,2},{4,3,1,8},{3,0,2,11},{1,11,4,0},{0,8,3,5}, - {5,1,7,3},{7,0,8,1},{4,3,5,4},{4,6,4,2},{3,2,4,7},{1,6,3,6},{0,7,8,1},{3,0,1,12},{9,1,4,2},{7,4,0,5},{1,7,0,8},{5,4,1,6},{9,1,5,1},{1,1,9,5},{4,1,1,10},{5,3,0,8},{2,2,5,7},{4,0,0,12},{9,0,7,0},{3,4,0,9},{0,2,6,8},{8,2,0,6},{3,2,6,5},{4,2,6,4},{3,6,4,3},{2,8,6,0},{5,0,3,8},{0,4,0,12},{0,16,0,0},{0,9,2,5},{4,0,11,1},{1,6,4,5}, - {0,1,6,9},{3,4,6,3},{3,0,10,3},{7,0,6,3},{1,4,9,2},{1,5,3,7},{8,5,2,1},{0,12,0,4},{7,2,3,4},{0,5,6,5},{11,1,1,3},{6,5,0,5},{2,1,5,8},{1,4,11,0},{9,1,1,5},{0,0,13,3},{5,8,2,1},{2,12,0,2},{3,3,6,4},{4,1,10,1},{4,0,5,7},{8,1,0,7},{5,1,9,1},{4,3,3,6},{0,2,2,12},{6,3,2,5},{0,0,12,4},{1,5,1,9},{2,6,5,3},{3,6,3,4},{2,12,2,0},{1,6,8,1}, - {10,1,1,4},{1,3,4,8},{7,4,4,1},{1,11,1,3},{1,2,10,3},{3,9,3,1},{8,5,1,2},{2,10,4,0},{4,2,0,10},{2,7,6,1},{8,2,3,3},{1,5,5,5},{3,1,0,12},{3,10,3,0},{8,0,5,3},{0,6,8,2},{0,3,13,0},{0,0,16,0},{1,9,4,2},{4,1,8,3},{1,6,6,3},{0,10,5,1},{0,1,12,3},{4,0,6,6},{3,8,3,2},{0,5,4,7},{1,0,14,1},{0,4,6,6},{3,9,1,3},{3,5,8,0},{3,6,6,1},{5,4,7,0}, - {3,0,12,1},{8,6,1,1},{2,9,5,0},{6,1,1,8},{4,1,2,9},{3,9,4,0},{5,2,9,0},{0,12,3,1},{1,4,10,1},{4,0,7,5},{3,1,2,10},{5,4,2,5},{5,5,5,1},{4,2,3,7},{1,7,5,3},{2,8,0,6},{8,1,2,5},{3,8,2,3},{6,1,2,7},{3,9,2,2},{9,0,0,7},{0,8,6,2},{8,4,3,1},{0,2,8,6},{6,5,1,4},{2,3,5,6},{2,10,3,1},{0,7,0,9},{4,2,7,3},{2,4,8,2},{7,1,1,7},{2,4,7,3}, - {2,4,10,0},{0,1,10,5},{4,7,1,4},{0,10,4,2},{9,0,1,6},{1,9,6,0},{3,3,4,6},{4,5,7,0},{5,5,2,4},{2,8,1,5},{2,3,6,5},{0,1,1,14},{3,2,3,8},{10,1,2,3},{9,1,6,0},{3,4,3,6},{2,2,0,12},{0,0,9,7},{4,0,9,3},{7,0,5,4},{4,5,6,1},{2,5,1,8},{2,5,9,0},{3,5,4,4},{1,3,11,1},{7,1,5,3},{3,2,7,4},{1,4,2,9},{1,11,2,2},{2,2,3,9},{5,0,10,1},{3,2,11,0}, - {1,10,3,2},{8,3,4,1},{3,6,7,0},{0,7,5,4},{1,3,3,9},{2,2,10,2},{1,9,5,1},{0,5,0,11},{3,0,3,10},{0,4,8,4},{2,7,7,0},{2,0,2,12},{1,2,11,2},{6,3,7,0},{0,6,2,8},{0,10,1,5},{0,9,0,7},{6,4,4,2},{6,0,1,9},{1,5,10,0},{5,4,6,1},{5,5,3,3},{0,0,4,12},{0,3,2,11},{1,4,1,10},{3,0,9,4},{5,5,0,6},{1,7,8,0},{2,0,3,11},{6,4,1,5},{10,0,6,0},{0,6,0,10}, - {0,4,11,1},{3,1,6,6},{2,5,8,1},{0,2,10,4},{3,1,11,1},{6,6,2,2},{1,1,10,4},{2,1,2,11},{6,1,8,1},{0,2,13,1},{0,7,6,3},{6,8,2,0},{3,0,0,13},{4,4,4,4},{6,2,0,8},{7,3,1,5},{0,11,4,1},{6,7,0,3},{2,6,3,5},{5,2,1,8},{7,1,8,0},{5,5,1,5},{1,8,3,4},{8,2,6,0},{6,0,10,0},{5,6,1,4},{1,4,4,7},{2,7,4,3},{1,4,8,3},{5,4,3,4},{1,10,2,3},{2,9,1,4}, - {2,2,11,1},{2,5,0,9},{0,0,1,15},{0,0,11,5},{0,4,7,5},{0,1,15,0},{2,1,0,13},{0,3,10,3},{8,0,2,6},{3,3,2,8},{3,5,5,3},{1,7,1,7},{1,3,2,10},{4,0,4,8},{2,0,9,5},{1,1,1,13},{2,2,7,5},{2,1,10,3},{4,2,1,9},{4,3,6,3},{1,3,5,7},{2,5,3,6},{1,0,8,7},{5,0,2,9},{2,8,5,1},{1,6,0,9},{0,0,5,11},{0,4,9,3},{2,0,7,7},{1,7,2,6},{2,1,1,12},{2,4,9,1}, - {0,5,7,4},{6,0,4,6},{3,2,10,1},{0,6,1,9},{2,6,1,7},{0,5,8,3},{4,1,0,11},{1,2,4,9},{4,1,5,6},{6,1,0,9},{1,4,3,8},{4,5,1,6},{1,0,5,10},{5,3,1,7},{0,9,1,6},{2,0,1,13},{2,0,6,8},{8,1,1,6},{1,5,9,1},{0,6,9,1},{0,3,5,8},{0,2,9,5},{5,2,8,1},{1,1,14,0},{3,2,9,2},{5,0,8,3},{0,5,10,1},{5,2,3,6},{2,6,7,1},{2,3,0,11},{0,1,9,6},{1,0,4,11}, - {3,0,5,8},{0,0,15,1},{2,4,5,5},{0,3,7,6},{2,0,0,14},{1,1,12,2},{2,6,8,0},{3,1,8,4},{0,1,5,10} - }; - - // All total orderings for 16 pixels [0,2] 2-bit selectors. - // BC1 selector order: 0, 1, 2 - // Note this is different from g_unique_total_orders4[], which reorders the selectors into linear order. - const uint32_t NUM_UNIQUE_TOTAL_ORDERINGS3 = 153; - static uint8_t g_unique_total_orders3[NUM_UNIQUE_TOTAL_ORDERINGS3][3] = - { - {6,0,10},{3,6,7},{3,0,13},{13,3,0},{12,4,0},{9,1,6},{2,13,1},{4,7,5},{7,5,4},{9,6,1},{7,4,5},{8,6,2},{16,0,0},{10,6,0},{2,7,7}, - {0,0,16},{0,3,13},{1,15,0},{0,2,14},{1,4,11},{15,1,0},{1,12,3},{9,2,5},{14,1,1},{8,2,6},{3,3,10},{4,2,10},{14,0,2},{0,14,2},{1,7,8},{6,6,4}, - {11,5,0},{6,4,6},{11,3,2},{4,3,9},{7,1,8},{10,4,2},{12,1,3},{11,0,5},{9,3,4},{1,0,15},{9,0,7},{2,6,8},{12,2,2},{6,2,8},{6,8,2},{15,0,1}, - {4,8,4},{0,4,12},{8,5,3},{5,9,2},{11,2,3},{12,3,1},{6,3,7},{1,1,14},{2,9,5},{1,8,7},{4,10,2},{7,7,2},{13,1,2},{0,15,1},{3,2,11},{7,0,9}, - {4,4,8},{3,8,5},{0,5,11},{13,2,1},{1,10,5},{4,11,1},{3,10,3},{5,10,1},{10,2,4},{0,6,10},{14,2,0},{11,4,1},{3,12,1},{1,13,2},{1,5,10},{5,11,0}, - {12,0,4},{8,1,7},{6,10,0},{3,13,0},{7,2,7},{0,7,9},{5,8,3},{0,12,4},{11,1,4},{13,0,3},{0,16,0},{5,7,4},{10,3,3},{10,0,6},{0,13,3},{4,6,6}, - {2,8,6},{2,5,9},{7,8,1},{2,1,13},{2,0,14},{7,3,6},{5,1,10},{3,11,2},{5,4,7},{8,3,5},{10,5,1},{6,9,1},{1,3,12},{4,5,7},{2,2,12},{4,1,11}, - {0,8,8},{4,12,0},{6,5,5},{8,7,1},{5,5,6},{3,7,6},{7,9,0},{4,9,3},{0,10,6},{8,0,8},{5,3,8},{10,1,5},{6,1,9},{7,6,3},{9,5,2},{0,1,15}, - {9,7,0},{2,14,0},{3,4,9},{8,4,4},{9,4,3},{0,9,7},{1,9,6},{3,9,4},{5,2,9},{2,3,11},{5,6,5},{1,14,1},{6,7,3},{2,4,10},{2,12,2},{8,8,0}, - {2,10,4},{4,0,12},{0,11,5},{2,11,3},{1,11,4},{3,5,8},{5,0,11},{3,1,12},{1,2,13},{1,6,9} - }; - - // For each total ordering, this table indicates which other total orderings are likely to improve quality using a least squares pass. Each array is sorted by usefulness. - static uint16_t g_best_total_orderings4[NUM_UNIQUE_TOTAL_ORDERINGS4][MAX_TOTAL_ORDERINGS4] = - { -#if RGBCX_USE_SMALLER_TABLES - #include "rgbcx_table4_small.h" -#else - #include "rgbcx_table4.h" -#endif - }; - - static uint8_t g_best_total_orderings3[NUM_UNIQUE_TOTAL_ORDERINGS3][32] = - { - { 12,1,3,5,27,2,4,38,8,7,16,18,6,10,41,79,40,23,46,9,20,88,22,37,14,19,24,126,99,119,35,11 }, - { 7,64,116,14,94,30,8,42,1,108,47,55,137,10,134,95,96,115,69,32,63,29,90,113,11,148,16,103,19,9,34,25 }, - { 12,1,0,5,3,7,4,27,8,6,38,40,41,16,18,46,9,10,20,23,79,62,14,22,88,99,37,126,92,19,120,11 }, - { 16,88,27,18,46,48,126,107,79,19,59,38,37,65,23,66,0,2,3,43,12,151,28,25,5,87,72,40,1,20,52,92 }, - { 79,48,88,16,27,65,18,38,46,19,37,4,72,33,126,41,52,0,12,92,5,1,2,107,3,77,23,91,43,51,22,74 }, - { 1,8,41,122,10,22,2,0,87,24,37,120,38,7,39,4,5,3,9,92,62,59,23,16,104,11,27,79,19,26,25,32 }, - { 2,76,99,28,40,86,93,21,138,60,6,0,17,128,145,119,98,144,141,82,147,54,67,75,5,12,27,132,146,1,38,14 }, - { 47,7,64,90,1,118,116,85,57,14,30,94,50,45,137,134,8,42,69,139,55,68,58,108,95,29,10,115,0,32,2,11 }, - { 49,8,10,30,124,11,32,113,130,58,125,9,100,53,104,115,131,103,24,7,1,39,45,36,139,0,137,22,90,44,114,105 }, - { 9,38,72,125,49,41,84,11,13,5,27,0,16,92,8,2,65,105,10,18,48,29,127,131,36,14,1,46,111,79,130,12 }, - { 130,8,10,100,104,131,49,32,53,39,30,36,113,24,11,22,124,44,83,58,7,103,1,4,9,125,5,0,91,33,115,74 }, - { 114,11,58,8,120,49,9,124,142,111,41,30,10,0,97,130,62,84,38,5,72,125,92,127,100,27,139,113,13,132,32,1 }, - { 60,46,28,27,40,20,0,17,18,2,126,16,6,38,86,23,79,54,1,93,5,88,41,14,21,111,7,48,3,84,72,62 }, - { 72,92,38,65,84,48,41,79,27,16,29,111,88,5,18,46,1,0,152,14,37,19,77,42,132,7,22,13,119,56,12,2 }, - { 7,55,1,95,29,56,64,116,143,8,14,30,47,94,152,90,65,67,10,133,42,72,146,84,16,48,6,0,25,108,77,21 }, - { 27,23,20,5,0,79,38,2,3,1,59,46,4,41,33,86,37,87,88,92,7,126,43,8,22,152,151,150,149,148,147,146 }, - { 12,0,1,2,7,6,3,5,28,4,8,14,60,40,17,19,21,86,126,93,10,18,9,29,48,99,65,25,84,119,72,41 }, - { 60,40,99,2,54,12,0,1,19,28,98,93,6,138,21,5,27,17,151,14,76,46,16,18,38,29,86,144,107,7,25,41 }, - { 12,0,1,2,3,5,6,7,4,28,8,60,14,40,16,17,21,10,19,9,86,38,126,41,93,27,29,48,62,84,79,99 }, - { 0,1,2,10,5,8,3,25,4,29,32,34,63,7,77,26,16,48,65,56,14,22,129,103,72,24,18,152,140,53,96,42 }, - { 46,126,18,54,12,16,1,0,5,2,27,98,20,23,6,3,88,48,28,7,19,8,4,60,151,38,37,21,79,14,65,40 }, - { 76,6,141,86,119,2,138,67,28,145,0,93,17,1,40,60,146,99,147,14,21,144,132,7,5,29,55,27,16,75,19,12 }, - { 71,5,51,39,22,80,0,43,10,122,8,62,41,24,104,87,35,37,2,91,33,120,36,38,1,131,9,100,130,66,3,4 }, - { 126,18,46,27,20,16,88,23,12,79,54,59,48,0,73,1,37,151,5,19,28,38,2,66,60,3,65,98,14,26,6,43 }, - { 22,10,8,5,0,71,35,80,104,39,24,51,100,1,62,32,2,130,11,41,7,9,53,43,49,83,122,120,30,44,37,38 }, - { 1,34,14,129,53,63,42,26,121,148,7,44,96,10,0,24,100,32,64,116,140,22,5,19,29,103,135,108,8,61,39,83 }, - { 1,7,34,63,44,25,135,14,24,108,22,0,83,94,5,129,35,101,47,121,2,19,42,53,6,110,103,8,148,10,16,123 }, - { 12,28,16,60,18,1,6,21,14,0,86,19,2,48,93,17,38,29,7,5,65,126,46,72,41,79,84,119,40,56,54,88 }, - { 0,2,12,27,5,46,38,40,41,79,88,99,3,23,1,62,20,4,22,37,92,35,18,8,16,24,10,60,7,120,98,54 }, - { 1,7,14,56,8,0,84,67,10,2,133,72,42,111,5,30,21,4,9,3,25,94,16,116,47,11,65,18,132,90,55,64 }, - { 30,8,124,139,45,11,58,90,113,137,7,115,10,32,1,49,94,85,9,47,108,103,0,97,63,14,50,114,53,106,100,25 }, - { 65,38,48,27,16,79,72,18,88,19,46,77,84,92,37,41,0,29,1,14,12,111,2,5,31,36,87,74,105,40,28,51 }, - { 10,8,30,113,130,100,53,32,115,103,104,7,1,121,39,49,131,44,24,36,63,137,34,45,22,90,108,83,26,11,94,139 }, - { 51,52,43,33,5,74,16,37,71,91,38,3,36,87,48,22,4,0,122,41,39,18,66,27,79,24,65,88,59,23,62,92 }, - { 1,7,63,53,108,121,94,44,103,100,14,10,129,47,32,26,24,25,148,42,135,22,0,61,83,8,39,104,5,64,115,34 }, - { 1,8,10,7,5,0,80,32,62,2,24,44,53,83,9,41,30,22,100,11,14,25,120,4,26,6,3,16,122,34,19,35 }, - { 74,4,36,48,33,91,39,79,22,16,65,5,131,38,24,71,27,52,0,105,51,18,88,104,3,31,10,37,72,19,41,130 }, - { 59,43,38,79,23,27,92,51,0,16,46,5,18,88,41,37,66,3,87,20,48,2,122,4,22,12,1,126,19,65,33,24 }, - { 12,28,1,27,0,16,2,46,65,60,21,3,5,18,6,19,48,14,4,7,79,88,86,29,22,72,93,40,23,8,17,41 }, - { 22,91,39,33,24,71,5,131,36,10,51,0,130,8,104,2,35,125,9,43,52,49,83,80,100,41,122,3,37,38,4,16 }, - { 12,0,1,2,5,3,4,8,7,27,18,38,10,6,16,46,9,20,41,23,126,79,22,14,19,99,88,54,37,48,62,35 }, - { 12,27,1,2,3,0,46,4,38,16,8,28,7,79,18,5,84,6,88,10,14,21,23,20,40,22,60,19,9,29,72,65 }, - { 1,14,7,55,95,29,8,94,30,56,10,108,77,116,152,64,32,48,63,42,143,148,16,25,137,65,11,0,115,9,19,72 }, - { 37,79,66,38,16,52,48,59,43,27,87,33,41,4,23,51,3,5,88,18,92,46,73,122,22,71,20,0,65,19,2,120 }, - { 24,32,83,22,53,1,8,10,7,30,35,5,103,0,100,101,121,113,34,123,63,2,44,25,71,115,80,14,26,108,51,39 }, - { 97,45,111,58,85,139,0,90,47,7,120,106,142,30,50,132,41,62,84,1,119,114,14,56,117,8,38,29,2,64,116,5 }, - { 12,28,16,18,1,60,6,14,2,21,0,86,126,19,48,93,7,27,17,29,5,65,54,38,72,79,84,88,119,145,8,111 }, - { 118,47,64,116,57,85,7,14,50,1,42,0,45,68,86,69,2,111,134,28,90,55,16,29,56,48,84,144,60,30,112,41 }, - { 12,1,2,0,7,6,28,5,3,4,8,14,60,21,18,40,17,86,10,9,16,29,19,93,126,79,38,84,72,27,111,119 }, - { 11,8,49,130,10,125,9,124,100,114,131,30,58,104,32,39,24,113,36,105,0,41,22,120,5,53,111,38,142,44,83,35 }, - { 50,70,47,118,85,57,106,0,45,7,64,90,81,14,2,134,28,62,86,55,69,1,78,119,68,56,18,67,16,60,29,21 }, - { 43,37,33,87,51,41,66,5,122,38,22,59,92,0,23,91,27,16,71,79,18,52,120,4,3,24,46,20,73,39,62,36 }, - { 79,48,4,16,27,88,43,33,18,38,65,37,46,3,19,51,52,22,66,87,74,5,41,91,23,59,0,71,122,72,20,92 }, - { 32,100,10,8,30,104,24,44,39,113,83,103,1,7,22,53,115,63,135,121,26,35,34,5,0,108,137,90,91,45,2,130 }, - { 0,1,2,5,16,12,6,7,14,3,19,18,29,20,4,21,40,8,17,35,23,48,126,22,25,56,26,10,98,27,38,65 }, - { 143,67,56,146,1,7,133,55,64,141,134,69,6,47,14,29,84,21,111,147,57,16,95,72,118,132,50,0,2,18,119,42 }, - { 1,7,67,14,133,111,8,84,0,21,2,47,64,132,55,10,95,147,119,42,16,5,72,56,4,3,6,29,9,25,18,30 }, - { 68,57,69,112,144,86,102,2,134,55,0,70,118,64,75,47,14,28,93,143,67,7,50,149,1,21,29,56,119,95,60,78 }, - { 58,97,114,30,124,45,11,139,8,90,0,142,7,10,41,113,84,62,49,111,85,1,9,5,137,120,32,14,2,117,47,38 }, - { 23,66,18,79,38,20,43,27,16,88,46,59,126,37,87,12,73,92,3,5,48,0,19,54,2,51,28,1,41,65,122,22 }, - { 0,12,2,27,5,40,46,38,1,41,3,79,88,23,99,4,20,62,22,54,92,18,8,37,16,35,10,7,19,120,144,24 }, - { 1,14,25,26,0,7,44,34,129,42,24,5,135,22,19,148,6,96,83,2,29,16,63,35,101,64,140,136,116,110,3,10 }, - { 12,1,2,27,3,4,38,5,7,8,18,16,46,6,0,40,41,10,79,23,88,9,20,22,14,19,37,92,48,126,28,21 }, - { 7,1,10,32,108,103,94,47,8,53,25,14,34,115,100,129,121,130,148,42,64,116,63,26,44,0,24,30,113,4,104,22 }, - { 47,134,7,14,55,69,64,95,1,29,85,118,56,116,45,57,102,143,50,90,42,30,16,94,0,8,67,75,133,2,18,48 }, - { 12,1,2,0,7,6,28,8,14,5,3,4,40,21,17,18,60,86,16,93,126,10,9,29,99,38,119,25,19,54,27,84 }, - { 59,16,27,18,23,88,79,37,46,66,38,20,73,126,3,43,48,87,92,51,41,12,19,5,52,107,65,0,151,122,54,2 }, - { 1,21,147,7,119,14,76,132,55,0,86,145,2,6,69,67,16,143,111,138,17,28,29,60,18,93,8,19,40,56,84,5 }, - { 144,86,112,2,68,102,69,0,149,93,75,28,57,55,145,60,21,67,99,134,143,40,146,119,82,110,62,6,29,26,78,14 }, - { 102,57,55,69,143,75,146,67,56,68,134,2,29,141,0,21,6,14,133,118,64,1,7,95,47,84,111,28,147,82,72,119 }, - { 0,70,57,119,50,145,2,86,28,118,69,78,149,47,60,68,67,55,93,81,134,21,14,62,64,7,5,1,132,85,41,16 }, - { 51,5,43,71,122,87,41,37,91,39,0,22,33,36,38,24,66,120,62,2,80,16,92,10,59,4,27,23,35,79,8,3 }, - { 12,1,2,0,7,6,28,5,8,14,3,21,40,4,60,17,86,18,16,93,10,9,126,119,99,29,19,41,38,27,25,92 }, - { 27,18,46,126,23,16,88,79,20,151,59,73,48,38,0,54,12,2,37,1,19,5,28,60,66,41,3,109,86,65,40,6 }, - { 48,79,4,33,16,74,65,38,88,27,91,52,18,36,22,19,46,0,37,3,51,5,71,39,72,43,24,41,92,87,2,10 }, - { 86,2,144,93,28,112,141,6,102,21,99,60,75,0,68,82,69,146,67,149,55,40,145,76,111,147,56,119,110,143,26,132 }, - { 6,138,2,99,86,17,40,93,28,21,145,141,0,60,119,147,128,76,67,54,1,12,5,27,144,14,38,98,146,41,29,19 }, - { 1,8,0,10,2,29,7,5,3,56,4,25,14,152,63,32,65,72,96,42,34,108,48,9,26,16,84,103,67,148,22,129 }, - { 149,145,0,86,2,28,93,144,62,60,119,101,21,41,5,35,78,99,26,40,12,68,57,67,110,120,69,18,55,76,132,70 }, - { 12,28,16,1,48,19,6,60,2,14,18,21,0,27,46,65,86,29,5,7,72,93,40,3,17,84,56,88,126,4,38,8 }, - { 1,8,5,10,7,24,2,62,0,41,22,122,120,9,4,3,32,87,11,37,38,83,100,44,25,104,16,26,39,80,14,6 }, - { 0,119,62,86,145,149,28,132,93,2,120,67,60,41,35,5,144,21,123,38,111,81,84,56,12,44,24,50,92,55,40,22 }, - { 2,93,99,28,40,144,60,0,86,150,76,21,149,98,6,25,1,61,82,26,12,5,54,141,7,18,145,16,27,138,110,38 }, - { 24,8,10,22,32,35,100,5,1,53,0,7,71,80,30,123,83,104,51,11,2,39,44,113,9,62,25,103,34,101,43,41 }, - { 12,1,2,0,7,6,28,5,40,60,8,16,3,18,14,4,86,21,17,93,41,10,9,99,27,119,38,19,126,22,48,145 }, - { 45,47,50,7,85,90,97,1,64,139,116,118,30,58,14,106,70,111,0,57,94,42,137,142,29,120,8,56,18,134,84,41 }, - { 12,0,2,5,27,38,1,46,41,40,79,144,3,22,88,23,28,60,99,62,6,24,26,7,4,16,10,35,37,18,14,20 }, - { 37,38,59,92,0,5,23,51,79,41,27,22,2,3,87,16,46,4,1,43,20,33,18,88,24,71,8,10,48,19,126,122 }, - { 12,28,16,60,1,18,6,21,19,14,48,0,2,86,93,5,46,29,17,27,65,7,3,72,38,126,119,40,84,37,56,4 }, - { 0,2,5,1,16,6,27,28,18,38,60,7,14,21,46,40,86,41,19,48,93,8,3,79,22,4,10,37,62,23,24,111 }, - { 85,7,90,30,47,139,45,50,94,58,137,1,8,64,14,116,118,115,113,11,124,108,0,10,97,57,32,70,42,106,29,114 }, - { 33,36,22,71,51,5,91,39,0,52,43,24,131,74,16,37,38,122,41,3,87,48,4,104,35,80,10,2,105,62,27,18 }, - { 12,1,27,2,0,16,3,28,46,18,4,6,5,72,21,79,38,7,14,60,88,8,65,19,48,29,23,40,22,20,86,126 }, - { 0,12,2,27,5,38,46,41,1,40,79,3,88,23,22,99,20,37,62,4,18,6,16,35,60,28,24,7,92,8,14,10 }, - { 7,47,1,30,137,8,116,94,90,64,14,115,108,118,57,10,148,113,42,85,32,11,63,50,103,45,124,134,55,9,69,34 }, - { 55,7,1,29,56,143,64,47,67,133,14,146,95,72,84,8,116,111,6,134,141,21,65,0,69,30,16,45,85,42,50,10 }, - { 14,1,42,8,10,29,108,63,55,148,95,32,7,19,25,115,103,34,56,129,77,0,16,152,94,30,113,26,2,5,48,4 }, - { 111,120,142,97,58,0,41,45,62,132,114,84,139,30,5,8,38,2,7,85,119,90,117,1,124,11,56,47,28,27,35,72 }, - { 1,0,14,2,6,5,16,19,7,29,42,18,3,25,12,35,21,8,26,17,40,4,20,48,109,99,22,96,55,101,10,61 }, - { 12,0,1,5,3,2,4,7,27,8,38,6,40,18,16,10,20,46,9,41,23,22,79,14,62,19,37,126,88,11,92,48 }, - { 10,8,104,39,24,32,22,83,44,100,30,130,53,91,113,5,11,1,35,33,7,49,0,2,103,71,36,124,9,80,131,34 }, - { 1,7,0,14,8,34,5,25,35,26,6,63,10,123,2,16,103,19,44,32,135,121,108,80,62,30,115,94,149,144,53,18 }, - { 75,68,146,141,102,67,2,21,6,57,69,143,0,55,82,86,28,144,147,29,93,112,56,119,133,14,76,60,84,134,111,145 }, - { 10,32,115,7,8,53,1,108,30,113,94,137,100,63,90,34,130,103,121,47,44,25,104,39,24,26,85,14,49,36,22,131 }, - { 39,24,10,22,8,130,91,104,83,49,5,33,100,11,0,35,32,131,71,36,9,44,53,2,80,51,30,1,41,7,43,62 }, - { 38,36,65,105,27,72,31,79,41,131,5,48,125,39,0,16,92,46,22,13,18,84,24,37,88,2,33,74,91,71,130,49 }, - { 0,106,62,50,45,119,85,81,132,28,2,86,41,47,38,60,35,117,5,29,7,30,145,90,55,70,14,111,18,67,93,56 }, - { 0,2,5,1,3,25,19,26,4,34,29,10,22,16,8,7,24,14,48,65,53,18,6,77,44,56,72,61,121,21,136,40 }, - { 7,1,94,8,47,115,10,32,113,103,30,108,137,63,14,64,116,148,129,42,90,25,34,118,53,57,11,49,85,9,96,50 }, - { 14,0,1,26,19,5,42,2,25,24,29,22,6,44,61,16,7,96,136,3,140,34,35,55,135,18,48,77,83,4,8,10 }, - { 1,7,14,0,25,6,34,5,26,16,63,2,19,8,35,101,108,29,94,10,18,42,123,144,129,47,61,21,3,62,149,4 }, - { 12,0,2,1,28,5,6,120,7,60,40,16,18,86,27,14,21,93,8,62,41,38,3,17,4,119,99,48,19,126,10,9 }, - { 86,144,93,2,28,149,0,60,99,112,110,145,40,21,102,26,75,62,69,1,12,101,119,25,76,67,7,68,55,5,6,14 }, - { 8,30,10,32,113,49,115,137,124,103,45,90,7,139,11,1,58,53,130,94,108,100,9,63,85,125,34,47,0,24,44,104 }, - { 120,142,111,41,58,114,97,0,11,62,84,124,5,30,8,38,132,127,27,139,92,10,72,45,49,9,28,2,29,56,16,1 }, - { 8,113,30,137,7,32,10,90,94,115,1,103,108,63,47,85,49,53,11,45,34,50,14,25,9,124,100,130,139,121,42,26 }, - { 64,7,14,47,134,55,1,42,95,69,116,90,94,30,8,29,56,137,45,108,85,10,57,16,102,143,118,19,63,32,11,50 }, - { 62,132,0,119,120,41,111,86,35,28,5,84,56,38,2,93,145,60,67,12,92,27,29,72,55,117,21,24,133,149,22,45 }, - { 57,68,69,118,134,64,50,47,55,14,7,2,102,144,0,112,70,86,85,1,95,29,116,143,42,75,16,56,28,45,21,48 }, - { 0,12,2,1,5,28,6,40,60,27,7,38,16,14,86,18,93,41,62,46,99,35,8,23,3,17,22,21,10,19,79,20 }, - { 12,1,2,27,16,3,38,111,4,0,18,5,7,46,40,8,79,6,14,28,88,10,48,41,19,84,21,9,22,23,20,72 }, - { 53,103,32,7,1,100,22,63,71,44,10,115,108,24,92,104,26,30,122,94,8,39,83,34,137,135,90,91,121,5,87,47 }, - { 87,37,41,0,22,38,2,92,1,24,4,8,3,59,10,5,39,23,71,79,122,27,16,46,33,7,91,20,18,51,9,120 }, - { 1,7,8,10,0,5,35,32,53,44,14,30,2,80,25,34,6,62,26,103,16,19,63,9,149,24,121,41,22,11,113,83 }, - { 11,58,8,30,124,49,10,113,9,114,139,45,97,32,7,137,90,1,0,130,115,125,100,24,5,94,53,41,14,13,35,38 }, - { 125,105,9,36,131,49,8,130,39,11,10,5,22,38,41,104,0,31,13,24,27,16,2,72,65,91,48,32,84,18,100,74 }, - { 12,1,0,2,6,3,7,5,4,8,14,28,16,60,18,10,21,17,19,9,40,27,86,93,29,38,54,11,25,48,46,41 }, - { 84,41,38,72,92,29,111,5,65,120,79,0,27,56,48,14,132,16,119,22,86,88,46,28,62,12,1,2,93,18,24,127 }, - { 99,28,40,60,2,93,138,0,98,17,86,54,76,12,27,1,21,144,128,38,5,14,46,18,25,16,109,6,41,145,7,29 }, - { 1,63,10,32,148,14,103,34,42,7,8,108,116,53,64,96,25,121,26,94,140,0,29,19,55,24,100,136,5,4,44,115 }, - { 131,100,130,49,10,8,36,104,39,0,48,41,11,38,4,24,27,22,16,44,79,5,33,2,53,9,125,74,91,120,32,83 }, - { 36,39,131,74,4,91,22,33,125,104,130,48,10,24,16,5,49,8,100,105,79,0,9,65,71,2,18,83,31,11,19,44 }, - { 0,12,2,1,6,5,7,28,40,60,16,14,18,62,86,27,93,8,17,38,21,41,35,99,3,19,10,23,22,4,9,48 }, - { 1,7,67,14,21,147,111,55,132,119,0,8,2,76,64,16,47,84,6,18,86,95,145,10,42,29,133,5,56,134,17,72 }, - { 69,55,47,134,102,143,7,57,118,95,14,64,29,56,1,50,75,67,146,2,0,133,68,16,21,6,141,85,116,18,72,65 }, - { 1,44,7,24,83,63,34,103,22,121,53,32,25,35,0,115,108,5,14,8,10,101,94,30,2,123,110,26,137,47,90,19 }, - { 14,1,25,42,34,0,26,96,19,29,140,5,53,10,2,121,3,24,44,22,55,77,129,7,63,16,8,4,6,61,100,48 }, - { 30,90,7,8,137,94,85,1,47,113,115,108,45,139,124,11,10,32,50,58,103,14,63,64,9,116,49,42,25,148,0,53 }, - { 40,99,2,60,28,17,0,54,93,98,86,138,6,12,21,76,1,5,27,144,128,38,19,46,14,41,145,7,16,67,3,109 }, - { 45,58,30,139,90,7,85,137,97,8,124,47,1,11,106,114,50,94,0,113,10,115,14,32,9,64,108,41,49,29,62,116 }, - { 14,42,10,1,63,96,32,25,34,8,129,29,0,103,55,19,26,53,77,5,95,2,4,7,3,16,148,56,18,24,121,108 }, - { 21,2,75,86,6,76,144,28,119,99,93,147,141,67,102,145,60,132,146,128,0,82,40,138,55,111,143,17,133,112,69,14 }, - { 111,120,41,62,84,132,0,5,38,119,56,92,72,142,27,28,29,35,58,80,2,86,65,79,12,14,1,24,145,16,21,48 }, - { 146,67,141,69,133,21,6,143,57,55,111,147,56,1,14,132,7,2,134,102,0,119,29,84,76,64,86,72,28,68,47,75 }, - { 12,1,0,5,27,3,7,4,38,8,6,41,16,40,46,10,18,79,2,9,23,86,20,22,62,14,37,88,92,19,24,11 }, - { 0,12,2,1,27,5,38,28,60,6,40,7,16,46,18,14,41,99,93,62,3,79,86,23,149,8,22,35,88,17,19,10 }, - { 141,6,21,67,147,102,146,2,76,119,132,69,55,111,86,75,28,133,143,0,1,145,14,128,56,99,17,60,29,93,84,68 }, - { 21,76,1,119,86,145,2,0,14,7,6,138,146,55,17,28,132,93,67,40,60,143,29,147,111,16,69,141,5,56,19,133 }, - { 1,8,108,14,7,116,64,42,10,63,94,32,115,103,113,96,30,34,55,47,95,148,29,140,129,25,134,53,69,26,19,11 }, - { 12,1,3,5,4,2,0,7,8,38,27,16,18,6,10,20,41,40,79,46,9,23,22,88,92,37,14,24,62,19,48,99 }, - { 1,14,7,0,6,25,5,16,19,2,42,26,29,35,61,8,18,129,101,21,3,110,34,148,96,10,17,4,22,40,12,20 }, - { 0,2,5,1,3,19,22,26,16,24,29,7,14,6,4,25,18,44,8,48,12,61,20,21,10,35,65,56,23,40,17,107 }, - { 1,7,8,29,56,0,10,14,2,42,72,5,4,65,3,30,84,94,67,9,25,133,111,11,32,108,16,63,21,96,26,48 } - }; -#ifdef _MSC_VER -#pragma endregion -#endif - - static inline uint32_t iabs(int32_t i) { return (i < 0) ? static_cast(-i) : static_cast(i); } - //static inline uint64_t iabs(int64_t i) { return (i < 0) ? static_cast(-i) : static_cast(i); } - - static inline uint8_t to_5(uint32_t v) { v = v * 31 + 128; return (uint8_t)((v + (v >> 8)) >> 8); } - static inline uint8_t to_6(uint32_t v) { v = v * 63 + 128; return (uint8_t)((v + (v >> 8)) >> 8); } - - template inline T square(T a) { return a * a; } - - static inline float clampf(float value, float low, float high) { if (value < low) value = low; else if (value > high) value = high; return value; } - - template inline S clamp(S value, S low, S high) { return (value < low) ? low : ((value > high) ? high : value); } - static inline int32_t clampi(int32_t value, int32_t low, int32_t high) { if (value < low) value = low; else if (value > high) value = high; return value; } - - static inline int squarei(int a) { return a * a; } - //static inline int absi(int a) { return (a < 0) ? -a : a; } - - template inline F lerp(F a, F b, F s) { return a + (b - a) * s; } - - static const uint32_t TOTAL_ORDER_4_0_16 = 15; - static const uint32_t TOTAL_ORDER_4_1_16 = 700; - static const uint32_t TOTAL_ORDER_4_2_16 = 753; - static const uint32_t TOTAL_ORDER_4_3_16 = 515; - static uint16_t g_total_ordering4_hash[4096]; - static float g_selector_factors4[NUM_UNIQUE_TOTAL_ORDERINGS4][3]; - - static const uint32_t TOTAL_ORDER_3_0_16 = 12; - static const uint32_t TOTAL_ORDER_3_1_16 = 15; - static const uint32_t TOTAL_ORDER_3_2_16 = 89; - static uint16_t g_total_ordering3_hash[256]; - static float g_selector_factors3[NUM_UNIQUE_TOTAL_ORDERINGS3][3]; - - struct hist4 - { - uint8_t m_hist[4]; - - hist4() - { - memset(m_hist, 0, sizeof(m_hist)); - } - - hist4(uint32_t i, uint32_t j, uint32_t k, uint32_t l) - { - m_hist[0] = (uint8_t)i; - m_hist[1] = (uint8_t)j; - m_hist[2] = (uint8_t)k; - m_hist[3] = (uint8_t)l; - } - - inline bool operator== (const hist4& h) const - { - if (m_hist[0] != h.m_hist[0]) return false; - if (m_hist[1] != h.m_hist[1]) return false; - if (m_hist[2] != h.m_hist[2]) return false; - if (m_hist[3] != h.m_hist[3]) return false; - return true; - } - - inline bool any_16() const - { - return (m_hist[0] == 16) || (m_hist[1] == 16) || (m_hist[2] == 16) || (m_hist[3] == 16); - } - - inline uint32_t lookup_total_ordering_index() const - { - if (m_hist[0] == 16) - return TOTAL_ORDER_4_0_16; - else if (m_hist[1] == 16) - return TOTAL_ORDER_4_1_16; - else if (m_hist[2] == 16) - return TOTAL_ORDER_4_2_16; - else if (m_hist[3] == 16) - return TOTAL_ORDER_4_3_16; - - // Must sum to 16, so m_hist[3] isn't needed. - return g_total_ordering4_hash[m_hist[0] | (m_hist[1] << 4) | (m_hist[2] << 8)]; - } - }; - - struct hist3 - { - uint8_t m_hist[3]; - - hist3() - { - memset(m_hist, 0, sizeof(m_hist)); - } - - hist3(uint32_t i, uint32_t j, uint32_t k) - { - m_hist[0] = (uint8_t)i; - m_hist[1] = (uint8_t)j; - m_hist[2] = (uint8_t)k; - } - - inline bool operator== (const hist3& h) const - { - if (m_hist[0] != h.m_hist[0]) return false; - if (m_hist[1] != h.m_hist[1]) return false; - if (m_hist[2] != h.m_hist[2]) return false; - return true; - } - - inline bool any_16() const - { - return (m_hist[0] == 16) || (m_hist[1] == 16) || (m_hist[2] == 16); - } - - inline uint32_t lookup_total_ordering_index() const - { - if (m_hist[0] == 16) - return TOTAL_ORDER_3_0_16; - else if (m_hist[1] == 16) - return TOTAL_ORDER_3_1_16; - else if (m_hist[2] == 16) - return TOTAL_ORDER_3_2_16; - - // Must sum to 16, so m_hist[2] isn't needed. - return g_total_ordering3_hash[m_hist[0] | (m_hist[1] << 4)]; - } - }; - - struct bc1_match_entry - { - uint8_t m_hi; - uint8_t m_lo; - uint8_t m_e; - }; - - static bc1_approx_mode g_bc1_approx_mode; - static bc1_match_entry g_bc1_match5_equals_1[256], g_bc1_match6_equals_1[256]; - static bc1_match_entry g_bc1_match5_half[256], g_bc1_match6_half[256]; - - static inline int scale_5_to_8(int v) { return (v << 3) | (v >> 2); } - static inline int scale_6_to_8(int v) { return (v << 2) | (v >> 4); } - - // v0, v1 = unexpanded DXT1 endpoint values (5/6-bits) - // c0, c1 = expanded DXT1 endpoint values (8-bits) - static inline int interp_5_6_ideal(int c0, int c1) { assert(c0 < 256 && c1 < 256); return (c0 * 2 + c1) / 3; } - static inline int interp_5_6_ideal_round(int c0, int c1) { assert(c0 < 256 && c1 < 256); return (c0 * 2 + c1 + 1) / 3; } - static inline int interp_half_5_6_ideal(int c0, int c1) { assert(c0 < 256 && c1 < 256); return (c0 + c1) / 2; } - - static inline int interp_5_nv(int v0, int v1) { assert(v0 < 32 && v1 < 32); return ((2 * v0 + v1) * 22) / 8; } - static inline int interp_6_nv(int c0, int c1) { assert(c0 < 256 && c1 < 256); const int gdiff = c1 - c0; return (256 * c0 + (gdiff / 4) + 128 + gdiff * 80) / 256; } - - static inline int interp_half_5_nv(int v0, int v1) { assert(v0 < 32 && v1 < 32); return ((v0 + v1) * 33) / 8; } - static inline int interp_half_6_nv(int c0, int c1) { assert(c0 < 256 && c1 < 256); const int gdiff = c1 - c0; return (256 * c0 + gdiff / 4 + 128 + gdiff * 128) / 256; } - - static inline int interp_5_6_amd(int c0, int c1) { assert(c0 < 256 && c1 < 256); return (c0 * 43 + c1 * 21 + 32) >> 6; } - static inline int interp_half_5_6_amd(int c0, int c1) { assert(c0 < 256 && c1 < 256); return (c0 + c1 + 1) >> 1; } - - static inline int interp_5(int v0, int v1, int c0, int c1, bc1_approx_mode mode) - { - assert(scale_5_to_8(v0) == c0 && scale_5_to_8(v1) == c1); - switch (mode) - { - case bc1_approx_mode::cBC1NVidia: return interp_5_nv(v0, v1); - case bc1_approx_mode::cBC1AMD: return interp_5_6_amd(c0, c1); - default: - case bc1_approx_mode::cBC1Ideal: return interp_5_6_ideal(c0, c1); - case bc1_approx_mode::cBC1IdealRound4: return interp_5_6_ideal_round(c0, c1); - } - } - - static inline int interp_6(int v0, int v1, int c0, int c1, bc1_approx_mode mode) - { - (void)v0; (void)v1; - assert(scale_6_to_8(v0) == c0 && scale_6_to_8(v1) == c1); - switch (mode) - { - case bc1_approx_mode::cBC1NVidia: return interp_6_nv(c0, c1); - case bc1_approx_mode::cBC1AMD: return interp_5_6_amd(c0, c1); - default: - case bc1_approx_mode::cBC1Ideal: return interp_5_6_ideal(c0, c1); - case bc1_approx_mode::cBC1IdealRound4: return interp_5_6_ideal_round(c0, c1); - } - } - - static inline int interp_half_5(int v0, int v1, int c0, int c1, bc1_approx_mode mode) - { - assert(scale_5_to_8(v0) == c0 && scale_5_to_8(v1) == c1); - switch (mode) - { - case bc1_approx_mode::cBC1NVidia: return interp_half_5_nv(v0, v1); - case bc1_approx_mode::cBC1AMD: return interp_half_5_6_amd(c0, c1); - case bc1_approx_mode::cBC1Ideal: - case bc1_approx_mode::cBC1IdealRound4: - default: - return interp_half_5_6_ideal(c0, c1); - } - } - - static inline int interp_half_6(int v0, int v1, int c0, int c1, bc1_approx_mode mode) - { - (void)v0; (void)v1; - assert(scale_6_to_8(v0) == c0 && scale_6_to_8(v1) == c1); - switch (mode) - { - case bc1_approx_mode::cBC1NVidia: return interp_half_6_nv(c0, c1); - case bc1_approx_mode::cBC1AMD: return interp_half_5_6_amd(c0, c1); - case bc1_approx_mode::cBC1Ideal: - case bc1_approx_mode::cBC1IdealRound4: - default: - return interp_half_5_6_ideal(c0, c1); - } - } - - static void prepare_bc1_single_color_table_half(bc1_match_entry* pTable, const uint8_t* pExpand, int size, bc1_approx_mode mode) - { - for (int i = 0; i < 256; i++) - { - int lowest_e = 256; - for (int lo = 0; lo < size; lo++) - { - const int lo_e = pExpand[lo]; - - for (int hi = 0; hi < size; hi++) - { - const int hi_e = pExpand[hi]; - - const int v = (size == 32) ? interp_half_5(hi, lo, hi_e, lo_e, mode) : interp_half_6(hi, lo, hi_e, lo_e, mode); - - int e = iabs(v - i); - - // We only need to factor in 3% error in BC1 ideal mode. - if ((mode == bc1_approx_mode::cBC1Ideal) || (mode == bc1_approx_mode::cBC1IdealRound4)) - e += (iabs(hi_e - lo_e) * 3) / 100; - - // Favor equal endpoints, for lower error on actual GPU's which approximate the interpolation. - if ((e < lowest_e) || ((e == lowest_e) && (lo == hi))) - { - pTable[i].m_hi = static_cast(hi); - pTable[i].m_lo = static_cast(lo); - - assert(e <= UINT8_MAX); - pTable[i].m_e = static_cast(e); - - lowest_e = e; - } - - } // hi - } // lo - } - } - - static void prepare_bc1_single_color_table(bc1_match_entry* pTable, const uint8_t* pExpand, int size, bc1_approx_mode mode) - { - for (int i = 0; i < 256; i++) - { - int lowest_e = 256; - for (int lo = 0; lo < size; lo++) - { - const int lo_e = pExpand[lo]; - - for (int hi = 0; hi < size; hi++) - { - const int hi_e = pExpand[hi]; - - const int v = (size == 32) ? interp_5(hi, lo, hi_e, lo_e, mode) : interp_6(hi, lo, hi_e, lo_e, mode); - - int e = iabs(v - i); - - if ((mode == bc1_approx_mode::cBC1Ideal) || (mode == bc1_approx_mode::cBC1IdealRound4)) - e += (iabs(hi_e - lo_e) * 3) / 100; - - // Favor equal endpoints, for lower error on actual GPU's which approximate the interpolation. - if ((e < lowest_e) || ((e == lowest_e) && (lo == hi))) - { - pTable[i].m_hi = static_cast(hi); - pTable[i].m_lo = static_cast(lo); - - assert(e <= UINT8_MAX); - pTable[i].m_e = static_cast(e); - - lowest_e = e; - } - - } // hi - } // lo - } - } - - // This table is: 9 * (w * w), 9 * ((1.0f - w) * w), 9 * ((1.0f - w) * (1.0f - w)) - // where w is [0,1/3,2/3,1]. 9 is the perfect multiplier. - static const uint32_t g_weight_vals4[4] = { 0x000009, 0x010204, 0x040201, 0x090000 }; - - // multiplier is 4 for 3-color - static const uint32_t g_weight_vals3[3] = { 0x000004, 0x040000, 0x010101 }; - - static inline void compute_selector_factors4(const hist4& h, float& iz00, float& iz10, float& iz11) - { - uint32_t weight_accum = 0; - for (uint32_t sel = 0; sel < 4; sel++) - weight_accum += g_weight_vals4[sel] * h.m_hist[sel]; - - float z00 = (float)((weight_accum >> 16) & 0xFF); - float z10 = (float)((weight_accum >> 8) & 0xFF); - float z11 = (float)(weight_accum & 0xFF); - float z01 = z10; - - float det = z00 * z11 - z01 * z10; - if (fabs(det) < 1e-8f) - det = 0.0f; - else - det = (3.0f / 255.0f) / det; - - iz00 = z11 * det; - iz10 = -z10 * det; - iz11 = z00 * det; - } - - static inline void compute_selector_factors3(const hist3& h, float& iz00, float& iz10, float& iz11) - { - uint32_t weight_accum = 0; - for (uint32_t sel = 0; sel < 3; sel++) - weight_accum += g_weight_vals3[sel] * h.m_hist[sel]; - - float z00 = (float)((weight_accum >> 16) & 0xFF); - float z10 = (float)((weight_accum >> 8) & 0xFF); - float z11 = (float)(weight_accum & 0xFF); - float z01 = z10; - - float det = z00 * z11 - z01 * z10; - if (fabs(det) < 1e-8f) - det = 0.0f; - else - det = (2.0f / 255.0f) / det; - - iz00 = z11 * det; - iz10 = -z10 * det; - iz11 = z00 * det; - } - - static bool g_initialized; - - void init(bc1_approx_mode mode) - { - g_bc1_approx_mode = mode; - - uint8_t bc1_expand5[32]; - for (int i = 0; i < 32; i++) - bc1_expand5[i] = static_cast((i << 3) | (i >> 2)); - prepare_bc1_single_color_table(g_bc1_match5_equals_1, bc1_expand5, 32, mode); - prepare_bc1_single_color_table_half(g_bc1_match5_half, bc1_expand5, 32, mode); - - uint8_t bc1_expand6[64]; - for (int i = 0; i < 64; i++) - bc1_expand6[i] = static_cast((i << 2) | (i >> 4)); - prepare_bc1_single_color_table(g_bc1_match6_equals_1, bc1_expand6, 64, mode); - prepare_bc1_single_color_table_half(g_bc1_match6_half, bc1_expand6, 64, mode); - - for (uint32_t i = 0; i < NUM_UNIQUE_TOTAL_ORDERINGS4; i++) - { - hist4 h; - h.m_hist[0] = (uint8_t)g_unique_total_orders4[i][0]; - h.m_hist[1] = (uint8_t)g_unique_total_orders4[i][1]; - h.m_hist[2] = (uint8_t)g_unique_total_orders4[i][2]; - h.m_hist[3] = (uint8_t)g_unique_total_orders4[i][3]; - - if (!h.any_16()) - { - const uint32_t index = h.m_hist[0] | (h.m_hist[1] << 4) | (h.m_hist[2] << 8); - assert(index < 4096); - g_total_ordering4_hash[index] = (uint16_t)i; - } - - compute_selector_factors4(h, g_selector_factors4[i][0], g_selector_factors4[i][1], g_selector_factors4[i][2]); - } - - for (uint32_t i = 0; i < NUM_UNIQUE_TOTAL_ORDERINGS3; i++) - { - hist3 h; - h.m_hist[0] = (uint8_t)g_unique_total_orders3[i][0]; - h.m_hist[1] = (uint8_t)g_unique_total_orders3[i][1]; - h.m_hist[2] = (uint8_t)g_unique_total_orders3[i][2]; - - if (!h.any_16()) - { - const uint32_t index = h.m_hist[0] | (h.m_hist[1] << 4); - assert(index < 256); - g_total_ordering3_hash[index] = (uint16_t)i; - } - - compute_selector_factors3(h, g_selector_factors3[i][0], g_selector_factors3[i][1], g_selector_factors3[i][2]); - } - - g_initialized = true; - } - - void encode_bc1_solid_block(void* pDst, uint32_t fr, uint32_t fg, uint32_t fb, bool allow_3color) - { - bc1_block* pDst_block = static_cast(pDst); - - uint32_t mask = 0xAA; - int max16 = -1, min16 = 0; - - if (allow_3color) - { - const uint32_t err4 = g_bc1_match5_equals_1[fr].m_e + g_bc1_match6_equals_1[fg].m_e + g_bc1_match5_equals_1[fb].m_e; - const uint32_t err3 = g_bc1_match5_half[fr].m_e + g_bc1_match6_half[fg].m_e + g_bc1_match5_half[fb].m_e; - - if (err3 < err4) - { - max16 = (g_bc1_match5_half[fr].m_hi << 11) | (g_bc1_match6_half[fg].m_hi << 5) | g_bc1_match5_half[fb].m_hi; - min16 = (g_bc1_match5_half[fr].m_lo << 11) | (g_bc1_match6_half[fg].m_lo << 5) | g_bc1_match5_half[fb].m_lo; - - if (max16 > min16) - std::swap(max16, min16); - } - } - - if (max16 == -1) - { - max16 = (g_bc1_match5_equals_1[fr].m_hi << 11) | (g_bc1_match6_equals_1[fg].m_hi << 5) | g_bc1_match5_equals_1[fb].m_hi; - min16 = (g_bc1_match5_equals_1[fr].m_lo << 11) | (g_bc1_match6_equals_1[fg].m_lo << 5) | g_bc1_match5_equals_1[fb].m_lo; - - if (min16 == max16) - { - // Always forbid 3 color blocks - // This is to guarantee that BC3 blocks never use punchthrough alpha (3 color) mode, which isn't supported on some (all?) GPU's. - mask = 0; - - // Make l > h - if (min16 > 0) - min16--; - else - { - // l = h = 0 - assert(min16 == max16 && max16 == 0); - - max16 = 1; - min16 = 0; - mask = 0x55; - } - - assert(max16 > min16); - } - - if (max16 < min16) - { - std::swap(max16, min16); - mask ^= 0x55; - } - } - - pDst_block->set_low_color(static_cast(max16)); - pDst_block->set_high_color(static_cast(min16)); - pDst_block->m_selectors[0] = static_cast(mask); - pDst_block->m_selectors[1] = static_cast(mask); - pDst_block->m_selectors[2] = static_cast(mask); - pDst_block->m_selectors[3] = static_cast(mask); - } - - static const float g_midpoint5[32] = { .015686f, .047059f, .078431f, .111765f, .145098f, .176471f, .207843f, .241176f, .274510f, .305882f, .337255f, .370588f, .403922f, .435294f, .466667f, .5f, .533333f, .564706f, .596078f, .629412f, .662745f, .694118f, .725490f, .758824f, .792157f, .823529f, .854902f, .888235f, .921569f, .952941f, .984314f, 1e+37f }; - static const float g_midpoint6[64] = { .007843f, .023529f, .039216f, .054902f, .070588f, .086275f, .101961f, .117647f, .133333f, .149020f, .164706f, .180392f, .196078f, .211765f, .227451f, .245098f, .262745f, .278431f, .294118f, .309804f, .325490f, .341176f, .356863f, .372549f, .388235f, .403922f, .419608f, .435294f, .450980f, .466667f, .482353f, .500000f, .517647f, .533333f, .549020f, .564706f, .580392f, .596078f, .611765f, .627451f, .643137f, .658824f, .674510f, .690196f, .705882f, .721569f, .737255f, .754902f, .772549f, .788235f, .803922f, .819608f, .835294f, .850980f, .866667f, .882353f, .898039f, .913725f, .929412f, .945098f, .960784f, .976471f, .992157f, 1e+37f }; - - struct vec3F { float c[3]; }; - - static inline void compute_least_squares_endpoints4_rgb( - vec3F* pXl, vec3F* pXh, - int total_r, int total_g, int total_b, - float iz00, float iz10, float iz11, - uint32_t s, const uint32_t r_sum[17], const uint32_t g_sum[17], const uint32_t b_sum[17]) - { - const float iz01 = iz10; - - const uint32_t f1 = g_unique_total_orders4[s][0]; - const uint32_t f2 = g_unique_total_orders4[s][0] + g_unique_total_orders4[s][1]; - const uint32_t f3 = g_unique_total_orders4[s][0] + g_unique_total_orders4[s][1] + g_unique_total_orders4[s][2]; - uint32_t uq00_r = (r_sum[f2] - r_sum[f1]) + (r_sum[f3] - r_sum[f2]) * 2 + (r_sum[16] - r_sum[f3]) * 3; - uint32_t uq00_g = (g_sum[f2] - g_sum[f1]) + (g_sum[f3] - g_sum[f2]) * 2 + (g_sum[16] - g_sum[f3]) * 3; - uint32_t uq00_b = (b_sum[f2] - b_sum[f1]) + (b_sum[f3] - b_sum[f2]) * 2 + (b_sum[16] - b_sum[f3]) * 3; - - float q10_r = (float)(total_r * 3 - uq00_r); - float q10_g = (float)(total_g * 3 - uq00_g); - float q10_b = (float)(total_b * 3 - uq00_b); - - pXl->c[0] = iz00 * (float)uq00_r + iz01 * q10_r; - pXh->c[0] = iz10 * (float)uq00_r + iz11 * q10_r; - - pXl->c[1] = iz00 * (float)uq00_g + iz01 * q10_g; - pXh->c[1] = iz10 * (float)uq00_g + iz11 * q10_g; - - pXl->c[2] = iz00 * (float)uq00_b + iz01 * q10_b; - pXh->c[2] = iz10 * (float)uq00_b + iz11 * q10_b; - } - - static inline bool compute_least_squares_endpoints4_rgb(const color32* pColors, const uint8_t* pSelectors, vec3F* pXl, vec3F* pXh, int total_r, int total_g, int total_b) - { - uint32_t uq00_r = 0, uq00_g = 0, uq00_b = 0; - uint32_t weight_accum = 0; - for (uint32_t i = 0; i < 16; i++) - { - const uint8_t r = pColors[i].c[0], g = pColors[i].c[1], b = pColors[i].c[2]; - const uint8_t sel = pSelectors[i]; - - weight_accum += g_weight_vals4[sel]; - uq00_r += sel * r; - uq00_g += sel * g; - uq00_b += sel * b; - } - - int q10_r = total_r * 3 - uq00_r; - int q10_g = total_g * 3 - uq00_g; - int q10_b = total_b * 3 - uq00_b; - - float z00 = (float)((weight_accum >> 16) & 0xFF); - float z10 = (float)((weight_accum >> 8) & 0xFF); - float z11 = (float)(weight_accum & 0xFF); - float z01 = z10; - - float det = z00 * z11 - z01 * z10; - if (fabs(det) < 1e-8f) - return false; - - det = (3.0f / 255.0f) / det; - - float iz00, iz01, iz10, iz11; - iz00 = z11 * det; - iz01 = -z01 * det; - iz10 = -z10 * det; - iz11 = z00 * det; - - pXl->c[0] = iz00 * (float)uq00_r + iz01 * q10_r; - pXh->c[0] = iz10 * (float)uq00_r + iz11 * q10_r; - - pXl->c[1] = iz00 * (float)uq00_g + iz01 * q10_g; - pXh->c[1] = iz10 * (float)uq00_g + iz11 * q10_g; - - pXl->c[2] = iz00 * (float)uq00_b + iz01 * q10_b; - pXh->c[2] = iz10 * (float)uq00_b + iz11 * q10_b; - - return true; - } - - static inline void compute_least_squares_endpoints3_rgb( - vec3F* pXl, vec3F* pXh, - int total_r, int total_g, int total_b, - float iz00, float iz10, float iz11, - uint32_t s, const uint32_t r_sum[17], const uint32_t g_sum[17], const uint32_t b_sum[17]) - { - const float iz01 = iz10; - - // Compensates for BC1 3-color ordering, which is selector 0, 2, 1 - const uint32_t f1 = g_unique_total_orders3[s][0]; - const uint32_t f2 = g_unique_total_orders3[s][0] + g_unique_total_orders3[s][2]; - uint32_t uq00_r = (r_sum[16] - r_sum[f2]) * 2 + (r_sum[f2] - r_sum[f1]); - uint32_t uq00_g = (g_sum[16] - g_sum[f2]) * 2 + (g_sum[f2] - g_sum[f1]); - uint32_t uq00_b = (b_sum[16] - b_sum[f2]) * 2 + (b_sum[f2] - b_sum[f1]); - - float q10_r = (float)(total_r * 2 - uq00_r); - float q10_g = (float)(total_g * 2 - uq00_g); - float q10_b = (float)(total_b * 2 - uq00_b); - - pXl->c[0] = iz00 * (float)uq00_r + iz01 * q10_r; - pXh->c[0] = iz10 * (float)uq00_r + iz11 * q10_r; - - pXl->c[1] = iz00 * (float)uq00_g + iz01 * q10_g; - pXh->c[1] = iz10 * (float)uq00_g + iz11 * q10_g; - - pXl->c[2] = iz00 * (float)uq00_b + iz01 * q10_b; - pXh->c[2] = iz10 * (float)uq00_b + iz11 * q10_b; - } - - static inline bool compute_least_squares_endpoints3_rgb(bool use_black, const color32* pColors, const uint8_t* pSelectors, vec3F* pXl, vec3F* pXh) - { - int uq00_r = 0, uq00_g = 0, uq00_b = 0; - uint32_t weight_accum = 0; - int total_r = 0, total_g = 0, total_b = 0; - for (uint32_t i = 0; i < 16; i++) - { - const uint8_t r = pColors[i].c[0], g = pColors[i].c[1], b = pColors[i].c[2]; - if (use_black) - { - if ((r | g | b) < 4) - continue; - } - - const uint8_t sel = pSelectors[i]; - assert(sel <= 3); - if (sel == 3) - continue; - - weight_accum += g_weight_vals3[sel]; - - static const uint8_t s_tran[3] = { 0, 2, 1 }; - const uint8_t tsel = s_tran[sel]; - uq00_r += tsel * r; - uq00_g += tsel * g; - uq00_b += tsel * b; - - total_r += r; - total_g += g; - total_b += b; - } - - int q10_r = total_r * 2 - uq00_r; - int q10_g = total_g * 2 - uq00_g; - int q10_b = total_b * 2 - uq00_b; - - float z00 = (float)((weight_accum >> 16) & 0xFF); - float z10 = (float)((weight_accum >> 8) & 0xFF); - float z11 = (float)(weight_accum & 0xFF); - float z01 = z10; - - float det = z00 * z11 - z01 * z10; - if (fabs(det) < 1e-8f) - return false; - - det = (2.0f / 255.0f) / det; - - float iz00, iz01, iz10, iz11; - iz00 = z11 * det; - iz01 = -z01 * det; - iz10 = -z10 * det; - iz11 = z00 * det; - - pXl->c[0] = iz00 * (float)uq00_r + iz01 * q10_r; - pXh->c[0] = iz10 * (float)uq00_r + iz11 * q10_r; - - pXl->c[1] = iz00 * (float)uq00_g + iz01 * q10_g; - pXh->c[1] = iz10 * (float)uq00_g + iz11 * q10_g; - - pXl->c[2] = iz00 * (float)uq00_b + iz01 * q10_b; - pXh->c[2] = iz10 * (float)uq00_b + iz11 * q10_b; - - return true; - } - - static inline void bc1_get_block_colors4(uint32_t block_r[4], uint32_t block_g[4], uint32_t block_b[4], uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb) - { - block_r[0] = (lr << 3) | (lr >> 2); block_g[0] = (lg << 2) | (lg >> 4); block_b[0] = (lb << 3) | (lb >> 2); - block_r[3] = (hr << 3) | (hr >> 2); block_g[3] = (hg << 2) | (hg >> 4); block_b[3] = (hb << 3) | (hb >> 2); - - if (g_bc1_approx_mode == bc1_approx_mode::cBC1Ideal) - { - block_r[1] = (block_r[0] * 2 + block_r[3]) / 3; block_g[1] = (block_g[0] * 2 + block_g[3]) / 3; block_b[1] = (block_b[0] * 2 + block_b[3]) / 3; - block_r[2] = (block_r[3] * 2 + block_r[0]) / 3; block_g[2] = (block_g[3] * 2 + block_g[0]) / 3; block_b[2] = (block_b[3] * 2 + block_b[0]) / 3; - } - else if (g_bc1_approx_mode == bc1_approx_mode::cBC1IdealRound4) - { - block_r[1] = (block_r[0] * 2 + block_r[3] + 1) / 3; block_g[1] = (block_g[0] * 2 + block_g[3] + 1) / 3; block_b[1] = (block_b[0] * 2 + block_b[3] + 1) / 3; - block_r[2] = (block_r[3] * 2 + block_r[0] + 1) / 3; block_g[2] = (block_g[3] * 2 + block_g[0] + 1) / 3; block_b[2] = (block_b[3] * 2 + block_b[0] + 1) / 3; - } - else if (g_bc1_approx_mode == bc1_approx_mode::cBC1AMD) - { - block_r[1] = interp_5_6_amd(block_r[0], block_r[3]); block_g[1] = interp_5_6_amd(block_g[0], block_g[3]); block_b[1] = interp_5_6_amd(block_b[0], block_b[3]); - block_r[2] = interp_5_6_amd(block_r[3], block_r[0]); block_g[2] = interp_5_6_amd(block_g[3], block_g[0]); block_b[2] = interp_5_6_amd(block_b[3], block_b[0]); - } - else - { - block_r[1] = interp_5_nv(lr, hr); block_g[1] = interp_6_nv(block_g[0], block_g[3]); block_b[1] = interp_5_nv(lb, hb); - block_r[2] = interp_5_nv(hr, lr); block_g[2] = interp_6_nv(block_g[3], block_g[0]); block_b[2] = interp_5_nv(hb, lb); - } - } - - static inline void bc1_get_block_colors3(uint32_t block_r[3], uint32_t block_g[3], uint32_t block_b[3], uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb) - { - block_r[0] = (lr << 3) | (lr >> 2); block_g[0] = (lg << 2) | (lg >> 4); block_b[0] = (lb << 3) | (lb >> 2); - block_r[1] = (hr << 3) | (hr >> 2); block_g[1] = (hg << 2) | (hg >> 4); block_b[1] = (hb << 3) | (hb >> 2); - - if ((g_bc1_approx_mode == bc1_approx_mode::cBC1Ideal) || (g_bc1_approx_mode == bc1_approx_mode::cBC1IdealRound4)) - { - block_r[2] = (block_r[0] + block_r[1]) / 2; block_g[2] = (block_g[0] + block_g[1]) / 2; block_b[2] = (block_b[0] + block_b[1]) / 2; - } - else if (g_bc1_approx_mode == bc1_approx_mode::cBC1AMD) - { - block_r[2] = interp_half_5_6_amd(block_r[0], block_r[1]); block_g[2] = interp_half_5_6_amd(block_g[0], block_g[1]); block_b[2] = interp_half_5_6_amd(block_b[0], block_b[1]); - } - else - { - block_r[2] = interp_half_5_nv(lr, hr); block_g[2] = interp_half_6_nv(block_g[0], block_g[1]); block_b[2] = interp_half_5_nv(lb, hb); - } - } - - static inline void bc1_find_sels4_noerr(const color32* pSrc_pixels, uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb, uint8_t sels[16], const uint8_t* pForce_selectors) - { - if (pForce_selectors) - { - memcpy(sels, pForce_selectors, 16); - return; - } - - uint32_t block_r[4], block_g[4], block_b[4]; - bc1_get_block_colors4(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); - - int ar = block_r[3] - block_r[0], ag = block_g[3] - block_g[0], ab = block_b[3] - block_b[0]; - - int dots[4]; - for (uint32_t i = 0; i < 4; i++) - dots[i] = (int)block_r[i] * ar + (int)block_g[i] * ag + (int)block_b[i] * ab; - - int t0 = dots[0] + dots[1], t1 = dots[1] + dots[2], t2 = dots[2] + dots[3]; - - ar *= 2; ag *= 2; ab *= 2; - - static const uint8_t s_sels[4] = { 3, 2, 1, 0 }; - - for (uint32_t i = 0; i < 16; i += 4) - { - const int d0 = pSrc_pixels[i + 0].r * ar + pSrc_pixels[i + 0].g * ag + pSrc_pixels[i + 0].b * ab; - const int d1 = pSrc_pixels[i + 1].r * ar + pSrc_pixels[i + 1].g * ag + pSrc_pixels[i + 1].b * ab; - const int d2 = pSrc_pixels[i + 2].r * ar + pSrc_pixels[i + 2].g * ag + pSrc_pixels[i + 2].b * ab; - const int d3 = pSrc_pixels[i + 3].r * ar + pSrc_pixels[i + 3].g * ag + pSrc_pixels[i + 3].b * ab; - - sels[i + 0] = s_sels[(d0 <= t0) + (d0 < t1) + (d0 < t2)]; - sels[i + 1] = s_sels[(d1 <= t0) + (d1 < t1) + (d1 < t2)]; - sels[i + 2] = s_sels[(d2 <= t0) + (d2 < t1) + (d2 < t2)]; - sels[i + 3] = s_sels[(d3 <= t0) + (d3 < t1) + (d3 < t2)]; - } - } - - static inline uint32_t bc1_find_sels4_fasterr(const color32* pSrc_pixels, uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb, uint8_t sels[16], uint32_t cur_err) - { - uint32_t block_r[4], block_g[4], block_b[4]; - bc1_get_block_colors4(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); - - int ar = block_r[3] - block_r[0], ag = block_g[3] - block_g[0], ab = block_b[3] - block_b[0]; - - int dots[4]; - for (uint32_t i = 0; i < 4; i++) - dots[i] = (int)block_r[i] * ar + (int)block_g[i] * ag + (int)block_b[i] * ab; - - int t0 = dots[0] + dots[1], t1 = dots[1] + dots[2], t2 = dots[2] + dots[3]; - - ar *= 2; ag *= 2; ab *= 2; - - static const uint8_t s_sels[4] = { 3, 2, 1, 0 }; - - uint32_t total_err = 0; - - for (uint32_t i = 0; i < 16; i += 4) - { - const int d0 = pSrc_pixels[i + 0].r * ar + pSrc_pixels[i + 0].g * ag + pSrc_pixels[i + 0].b * ab; - const int d1 = pSrc_pixels[i + 1].r * ar + pSrc_pixels[i + 1].g * ag + pSrc_pixels[i + 1].b * ab; - const int d2 = pSrc_pixels[i + 2].r * ar + pSrc_pixels[i + 2].g * ag + pSrc_pixels[i + 2].b * ab; - const int d3 = pSrc_pixels[i + 3].r * ar + pSrc_pixels[i + 3].g * ag + pSrc_pixels[i + 3].b * ab; - - uint8_t sel0 = s_sels[(d0 <= t0) + (d0 < t1) + (d0 < t2)]; - uint8_t sel1 = s_sels[(d1 <= t0) + (d1 < t1) + (d1 < t2)]; - uint8_t sel2 = s_sels[(d2 <= t0) + (d2 < t1) + (d2 < t2)]; - uint8_t sel3 = s_sels[(d3 <= t0) + (d3 < t1) + (d3 < t2)]; - - sels[i + 0] = sel0; - sels[i + 1] = sel1; - sels[i + 2] = sel2; - sels[i + 3] = sel3; - - total_err += squarei(pSrc_pixels[i + 0].r - block_r[sel0]) + squarei(pSrc_pixels[i + 0].g - block_g[sel0]) + squarei(pSrc_pixels[i + 0].b - block_b[sel0]); - total_err += squarei(pSrc_pixels[i + 1].r - block_r[sel1]) + squarei(pSrc_pixels[i + 1].g - block_g[sel1]) + squarei(pSrc_pixels[i + 1].b - block_b[sel1]); - total_err += squarei(pSrc_pixels[i + 2].r - block_r[sel2]) + squarei(pSrc_pixels[i + 2].g - block_g[sel2]) + squarei(pSrc_pixels[i + 2].b - block_b[sel2]); - total_err += squarei(pSrc_pixels[i + 3].r - block_r[sel3]) + squarei(pSrc_pixels[i + 3].g - block_g[sel3]) + squarei(pSrc_pixels[i + 3].b - block_b[sel3]); - - if (total_err >= cur_err) - break; - } - - return total_err; - } - - static inline uint32_t bc1_find_sels4_check2_err(const color32* pSrc_pixels, uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb, uint8_t sels[16], uint32_t cur_err) - { - uint32_t block_r[4], block_g[4], block_b[4]; - bc1_get_block_colors4(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); - - int dr = block_r[3] - block_r[0], dg = block_g[3] - block_g[0], db = block_b[3] - block_b[0]; - - const float f = 4.0f / (float)(squarei(dr) + squarei(dg) + squarei(db) + .00000125f); - - uint32_t total_err = 0; - - for (uint32_t i = 0; i < 16; i++) - { - const int r = pSrc_pixels[i].r; - const int g = pSrc_pixels[i].g; - const int b = pSrc_pixels[i].b; - - int sel = (int)((float)((r - (int)block_r[0]) * dr + (g - (int)block_g[0]) * dg + (b - (int)block_b[0]) * db) * f + .5f); - sel = clampi(sel, 1, 3); - - uint32_t err0 = squarei((int)block_r[sel - 1] - (int)r) + squarei((int)block_g[sel - 1] - (int)g) + squarei((int)block_b[sel - 1] - (int)b); - uint32_t err1 = squarei((int)block_r[sel] - (int)r) + squarei((int)block_g[sel] - (int)g) + squarei((int)block_b[sel] - (int)b); - - int best_sel = sel; - uint32_t best_err = err1; - if (err0 == err1) - { - // Prefer non-interpolation - if ((best_sel - 1) == 0) - best_sel = 0; - } - else if (err0 < best_err) - { - best_sel = sel - 1; - best_err = err0; - } - - total_err += best_err; - - if (total_err >= cur_err) - break; - - sels[i] = (uint8_t)best_sel; - } - return total_err; - } - - static inline uint32_t bc1_find_sels4_fullerr(const color32* pSrc_pixels, uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb, uint8_t sels[16], uint32_t cur_err) - { - uint32_t block_r[4], block_g[4], block_b[4]; - bc1_get_block_colors4(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); - - uint32_t total_err = 0; - - for (uint32_t i = 0; i < 16; i++) - { - const int r = pSrc_pixels[i].r; - const int g = pSrc_pixels[i].g; - const int b = pSrc_pixels[i].b; - - uint32_t best_err = squarei((int)block_r[0] - (int)r) + squarei((int)block_g[0] - (int)g) + squarei((int)block_b[0] - (int)b); - uint8_t best_sel = 0; - - for (uint32_t j = 1; (j < 4) && best_err; j++) - { - uint32_t err = squarei((int)block_r[j] - (int)r) + squarei((int)block_g[j] - (int)g) + squarei((int)block_b[j] - (int)b); - if ((err < best_err) || ((err == best_err) && (j == 3))) - { - best_err = err; - best_sel = (uint8_t)j; - } - } - - total_err += best_err; - - if (total_err >= cur_err) - break; - - sels[i] = (uint8_t)best_sel; - } - return total_err; - } - - static inline uint32_t bc1_find_sels4(uint32_t flags, const color32* pSrc_pixels, uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb, uint8_t sels[16], uint32_t cur_err, const uint8_t* pForce_selectors) - { - uint32_t err; - - if (pForce_selectors) - { - memcpy(sels, pForce_selectors, 16); - - uint32_t block_r[4], block_g[4], block_b[4]; - bc1_get_block_colors4(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); - - err = 0; - for (uint32_t i = 0; i < 16; i++) - { - const int r = pSrc_pixels[i].r; - const int g = pSrc_pixels[i].g; - const int b = pSrc_pixels[i].b; - - const uint32_t sel = pForce_selectors[i]; - assert(sel <= 3); - - err += squarei((int)block_r[sel] - (int)r) + squarei((int)block_g[sel] - (int)g) + squarei((int)block_b[sel] - (int)b); - } - } - else - { - if (flags & cEncodeBC1UseFasterMSEEval) - err = bc1_find_sels4_fasterr(pSrc_pixels, lr, lg, lb, hr, hg, hb, sels, cur_err); - else if (flags & cEncodeBC1UseFullMSEEval) - err = bc1_find_sels4_fullerr(pSrc_pixels, lr, lg, lb, hr, hg, hb, sels, cur_err); - else - err = bc1_find_sels4_check2_err(pSrc_pixels, lr, lg, lb, hr, hg, hb, sels, cur_err); - } - - return err; - } - - static inline uint32_t bc1_find_sels3_fullerr(bool use_black, const color32* pSrc_pixels, uint32_t lr, uint32_t lg, uint32_t lb, uint32_t hr, uint32_t hg, uint32_t hb, uint8_t sels[16], uint32_t cur_err, const uint8_t* pForce_selectors) - { - uint32_t block_r[4], block_g[4], block_b[4]; - bc1_get_block_colors3(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); - - uint32_t total_err = 0; - - if (pForce_selectors) - { - memcpy(sels, pForce_selectors, 16); - - //uint32_t block_r[4], block_g[4], block_b[4]; - //bc1_get_block_colors3(block_r, block_g, block_b, lr, lg, lb, hr, hg, hb); - - block_r[3] = 0; block_g[3] = 0; block_b[3] = 0; - - for (uint32_t i = 0; i < 16; i++) - { - const int r = pSrc_pixels[i].r; - const int g = pSrc_pixels[i].g; - const int b = pSrc_pixels[i].b; - - const uint32_t sel = pForce_selectors[i]; - assert(sel <= 3); - - total_err += squarei((int)block_r[sel] - (int)r) + squarei((int)block_g[sel] - (int)g) + squarei((int)block_b[sel] - (int)b); - } - } - else - { - for (uint32_t i = 0; i < 16; i++) - { - const int r = pSrc_pixels[i].r; - const int g = pSrc_pixels[i].g; - const int b = pSrc_pixels[i].b; - - uint32_t best_err = squarei((int)block_r[0] - (int)r) + squarei((int)block_g[0] - (int)g) + squarei((int)block_b[0] - (int)b); - uint32_t best_sel = 0; - - uint32_t err1 = squarei((int)block_r[1] - (int)r) + squarei((int)block_g[1] - (int)g) + squarei((int)block_b[1] - (int)b); - if (err1 < best_err) - { - best_err = err1; - best_sel = 1; - } - - uint32_t err2 = squarei((int)block_r[2] - (int)r) + squarei((int)block_g[2] - (int)g) + squarei((int)block_b[2] - (int)b); - if (err2 < best_err) - { - best_err = err2; - best_sel = 2; - } - - if (use_black) - { - uint32_t err3 = squarei(r) + squarei(g) + squarei(b); - if (err3 < best_err) - { - best_err = err3; - best_sel = 3; - } - } - - total_err += best_err; - if (total_err >= cur_err) - return total_err; - - sels[i] = (uint8_t)best_sel; - } - } - - return total_err; - } - - static inline void precise_round_565(const vec3F& xl, const vec3F& xh, - int& trial_lr, int& trial_lg, int& trial_lb, - int& trial_hr, int& trial_hg, int& trial_hb) - { - trial_lr = (int)(xl.c[0] * 31.0f); - trial_lg = (int)(xl.c[1] * 63.0f); - trial_lb = (int)(xl.c[2] * 31.0f); - - trial_hr = (int)(xh.c[0] * 31.0f); - trial_hg = (int)(xh.c[1] * 63.0f); - trial_hb = (int)(xh.c[2] * 31.0f); - - if ((uint32_t)(trial_lr | trial_lb | trial_hr | trial_hb) > 31U) - { - trial_lr = ((uint32_t)trial_lr > 31U) ? (~trial_lr >> 31) & 31 : trial_lr; - trial_hr = ((uint32_t)trial_hr > 31U) ? (~trial_hr >> 31) & 31 : trial_hr; - - trial_lb = ((uint32_t)trial_lb > 31U) ? (~trial_lb >> 31) & 31 : trial_lb; - trial_hb = ((uint32_t)trial_hb > 31U) ? (~trial_hb >> 31) & 31 : trial_hb; - } - - if ((uint32_t)(trial_lg | trial_hg) > 63U) - { - trial_lg = ((uint32_t)trial_lg > 63U) ? (~trial_lg >> 31) & 63 : trial_lg; - trial_hg = ((uint32_t)trial_hg > 63U) ? (~trial_hg >> 31) & 63 : trial_hg; - } - - trial_lr = (trial_lr + (xl.c[0] > g_midpoint5[trial_lr])) & 31; - trial_lg = (trial_lg + (xl.c[1] > g_midpoint6[trial_lg])) & 63; - trial_lb = (trial_lb + (xl.c[2] > g_midpoint5[trial_lb])) & 31; - - trial_hr = (trial_hr + (xh.c[0] > g_midpoint5[trial_hr])) & 31; - trial_hg = (trial_hg + (xh.c[1] > g_midpoint6[trial_hg])) & 63; - trial_hb = (trial_hb + (xh.c[2] > g_midpoint5[trial_hb])) & 31; - } - - static inline void precise_round_565_noscale(vec3F xl, vec3F xh, - int& trial_lr, int& trial_lg, int& trial_lb, - int& trial_hr, int& trial_hg, int& trial_hb) - { - xl.c[0] *= 1.0f / 255.0f; - xl.c[1] *= 1.0f / 255.0f; - xl.c[2] *= 1.0f / 255.0f; - - xh.c[0] *= 1.0f / 255.0f; - xh.c[1] *= 1.0f / 255.0f; - xh.c[2] *= 1.0f / 255.0f; - - precise_round_565(xl, xh, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb); - } - - static inline void bc1_encode4(bc1_block* pDst_block, int lr, int lg, int lb, int hr, int hg, int hb, const uint8_t sels[16]) - { - uint32_t lc16 = bc1_block::pack_unscaled_color(lr, lg, lb); - uint32_t hc16 = bc1_block::pack_unscaled_color(hr, hg, hb); - - // Always forbid 3 color blocks - if (lc16 == hc16) - { - uint8_t mask = 0; - - // Make l > h - if (hc16 > 0) - hc16--; - else - { - // lc16 = hc16 = 0 - assert(lc16 == hc16 && hc16 == 0); - - hc16 = 0; - lc16 = 1; - mask = 0x55; // select hc16 - } - - assert(lc16 > hc16); - pDst_block->set_low_color(static_cast(lc16)); - pDst_block->set_high_color(static_cast(hc16)); - - pDst_block->m_selectors[0] = mask; - pDst_block->m_selectors[1] = mask; - pDst_block->m_selectors[2] = mask; - pDst_block->m_selectors[3] = mask; - } - else - { - uint8_t invert_mask = 0; - if (lc16 < hc16) - { - std::swap(lc16, hc16); - invert_mask = 0x55; - } - - assert(lc16 > hc16); - pDst_block->set_low_color((uint16_t)lc16); - pDst_block->set_high_color((uint16_t)hc16); - - uint32_t packed_sels = 0; - static const uint8_t s_sel_trans[4] = { 0, 2, 3, 1 }; - for (uint32_t i = 0; i < 16; i++) - packed_sels |= ((uint32_t)s_sel_trans[sels[i]] << (i * 2)); - - pDst_block->m_selectors[0] = (uint8_t)packed_sels ^ invert_mask; - pDst_block->m_selectors[1] = (uint8_t)(packed_sels >> 8) ^ invert_mask; - pDst_block->m_selectors[2] = (uint8_t)(packed_sels >> 16) ^ invert_mask; - pDst_block->m_selectors[3] = (uint8_t)(packed_sels >> 24) ^ invert_mask; - } - } - - static inline void bc1_encode3(bc1_block* pDst_block, int lr, int lg, int lb, int hr, int hg, int hb, const uint8_t sels[16]) - { - uint32_t lc16 = bc1_block::pack_unscaled_color(lr, lg, lb); - uint32_t hc16 = bc1_block::pack_unscaled_color(hr, hg, hb); - - bool invert_flag = false; - if (lc16 > hc16) - { - std::swap(lc16, hc16); - invert_flag = true; - } - - assert(lc16 <= hc16); - - pDst_block->set_low_color((uint16_t)lc16); - pDst_block->set_high_color((uint16_t)hc16); - - uint32_t packed_sels = 0; - - if (invert_flag) - { - static const uint8_t s_sel_trans_inv[4] = { 1, 0, 2, 3 }; - - for (uint32_t i = 0; i < 16; i++) - packed_sels |= ((uint32_t)s_sel_trans_inv[sels[i]] << (i * 2)); - } - else - { - for (uint32_t i = 0; i < 16; i++) - packed_sels |= ((uint32_t)sels[i] << (i * 2)); - } - - pDst_block->m_selectors[0] = (uint8_t)packed_sels; - pDst_block->m_selectors[1] = (uint8_t)(packed_sels >> 8); - pDst_block->m_selectors[2] = (uint8_t)(packed_sels >> 16); - pDst_block->m_selectors[3] = (uint8_t)(packed_sels >> 24); - } - - struct bc1_encode_results - { - int lr, lg, lb; - int hr, hg, hb; - uint8_t sels[16]; - bool m_3color; - }; - - static bool try_3color_block_useblack(const color32* pSrc_pixels, uint32_t flags, uint32_t& cur_err, bc1_encode_results& results, const uint8_t* pForce_selectors) - { - int total_r = 0, total_g = 0, total_b = 0; - int max_r = 0, max_g = 0, max_b = 0; - int min_r = 255, min_g = 255, min_b = 255; - int total_pixels = 0; - for (uint32_t i = 0; i < 16; i++) - { - const int r = pSrc_pixels[i].r, g = pSrc_pixels[i].g, b = pSrc_pixels[i].b; - if ((r | g | b) < 4) - continue; - - max_r = std::max(max_r, r); max_g = std::max(max_g, g); max_b = std::max(max_b, b); - min_r = std::min(min_r, r); min_g = std::min(min_g, g); min_b = std::min(min_b, b); - total_r += r; total_g += g; total_b += b; - - total_pixels++; - } - - if (!total_pixels) - return false; - - int half_total_pixels = total_pixels >> 1; - int avg_r = (total_r + half_total_pixels) / total_pixels; - int avg_g = (total_g + half_total_pixels) / total_pixels; - int avg_b = (total_b + half_total_pixels) / total_pixels; - - uint32_t low_c = 0, high_c = 0; - - int icov[6] = { 0, 0, 0, 0, 0, 0 }; - for (uint32_t i = 0; i < 16; i++) - { - int r = (int)pSrc_pixels[i].r; - int g = (int)pSrc_pixels[i].g; - int b = (int)pSrc_pixels[i].b; - - if ((r | g | b) < 4) - continue; - - r -= avg_r; - g -= avg_g; - b -= avg_b; - - icov[0] += r * r; - icov[1] += r * g; - icov[2] += r * b; - icov[3] += g * g; - icov[4] += g * b; - icov[5] += b * b; - } - - float cov[6]; - for (uint32_t i = 0; i < 6; i++) - cov[i] = (float)(icov[i]) * (1.0f / 255.0f); - - float xr = (float)(max_r - min_r); - float xg = (float)(max_g - min_g); - float xb = (float)(max_b - min_b); - - if (icov[2] < 0) - xr = -xr; - - if (icov[4] < 0) - xg = -xg; - - for (uint32_t power_iter = 0; power_iter < 4; power_iter++) - { - float r = xr * cov[0] + xg * cov[1] + xb * cov[2]; - float g = xr * cov[1] + xg * cov[3] + xb * cov[4]; - float b = xr * cov[2] + xg * cov[4] + xb * cov[5]; - xr = r; xg = g; xb = b; - } - - float k = maximum(fabsf(xr), fabsf(xg), fabsf(xb)); - int saxis_r = 306, saxis_g = 601, saxis_b = 117; - if (k >= 2) - { - float m = 1024.0f / k; - saxis_r = (int)(xr * m); - saxis_g = (int)(xg * m); - saxis_b = (int)(xb * m); - } - - int low_dot = INT_MAX, high_dot = INT_MIN; - for (uint32_t i = 0; i < 16; i++) - { - int r = (int)pSrc_pixels[i].r, g = (int)pSrc_pixels[i].g, b = (int)pSrc_pixels[i].b; - - if ((r | g | b) < 4) - continue; - - int dot = r * saxis_r + g * saxis_g + b * saxis_b; - if (dot < low_dot) - { - low_dot = dot; - low_c = i; - } - if (dot > high_dot) - { - high_dot = dot; - high_c = i; - } - } - - int lr = to_5(pSrc_pixels[low_c].r); - int lg = to_6(pSrc_pixels[low_c].g); - int lb = to_5(pSrc_pixels[low_c].b); - - int hr = to_5(pSrc_pixels[high_c].r); - int hg = to_6(pSrc_pixels[high_c].g); - int hb = to_5(pSrc_pixels[high_c].b); - - uint8_t trial_sels[16]; - uint32_t trial_err = bc1_find_sels3_fullerr(true, pSrc_pixels, lr, lg, lb, hr, hg, hb, trial_sels, UINT32_MAX, pForce_selectors); - - if (trial_err) - { - const uint32_t total_ls_passes = flags & cEncodeBC1TwoLeastSquaresPasses ? 2 : 1; - for (uint32_t trials = 0; trials < total_ls_passes; trials++) - { - vec3F xl, xh; - int lr2, lg2, lb2, hr2, hg2, hb2; - if (!compute_least_squares_endpoints3_rgb(true, pSrc_pixels, trial_sels, &xl, &xh)) - { - lr2 = g_bc1_match5_half[avg_r].m_hi; - lg2 = g_bc1_match6_half[avg_g].m_hi; - lb2 = g_bc1_match5_half[avg_b].m_hi; - - hr2 = g_bc1_match5_half[avg_r].m_lo; - hg2 = g_bc1_match6_half[avg_g].m_lo; - hb2 = g_bc1_match5_half[avg_b].m_lo; - } - else - { - precise_round_565(xl, xh, hr2, hg2, hb2, lr2, lg2, lb2); - } - - if ((lr == lr2) && (lg == lg2) && (lb == lb2) && (hr == hr2) && (hg == hg2) && (hb == hb2)) - break; - - uint8_t trial_sels2[16]; - uint32_t trial_err2 = bc1_find_sels3_fullerr(true, pSrc_pixels, lr2, lg2, lb2, hr2, hg2, hb2, trial_sels2, trial_err, pForce_selectors); - - if (trial_err2 < trial_err) - { - trial_err = trial_err2; - lr = lr2; lg = lg2; lb = lb2; - hr = hr2; hg = hg2; hb = hb2; - memcpy(trial_sels, trial_sels2, sizeof(trial_sels)); - } - else - break; - } - } - - if (trial_err < cur_err) - { - results.m_3color = true; - results.lr = lr; - results.lg = lg; - results.lb = lb; - results.hr = hr; - results.hg = hg; - results.hb = hb; - memcpy(results.sels, trial_sels, 16); - - cur_err = trial_err; - - return true; - } - - return false; - } - - static bool try_3color_block(const color32* pSrc_pixels, uint32_t flags, uint32_t& cur_err, - int avg_r, int avg_g, int avg_b, int lr, int lg, int lb, int hr, int hg, int hb, int total_r, int total_g, int total_b, uint32_t total_orderings_to_try, - bc1_encode_results& results, const uint8_t* pForce_selectors) - { - if (pForce_selectors) - { - for (uint32_t i = 0; i < 16; i++) - if (pForce_selectors[i] == 3) - return false; - } - - uint8_t trial_sels[16]; - uint32_t trial_err = bc1_find_sels3_fullerr(false, pSrc_pixels, lr, lg, lb, hr, hg, hb, trial_sels, UINT32_MAX, pForce_selectors); - - if (trial_err) - { - const uint32_t total_ls_passes = flags & cEncodeBC1TwoLeastSquaresPasses ? 2 : 1; - for (uint32_t trials = 0; trials < total_ls_passes; trials++) - { - vec3F xl, xh; - int lr2, lg2, lb2, hr2, hg2, hb2; - if (!compute_least_squares_endpoints3_rgb(false, pSrc_pixels, trial_sels, &xl, &xh)) - { - lr2 = g_bc1_match5_half[avg_r].m_hi; - lg2 = g_bc1_match6_half[avg_g].m_hi; - lb2 = g_bc1_match5_half[avg_b].m_hi; - - hr2 = g_bc1_match5_half[avg_r].m_lo; - hg2 = g_bc1_match6_half[avg_g].m_lo; - hb2 = g_bc1_match5_half[avg_b].m_lo; - } - else - { - precise_round_565(xl, xh, hr2, hg2, hb2, lr2, lg2, lb2); - } - - if ((lr == lr2) && (lg == lg2) && (lb == lb2) && (hr == hr2) && (hg == hg2) && (hb == hb2)) - break; - - uint8_t trial_sels2[16]; - uint32_t trial_err2 = bc1_find_sels3_fullerr(false, pSrc_pixels, lr2, lg2, lb2, hr2, hg2, hb2, trial_sels2, trial_err, pForce_selectors); - - if (trial_err2 < trial_err) - { - trial_err = trial_err2; - lr = lr2; lg = lg2; lb = lb2; - hr = hr2; hg = hg2; hb = hb2; - memcpy(trial_sels, trial_sels2, sizeof(trial_sels)); - } - else - break; - } - } - - if ((trial_err) && (flags & cEncodeBC1UseLikelyTotalOrderings) && (total_orderings_to_try)) - { - hist3 h; - for (uint32_t i = 0; i < 16; i++) - { - assert(trial_sels[i] < 3); - h.m_hist[trial_sels[i]]++; - } - - const uint32_t orig_total_order_index = h.lookup_total_ordering_index(); - - int r0, g0, b0, r3, g3, b3; - r0 = (lr << 3) | (lr >> 2); g0 = (lg << 2) | (lg >> 4); b0 = (lb << 3) | (lb >> 2); - r3 = (hr << 3) | (hr >> 2); g3 = (hg << 2) | (hg >> 4); b3 = (hb << 3) | (hb >> 2); - - int ar = r3 - r0, ag = g3 - g0, ab = b3 - b0; - - int dots[16]; - for (uint32_t i = 0; i < 16; i++) - { - int r = pSrc_pixels[i].r; - int g = pSrc_pixels[i].g; - int b = pSrc_pixels[i].b; - int d = 0x1000000 + (r * ar + g * ag + b * ab); - assert(d >= 0); - dots[i] = (d << 4) + i; - } - - std::sort(dots, dots + 16); - - uint32_t r_sum[17], g_sum[17], b_sum[17]; - uint32_t r = 0, g = 0, b = 0; - for (uint32_t i = 0; i < 16; i++) - { - const uint32_t p = dots[i] & 15; - - r_sum[i] = r; - g_sum[i] = g; - b_sum[i] = b; - - r += pSrc_pixels[p].r; - g += pSrc_pixels[p].g; - b += pSrc_pixels[p].b; - } - - r_sum[16] = total_r; - g_sum[16] = total_g; - b_sum[16] = total_b; - - const uint32_t q_total = (flags & cEncodeBC1Exhaustive) ? NUM_UNIQUE_TOTAL_ORDERINGS3 : std::min(total_orderings_to_try, MAX_TOTAL_ORDERINGS3); - for (uint32_t q = 0; q < q_total; q++) - { - const uint32_t s = (flags & cEncodeBC1Exhaustive) ? q : g_best_total_orderings3[orig_total_order_index][q]; - - int trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb; - - vec3F xl, xh; - - if ((s == TOTAL_ORDER_3_0_16) || (s == TOTAL_ORDER_3_1_16) || (s == TOTAL_ORDER_3_2_16)) - { - trial_lr = g_bc1_match5_half[avg_r].m_hi; - trial_lg = g_bc1_match6_half[avg_g].m_hi; - trial_lb = g_bc1_match5_half[avg_b].m_hi; - - trial_hr = g_bc1_match5_half[avg_r].m_lo; - trial_hg = g_bc1_match6_half[avg_g].m_lo; - trial_hb = g_bc1_match5_half[avg_b].m_lo; - } - else - { - compute_least_squares_endpoints3_rgb(&xl, &xh, total_r, total_g, total_b, - g_selector_factors3[s][0], g_selector_factors3[s][1], g_selector_factors3[s][2], s, r_sum, g_sum, b_sum); - - precise_round_565(xl, xh, trial_hr, trial_hg, trial_hb, trial_lr, trial_lg, trial_lb); - } - - uint8_t trial_sels2[16]; - uint32_t trial_err2 = bc1_find_sels3_fullerr(false, pSrc_pixels, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb, trial_sels2, UINT32_MAX, pForce_selectors); - - if (trial_err2 < trial_err) - { - trial_err = trial_err2; - - lr = trial_lr; - lg = trial_lg; - lb = trial_lb; - - hr = trial_hr; - hg = trial_hg; - hb = trial_hb; - - memcpy(trial_sels, trial_sels2, sizeof(trial_sels)); - } - - } // s - } - - if (trial_err < cur_err) - { - results.m_3color = true; - results.lr = lr; - results.lg = lg; - results.lb = lb; - results.hr = hr; - results.hg = hg; - results.hb = hb; - memcpy(results.sels, trial_sels, 16); - - cur_err = trial_err; - - return true; - } - - return false; - } - - void encode_bc1(uint32_t level, void* pDst, const uint8_t* pPixels, bool allow_3color, bool allow_transparent_texels_for_black, const uint8_t* pForce_selectors) - { - uint32_t flags = 0, total_orderings4 = 1, total_orderings3 = 1; - - static_assert(MAX_TOTAL_ORDERINGS3 >= 32, "MAX_TOTAL_ORDERINGS3 >= 32"); - static_assert(MAX_TOTAL_ORDERINGS4 >= 32, "MAX_TOTAL_ORDERINGS4 >= 32"); - - switch (level) - { - case 0: - // Faster/higher quality than stb_dxt default. - flags = cEncodeBC1BoundingBoxInt; - break; - case 1: - // Faster/higher quality than stb_dxt default. A bit higher average quality vs. mode 0. - flags = cEncodeBC1Use2DLS; - break; - case 2: - // On average mode 2 is a little weaker than modes 0/1, but it's stronger on outliers (very tough textures). - // Slightly stronger than stb_dxt. - flags = 0; - break; - case 3: - // Slightly stronger than stb_dxt HIGHQUAL. - flags = cEncodeBC1TwoLeastSquaresPasses; - break; - case 4: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1Use6PowerIters; - break; - default: - case 5: - // stb_dxt HIGHQUAL + permit 3 color (if it's enabled). - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFasterMSEEval; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - break; - case 6: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFasterMSEEval | cEncodeBC1UseLikelyTotalOrderings; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - break; - case 7: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFasterMSEEval | cEncodeBC1UseLikelyTotalOrderings; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = 4; - break; - case 8: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFasterMSEEval | cEncodeBC1UseLikelyTotalOrderings; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = 8; - break; - case 9: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseLikelyTotalOrderings; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = 11; - total_orderings3 = 3; - break; - case 10: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseLikelyTotalOrderings; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = 20; - total_orderings3 = 8; - break; - case 11: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseLikelyTotalOrderings; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = 28; - total_orderings3 = 16; - break; - case 12: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseLikelyTotalOrderings; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = 32; - total_orderings3 = 32; - break; - case 13: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | (20 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = 32; - total_orderings3 = 32; - break; - case 14: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | (32 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = 32; - total_orderings3 = 32; - break; - case 15: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | (32 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = ((((32 + MAX_TOTAL_ORDERINGS4) / 2) + 32) / 2); - total_orderings3 = 32; - break; - case 16: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | (256 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = (32 + MAX_TOTAL_ORDERINGS4) / 2; - total_orderings3 = 32; - break; - case 17: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | (256 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = MAX_TOTAL_ORDERINGS4; - total_orderings3 = 32; - break; - case 18: - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | cEncodeBC1Iterative | (256 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = MAX_TOTAL_ORDERINGS4; - total_orderings3 = 32; - break; - case 19: - // This hidden mode is *extremely* slow and abuses the encoder. It's just for testing/training. - flags = cEncodeBC1TwoLeastSquaresPasses | cEncodeBC1UseFullMSEEval | cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use6PowerIters | cEncodeBC1Exhaustive | cEncodeBC1Iterative | (256 << cEncodeBC1EndpointSearchRoundsShift) | cEncodeBC1TryAllInitialEndponts; - flags |= (allow_3color ? cEncodeBC1Use3ColorBlocks : 0) | (allow_transparent_texels_for_black ? cEncodeBC1Use3ColorBlocksForBlackPixels : 0); - total_orderings4 = 32; - total_orderings3 = 32; - break; - } - - encode_bc1(pDst, pPixels, flags, total_orderings4, total_orderings3, pForce_selectors); - } - - static inline void encode_bc1_pick_initial(const color32* pSrc_pixels, uint32_t flags, bool grayscale_flag, - int min_r, int min_g, int min_b, int max_r, int max_g, int max_b, - int avg_r, int avg_g, int avg_b, int total_r, int total_g, int total_b, - int& lr, int& lg, int& lb, int& hr, int& hg, int& hb) - { - if (grayscale_flag) - { - const int fr = pSrc_pixels[0].r; - - // Grayscale blocks are a common enough case to specialize. - if ((max_r - min_r) < 2) - { - lr = lb = hr = hb = to_5(fr); - lg = hg = to_6(fr); - } - else - { - lr = lb = to_5(min_r); - lg = to_6(min_r); - - hr = hb = to_5(max_r); - hg = to_6(max_r); - } - } - else if (flags & cEncodeBC1Use2DLS) - { - // 2D Least Squares approach from Humus's example, with added inset and optimal rounding. - int big_chan = 0, min_chan_val = min_r, max_chan_val = max_r; - if ((max_g - min_g) > (max_chan_val - min_chan_val)) - big_chan = 1, min_chan_val = min_g, max_chan_val = max_g; - - if ((max_b - min_b) > (max_chan_val - min_chan_val)) - big_chan = 2, min_chan_val = min_b, max_chan_val = max_b; - - int sum_xy_r = 0, sum_xy_g = 0, sum_xy_b = 0; - vec3F l, h; - if (big_chan == 0) - { - for (uint32_t i = 0; i < 16; i++) - { - const int r = pSrc_pixels[i].r, g = pSrc_pixels[i].g, b = pSrc_pixels[i].b; - sum_xy_r += r * r, sum_xy_g += r * g, sum_xy_b += r * b; - } - - int sum_x = total_r; - int sum_x2 = sum_xy_r; - - float div = (float)(16 * sum_x2 - sum_x * sum_x); - float b_y = 0.0f, b_z = 0.0f; - if (fabs(div) > 1e-8f) - { - div = 1.0f / div; - b_y = (16 * sum_xy_g - sum_x * total_g) * div; - b_z = (16 * sum_xy_b - sum_x * total_b) * div; - } - - float a_y = (total_g - b_y * sum_x) / 16.0f; - float a_z = (total_b - b_z * sum_x) / 16.0f; - - l.c[1] = a_y + b_y * min_chan_val; - l.c[2] = a_z + b_z * min_chan_val; - - h.c[1] = a_y + b_y * max_chan_val; - h.c[2] = a_z + b_z * max_chan_val; - - float dg = (h.c[1] - l.c[1]); - float db = (h.c[2] - l.c[2]); - - h.c[1] = l.c[1] + dg * (15.0f / 16.0f); - h.c[2] = l.c[2] + db * (15.0f / 16.0f); - - l.c[1] = l.c[1] + dg * (1.0f / 16.0f); - l.c[2] = l.c[2] + db * (1.0f / 16.0f); - - float d = (float)(max_chan_val - min_chan_val); - float fmin_chan_val = min_chan_val + d * (1.0f / 16.0f); - float fmax_chan_val = min_chan_val + d * (15.0f / 16.0f); - - l.c[0] = fmin_chan_val; - h.c[0] = fmax_chan_val; - } - else if (big_chan == 1) - { - for (uint32_t i = 0; i < 16; i++) - { - const int r = pSrc_pixels[i].r, g = pSrc_pixels[i].g, b = pSrc_pixels[i].b; - sum_xy_r += g * r, sum_xy_g += g * g, sum_xy_b += g * b; - } - - int sum_x = total_g; - int sum_x2 = sum_xy_g; - - float div = (float)(16 * sum_x2 - sum_x * sum_x); - float b_x = 0.0f, b_z = 0.0f; - if (fabs(div) > 1e-8f) - { - div = 1.0f / div; - b_x = (16 * sum_xy_r - sum_x * total_r) * div; - b_z = (16 * sum_xy_b - sum_x * total_b) * div; - } - - float a_x = (total_r - b_x * sum_x) / 16.0f; - float a_z = (total_b - b_z * sum_x) / 16.0f; - - l.c[0] = a_x + b_x * min_chan_val; - l.c[2] = a_z + b_z * min_chan_val; - - h.c[0] = a_x + b_x * max_chan_val; - h.c[2] = a_z + b_z * max_chan_val; - - float dr = (h.c[0] - l.c[0]); - float db = (h.c[2] - l.c[2]); - - h.c[0] = l.c[0] + dr * (15.0f / 16.0f); - h.c[2] = l.c[2] + db * (15.0f / 16.0f); - - l.c[0] = l.c[0] + dr * (1.0f / 16.0f); - l.c[2] = l.c[2] + db * (1.0f / 16.0f); - - float d = (float)(max_chan_val - min_chan_val); - float fmin_chan_val = min_chan_val + d * (1.0f / 16.0f); - float fmax_chan_val = min_chan_val + d * (15.0f / 16.0f); - - l.c[1] = fmin_chan_val; - h.c[1] = fmax_chan_val; - } - else - { - for (uint32_t i = 0; i < 16; i++) - { - const int r = pSrc_pixels[i].r, g = pSrc_pixels[i].g, b = pSrc_pixels[i].b; - sum_xy_r += b * r, sum_xy_g += b * g, sum_xy_b += b * b; - } - - int sum_x = total_b; - int sum_x2 = sum_xy_b; - - float div = (float)(16 * sum_x2 - sum_x * sum_x); - float b_x = 0.0f, b_y = 0.0f; - if (fabs(div) > 1e-8f) - { - div = 1.0f / div; - b_x = (16 * sum_xy_r - sum_x * total_r) * div; - b_y = (16 * sum_xy_g - sum_x * total_g) * div; - } - - float a_x = (total_r - b_x * sum_x) / 16.0f; - float a_y = (total_g - b_y * sum_x) / 16.0f; - - l.c[0] = a_x + b_x * min_chan_val; - l.c[1] = a_y + b_y * min_chan_val; - - h.c[0] = a_x + b_x * max_chan_val; - h.c[1] = a_y + b_y * max_chan_val; - - float dr = (h.c[0] - l.c[0]); - float dg = (h.c[1] - l.c[1]); - - h.c[0] = l.c[0] + dr * (15.0f / 16.0f); - h.c[1] = l.c[1] + dg * (15.0f / 16.0f); - - l.c[0] = l.c[0] + dr * (1.0f / 16.0f); - l.c[1] = l.c[1] + dg * (1.0f / 16.0f); - - float d = (float)(max_chan_val - min_chan_val); - float fmin_chan_val = min_chan_val + d * (1.0f / 16.0f); - float fmax_chan_val = min_chan_val + d * (15.0f / 16.0f); - - l.c[2] = fmin_chan_val; - h.c[2] = fmax_chan_val; - } - - precise_round_565_noscale(l, h, lr, lg, lb, hr, hg, hb); - } - else if (flags & cEncodeBC1BoundingBox) - { - // Algorithm from icbc.h compress_dxt1_fast() - vec3F l, h; - l.c[0] = min_r * (1.0f / 255.0f); - l.c[1] = min_g * (1.0f / 255.0f); - l.c[2] = min_b * (1.0f / 255.0f); - - h.c[0] = max_r * (1.0f / 255.0f); - h.c[1] = max_g * (1.0f / 255.0f); - h.c[2] = max_b * (1.0f / 255.0f); - - const float bias = 8.0f / 255.0f; - float inset_r = (h.c[0] - l.c[0] - bias) * (1.0f / 16.0f); - float inset_g = (h.c[1] - l.c[1] - bias) * (1.0f / 16.0f); - float inset_b = (h.c[2] - l.c[2] - bias) * (1.0f / 16.0f); - - l.c[0] = clampf(l.c[0] + inset_r, 0.0f, 1.0f); - l.c[1] = clampf(l.c[1] + inset_g, 0.0f, 1.0f); - l.c[2] = clampf(l.c[2] + inset_b, 0.0f, 1.0f); - - h.c[0] = clampf(h.c[0] - inset_r, 0.0f, 1.0f); - h.c[1] = clampf(h.c[1] - inset_g, 0.0f, 1.0f); - h.c[2] = clampf(h.c[2] - inset_b, 0.0f, 1.0f); - - int icov_xz = 0, icov_yz = 0; - for (uint32_t i = 0; i < 16; i++) - { - int r = (int)pSrc_pixels[i].r - avg_r; - int g = (int)pSrc_pixels[i].g - avg_g; - int b = (int)pSrc_pixels[i].b - avg_b; - icov_xz += r * b; - icov_yz += g * b; - } - - if (icov_xz < 0) - std::swap(l.c[0], h.c[0]); - - if (icov_yz < 0) - std::swap(l.c[1], h.c[1]); - - precise_round_565(l, h, lr, lg, lb, hr, hg, hb); - } - else if (flags & cEncodeBC1BoundingBoxInt) - { - // Algorithm from icbc.h compress_dxt1_fast(), but converted to integer. - int inset_r = (max_r - min_r - 8) >> 4; - int inset_g = (max_g - min_g - 8) >> 4; - int inset_b = (max_b - min_b - 8) >> 4; - - min_r += inset_r; - min_g += inset_g; - min_b += inset_b; - if ((uint32_t)(min_r | min_g | min_b) > 255U) - { - min_r = clampi(min_r, 0, 255); - min_g = clampi(min_g, 0, 255); - min_b = clampi(min_b, 0, 255); - } - - max_r -= inset_r; - max_g -= inset_g; - max_b -= inset_b; - if ((uint32_t)(max_r | max_g | max_b) > 255U) - { - max_r = clampi(max_r, 0, 255); - max_g = clampi(max_g, 0, 255); - max_b = clampi(max_b, 0, 255); - } - - int icov_xz = 0, icov_yz = 0; - for (uint32_t i = 0; i < 16; i++) - { - int r = (int)pSrc_pixels[i].r - avg_r; - int g = (int)pSrc_pixels[i].g - avg_g; - int b = (int)pSrc_pixels[i].b - avg_b; - icov_xz += r * b; - icov_yz += g * b; - } - - int x0 = min_r; - int y0 = min_g; - int x1 = max_r; - int y1 = max_g; - - if (icov_xz < 0) - std::swap(x0, x1); - - if (icov_yz < 0) - std::swap(y0, y1); - - lr = to_5(x0); - lg = to_6(y0); - lb = to_5(min_b); - - hr = to_5(x1); - hg = to_6(y1); - hb = to_5(max_b); - } - else - { - // Select 2 colors along the principle axis. (There must be a faster/simpler way.) - uint32_t low_c = 0, high_c = 0; - - int icov[6] = { 0, 0, 0, 0, 0, 0 }; - for (uint32_t i = 0; i < 16; i++) - { - int r = (int)pSrc_pixels[i].r - avg_r; - int g = (int)pSrc_pixels[i].g - avg_g; - int b = (int)pSrc_pixels[i].b - avg_b; - icov[0] += r * r; - icov[1] += r * g; - icov[2] += r * b; - icov[3] += g * g; - icov[4] += g * b; - icov[5] += b * b; - } - - int saxis_r = 306, saxis_g = 601, saxis_b = 117; - - float xr = (float)(max_r - min_r); - float xg = (float)(max_g - min_g); - float xb = (float)(max_b - min_b); - - if (icov[2] < 0) - xr = -xr; - - if (icov[4] < 0) - xg = -xg; - - float cov[6]; - for (uint32_t i = 0; i < 6; i++) - cov[i] = (float)(icov[i]) * (1.0f / 255.0f); - - const uint32_t total_power_iters = (flags & cEncodeBC1Use6PowerIters) ? 6 : 4; - for (uint32_t power_iter = 0; power_iter < total_power_iters; power_iter++) - { - float r = xr * cov[0] + xg * cov[1] + xb * cov[2]; - float g = xr * cov[1] + xg * cov[3] + xb * cov[4]; - float b = xr * cov[2] + xg * cov[4] + xb * cov[5]; - xr = r; xg = g; xb = b; - } - - float k = maximum(fabsf(xr), fabsf(xg), fabsf(xb)); - if (k >= 2) - { - float m = 2048.0f / k; - saxis_r = (int)(xr * m); - saxis_g = (int)(xg * m); - saxis_b = (int)(xb * m); - } - - int low_dot = INT_MAX, high_dot = INT_MIN; - - saxis_r = (int)((uint32_t)saxis_r << 4U); - saxis_g = (int)((uint32_t)saxis_g << 4U); - saxis_b = (int)((uint32_t)saxis_b << 4U); - - for (uint32_t i = 0; i < 16; i += 4) - { - int dot0 = ((pSrc_pixels[i].r * saxis_r + pSrc_pixels[i].g * saxis_g + pSrc_pixels[i].b * saxis_b) & ~0xF) + i; - int dot1 = ((pSrc_pixels[i + 1].r * saxis_r + pSrc_pixels[i + 1].g * saxis_g + pSrc_pixels[i + 1].b * saxis_b) & ~0xF) + i + 1; - int dot2 = ((pSrc_pixels[i + 2].r * saxis_r + pSrc_pixels[i + 2].g * saxis_g + pSrc_pixels[i + 2].b * saxis_b) & ~0xF) + i + 2; - int dot3 = ((pSrc_pixels[i + 3].r * saxis_r + pSrc_pixels[i + 3].g * saxis_g + pSrc_pixels[i + 3].b * saxis_b) & ~0xF) + i + 3; - - int min_d01 = std::min(dot0, dot1); - int max_d01 = std::max(dot0, dot1); - - int min_d23 = std::min(dot2, dot3); - int max_d23 = std::max(dot2, dot3); - - int min_d = std::min(min_d01, min_d23); - int max_d = std::max(max_d01, max_d23); - - low_dot = std::min(low_dot, min_d); - high_dot = std::max(high_dot, max_d); - } - low_c = low_dot & 15; - high_c = high_dot & 15; - - lr = to_5(pSrc_pixels[low_c].r); - lg = to_6(pSrc_pixels[low_c].g); - lb = to_5(pSrc_pixels[low_c].b); - - hr = to_5(pSrc_pixels[high_c].r); - hg = to_6(pSrc_pixels[high_c].g); - hb = to_5(pSrc_pixels[high_c].b); - } - } - - static const int8_t s_adjacent_voxels[16][4] = - { - { 1,0,0, 3 }, // 0 - { 0,1,0, 4 }, // 1 - { 0,0,1, 5 }, // 2 - { -1,0,0, 0 }, // 3 - { 0,-1,0, 1 }, // 4 - { 0,0,-1, 2 }, // 5 - { 1,1,0, 9 }, // 6 - { 1,0,1, 10 }, // 7 - { 0,1,1, 11 }, // 8 - { -1,-1,0, 6 }, // 9 - { -1,0,-1, 7 }, // 10 - { 0,-1,-1, 8 }, // 11 - { -1,1,0, 13 }, // 12 - { 1,-1,0, 12 }, // 13 - { 0,-1,1, 15 }, // 14 - { 0,1,-1, 14 }, // 15 - }; - - // From icbc's high quality mode. - static inline void encode_bc1_endpoint_search(const color32* pSrc_pixels, bool any_black_pixels, - uint32_t flags, bc1_encode_results& results, uint32_t cur_err, const uint8_t* pForce_selectors) - { - int& lr = results.lr, & lg = results.lg, & lb = results.lb, & hr = results.hr, & hg = results.hg, & hb = results.hb; - uint8_t* sels = results.sels; - - int prev_improvement_index = 0, forbidden_direction = -1; - - const int endpoint_search_rounds = (flags & cEncodeBC1EndpointSearchRoundsMask) >> cEncodeBC1EndpointSearchRoundsShift; - for (int i = 0; i < endpoint_search_rounds; i++) - { - assert(s_adjacent_voxels[s_adjacent_voxels[i & 15][3]][3] == (i & 15)); - - if (forbidden_direction == (i & 31)) - continue; - - const int8_t delta[3] = { s_adjacent_voxels[i & 15][0], s_adjacent_voxels[i & 15][1], s_adjacent_voxels[i & 15][2] }; - - int trial_lr = lr, trial_lg = lg, trial_lb = lb, trial_hr = hr, trial_hg = hg, trial_hb = hb; - - if ((i >> 4) & 1) - { - trial_lr = clampi(trial_lr + delta[0], 0, 31); - trial_lg = clampi(trial_lg + delta[1], 0, 63); - trial_lb = clampi(trial_lb + delta[2], 0, 31); - } - else - { - trial_hr = clampi(trial_hr + delta[0], 0, 31); - trial_hg = clampi(trial_hg + delta[1], 0, 63); - trial_hb = clampi(trial_hb + delta[2], 0, 31); - } - - uint8_t trial_sels[16]; - - uint32_t trial_err; - if (results.m_3color) - { - trial_err = bc1_find_sels3_fullerr( - ((any_black_pixels) && ((flags & cEncodeBC1Use3ColorBlocksForBlackPixels) != 0)), - pSrc_pixels, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb, trial_sels, cur_err, pForce_selectors); - } - else - { - trial_err = bc1_find_sels4(flags, pSrc_pixels, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb, trial_sels, cur_err, pForce_selectors); - } - - if (trial_err < cur_err) - { - cur_err = trial_err; - - forbidden_direction = s_adjacent_voxels[i & 15][3] | (i & 16); - - lr = trial_lr, lg = trial_lg, lb = trial_lb, hr = trial_hr, hg = trial_hg, hb = trial_hb; - - memcpy(sels, trial_sels, 16); - - prev_improvement_index = i; - } - - if (i - prev_improvement_index > 32) - break; - } - } - - void encode_bc1(void* pDst, const uint8_t* pPixels, uint32_t flags, uint32_t total_orderings_to_try, uint32_t total_orderings_to_try3, const uint8_t* pForce_selectors) - { - assert(g_initialized); - - const color32* pSrc_pixels = (const color32*)pPixels; - bc1_block* pDst_block = static_cast(pDst); - - int avg_r, avg_g, avg_b, min_r, min_g, min_b, max_r, max_g, max_b; - - const uint32_t fr = pSrc_pixels[0].r, fg = pSrc_pixels[0].g, fb = pSrc_pixels[0].b; - - uint32_t j; - for (j = 15; j >= 1; --j) - if ((pSrc_pixels[j].r != fr) || (pSrc_pixels[j].g != fg) || (pSrc_pixels[j].b != fb)) - break; - - if (j == 0) - { - encode_bc1_solid_block(pDst, fr, fg, fb, (flags & (cEncodeBC1Use3ColorBlocks | cEncodeBC1Use3ColorBlocksForBlackPixels)) != 0); - return; - } - - int total_r = fr, total_g = fg, total_b = fb; - - max_r = fr, max_g = fg, max_b = fb; - min_r = fr, min_g = fg, min_b = fb; - - uint32_t grayscale_flag = (fr == fg) && (fr == fb); - uint32_t any_black_pixels = (fr | fg | fb) < 4; - - for (uint32_t i = 1; i < 16; i++) - { - const int r = pSrc_pixels[i].r, g = pSrc_pixels[i].g, b = pSrc_pixels[i].b; - - grayscale_flag &= ((r == g) && (r == b)); - any_black_pixels |= ((r | g | b) < 4); - - max_r = std::max(max_r, r); max_g = std::max(max_g, g); max_b = std::max(max_b, b); - min_r = std::min(min_r, r); min_g = std::min(min_g, g); min_b = std::min(min_b, b); - total_r += r; total_g += g; total_b += b; - } - - avg_r = (total_r + 8) >> 4, avg_g = (total_g + 8) >> 4, avg_b = (total_b + 8) >> 4; - - bc1_encode_results results; - results.m_3color = false; - - uint8_t* sels = results.sels; - int& lr = results.lr, & lg = results.lg, & lb = results.lb, & hr = results.hr, & hg = results.hg, & hb = results.hb; - int orig_lr = 0, orig_lg = 0, orig_lb = 0, orig_hr = 0, orig_hg = 0, orig_hb = 0; - - lr = 0, lg = 0, lb = 0, hr = 0, hg = 0, hb = 0; - - const bool needs_block_error = ((flags & (cEncodeBC1UseLikelyTotalOrderings | cEncodeBC1Use3ColorBlocks | cEncodeBC1UseFullMSEEval | cEncodeBC1EndpointSearchRoundsMask)) != 0) || - (any_black_pixels && ((flags & cEncodeBC1Use3ColorBlocksForBlackPixels) != 0)); - - uint32_t cur_err = UINT32_MAX; - - if (!needs_block_error) - { - assert((flags & cEncodeBC1TryAllInitialEndponts) == 0); - - encode_bc1_pick_initial(pSrc_pixels, flags, grayscale_flag != 0, - min_r, min_g, min_b, max_r, max_g, max_b, - avg_r, avg_g, avg_b, total_r, total_g, total_b, - lr, lg, lb, hr, hg, hb); - - orig_lr = lr, orig_lg = lg, orig_lb = lb, orig_hr = hr, orig_hg = hg, orig_hb = hb; - - bc1_find_sels4_noerr(pSrc_pixels, lr, lg, lb, hr, hg, hb, sels, pForce_selectors); - - const uint32_t total_ls_passes = flags & cEncodeBC1TwoLeastSquaresPasses ? 2 : 1; - for (uint32_t ls_pass = 0; ls_pass < total_ls_passes; ls_pass++) - { - int trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb; - - vec3F xl, xh; - if (!compute_least_squares_endpoints4_rgb(pSrc_pixels, sels, &xl, &xh, total_r, total_g, total_b)) - { - // All selectors equal - treat it as a solid block which should always be equal or better. - trial_lr = g_bc1_match5_equals_1[avg_r].m_hi; - trial_lg = g_bc1_match6_equals_1[avg_g].m_hi; - trial_lb = g_bc1_match5_equals_1[avg_b].m_hi; - - trial_hr = g_bc1_match5_equals_1[avg_r].m_lo; - trial_hg = g_bc1_match6_equals_1[avg_g].m_lo; - trial_hb = g_bc1_match5_equals_1[avg_b].m_lo; - - // In high/higher quality mode, let it try again in case the optimal tables have caused the sels to diverge. - } - else - { - precise_round_565(xl, xh, trial_hr, trial_hg, trial_hb, trial_lr, trial_lg, trial_lb); - } - - if ((lr == trial_lr) && (lg == trial_lg) && (lb == trial_lb) && (hr == trial_hr) && (hg == trial_hg) && (hb == trial_hb)) - break; - - bc1_find_sels4_noerr(pSrc_pixels, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb, sels, pForce_selectors); - - lr = trial_lr; - lg = trial_lg; - lb = trial_lb; - hr = trial_hr; - hg = trial_hg; - hb = trial_hb; - - } // ls_pass - } - else - { - const uint32_t total_rounds = (flags & cEncodeBC1TryAllInitialEndponts) ? 2 : 1; - for (uint32_t round = 0; round < total_rounds; round++) - { - uint32_t modified_flags = flags; - if (round == 1) - { - modified_flags &= ~(cEncodeBC1Use2DLS | cEncodeBC1BoundingBox); - modified_flags |= cEncodeBC1BoundingBox; - } - - int round_lr, round_lg, round_lb, round_hr, round_hg, round_hb; - uint8_t round_sels[16]; - - encode_bc1_pick_initial(pSrc_pixels, modified_flags, grayscale_flag != 0, - min_r, min_g, min_b, max_r, max_g, max_b, - avg_r, avg_g, avg_b, total_r, total_g, total_b, - round_lr, round_lg, round_lb, round_hr, round_hg, round_hb); - - int orig_round_lr = round_lr, orig_round_lg = round_lg, orig_round_lb = round_lb, orig_round_hr = round_hr, orig_round_hg = round_hg, orig_round_hb = round_hb; - - uint32_t round_err = bc1_find_sels4(flags, pSrc_pixels, round_lr, round_lg, round_lb, round_hr, round_hg, round_hb, round_sels, UINT32_MAX, pForce_selectors); - - const uint32_t total_ls_passes = flags & cEncodeBC1TwoLeastSquaresPasses ? 2 : 1; - for (uint32_t ls_pass = 0; ls_pass < total_ls_passes; ls_pass++) - { - int trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb; - - vec3F xl, xh; - if (!compute_least_squares_endpoints4_rgb(pSrc_pixels, round_sels, &xl, &xh, total_r, total_g, total_b)) - { - // All selectors equal - treat it as a solid block which should always be equal or better. - trial_lr = g_bc1_match5_equals_1[avg_r].m_hi; - trial_lg = g_bc1_match6_equals_1[avg_g].m_hi; - trial_lb = g_bc1_match5_equals_1[avg_b].m_hi; - - trial_hr = g_bc1_match5_equals_1[avg_r].m_lo; - trial_hg = g_bc1_match6_equals_1[avg_g].m_lo; - trial_hb = g_bc1_match5_equals_1[avg_b].m_lo; - - // In high/higher quality mode, let it try again in case the optimal tables have caused the sels to diverge. - } - else - { - precise_round_565(xl, xh, trial_hr, trial_hg, trial_hb, trial_lr, trial_lg, trial_lb); - } - - if ((round_lr == trial_lr) && (round_lg == trial_lg) && (round_lb == trial_lb) && (round_hr == trial_hr) && (round_hg == trial_hg) && (round_hb == trial_hb)) - break; - - uint8_t trial_sels[16]; - uint32_t trial_err = bc1_find_sels4(flags, pSrc_pixels, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb, trial_sels, round_err, pForce_selectors); - - if (trial_err < round_err) - { - round_lr = trial_lr; - round_lg = trial_lg; - round_lb = trial_lb; - - round_hr = trial_hr; - round_hg = trial_hg; - round_hb = trial_hb; - - round_err = trial_err; - memcpy(round_sels, trial_sels, 16); - } - else - break; - - } // ls_pass - - if (round_err <= cur_err) - { - cur_err = round_err; - - lr = round_lr; - lg = round_lg; - lb = round_lb; - hr = round_hr; - hg = round_hg; - hb = round_hb; - - orig_lr = orig_round_lr; - orig_lg = orig_round_lg; - orig_lb = orig_round_lb; - orig_hr = orig_round_hr; - orig_hg = orig_round_hg; - orig_hb = orig_round_hb; - - memcpy(sels, round_sels, 16); - } - - } // round - } - - if ((cur_err) && (flags & cEncodeBC1UseLikelyTotalOrderings)) - { - assert(needs_block_error); - - const uint32_t total_iters = (flags & cEncodeBC1Iterative) ? 2 : 1; - for (uint32_t iter_index = 0; iter_index < total_iters; iter_index++) - { - const uint32_t orig_err = cur_err; - - hist4 h; - for (uint32_t i = 0; i < 16; i++) - { - assert(sels[i] < 4); - h.m_hist[sels[i]]++; - } - - const uint32_t orig_total_order_index = h.lookup_total_ordering_index(); - - int r0, g0, b0, r3, g3, b3; - r0 = (lr << 3) | (lr >> 2); g0 = (lg << 2) | (lg >> 4); b0 = (lb << 3) | (lb >> 2); - r3 = (hr << 3) | (hr >> 2); g3 = (hg << 2) | (hg >> 4); b3 = (hb << 3) | (hb >> 2); - - int ar = r3 - r0, ag = g3 - g0, ab = b3 - b0; - - int dots[16]; - for (uint32_t i = 0; i < 16; i++) - { - int r = pSrc_pixels[i].r; - int g = pSrc_pixels[i].g; - int b = pSrc_pixels[i].b; - int d = 0x1000000 + (r * ar + g * ag + b * ab); - assert(d >= 0); - dots[i] = (d << 4) + i; - } - - std::sort(dots, dots + 16); - - uint32_t r_sum[17], g_sum[17], b_sum[17]; - uint32_t r = 0, g = 0, b = 0; - for (uint32_t i = 0; i < 16; i++) - { - const uint32_t p = dots[i] & 15; - - r_sum[i] = r; - g_sum[i] = g; - b_sum[i] = b; - - r += pSrc_pixels[p].r; - g += pSrc_pixels[p].g; - b += pSrc_pixels[p].b; - } - - r_sum[16] = total_r; - g_sum[16] = total_g; - b_sum[16] = total_b; - - const uint32_t q_total = (flags & cEncodeBC1Exhaustive) ? NUM_UNIQUE_TOTAL_ORDERINGS4 : clampi(total_orderings_to_try, MIN_TOTAL_ORDERINGS, MAX_TOTAL_ORDERINGS4); - for (uint32_t q = 0; q < q_total; q++) - { - const uint32_t s = (flags & cEncodeBC1Exhaustive) ? q : g_best_total_orderings4[orig_total_order_index][q]; - - int trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb; - - vec3F xl, xh; - - if ((s == TOTAL_ORDER_4_0_16) || (s == TOTAL_ORDER_4_1_16) || (s == TOTAL_ORDER_4_2_16) || (s == TOTAL_ORDER_4_3_16)) - { - trial_lr = g_bc1_match5_equals_1[avg_r].m_hi; - trial_lg = g_bc1_match6_equals_1[avg_g].m_hi; - trial_lb = g_bc1_match5_equals_1[avg_b].m_hi; - - trial_hr = g_bc1_match5_equals_1[avg_r].m_lo; - trial_hg = g_bc1_match6_equals_1[avg_g].m_lo; - trial_hb = g_bc1_match5_equals_1[avg_b].m_lo; - } - else - { - compute_least_squares_endpoints4_rgb(&xl, &xh, total_r, total_g, total_b, - g_selector_factors4[s][0], g_selector_factors4[s][1], g_selector_factors4[s][2], s, r_sum, g_sum, b_sum); - - precise_round_565(xl, xh, trial_hr, trial_hg, trial_hb, trial_lr, trial_lg, trial_lb); - } - - uint8_t trial_sels[16]; - - uint32_t trial_err = bc1_find_sels4(flags, pSrc_pixels, trial_lr, trial_lg, trial_lb, trial_hr, trial_hg, trial_hb, trial_sels, cur_err, pForce_selectors); - - if (trial_err < cur_err) - { - cur_err = trial_err; - - lr = trial_lr; - lg = trial_lg; - lb = trial_lb; - - hr = trial_hr; - hg = trial_hg; - hb = trial_hb; - - memcpy(sels, trial_sels, 16); - } - - } // s - - if ((!cur_err) || (cur_err == orig_err)) - break; - - } // iter_index - } - - if (((flags & (cEncodeBC1Use3ColorBlocks | cEncodeBC1Use3ColorBlocksForBlackPixels)) != 0) && (cur_err)) - { - if (flags & cEncodeBC1Use3ColorBlocks) - { - assert(needs_block_error); - try_3color_block(pSrc_pixels, flags, cur_err, avg_r, avg_g, avg_b, orig_lr, orig_lg, orig_lb, orig_hr, orig_hg, orig_hb, total_r, total_g, total_b, total_orderings_to_try3, results, pForce_selectors); - } - - if ((any_black_pixels) && ((flags & cEncodeBC1Use3ColorBlocksForBlackPixels) != 0)) - { - assert(needs_block_error); - try_3color_block_useblack(pSrc_pixels, flags, cur_err, results, pForce_selectors); - } - } - - if ((flags & cEncodeBC1EndpointSearchRoundsMask) && (cur_err)) - { - assert(needs_block_error); - - encode_bc1_endpoint_search(pSrc_pixels, any_black_pixels != 0, flags, results, cur_err, pForce_selectors); - } - - if (results.m_3color) - bc1_encode3(pDst_block, results.lr, results.lg, results.lb, results.hr, results.hg, results.hb, results.sels); - else - bc1_encode4(pDst_block, results.lr, results.lg, results.lb, results.hr, results.hg, results.hb, results.sels); - } - - // BC3-5 - - void encode_bc4(void* pDst, const uint8_t* pPixels, uint32_t stride) - { - assert(g_initialized); - - uint32_t min0_v, max0_v, min1_v, max1_v, min2_v, max2_v, min3_v, max3_v; - - { - min0_v = max0_v = pPixels[0 * stride]; - min1_v = max1_v = pPixels[1 * stride]; - min2_v = max2_v = pPixels[2 * stride]; - min3_v = max3_v = pPixels[3 * stride]; - } - - { - uint32_t v0 = pPixels[4 * stride]; min0_v = std::min(min0_v, v0); max0_v = std::max(max0_v, v0); - uint32_t v1 = pPixels[5 * stride]; min1_v = std::min(min1_v, v1); max1_v = std::max(max1_v, v1); - uint32_t v2 = pPixels[6 * stride]; min2_v = std::min(min2_v, v2); max2_v = std::max(max2_v, v2); - uint32_t v3 = pPixels[7 * stride]; min3_v = std::min(min3_v, v3); max3_v = std::max(max3_v, v3); - } - - { - uint32_t v0 = pPixels[8 * stride]; min0_v = std::min(min0_v, v0); max0_v = std::max(max0_v, v0); - uint32_t v1 = pPixels[9 * stride]; min1_v = std::min(min1_v, v1); max1_v = std::max(max1_v, v1); - uint32_t v2 = pPixels[10 * stride]; min2_v = std::min(min2_v, v2); max2_v = std::max(max2_v, v2); - uint32_t v3 = pPixels[11 * stride]; min3_v = std::min(min3_v, v3); max3_v = std::max(max3_v, v3); - } - - { - uint32_t v0 = pPixels[12 * stride]; min0_v = std::min(min0_v, v0); max0_v = std::max(max0_v, v0); - uint32_t v1 = pPixels[13 * stride]; min1_v = std::min(min1_v, v1); max1_v = std::max(max1_v, v1); - uint32_t v2 = pPixels[14 * stride]; min2_v = std::min(min2_v, v2); max2_v = std::max(max2_v, v2); - uint32_t v3 = pPixels[15 * stride]; min3_v = std::min(min3_v, v3); max3_v = std::max(max3_v, v3); - } - - const uint32_t min_v = minimum(min0_v, min1_v, min2_v, min3_v); - const uint32_t max_v = maximum(max0_v, max1_v, max2_v, max3_v); - - uint8_t* pDst_bytes = static_cast(pDst); - pDst_bytes[0] = (uint8_t)max_v; - pDst_bytes[1] = (uint8_t)min_v; - - if (max_v == min_v) - { - memset(pDst_bytes + 2, 0, 6); - return; - } - - const uint32_t delta = max_v - min_v; - - // min_v is now 0. Compute thresholds between values by scaling max_v. It's x14 because we're adding two x7 scale factors. - const int t0 = delta * 13; - const int t1 = delta * 11; - const int t2 = delta * 9; - const int t3 = delta * 7; - const int t4 = delta * 5; - const int t5 = delta * 3; - const int t6 = delta * 1; - - // BC4 floors in its divisions, which we compensate for with the 4 bias. - // This function is optimal for all possible inputs (i.e. it outputs the same results as checking all 8 values and choosing the closest one). - const int bias = 4 - min_v * 14; - - static const uint32_t s_tran0[8] = { 1U , 7U , 6U , 5U , 4U , 3U , 2U , 0U }; - static const uint32_t s_tran1[8] = { 1U << 3U, 7U << 3U, 6U << 3U, 5U << 3U, 4U << 3U, 3U << 3U, 2U << 3U, 0U << 3U }; - static const uint32_t s_tran2[8] = { 1U << 6U, 7U << 6U, 6U << 6U, 5U << 6U, 4U << 6U, 3U << 6U, 2U << 6U, 0U << 6U }; - static const uint32_t s_tran3[8] = { 1U << 9U, 7U << 9U, 6U << 9U, 5U << 9U, 4U << 9U, 3U << 9U, 2U << 9U, 0U << 9U }; - - uint64_t a0, a1, a2, a3; - { - const int v0 = pPixels[0 * stride] * 14 + bias; - const int v1 = pPixels[1 * stride] * 14 + bias; - const int v2 = pPixels[2 * stride] * 14 + bias; - const int v3 = pPixels[3 * stride] * 14 + bias; - a0 = s_tran0[(v0 >= t0) + (v0 >= t1) + (v0 >= t2) + (v0 >= t3) + (v0 >= t4) + (v0 >= t5) + (v0 >= t6)]; - a1 = s_tran1[(v1 >= t0) + (v1 >= t1) + (v1 >= t2) + (v1 >= t3) + (v1 >= t4) + (v1 >= t5) + (v1 >= t6)]; - a2 = s_tran2[(v2 >= t0) + (v2 >= t1) + (v2 >= t2) + (v2 >= t3) + (v2 >= t4) + (v2 >= t5) + (v2 >= t6)]; - a3 = s_tran3[(v3 >= t0) + (v3 >= t1) + (v3 >= t2) + (v3 >= t3) + (v3 >= t4) + (v3 >= t5) + (v3 >= t6)]; - } - - { - const int v0 = pPixels[4 * stride] * 14 + bias; - const int v1 = pPixels[5 * stride] * 14 + bias; - const int v2 = pPixels[6 * stride] * 14 + bias; - const int v3 = pPixels[7 * stride] * 14 + bias; - a0 |= (uint64_t)(s_tran0[(v0 >= t0) + (v0 >= t1) + (v0 >= t2) + (v0 >= t3) + (v0 >= t4) + (v0 >= t5) + (v0 >= t6)] << 12U); - a1 |= (uint64_t)(s_tran1[(v1 >= t0) + (v1 >= t1) + (v1 >= t2) + (v1 >= t3) + (v1 >= t4) + (v1 >= t5) + (v1 >= t6)] << 12U); - a2 |= (uint64_t)(s_tran2[(v2 >= t0) + (v2 >= t1) + (v2 >= t2) + (v2 >= t3) + (v2 >= t4) + (v2 >= t5) + (v2 >= t6)] << 12U); - a3 |= (uint64_t)(s_tran3[(v3 >= t0) + (v3 >= t1) + (v3 >= t2) + (v3 >= t3) + (v3 >= t4) + (v3 >= t5) + (v3 >= t6)] << 12U); - } - - { - const int v0 = pPixels[8 * stride] * 14 + bias; - const int v1 = pPixels[9 * stride] * 14 + bias; - const int v2 = pPixels[10 * stride] * 14 + bias; - const int v3 = pPixels[11 * stride] * 14 + bias; - a0 |= (((uint64_t)s_tran0[(v0 >= t0) + (v0 >= t1) + (v0 >= t2) + (v0 >= t3) + (v0 >= t4) + (v0 >= t5) + (v0 >= t6)]) << 24U); - a1 |= (((uint64_t)s_tran1[(v1 >= t0) + (v1 >= t1) + (v1 >= t2) + (v1 >= t3) + (v1 >= t4) + (v1 >= t5) + (v1 >= t6)]) << 24U); - a2 |= (((uint64_t)s_tran2[(v2 >= t0) + (v2 >= t1) + (v2 >= t2) + (v2 >= t3) + (v2 >= t4) + (v2 >= t5) + (v2 >= t6)]) << 24U); - a3 |= (((uint64_t)s_tran3[(v3 >= t0) + (v3 >= t1) + (v3 >= t2) + (v3 >= t3) + (v3 >= t4) + (v3 >= t5) + (v3 >= t6)]) << 24U); - } - - { - const int v0 = pPixels[12 * stride] * 14 + bias; - const int v1 = pPixels[13 * stride] * 14 + bias; - const int v2 = pPixels[14 * stride] * 14 + bias; - const int v3 = pPixels[15 * stride] * 14 + bias; - a0 |= (((uint64_t)s_tran0[(v0 >= t0) + (v0 >= t1) + (v0 >= t2) + (v0 >= t3) + (v0 >= t4) + (v0 >= t5) + (v0 >= t6)]) << 36U); - a1 |= (((uint64_t)s_tran1[(v1 >= t0) + (v1 >= t1) + (v1 >= t2) + (v1 >= t3) + (v1 >= t4) + (v1 >= t5) + (v1 >= t6)]) << 36U); - a2 |= (((uint64_t)s_tran2[(v2 >= t0) + (v2 >= t1) + (v2 >= t2) + (v2 >= t3) + (v2 >= t4) + (v2 >= t5) + (v2 >= t6)]) << 36U); - a3 |= (((uint64_t)s_tran3[(v3 >= t0) + (v3 >= t1) + (v3 >= t2) + (v3 >= t3) + (v3 >= t4) + (v3 >= t5) + (v3 >= t6)]) << 36U); - } - - const uint64_t f = a0 | a1 | a2 | a3; - - pDst_bytes[2] = (uint8_t)f; - pDst_bytes[3] = (uint8_t)(f >> 8U); - pDst_bytes[4] = (uint8_t)(f >> 16U); - pDst_bytes[5] = (uint8_t)(f >> 24U); - pDst_bytes[6] = (uint8_t)(f >> 32U); - pDst_bytes[7] = (uint8_t)(f >> 40U); - } - - uint32_t encode_bc4_hq(void* pDst, const uint8_t* pPixels, uint32_t stride, uint32_t search_rad, uint32_t mode_flag, const uint8_t* pForce_selectors) - { - assert(mode_flag); - - uint8_t* pDst_bytes = static_cast(pDst); - - uint32_t min_val = 255, max_val = 0; - for (uint32_t i = 0; i < 16; i++) - { - uint32_t val = pPixels[i * stride]; - min_val = std::min(val, min_val); - max_val = std::max(val, max_val); - } - - if (min_val == max_val) - { - if (mode_flag & BC4_USE_MODE6_FLAG) - { - pDst_bytes[0] = (uint8_t)min_val; - pDst_bytes[1] = (uint8_t)min_val; - - memset(pDst_bytes + 2, 0, 6); - - assert(!(pDst_bytes[0] > pDst_bytes[1])); - } - else - { - // Use an 8 value encoding - if (min_val > 0) - { - pDst_bytes[0] = (uint8_t)min_val; - pDst_bytes[1] = (uint8_t)min_val - 1; - - memset(pDst_bytes + 2, 0, 6); - } - else - { - static const uint8_t s_const_1_vals[8] = { 1, 0, 0x49, 0x92, 0x24, 0x49, 0x92, 0x24 }; - memcpy(pDst_bytes, s_const_1_vals, 8); - } - - assert(pDst_bytes[0] > pDst_bytes[1]); - } - -#if defined(_DEBUG) || defined(DEBUG) - { - bc4_block* pBlock = (bc4_block*)pDst; - uint8_t pixels[16]; - unpack_bc4(pDst, pixels, 1); - for (uint32_t i = 0; i < 16; i++) - assert(pixels[i] == min_val); - if (mode_flag & BC4_USE_MODE6_FLAG) - { - assert(pBlock->is_alpha6_block()); - } - else - { - assert(!pBlock->is_alpha6_block()); - } - } -#endif - - return 0; - } - - uint32_t best_err = UINT32_MAX; - for (uint32_t mode = 0; mode < 2; mode++) - { - if ((mode_flag & (1 << mode)) == 0) - continue; - - for (int lo_delta = -(int)search_rad; lo_delta <= (int)search_rad; lo_delta++) - { - for (int hi_delta = -(int)search_rad; hi_delta <= (int)search_rad; hi_delta++) - { - bc4_block trial_block; - trial_block.m_endpoints[0] = (uint8_t)clamp(max_val + hi_delta, 0, 255); - trial_block.m_endpoints[1] = (uint8_t)clamp(min_val + lo_delta, 0, 255); - - if (trial_block.m_endpoints[0] == trial_block.m_endpoints[1]) - continue; - - if (mode == 0) - { - if (trial_block.is_alpha6_block()) - std::swap(trial_block.m_endpoints[0], trial_block.m_endpoints[1]); - } - else if (!trial_block.is_alpha6_block()) - std::swap(trial_block.m_endpoints[0], trial_block.m_endpoints[1]); - - uint8_t block_vals[8]; - trial_block.get_block_values(block_vals, trial_block.m_endpoints[0], trial_block.m_endpoints[1]); - - uint32_t trial_err = 0; - uint8_t trial_sels[16]; - - if (pForce_selectors) - { - memcpy(trial_sels, pForce_selectors, 16); - - for (uint32_t i = 0; i < 16; i++) - trial_err += squarei(block_vals[pForce_selectors[i]] - pPixels[i * stride]); - } - else - { - for (uint32_t i = 0; i < 16; i++) - { - uint32_t best_index_err = UINT32_MAX; - uint32_t best_index = 0; - for (uint32_t j = 0; j < 8; j++) - { - uint32_t err = squarei(block_vals[j] - pPixels[i * stride]); - if (err < best_index_err) - { - best_index_err = err; - best_index = j; - if (!err) - break; - } - } - - trial_err += best_index_err; - if (trial_err >= best_err) - break; - - trial_sels[i] = (uint8_t)best_index; - } // i - } - - if (trial_err < best_err) - { - best_err = trial_err; - - uint64_t sel_vals = 0; - for (uint32_t i = 0; i < 16; i++) - sel_vals |= ((uint64_t)trial_sels[i] << (i * 3)); - - trial_block.m_selectors[0] = (uint8_t)sel_vals; - trial_block.m_selectors[1] = (uint8_t)(sel_vals >> 8); - trial_block.m_selectors[2] = (uint8_t)(sel_vals >> 16); - trial_block.m_selectors[3] = (uint8_t)(sel_vals >> 24); - trial_block.m_selectors[4] = (uint8_t)(sel_vals >> 32); - trial_block.m_selectors[5] = (uint8_t)(sel_vals >> 40); - - memcpy(pDst_bytes, &trial_block, sizeof(bc4_block)); - } // if (trial_err < best_err) - - } // hi_delta - - } // lo_delta - - } // mode - - return best_err; - } - - void encode_bc3(void* pDst, const uint8_t* pPixels, uint32_t flags, uint32_t total_orderings_to_try) - { - assert(g_initialized); - - // 3-color blocks are not allowed with BC3 (on most GPU's). - flags &= ~(cEncodeBC1Use3ColorBlocksForBlackPixels | cEncodeBC1Use3ColorBlocks); - - encode_bc4(pDst, pPixels + 3, 4); - encode_bc1(static_cast(pDst) + 8, pPixels, flags, total_orderings_to_try); - } - - void encode_bc3(uint32_t level, void* pDst, const uint8_t* pPixels) - { - assert(g_initialized); - - encode_bc4(pDst, pPixels + 3, 4); - encode_bc1(level, static_cast(pDst) + 8, pPixels, false, false); - } - - void encode_bc3_hq(uint32_t level, void* pDst, const uint8_t* pPixels, uint32_t alpha_search_rad, uint32_t alpha_modes) - { - assert(g_initialized); - - encode_bc4_hq(pDst, pPixels + 3, 4, alpha_search_rad, alpha_modes); - encode_bc1(level, static_cast(pDst) + 8, pPixels, false, false); - } - - void encode_bc5(void* pDst, const uint8_t* pPixels, uint32_t chan0, uint32_t chan1, uint32_t stride) - { - assert(g_initialized); - - encode_bc4(pDst, pPixels + chan0, stride); - encode_bc4(static_cast(pDst) + 8, pPixels + chan1, stride); - } - - void encode_bc5_hq(void* pDst, const uint8_t* pPixels, uint32_t chan0, uint32_t chan1, uint32_t stride, uint32_t alpha_search_rad, uint32_t alpha_modes) - { - assert(g_initialized); - - encode_bc4_hq(pDst, pPixels + chan0, stride, alpha_search_rad, alpha_modes); - encode_bc4_hq(static_cast(pDst) + 8, pPixels + chan1, stride, alpha_search_rad, alpha_modes); - } - - bool unpack_bc1_block_colors(const void* pBlock_bits, color32* c, bc1_approx_mode mode) - { - const bc1_block* pBlock = static_cast(pBlock_bits); - - const uint32_t l = pBlock->get_low_color(); - const uint32_t h = pBlock->get_high_color(); - - const int cr0 = (l >> 11) & 31; - const int cg0 = (l >> 5) & 63; - const int cb0 = l & 31; - const int r0 = (cr0 << 3) | (cr0 >> 2); - const int g0 = (cg0 << 2) | (cg0 >> 4); - const int b0 = (cb0 << 3) | (cb0 >> 2); - - const int cr1 = (h >> 11) & 31; - const int cg1 = (h >> 5) & 63; - const int cb1 = h & 31; - const int r1 = (cr1 << 3) | (cr1 >> 2); - const int g1 = (cg1 << 2) | (cg1 >> 4); - const int b1 = (cb1 << 3) | (cb1 >> 2); - - bool used_punchthrough = false; - - if (l > h) - { - c[0].set_noclamp_rgba(r0, g0, b0, 255); - c[1].set_noclamp_rgba(r1, g1, b1, 255); - switch (mode) - { - case bc1_approx_mode::cBC1Ideal: - c[2].set_noclamp_rgba((r0 * 2 + r1) / 3, (g0 * 2 + g1) / 3, (b0 * 2 + b1) / 3, 255); - c[3].set_noclamp_rgba((r1 * 2 + r0) / 3, (g1 * 2 + g0) / 3, (b1 * 2 + b0) / 3, 255); - break; - case bc1_approx_mode::cBC1IdealRound4: - c[2].set_noclamp_rgba((r0 * 2 + r1 + 1) / 3, (g0 * 2 + g1 + 1) / 3, (b0 * 2 + b1 + 1) / 3, 255); - c[3].set_noclamp_rgba((r1 * 2 + r0 + 1) / 3, (g1 * 2 + g0 + 1) / 3, (b1 * 2 + b0 + 1) / 3, 255); - break; - case bc1_approx_mode::cBC1NVidia: - c[2].set_noclamp_rgba(interp_5_nv(cr0, cr1), interp_6_nv(g0, g1), interp_5_nv(cb0, cb1), 255); - c[3].set_noclamp_rgba(interp_5_nv(cr1, cr0), interp_6_nv(g1, g0), interp_5_nv(cb1, cb0), 255); - break; - case bc1_approx_mode::cBC1AMD: - c[2].set_noclamp_rgba(interp_5_6_amd(r0, r1), interp_5_6_amd(g0, g1), interp_5_6_amd(b0, b1), 255); - c[3].set_noclamp_rgba(interp_5_6_amd(r1, r0), interp_5_6_amd(g1, g0), interp_5_6_amd(b1, b0), 255); - break; - } - } - else - { - c[0].set_noclamp_rgba(r0, g0, b0, 255); - c[1].set_noclamp_rgba(r1, g1, b1, 255); - switch (mode) - { - case bc1_approx_mode::cBC1Ideal: - case bc1_approx_mode::cBC1IdealRound4: - c[2].set_noclamp_rgba((r0 + r1) / 2, (g0 + g1) / 2, (b0 + b1) / 2, 255); - break; - case bc1_approx_mode::cBC1NVidia: - c[2].set_noclamp_rgba(interp_half_5_nv(cr0, cr1), interp_half_6_nv(g0, g1), interp_half_5_nv(cb0, cb1), 255); - break; - case bc1_approx_mode::cBC1AMD: - c[2].set_noclamp_rgba(interp_half_5_6_amd(r0, r1), interp_half_5_6_amd(g0, g1), interp_half_5_6_amd(b0, b1), 255); - break; - } - - c[3].set_noclamp_rgba(0, 0, 0, 0); - used_punchthrough = true; - } - - return used_punchthrough; - } - - // Returns true if the block uses 3 color punchthrough alpha mode. - bool unpack_bc1(const void* pBlock_bits, void* pPixels, bool set_alpha, bc1_approx_mode mode) - { - color32* pDst_pixels = static_cast(pPixels); - - static_assert(sizeof(bc1_block) == 8, "sizeof(bc1_block) == 8"); - static_assert(sizeof(bc4_block) == 8, "sizeof(bc4_block) == 8"); - - const bc1_block* pBlock = static_cast(pBlock_bits); - - color32 c[4]; - const bool used_punchthrough = unpack_bc1_block_colors(pBlock_bits, c, mode); - - if (set_alpha) - { - for (uint32_t y = 0; y < 4; y++, pDst_pixels += 4) - { - pDst_pixels[0] = c[pBlock->get_selector(0, y)]; - pDst_pixels[1] = c[pBlock->get_selector(1, y)]; - pDst_pixels[2] = c[pBlock->get_selector(2, y)]; - pDst_pixels[3] = c[pBlock->get_selector(3, y)]; - } - } - else - { - for (uint32_t y = 0; y < 4; y++, pDst_pixels += 4) - { - pDst_pixels[0].set_rgb(c[pBlock->get_selector(0, y)]); - pDst_pixels[1].set_rgb(c[pBlock->get_selector(1, y)]); - pDst_pixels[2].set_rgb(c[pBlock->get_selector(2, y)]); - pDst_pixels[3].set_rgb(c[pBlock->get_selector(3, y)]); - } - } - - return used_punchthrough; - } - - void unpack_bc4(const void* pBlock_bits, uint8_t* pPixels, uint32_t stride) - { - static_assert(sizeof(bc4_block) == 8, "sizeof(bc4_block) == 8"); - - const bc4_block* pBlock = static_cast(pBlock_bits); - - uint8_t sel_values[8]; - bc4_block::get_block_values(sel_values, pBlock->get_low_alpha(), pBlock->get_high_alpha()); - - const uint64_t selector_bits = pBlock->get_selector_bits(); - - for (uint32_t y = 0; y < 4; y++, pPixels += (stride * 4U)) - { - pPixels[0] = sel_values[pBlock->get_selector(0, y, selector_bits)]; - pPixels[stride * 1] = sel_values[pBlock->get_selector(1, y, selector_bits)]; - pPixels[stride * 2] = sel_values[pBlock->get_selector(2, y, selector_bits)]; - pPixels[stride * 3] = sel_values[pBlock->get_selector(3, y, selector_bits)]; - } - } - - // Returns false if the block uses 3-color punchthrough alpha mode, which isn't supported on some GPU's for BC3. - bool unpack_bc3(const void* pBlock_bits, void* pPixels, bc1_approx_mode mode) - { - color32* pDst_pixels = static_cast(pPixels); - - bool success = true; - - if (unpack_bc1((const uint8_t*)pBlock_bits + sizeof(bc4_block), pDst_pixels, true, mode)) - success = false; - - unpack_bc4(pBlock_bits, &pDst_pixels[0].a, sizeof(color32)); - - return success; - } - - // writes RG - void unpack_bc5(const void* pBlock_bits, void* pPixels, uint32_t chan0, uint32_t chan1, uint32_t stride) - { - unpack_bc4(pBlock_bits, (uint8_t*)pPixels + chan0, stride); - unpack_bc4((const uint8_t*)pBlock_bits + sizeof(bc4_block), (uint8_t*)pPixels + chan1, stride); - } - -} // namespace rgbcx diff --git a/src/ktx.imageio/bc7enc-rdo/rgbcx.h b/src/ktx.imageio/bc7enc-rdo/rgbcx.h deleted file mode 100644 index 5781044a98..0000000000 --- a/src/ktx.imageio/bc7enc-rdo/rgbcx.h +++ /dev/null @@ -1,475 +0,0 @@ -// clang-format off -#pragma once - -// rgbcx.h v1.13 -// High-performance scalar encoders and RDO (Rate Distortion Optimization) post processors for BC1-5. -// Public Domain or MIT license (you choose - see below), written by Richard Geldreich 2020 . -// -// Influential references: -// https://tinyurl.com/y3vxz457 (Ortego and Ramchandran, "Rate-distortion Methods for Image and Video Compression", 1998) -// http://sjbrown.co.uk/2006/01/19/dxt-compression-techniques/ -// https://github.com/nothings/stb/blob/master/stb_dxt.h -// https://gist.github.com/castano/c92c7626f288f9e99e158520b14a61cf -// https://github.com/castano/icbc/blob/master/icbc.h -// http://www.humus.name/index.php?page=3D&ID=79 -// -// This is a single header file library. Be sure to "#define RGBCX_IMPLEMENTATION" in one .cpp file somewhere. -// -// Instructions: -// -// The library MUST be initialized by calling this function at least once before using any encoder or decoder functions: -// -// void rgbcx::init(bc1_approx_mode mode = cBC1Ideal); -// -// This function manipulates global state, so it is not thread safe. -// You can call it multiple times to change the global BC1 approximation mode. -// Important: BC1/3 textures encoded using non-ideal BC1 approximation modes should only be sampled on parts from that vendor. -// If you encode for AMD, average error on AMD parts will go down, but average error on NVidia parts will go up and vice versa. -// If in doubt, encode in ideal BC1 mode. -// -// Call these functions to encode BC1-5: -// void rgbcx::encode_bc1(uint32_t level, void* pDst, const uint8_t* pPixels, bool allow_3color, bool use_transparent_texels_for_black); -// void rgbcx::encode_bc3(uint32_t level, void* pDst, const uint8_t* pPixels); -// void rgbcx::encode_bc4(void* pDst, const uint8_t* pPixels, uint32_t stride = 4); -// void rgbcx::encode_bc5(void* pDst, const uint8_t* pPixels, uint32_t chan0 = 0, uint32_t chan1 = 1, uint32_t stride = 4); -// -// - level ranges from MIN_LEVEL to MAX_LEVEL. The higher the level, the slower the encoder goes, but the higher the average quality. -// levels [0,4] are fast and compete against stb_dxt (default and HIGHQUAL). The remaining levels compete against squish/NVTT/icbc and icbc HQ. -// If in doubt just use level 10, set allow_3color to true and use_transparent_texels_for_black to false, and adjust as needed. -// -// - pDst is a pointer to the 8-byte (BC1/4) or 16-byte (BC3/5) destination block. -// -// - pPixels is a pointer to the 32-bpp pixels, in either RGBX or RGBA format (R is first in memory). -// Alpha is always ignored by encode_bc1(). -// -// - allow_3color: If true the encoder will use 3-color blocks. This flag is ignored unless level is >= 5 (because lower levels compete against stb_dxt and it doesn't support 3-color blocks). -// Do not enable on BC3-5 textures. 3-color block usage slows down encoding. -// -// - use_transparent_texels_for_black: If true the encoder will use 3-color block transparent black pixels to code very dark or black texels. Your engine/shader MUST ignore the sampled -// alpha value for textures encoded in this mode. This is how NVidia's classic "nvdxt" encoder (used by many original Xbox titles) used to work by default on DXT1C textures. It increases -// average quality substantially (because dark texels/black are very common) and is highly recommended. -// Do not enable on BC3-5 textures. -// -// - stride is the source pixel stride, in bytes. It's typically 4. -// -// - chan0 and chan1 are the source channels. Typically they will be 0 and 1. -// -// All encoding and decoding functions are threade-safe. -// -// To reduce the compiled size of the encoder, set #define RGBCX_USE_SMALLER_TABLES to 1 before including this header. -// -#ifndef RGBCX_INCLUDE_H -#define RGBCX_INCLUDE_H - -#ifdef _MSC_VER -#pragma warning (disable:4201) //nameless struct/union -#endif - -#include -#include -#include -#include -#include - -// By default, the table used to accelerate cluster fit on 4 color blocks uses a 969x128 entry table. -// To reduce the executable size, set RGBCX_USE_SMALLER_TABLES to 1, which selects the smaller 969x32 entry table. -#ifndef RGBCX_USE_SMALLER_TABLES -#define RGBCX_USE_SMALLER_TABLES 1 -#endif - -namespace rgbcx -{ - enum class bc1_approx_mode - { - // The default mode. No rounding for 4-color colors 2,3. My older tools/compressors use this mode. - // This matches the D3D10 docs on BC1. - cBC1Ideal = 0, - - // NVidia GPU mode. - cBC1NVidia = 1, - - // AMD GPU mode. - cBC1AMD = 2, - - // This mode matches AMD Compressonator's output. It rounds 4-color colors 2,3 (not 3-color color 2). - // This matches the D3D9 docs on DXT1. - cBC1IdealRound4 = 3 - }; - - enum class eNoClamp { cNoClamp }; - static inline uint8_t clamp255(int32_t i) { return (uint8_t)((i & 0xFFFFFF00U) ? (~(i >> 31)) : i); } - - template inline S maximum(S a, S b) { return (a > b) ? a : b; } - template inline S maximum(S a, S b, S c) { return maximum(maximum(a, b), c); } - template inline S maximum(S a, S b, S c, S d) { return maximum(maximum(maximum(a, b), c), d); } - - template inline S minimum(S a, S b) { return (a < b) ? a : b; } - template inline S minimum(S a, S b, S c) { return minimum(minimum(a, b), c); } - template inline S minimum(S a, S b, S c, S d) { return minimum(minimum(minimum(a, b), c), d); } - - struct color32 - { - union - { - struct - { - uint8_t r; - uint8_t g; - uint8_t b; - uint8_t a; - }; - - uint8_t c[4]; - - uint32_t m; - }; - - color32() { } - - color32(uint32_t vr, uint32_t vg, uint32_t vb, uint32_t va) { set(vr, vg, vb, va); } - color32(eNoClamp unused, uint32_t vr, uint32_t vg, uint32_t vb, uint32_t va) { (void)unused; set_noclamp_rgba(vr, vg, vb, va); } - - void set(uint32_t vr, uint32_t vg, uint32_t vb, uint32_t va) { c[0] = static_cast(vr); c[1] = static_cast(vg); c[2] = static_cast(vb); c[3] = static_cast(va); } - - void set_noclamp_rgb(uint32_t vr, uint32_t vg, uint32_t vb) { c[0] = static_cast(vr); c[1] = static_cast(vg); c[2] = static_cast(vb); } - void set_noclamp_rgba(uint32_t vr, uint32_t vg, uint32_t vb, uint32_t va) { set(vr, vg, vb, va); } - - void set_clamped(int vr, int vg, int vb, int va) { c[0] = clamp255(vr); c[1] = clamp255(vg); c[2] = clamp255(vb); c[3] = clamp255(va); } - - uint8_t operator[] (uint32_t idx) const { assert(idx < 4); return c[idx]; } - uint8_t& operator[] (uint32_t idx) { assert(idx < 4); return c[idx]; } - - bool operator== (const color32& rhs) const { return m == rhs.m; } - - void set_rgb(const color32& other) { c[0] = static_cast(other.c[0]); c[1] = static_cast(other.c[1]); c[2] = static_cast(other.c[2]); } - - static color32 comp_min(const color32& a, const color32& b) { return color32(eNoClamp::cNoClamp, std::min(a[0], b[0]), std::min(a[1], b[1]), std::min(a[2], b[2]), std::min(a[3], b[3])); } - static color32 comp_max(const color32& a, const color32& b) { return color32(eNoClamp::cNoClamp, std::max(a[0], b[0]), std::max(a[1], b[1]), std::max(a[2], b[2]), std::max(a[3], b[3])); } - }; - - // init() MUST be called once before using the BC1 encoder. - // This function may be called multiple times to change the BC1 approximation mode. - // This function initializes global state, so don't call it while other threads inside the encoder. - // Important: If you encode textures for a specific vendor's GPU's, beware that using that texture data on other GPU's may result in ugly artifacts. - // Encode to cBC1Ideal unless you know the texture data will only be deployed or used on a specific vendor's GPU. - void init(bc1_approx_mode mode = bc1_approx_mode::cBC1Ideal); - - // Optimally encodes a solid color block to BC1 format. - void encode_bc1_solid_block(void* pDst, uint32_t fr, uint32_t fg, uint32_t fb, bool allow_3color); - - // BC1 low-level API encoder flags. You can ignore this if you use the simple level API. - enum - { - // Try to improve quality using the most likely total orderings. - // The total_orderings_to_try parameter will then control the number of total orderings to try for 4 color blocks, and the - // total_orderings_to_try3 parameter will control the number of total orderings to try for 3 color blocks (if they are enabled). - cEncodeBC1UseLikelyTotalOrderings = 2, - - // Use 2 least squares pass, instead of one (same as stb_dxt's HIGHQUAL option). - // Recommended if you're enabling cEncodeBC1UseLikelyTotalOrderings. - cEncodeBC1TwoLeastSquaresPasses = 4, - - // cEncodeBC1Use3ColorBlocksForBlackPixels allows the BC1 encoder to use 3-color blocks for blocks containing black or very dark pixels. - // You shader/engine MUST ignore the alpha channel on textures encoded with this flag. - // Average quality goes up substantially for my 100 texture corpus (~.5 dB), so it's worth using if you can. - // Note the BC1 encoder does not actually support transparency in 3-color mode. - // Don't set when encoding to BC3. - cEncodeBC1Use3ColorBlocksForBlackPixels = 8, - - // If cEncodeBC1Use3ColorBlocks is set, the encoder can use 3-color mode for a small but noticeable gain in average quality, but lower perf. - // If you also specify the cEncodeBC1UseLikelyTotalOrderings flag, set the total_orderings_to_try3 paramter to the number of total orderings to try. - // Don't set when encoding to BC3. - cEncodeBC1Use3ColorBlocks = 16, - - // cEncodeBC1Iterative will greatly increase encode time, but is very slightly higher quality. - // Same as squish's iterative cluster fit option. Not really worth the tiny boost in quality, unless you just don't care about perf. at all. - cEncodeBC1Iterative = 32, - - // cEncodeBC1BoundingBox enables a fast all-integer PCA approximation on 4-color blocks. - // At level 0 options (no other flags), this is ~15% faster, and higher *average* quality. - cEncodeBC1BoundingBox = 64, - - // Use a slightly lower quality, but ~30% faster MSE evaluation function for 4-color blocks. - cEncodeBC1UseFasterMSEEval = 128, - - // Examine all colors to compute selectors/MSE (slower than default) - cEncodeBC1UseFullMSEEval = 256, - - // Use 2D least squares+inset+optimal rounding (the method used in Humus's GPU texture encoding demo), instead of PCA. - // Around 18% faster, very slightly lower average quality to better (depends on the content). - cEncodeBC1Use2DLS = 512, - - // Use 6 power iterations vs. 4 for PCA. - cEncodeBC1Use6PowerIters = 2048, - - // Check all total orderings - *very* slow. The encoder is not designed to be used in this way. - cEncodeBC1Exhaustive = 8192, - - // Try 2 different ways of choosing the initial endpoints. - cEncodeBC1TryAllInitialEndponts = 16384, - - // Same as cEncodeBC1BoundingBox, but implemented using integer math (faster, slightly less quality) - cEncodeBC1BoundingBoxInt = 32768, - - // Try refining the final endpoints by examining nearby colors. - cEncodeBC1EndpointSearchRoundsShift = 22, - cEncodeBC1EndpointSearchRoundsMask = 1023U << cEncodeBC1EndpointSearchRoundsShift, - }; - - const uint32_t MIN_TOTAL_ORDERINGS = 1; - const uint32_t MAX_TOTAL_ORDERINGS3 = 32; - -#if RGBCX_USE_SMALLER_TABLES - const uint32_t MAX_TOTAL_ORDERINGS4 = 32; -#else - const uint32_t MAX_TOTAL_ORDERINGS4 = 128; -#endif - - // DEFAULT_TOTAL_ORDERINGS_TO_TRY is around 3x faster than libsquish at slightly higher average quality. 10-16 is a good range to start to compete against libsquish. - const uint32_t DEFAULT_TOTAL_ORDERINGS_TO_TRY = 10; - - const uint32_t DEFAULT_TOTAL_ORDERINGS_TO_TRY3 = 1; - - // Encodes a 4x4 block of RGBX (X=ignored) pixels to BC1 format. - // This is the simplified interface for BC1 encoding, which accepts a level parameter and converts that to the best overall flags. - // The pixels are in RGBA format, where R is first in memory. The BC1 encoder completely ignores the alpha channel (i.e. there is no punchthrough alpha support). - // This is the recommended function to use for BC1 encoding, becuase it configures the encoder for you in the best possible way (on average). - // Note that the 3 color modes won't be used at all until level 5 or higher. - // No transparency supported, however if you set use_transparent_texels_for_black to true the encocer will use transparent selectors on very dark/black texels to reduce MSE. - const uint32_t MIN_LEVEL = 0, MAX_LEVEL = 18; - void encode_bc1(uint32_t level, void* pDst, const uint8_t* pPixels, bool allow_3color, bool use_transparent_texels_for_black, const uint8_t* pForce_selectors = nullptr); - - // Low-level interface for BC1 encoding. - // Always returns a 4 color block, unless cEncodeBC1Use3ColorBlocksForBlackPixels or cEncodeBC1Use3ColorBlock flags are specified. - // total_orderings_to_try controls the perf. vs. quality tradeoff on 4-color blocks when the cEncodeBC1UseLikelyTotalOrderings flag is used. It must range between [MIN_TOTAL_ORDERINGS, MAX_TOTAL_ORDERINGS4]. - // total_orderings_to_try3 controls the perf. vs. quality tradeoff on 3-color bocks when the cEncodeBC1UseLikelyTotalOrderings and the cEncodeBC1Use3ColorBlocks flags are used. Valid range is [0,MAX_TOTAL_ORDERINGS3] (0=disabled). - void encode_bc1(void* pDst, const uint8_t* pPixels, uint32_t flags = 0, uint32_t total_orderings_to_try = DEFAULT_TOTAL_ORDERINGS_TO_TRY, uint32_t total_orderings_to_try3 = DEFAULT_TOTAL_ORDERINGS_TO_TRY3, const uint8_t *pForce_selectors = nullptr); - - // Constants used for high quality BC4/BC5 encoding (and alpha of BC3) - const uint32_t BC4_DEFAULT_SEARCH_RAD = 3; - const uint32_t BC4_USE_MODE8_FLAG = 1; - const uint32_t BC4_USE_MODE6_FLAG = 2; - const uint32_t BC4_USE_ALL_MODES = 3; - - // Encodes a 4x4 block of RGBA pixels to BC3 format. - // There are two encode_bc3() functions. - // The first is the recommended function, which accepts a level parameter. - // The second is a low-level version that allows fine control over BC1 encoding. - void encode_bc3(uint32_t level, void* pDst, const uint8_t* pPixels); - void encode_bc3(void* pDst, const uint8_t* pPixels, uint32_t flags = 0, uint32_t total_orderings_to_try = DEFAULT_TOTAL_ORDERINGS_TO_TRY); - void encode_bc3_hq(uint32_t level, void* pDst, const uint8_t* pPixels, uint32_t alpha_search_rad = BC4_DEFAULT_SEARCH_RAD, uint32_t alpha_modes = BC4_USE_ALL_MODES); - - // Encodes a single channel to BC4. - // stride is the source pixel stride in bytes. - void encode_bc4(void* pDst, const uint8_t* pPixels, uint32_t stride = 4); - uint32_t encode_bc4_hq(void* pDst, const uint8_t* pPixels, uint32_t stride = 4, uint32_t search_rad = BC4_DEFAULT_SEARCH_RAD, uint32_t mode_flag = BC4_USE_ALL_MODES, const uint8_t* pForce_selectors = nullptr); - - // Encodes two channels to BC5. - // chan0/chan1 control which channels, stride is the source pixel stride in bytes. - void encode_bc5(void* pDst, const uint8_t* pPixels, uint32_t chan0 = 0, uint32_t chan1 = 1, uint32_t stride = 4); - void encode_bc5_hq(void* pDst, const uint8_t* pPixels, uint32_t chan0 = 0, uint32_t chan1 = 1, uint32_t stride = 4, uint32_t alpha_search_rad = BC4_DEFAULT_SEARCH_RAD, uint32_t alpha_modes = BC4_USE_ALL_MODES); - - // Decompression functions. - - bool unpack_bc1_block_colors(const void* pBlock_bits, color32* c, bc1_approx_mode mode = bc1_approx_mode::cBC1Ideal); - - // Returns true if the block uses 3 color punchthrough alpha mode. - bool unpack_bc1(const void* pBlock_bits, void* pPixels, bool set_alpha = true, bc1_approx_mode mode = bc1_approx_mode::cBC1Ideal); - - void unpack_bc4(const void* pBlock_bits, uint8_t* pPixels, uint32_t stride = 4); - - // Returns true if the block uses 3 color punchthrough alpha mode. - bool unpack_bc3(const void* pBlock_bits, void* pPixels, bc1_approx_mode mode = bc1_approx_mode::cBC1Ideal); - - void unpack_bc5(const void* pBlock_bits, void* pPixels, uint32_t chan0 = 0, uint32_t chan1 = 1, uint32_t stride = 4); - - // Rate Distortion Optimization (RDO) - enum dxt_constants - { - cDXT1SelectorBits = 2U, cDXT1SelectorValues = 1U << cDXT1SelectorBits, cDXT1SelectorMask = cDXT1SelectorValues - 1U, - cDXT5SelectorBits = 3U, cDXT5SelectorValues = 1U << cDXT5SelectorBits, cDXT5SelectorMask = cDXT5SelectorValues - 1U, - }; - - struct bc1_block - { - enum { cTotalEndpointBytes = 2, cTotalSelectorBytes = 4 }; - - uint8_t m_low_color[cTotalEndpointBytes]; - uint8_t m_high_color[cTotalEndpointBytes]; - uint8_t m_selectors[cTotalSelectorBytes]; - - inline uint32_t get_low_color() const { return m_low_color[0] | (m_low_color[1] << 8U); } - inline uint32_t get_high_color() const { return m_high_color[0] | (m_high_color[1] << 8U); } - inline bool is_3color() const { return get_low_color() <= get_high_color(); } - inline void set_low_color(uint16_t c) { m_low_color[0] = static_cast(c & 0xFF); m_low_color[1] = static_cast((c >> 8) & 0xFF); } - inline void set_high_color(uint16_t c) { m_high_color[0] = static_cast(c & 0xFF); m_high_color[1] = static_cast((c >> 8) & 0xFF); } - inline uint32_t get_selector(uint32_t x, uint32_t y) const { assert((x < 4U) && (y < 4U)); return (m_selectors[y] >> (x * cDXT1SelectorBits)) & cDXT1SelectorMask; } - inline void set_selector(uint32_t x, uint32_t y, uint32_t val) { assert((x < 4U) && (y < 4U) && (val < 4U)); m_selectors[y] &= (~(cDXT1SelectorMask << (x * cDXT1SelectorBits))); m_selectors[y] |= (val << (x * cDXT1SelectorBits)); } - - inline uint32_t get_endpoint_bits() const { return m_low_color[0] | (m_low_color[1] << 8) | (m_high_color[0] << 16) | (m_high_color[1] << 24); } - inline void set_endpoint_bits(uint32_t s) { m_low_color[0] = (uint8_t)s; m_low_color[1] = (uint8_t)(s >> 8); m_high_color[0] = (uint8_t)(s >> 16); m_high_color[1] = (uint8_t)(s >> 24); } - - inline uint32_t get_selector_bits() const { return m_selectors[0] | (m_selectors[1] << 8) | (m_selectors[2] << 16) | (m_selectors[3] << 24); } - inline void set_selector_bits(uint32_t s) { m_selectors[0] = (uint8_t)s; m_selectors[1] = (uint8_t)(s >> 8); m_selectors[2] = (uint8_t)(s >> 16); m_selectors[3] = (uint8_t)(s >> 24); } - - inline bool any_selectors_transparent() const - { - uint32_t sel_bits = get_selector_bits(); - for (uint32_t i = 0; i < 16; i++) - { - if ((sel_bits & 3) == 3) - return true; - - sel_bits >>= 2; - } - return false; - } - - static inline uint16_t pack_color(const color32& color, bool scaled, uint32_t bias = 127U) - { - uint32_t r = color.r, g = color.g, b = color.b; - if (scaled) - { - r = (r * 31U + bias) / 255U; - g = (g * 63U + bias) / 255U; - b = (b * 31U + bias) / 255U; - } - return static_cast(minimum(b, 31U) | (minimum(g, 63U) << 5U) | (minimum(r, 31U) << 11U)); - } - - static inline uint16_t pack_unscaled_color(uint32_t r, uint32_t g, uint32_t b) { return static_cast(b | (g << 5U) | (r << 11U)); } - - static inline void unpack_color(uint32_t c, uint32_t& r, uint32_t& g, uint32_t& b) - { - r = (c >> 11) & 31; - g = (c >> 5) & 63; - b = c & 31; - - r = (r << 3) | (r >> 2); - g = (g << 2) | (g >> 4); - b = (b << 3) | (b >> 2); - } - - static inline void unpack_color_unscaled(uint32_t c, uint32_t& r, uint32_t& g, uint32_t& b) - { - r = (c >> 11) & 31; - g = (c >> 5) & 63; - b = c & 31; - } - }; - - struct bc4_block - { - enum { cBC4SelectorBits = 3, cTotalSelectorBytes = 6, cMaxSelectorValues = 8 }; - uint8_t m_endpoints[2]; - - uint8_t m_selectors[cTotalSelectorBytes]; - - inline uint32_t get_low_alpha() const { return m_endpoints[0]; } - inline uint32_t get_high_alpha() const { return m_endpoints[1]; } - inline bool is_alpha6_block() const { return get_low_alpha() <= get_high_alpha(); } - - inline uint64_t get_selector_bits() const - { - return ((uint64_t)((uint32_t)m_selectors[0] | ((uint32_t)m_selectors[1] << 8U) | ((uint32_t)m_selectors[2] << 16U) | ((uint32_t)m_selectors[3] << 24U))) | - (((uint64_t)m_selectors[4]) << 32U) | - (((uint64_t)m_selectors[5]) << 40U); - } - - inline void set_selector_bits(uint64_t v) - { - for (uint32_t i = 0; i < 6; i++) - { - m_selectors[i] = (uint8_t)v; - v >>= 8; - } - } - - inline uint32_t get_selector(uint32_t x, uint32_t y, uint64_t selector_bits) const - { - assert((x < 4U) && (y < 4U)); - return (selector_bits >> (((y * 4) + x) * cBC4SelectorBits)) & (cMaxSelectorValues - 1); - } - - static inline uint32_t get_block_values6(uint8_t* pDst, uint32_t l, uint32_t h) - { - pDst[0] = static_cast(l); - pDst[1] = static_cast(h); - pDst[2] = static_cast((l * 4 + h) / 5); - pDst[3] = static_cast((l * 3 + h * 2) / 5); - pDst[4] = static_cast((l * 2 + h * 3) / 5); - pDst[5] = static_cast((l + h * 4) / 5); - pDst[6] = 0; - pDst[7] = 255; - return 6; - } - - static inline uint32_t get_block_values8(uint8_t* pDst, uint32_t l, uint32_t h) - { - pDst[0] = static_cast(l); - pDst[1] = static_cast(h); - pDst[2] = static_cast((l * 6 + h) / 7); - pDst[3] = static_cast((l * 5 + h * 2) / 7); - pDst[4] = static_cast((l * 4 + h * 3) / 7); - pDst[5] = static_cast((l * 3 + h * 4) / 7); - pDst[6] = static_cast((l * 2 + h * 5) / 7); - pDst[7] = static_cast((l + h * 6) / 7); - return 8; - } - - static inline uint32_t get_block_values(uint8_t* pDst, uint32_t l, uint32_t h) - { - if (l > h) - return get_block_values8(pDst, l, h); - else - return get_block_values6(pDst, l, h); - } - }; - -} -#endif // #ifndef RGBCX_INCLUDE_H - -#ifdef RGBCX_IMPLEMENTATION -#endif //#ifdef RGBCX_IMPLEMENTATION - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright(c) 2020 Richard Geldreich, Jr. -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files(the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions : -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain(www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non - commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain.We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors.We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/src/ktx.imageio/bc7enc-rdo/rgbcx_table4_small.h b/src/ktx.imageio/bc7enc-rdo/rgbcx_table4_small.h deleted file mode 100644 index 156d763e9f..0000000000 --- a/src/ktx.imageio/bc7enc-rdo/rgbcx_table4_small.h +++ /dev/null @@ -1,972 +0,0 @@ -// clang-format off -#pragma once - -{ 202, 120, 13, 318, 15, 23, 403, 450, 5, 51, 260, 128, 77, 21, 33, 494, 515, 523, 4, 141, 269, 1, 2, 700, 137, 49, 48, 102, 7, 64, 753, 82 }, -{ 13,141,23,217,115,51,77,2,64,21,0,4,5,317,137,269,202,33,318,7,291,352,9,10,3,180,32,6,365,102,341,349 }, -{ 29,58,262,1,52,74,6,171,5,287,151,334,27,500,75,26,331,223,53,635,220,19,50,45,46,17,14,396,163,409,324,70 }, -{ 40,51,33,453,14,23,62,56,12,196,730,475,153,99,403,775,117,130,585,34,4,17,162,11,139,57,102,38,108,47,123,440 }, -{ 33,23,51,13,102,64,202,128,12,40,15,196,153,10,1,2,77,99,141,0,515,5,117,3,120,403,700,165,22,14,269,453 }, -{ 13,23,51,4,77,141,202,33,115,64,32,128,0,11,177,40,15,102,2,217,7,137,269,21,90,59,515,1,180,403,22,6 }, -{ 26,235,19,47,648,624,78,145,27,112,122,64,444,6,630,453,25,42,65,130,711,85,390,113,416,108,665,29,730,138,644,95 }, -{ 64,141,352,751,217,247,237,437,177,269,86,954,947,875,32,318,95,77,304,92,597,180,232,291,128,864,349,588,372,202,312,1 }, -{ 642,898,180,638,901,341,82,197,10,951,15,515,165,762,700,253,811,753,752,365,143,479,244,569,8,110,351,873,55,31,499,116 }, -{ 221,23,51,125,438,254,13,21,39,49,308,656,0,115,530,159,158,401,30,166,912,386,165,688,518,9,105,627,424,22,421,33 }, -{ 143,31,1,44,197,8,180,125,116,55,13,498,23,341,638,242,93,15,2,141,0,901,752,115,36,206,165,479,338,365,515,762 }, -{ 12,23,51,13,14,15,37,99,515,38,700,117,2,196,134,153,753,64,54,33,128,120,21,0,328,5,139,82,453,719,457,1 }, -{ 13,15,23,515,961,700,457,753,51,115,4,165,197,2,38,569,1,474,0,37,99,719,5,12,629,14,11,3,33,77,64,10 }, -{ 15,515,700,753,1,0,2,4,3,23,134,12,961,5,10,197,11,33,82,120,457,51,165,7,6,341,217,21,77,9,40,180 }, -{ 13,51,23,457,719,961,730,401,165,453,0,117,386,15,134,1,758,153,12,54,515,99,11,2,700,5,753,4,308,33,6,899 }, -{ 134,898,82,117,13,33,77,102,23,260,341,351,120,901,197,153,961,111,196,110,180,457,854,10,450,8,165,40,4,115,0,365 }, -{ 60,18,126,167,35,16,191,71,24,92,121,271,68,107,212,146,118,150,199,7,21,1,9,575,727,5,566,48,0,132,108,273 }, -{ 62,136,129,123,128,41,162,17,249,211,214,789,618,710,38,678,248,507,57,64,152,269,119,3,177,183,597,106,4,179,216,90 }, -{ 403,523,51,475,494,453,817,899,202,23,450,13,421,120,102,730,33,128,4,1,805,5,7,153,757,260,318,196,77,457,326,65 }, -{ 4,59,3,62,12,33,56,193,27,21,102,17,40,77,76,84,32,0,6,123,119,177,128,11,18,611,605,25,13,51,73,210 }, -{ 43,20,319,422,414,945,0,7,819,61,5,376,325,173,804,904,470,693,97,707,14,49,22,104,147,107,95,32,426,1,330,577 }, -{ 13,23,51,2,0,115,4,141,217,33,10,77,1,15,64,180,3,515,7,6,22,102,11,5,40,9,165,700,202,197,317,341 }, -{ 28,49,0,105,1,24,65,159,35,55,95,239,16,2,109,7,9,14,170,320,347,168,424,158,10,301,124,5,67,21,64,36 }, -{ 15,515,700,753,0,1,13,2,117,4,12,10,5,165,457,3,9,134,11,7,6,51,77,64,961,82,33,197,14,341,120,141 }, -{ 7,71,14,149,97,18,60,16,150,92,398,189,140,124,24,273,35,2,69,302,154,68,0,336,517,43,66,28,118,251,230,1 }, -{ 4,102,33,77,40,59,11,624,210,12,128,342,5,503,91,139,64,32,25,494,202,678,416,0,403,275,21,450,196,318,523,177 }, -{ 25,19,42,6,122,813,256,235,85,26,436,53,297,573,680,390,445,63,27,416,80,233,65,73,389,283,45,605,194,17,250,343 }, -{ 402,102,202,128,33,300,403,23,12,77,40,21,342,117,483,99,25,494,6,4,63,32,84,569,139,757,475,318,19,26,196,134 }, -{ 158,9,0,109,39,49,65,22,35,168,55,24,68,124,159,16,185,344,333,154,254,272,175,289,1,577,95,28,105,810,30,169 }, -{ 197,180,115,237,498,165,2,5,287,546,400,3,61,34,509,13,297,80,341,52,45,186,58,881,23,873,468,176,64,17,311,250 }, -{ 120,968,373,260,704,110,450,202,137,318,77,95,269,326,217,717,661,652,851,349,93,1,518,98,827,291,21,177,82,33,848,719 }, -{ 44,116,144,268,434,489,367,384,98,127,918,93,948,31,206,940,855,0,203,137,9,22,617,141,332,105,393,492,959,282,299,131 }, -{ 13,77,23,33,51,0,64,141,102,4,2,115,1,6,202,15,10,128,269,7,177,180,3,40,22,11,515,217,117,318,700,137 }, -{ 15,515,700,753,4,11,141,40,165,23,64,180,13,202,32,3,51,125,5,197,21,128,0,93,77,1,120,82,269,117,110,59 }, -{ 176,231,585,62,34,14,412,161,56,236,527,57,17,3,51,202,4,23,369,283,128,13,472,440,84,361,136,457,381,130,719,53 }, -{ 9,0,180,217,237,101,141,352,88,100,230,64,175,317,115,498,68,39,30,1,702,83,213,36,365,208,752,13,252,321,952,546 }, -{ 28,9,22,1,49,0,109,39,83,95,86,30,13,105,128,55,141,168,158,67,31,159,208,12,96,5,185,2,160,64,137,23 }, -{ 72,4,38,12,51,89,477,11,57,76,401,308,23,474,99,148,413,179,59,13,431,152,54,569,17,3,205,629,197,421,405,15 }, -{ 457,13,23,961,15,51,515,700,165,12,753,629,11,1,719,117,0,3,2,37,569,197,40,328,33,5,153,134,99,64,38,196 }, -{ 254,100,310,9,30,1,39,625,166,265,190,0,272,557,131,731,31,98,578,688,404,93,101,88,49,21,127,264,44,36,252,478 }, -{ 51,23,12,13,15,128,99,120,10,202,515,153,64,82,700,33,165,2,5,117,403,1,141,0,3,196,37,453,753,197,260,93 }, -{ 38,99,542,139,453,117,196,23,457,13,328,111,37,134,961,11,12,51,40,775,587,401,474,54,153,477,41,629,33,475,14,277 }, -{ 6,85,25,233,343,91,26,63,138,29,19,65,283,4,81,235,42,122,605,64,648,256,174,370,74,389,718,59,45,194,445,416 }, -{ 49,5,97,20,197,21,18,193,0,64,408,729,173,350,43,422,165,7,14,104,61,32,509,713,523,102,120,95,125,397,35,232 }, -{ 144,116,268,434,384,489,367,206,93,855,940,44,98,332,617,127,959,911,137,282,203,31,22,219,141,9,131,276,417,0,1,120 }, -{ 17,106,64,62,32,255,136,292,476,162,129,241,123,141,41,237,720,214,209,352,519,211,186,148,752,247,507,90,21,77,197,119 }, -{ 2,29,52,50,5,58,14,6,27,1,366,357,45,53,17,19,171,151,26,181,133,38,218,764,287,583,61,113,3,487,600,281 }, -{ 130,59,196,412,381,730,711,236,77,210,202,402,453,99,401,108,361,803,291,283,153,4,57,51,128,183,14,719,503,117,23,11 }, -{ 13,23,51,141,77,4,33,64,115,0,217,10,180,202,2,102,11,9,15,165,40,21,128,352,22,7,197,3,317,515,269,1 }, -{ 23,13,202,51,120,15,21,5,141,1,128,269,137,515,64,102,125,48,98,33,260,523,318,93,700,165,450,77,2,12,403,82 }, -{ 1,2,14,46,29,67,38,52,5,171,58,24,103,69,96,70,83,181,54,75,163,223,16,45,112,309,155,0,186,35,18,108 }, -{ 15,515,700,753,13,0,1,2,153,5,23,10,117,3,9,7,134,165,12,6,341,33,4,14,77,457,115,21,719,180,217,82 }, -{ 197,165,509,13,391,180,308,115,23,546,5,498,2,29,3,401,901,61,34,80,14,457,250,569,237,873,38,297,45,15,468,386 }, -{ 19,73,27,250,200,714,444,472,26,53,34,17,813,322,283,390,128,297,78,123,432,14,436,136,106,690,57,122,389,80,503,3 }, -{ 3,17,21,45,62,32,38,12,155,14,2,328,5,99,401,536,828,13,227,488,106,51,719,119,540,76,165,221,115,629,209,41 }, -{ 115,341,873,197,365,13,901,180,569,752,317,1,10,498,143,634,261,0,509,15,943,237,44,31,116,601,165,127,282,23,141,64 }, -{ 453,51,23,403,33,421,475,102,15,153,196,515,13,700,117,523,12,40,753,21,4,134,0,494,670,899,22,801,730,10,11,401 }, -{ 23,13,51,33,12,117,153,134,453,196,15,99,515,40,14,700,128,102,11,753,77,64,403,202,0,401,475,37,65,2,3,38 }, -{ 2,7,5,14,70,1,29,61,52,45,6,112,66,16,21,32,592,46,38,135,87,58,186,315,290,128,113,0,64,48,227,23 }, -{ 33,23,102,51,128,13,64,202,141,1,77,10,153,40,196,117,2,3,0,5,15,269,403,12,137,134,318,165,120,6,453,99 }, -{ 16,92,7,20,43,35,126,71,60,14,107,18,68,97,0,121,279,149,24,246,191,48,118,575,55,140,362,783,230,150,375,566 }, -{ 13,23,4,33,77,64,51,102,141,128,32,10,0,202,40,115,59,22,90,11,177,21,291,6,7,318,180,117,137,2,95,165 }, -{ 507,162,129,41,4,211,62,38,123,59,57,248,183,130,99,11,3,361,202,17,402,556,266,305,803,210,128,184,152,136,313,117 }, -{ 643,123,193,650,802,18,25,389,718,256,65,289,84,91,619,511,415,90,235,63,57,510,324,216,862,102,6,183,108,397,217,736 }, -{ 13,23,15,1,515,51,0,2,700,5,753,165,141,115,12,3,4,180,21,197,457,7,6,10,120,9,33,202,77,32,8,11 }, -{ 23,51,13,453,64,403,12,21,5,202,128,475,165,141,523,95,125,115,3,1,4,730,120,32,2,494,180,719,457,197,450,401 }, -{ 204,74,135,66,6,174,192,7,138,172,85,353,348,580,280,97,95,500,29,64,426,32,87,889,65,81,25,2,52,43,568,673 }, -{ 35,0,68,69,24,9,1,16,65,103,149,133,18,114,28,50,83,2,189,7,46,14,101,336,175,124,251,55,71,218,38,238 }, -{ 16,101,0,118,9,18,24,68,35,154,71,124,60,212,191,520,55,806,694,167,28,39,364,375,1,346,252,65,604,302,22,21 }, -{ 0,9,16,35,1,24,68,18,65,21,103,67,13,149,28,189,71,23,101,238,114,7,335,133,486,141,22,212,48,50,30,118 }, -{ 13,202,23,77,33,51,128,5,21,141,115,32,102,64,4,0,318,269,10,15,291,2,494,177,11,217,3,515,22,137,6,700 }, -{ 16,92,60,35,7,18,24,68,150,149,14,71,0,375,97,126,118,107,230,191,246,273,140,55,175,653,9,575,2,28,566,517 }, -{ 76,90,21,179,316,148,205,32,464,288,184,257,245,1,89,2,460,57,152,45,38,358,645,5,12,449,350,48,37,17,4,14 }, -{ 19,27,26,813,80,297,17,495,436,53,73,200,4,378,250,59,106,25,45,128,361,42,113,469,122,390,77,40,736,6,11,136 }, -{ 6,26,235,138,19,145,112,70,331,262,25,42,52,624,27,453,122,47,500,78,648,85,29,2,630,632,409,113,50,226,108,75 }, -{ 7,16,14,24,92,35,18,2,46,9,60,140,0,87,50,5,54,13,12,38,171,23,126,21,58,64,1,70,128,71,220,163 }, -{ 90,205,257,184,32,179,460,5,245,45,2,288,769,524,57,21,152,229,17,1,497,4,292,59,619,452,432,76,476,11,266,14 }, -{ 15,515,700,753,4,5,11,141,13,1,33,3,0,128,202,23,180,21,2,64,269,32,117,134,120,40,102,318,153,17,137,352 }, -{ 47,130,711,108,453,412,730,196,390,283,78,27,51,183,381,236,128,200,719,14,153,472,503,34,59,250,3,4,57,803,123,432 }, -{ 12,277,51,474,111,153,23,99,13,37,961,94,629,542,569,431,79,139,38,134,117,453,33,188,196,40,115,15,11,157,401,515 }, -{ 17,495,469,106,26,378,80,27,161,483,19,742,527,436,383,862,73,136,53,814,297,6,119,84,62,56,25,3,209,611,4,128 }, -{ 81,681,636,91,0,750,370,104,718,138,18,693,173,784,29,397,348,74,192,673,174,65,6,207,64,280,306,52,671,32,355,319 }, -{ 15,515,700,753,33,77,4,102,115,117,40,13,1,153,134,11,5,217,23,196,2,21,3,317,32,365,0,341,291,59,12,51 }, -{ 0,9,28,35,68,1,65,67,101,39,69,175,16,238,13,22,96,124,18,24,251,30,55,12,23,2,50,141,114,5,154,103 }, -{ 23,33,77,13,117,40,11,102,64,4,51,403,153,453,10,0,196,134,128,65,12,291,86,99,95,59,15,141,202,180,137,719 }, -{ 214,90,289,6,874,64,25,65,235,42,751,249,256,312,194,85,746,875,174,32,525,288,519,835,247,348,233,544,217,524,437,352 }, -{ 1,22,2,0,36,67,28,5,49,95,12,50,168,83,105,55,7,9,14,194,103,23,114,21,584,46,10,13,38,69,208,159 }, -{ 269,141,13,202,33,180,318,77,291,137,102,352,128,23,349,51,31,217,372,317,125,197,44,21,11,5,901,1,18,0,4,494 }, -{ 435,144,274,88,203,418,30,1,190,410,96,778,100,530,521,326,466,795,686,166,960,321,382,264,367,822,131,31,692,9,213,93 }, -{ 76,72,90,21,37,179,12,205,32,428,148,38,308,405,4,413,57,184,749,245,316,221,54,645,288,1,152,155,464,257,2,14 }, -{ 77,33,64,102,13,141,23,2,40,1,51,10,0,115,6,180,202,128,4,3,177,269,15,7,22,165,291,14,217,318,137,11 }, -{ 397,81,4,32,65,788,693,804,681,11,249,21,91,64,690,494,3,0,422,56,348,725,194,123,23,59,523,319,61,510,95,90 }, -{ 60,126,16,7,92,121,314,246,35,107,150,132,14,146,24,18,199,298,232,71,359,140,672,97,392,649,5,423,95,21,22,388 }, -{ 15,515,141,217,115,700,13,23,120,317,753,180,33,260,110,137,341,51,1,365,4,77,64,202,0,40,36,352,197,269,10,21 }, -{ 111,134,117,474,23,13,961,12,569,431,37,15,51,115,515,700,277,99,753,38,197,405,457,4,72,94,629,45,11,89,54,148 }, -{ 23,13,51,5,1,15,2,21,12,202,141,0,515,165,120,32,4,64,700,3,115,197,269,125,753,7,9,128,6,180,453,403 }, -{ 13,141,4,23,5,2,115,217,202,51,180,137,269,352,77,1,317,3,21,318,0,15,9,64,10,197,11,341,33,515,752,7 }, -{ 165,125,197,13,391,21,23,558,48,380,97,120,298,33,14,426,66,115,32,386,900,180,6,98,357,237,326,509,51,278,221,457 }, -{ 120,82,15,260,515,1,351,77,450,700,13,21,141,23,753,202,217,93,110,33,51,854,5,128,326,102,137,180,817,48,269,352 }, -{ 23,13,15,51,515,700,961,753,0,457,1,2,4,115,10,453,569,5,33,165,11,719,14,40,64,197,3,21,474,629,38,401 }, -{ 264,166,39,30,9,100,435,254,93,921,190,363,1,625,411,382,897,656,203,478,404,812,438,110,473,88,18,691,156,141,274,272 }, -{ 9,0,252,100,166,39,101,265,364,68,88,329,520,18,419,676,118,167,404,604,16,1,21,30,212,158,553,49,382,274,48,13 }, -{ 15,515,700,753,4,11,141,5,3,13,202,1,180,21,2,165,269,23,40,64,0,318,12,32,128,51,77,117,523,197,120,457 }, -{ 24,1,2,69,35,16,67,18,14,50,0,46,68,9,38,7,133,71,83,149,28,108,189,218,65,114,238,29,75,54,5,96 }, -{ 90,289,214,64,874,13,77,712,66,751,4,23,51,192,32,0,202,194,312,177,33,65,234,104,875,288,59,5,835,416,102,95 }, -{ 0,9,49,127,98,31,301,28,371,159,1,395,512,737,158,761,916,623,16,44,242,39,170,18,293,105,24,272,101,22,23,385 }, -{ 17,62,136,214,123,129,32,292,119,209,710,106,141,162,128,64,45,4,77,249,11,618,211,3,207,130,519,183,38,177,21,269 }, -{ 5,107,581,356,279,32,441,362,493,660,13,298,0,534,49,147,21,22,132,121,97,423,7,590,259,683,14,786,126,508,60,246 }, -{ 51,13,15,730,453,23,515,719,386,457,12,700,403,475,899,1,6,523,753,421,99,401,165,33,2,19,361,5,0,670,120,27 }, -{ 49,28,9,159,272,22,254,131,158,327,95,105,0,39,35,168,347,286,374,55,65,627,424,912,68,578,1,24,239,175,688,169 }, -{ 15,515,700,33,753,4,77,141,341,317,1,10,13,180,102,22,40,117,115,365,5,901,23,197,134,11,217,351,64,82,21,137 }, -{ 134,15,13,515,23,700,12,753,51,474,37,961,197,10,457,569,4,0,99,2,115,38,165,153,94,3,139,11,1,82,33,5 }, -{ 7,2,20,58,5,14,128,66,6,29,32,43,21,52,16,38,631,61,74,97,46,135,113,25,202,192,13,0,884,45,112,87 }, -{ 77,13,33,202,23,128,102,4,141,342,117,0,269,318,134,22,11,21,32,153,403,291,49,64,137,51,40,15,494,5,196,98 }, -{ 2,1,14,6,46,38,29,65,5,36,67,0,103,7,22,86,133,50,108,208,52,83,24,323,283,69,28,18,10,25,23,75 }, -{ 15,515,700,753,1,5,4,2,3,13,0,11,180,341,12,33,10,197,134,365,77,23,21,901,6,117,165,7,37,32,17,102 }, -{ 203,268,206,93,417,940,31,8,120,137,44,499,959,473,202,692,728,559,0,260,10,326,141,564,817,127,341,1,450,22,110,23 }, -{ 15,82,515,120,700,0,10,753,33,8,64,165,110,31,260,93,13,197,23,22,40,4,351,44,77,9,11,153,102,51,1,196 }, -{ 60,0,16,7,14,43,20,71,28,10,2,22,154,18,13,24,92,1,51,576,35,615,805,925,68,126,124,149,97,64,23,55 }, -{ 19,6,26,80,5,84,27,17,25,2,504,129,45,240,56,123,4,119,618,1,76,106,64,51,14,3,128,65,32,710,0,42 }, -{ 15,515,700,753,13,4,77,23,33,51,0,5,8,10,11,31,44,1,82,22,202,64,110,102,93,21,291,40,141,180,9,49 }, -{ 195,98,271,223,132,167,146,407,1,360,121,834,393,591,212,199,293,259,522,107,354,147,156,191,807,590,48,18,125,16,765,541 }, -{ 128,202,77,210,402,318,33,102,6,40,403,29,342,269,196,757,99,139,2,111,42,4,494,117,275,300,13,12,678,0,177,122 }, -{ 13,33,23,40,51,102,4,117,77,64,134,0,128,153,202,196,453,11,15,12,1,22,403,141,59,14,10,475,515,65,700,95 }, -{ 7,16,14,24,18,2,28,0,92,71,1,22,6,35,60,20,168,10,154,118,5,302,124,69,97,109,703,158,420,12,149,66 }, -{ 15,1,515,23,0,13,700,2,51,753,180,5,120,165,197,21,115,4,33,9,141,7,12,6,3,457,386,202,260,523,8,31 }, -{ 60,107,121,132,146,126,199,279,150,92,16,649,441,35,955,7,21,0,423,5,18,195,598,298,493,356,32,653,22,362,953,10 }, -{ 31,44,98,276,284,299,116,935,9,201,0,131,39,127,144,662,1,137,371,492,567,489,93,254,49,268,22,28,30,293,434,737 }, -{ 13,15,23,515,700,0,1,51,753,4,2,10,77,202,5,115,3,165,197,457,9,12,11,961,33,120,22,141,180,7,6,40 }, -{ 123,162,184,257,17,183,229,130,129,3,84,136,99,152,556,383,57,497,12,205,4,62,56,452,80,266,128,14,40,119,27,106 }, -{ 196,33,117,40,153,23,134,13,51,102,453,0,15,475,12,14,515,2,22,700,4,21,753,64,401,670,730,1,9,11,10,99 }, -{ 224,219,187,131,258,385,442,871,836,31,98,908,44,574,127,944,137,839,116,36,613,1,254,39,926,160,829,96,93,371,860,827 }, -{ 121,195,156,132,146,360,590,407,786,522,883,591,259,929,626,941,150,687,5,55,296,379,467,178,586,465,279,21,1,13,60,354 }, -{ 2,1,14,29,6,5,46,52,38,19,114,75,26,65,108,96,25,50,36,70,103,309,17,236,218,74,12,86,0,3,10,112 }, -{ 15,515,82,700,120,753,10,0,8,197,260,165,351,64,13,110,117,93,31,1,9,33,22,23,457,44,450,77,102,898,40,49 }, -{ 7,66,97,2,172,74,226,52,29,135,192,232,43,324,92,5,38,20,222,14,6,568,87,107,353,620,580,16,138,174,448,32 }, -{ 62,129,123,162,136,249,618,183,507,57,4,152,17,59,11,184,117,77,3,128,211,41,130,205,12,40,33,106,64,229,38,313 }, -{ 1,13,15,2,4,515,23,0,3,115,700,5,51,77,341,141,753,180,33,217,197,202,901,6,21,165,11,365,318,317,10,102 }, -{ 6,26,235,19,145,47,112,78,64,27,453,95,29,444,25,624,85,108,648,70,32,130,74,42,711,630,632,138,65,122,113,730 }, -{ 23,51,12,15,13,99,515,153,117,10,700,37,120,82,165,2,753,64,128,0,403,3,5,1,134,197,453,31,202,457,110,21 }, -{ 16,24,18,71,64,35,92,7,246,146,9,108,60,118,199,5,140,2,267,0,230,830,32,133,1,68,50,330,247,563,36,12 }, -{ 15,515,700,753,0,1,13,2,23,3,4,217,51,5,115,8,9,180,341,10,7,6,317,77,33,372,901,197,365,11,120,165 }, -{ 234,639,178,202,77,142,5,455,450,49,416,0,147,427,198,21,315,329,13,318,325,557,120,344,113,259,22,128,61,105,23,494 }, -{ 1,31,36,44,141,180,55,2,64,22,98,116,13,352,0,115,10,127,5,164,253,498,237,165,341,197,4,86,15,170,125,23 }, -{ 15,120,13,141,23,260,217,515,1,77,51,110,180,700,317,82,269,137,115,202,21,753,64,5,351,291,0,450,352,93,36,326 }, -{ 26,6,112,396,19,145,25,122,648,287,42,74,624,222,416,45,138,66,644,151,113,651,29,573,64,280,445,27,525,85,70,58 }, -{ 156,360,5,146,121,21,271,522,354,132,49,13,18,195,16,340,60,591,446,586,727,0,107,407,167,48,1,463,199,566,32,23 }, -{ 5,61,49,147,178,612,660,120,21,182,23,427,259,683,33,4,77,70,13,3,376,98,64,0,481,344,48,595,291,263,141,51 }, -{ 89,79,468,179,358,205,94,405,115,498,72,180,365,431,37,111,341,734,188,317,482,217,11,4,245,152,413,216,12,474,490,752 }, -{ 24,16,35,68,18,71,7,92,0,108,9,14,118,101,336,175,375,302,28,124,154,55,149,60,398,1,65,2,140,273,345,230 }, -{ 51,730,421,801,453,386,23,523,13,475,719,401,670,365,899,403,115,457,758,165,33,494,450,6,423,805,629,56,569,514,958,388 }, -{ 113,45,6,311,29,2,151,614,145,491,112,80,5,27,61,74,315,66,209,631,19,25,58,17,73,26,1,243,70,64,611,287 }, -{ 4,339,188,471,11,59,79,12,377,94,99,33,77,102,51,111,37,152,13,961,474,542,40,342,3,23,128,403,202,177,184,57 }, -{ 15,4,515,11,700,33,82,40,0,120,753,10,8,110,13,93,23,165,77,260,64,31,22,51,44,102,351,1,125,9,197,21 }, -{ 16,24,18,0,35,68,28,71,124,118,60,7,9,55,14,92,109,101,419,175,22,252,154,375,149,302,158,346,2,49,1,126 }, -{ 17,45,227,21,106,3,2,243,209,5,48,32,221,62,207,50,29,186,290,270,263,52,14,496,400,119,46,255,54,430,38,721 }, -{ 340,354,586,658,156,195,698,668,1,296,9,18,883,363,447,379,303,98,411,13,31,163,51,5,371,48,919,846,121,21,360,70 }, -{ 277,153,111,12,23,51,474,99,38,37,139,117,41,457,79,453,542,13,11,33,134,157,629,188,961,14,196,401,102,569,15,94 }, -{ 0,18,16,159,49,24,9,105,35,68,7,28,22,1,60,344,55,101,109,2,14,158,13,23,71,118,455,286,272,424,5,327 }, -{ 0,105,9,49,16,18,158,28,518,24,101,320,1,68,170,301,272,127,7,286,35,890,109,39,159,98,21,344,31,55,371,23 }, -{ 141,1,180,15,13,2,365,217,515,352,317,115,341,0,4,5,269,700,23,21,3,752,197,77,753,51,31,901,10,202,8,64 }, -{ 4,23,51,33,19,17,102,153,485,880,40,403,196,26,300,453,27,117,78,0,12,200,47,5,11,14,342,99,53,77,475,2 }, -{ 62,184,56,440,130,229,183,3,556,152,99,162,12,266,17,548,136,57,305,161,123,14,452,4,383,403,257,34,40,84,33,139 }, -{ 13,23,77,141,64,202,33,51,269,115,0,102,21,4,217,128,5,32,318,137,291,9,15,2,180,10,3,317,177,515,7,6 }, -{ 1,22,36,105,170,0,86,2,31,28,239,64,55,5,10,98,9,44,127,95,654,67,301,143,13,12,49,23,320,141,83,21 }, -{ 15,515,700,753,0,1,13,2,23,901,5,8,51,82,9,180,457,4,7,12,3,6,10,120,341,141,22,898,197,351,115,260 }, -{ 1,39,274,98,100,265,190,30,438,310,166,223,88,96,909,31,264,625,530,9,382,812,21,252,593,0,254,539,44,131,23,778 }, -{ 18,212,167,118,363,1,447,411,146,60,271,16,781,121,647,9,621,562,21,478,664,68,815,5,354,98,48,101,24,446,777,463 }, -{ 24,28,22,0,7,1,2,16,14,65,35,49,158,95,109,159,55,105,10,18,124,9,67,5,239,149,12,289,108,68,21,424 }, -{ 105,22,131,272,286,98,55,239,1,31,320,9,127,327,36,185,28,374,86,219,0,64,187,44,578,164,224,913,535,115,601,13 }, -{ 22,31,28,301,127,98,44,0,105,1,512,395,9,293,109,299,95,338,239,125,242,116,36,320,55,841,900,685,599,23,13,763 }, -{ 2,1,58,29,5,14,52,46,186,334,45,155,151,50,400,75,38,69,502,61,48,227,223,7,163,17,262,67,549,21,70,113 }, -{ 7,107,135,232,97,14,2,92,66,16,172,192,278,387,298,356,38,35,448,52,46,43,60,29,20,126,324,526,357,359,64,5 }, -{ 20,43,104,426,173,7,560,414,707,784,319,81,0,861,422,819,38,74,715,52,376,97,879,32,330,22,49,64,66,95,192,526 }, -{ 104,74,636,66,204,0,355,81,222,25,29,319,145,784,20,65,90,4,174,194,7,64,6,746,138,173,750,715,91,43,192,32 }, -{ 0,9,101,35,68,39,65,28,252,124,67,154,364,336,100,166,30,1,289,55,149,346,16,114,158,88,439,24,429,22,570,194 }, -{ 57,14,4,231,236,585,176,59,369,23,361,13,719,51,300,342,12,457,56,3,62,38,202,401,34,46,2,322,11,215,210,507 }, -{ 1,2,15,3,141,0,515,5,33,700,13,64,77,180,6,128,753,10,4,269,102,202,11,7,134,197,352,120,117,318,12,291 }, -{ 5,1,21,202,13,32,48,23,0,61,259,22,494,120,70,49,51,18,137,128,465,12,178,115,2,453,403,141,58,3,90,450 }, -{ 141,205,4,72,59,79,245,11,352,94,152,76,247,216,21,188,452,217,497,12,89,37,111,339,588,77,64,875,864,115,358,464 }, -{ 15,515,700,753,0,1,2,13,5,4,23,3,8,341,365,51,115,10,120,457,6,141,77,197,31,7,165,9,202,450,961,260 }, -{ 5,2,50,14,58,38,171,46,29,1,45,186,17,52,155,218,48,281,61,487,54,36,67,21,328,334,151,227,760,114,400,133 }, -{ 457,120,70,125,318,64,23,48,795,291,202,761,751,415,77,846,269,758,21,237,96,260,391,165,87,1,128,5,221,13,137,763 }, -{ 13,23,51,33,4,40,117,102,453,64,153,196,0,77,15,11,12,475,1,65,134,10,515,22,21,14,700,59,403,141,2,753 }, -{ 229,152,57,266,452,381,432,12,313,184,99,471,17,4,62,339,157,3,129,59,128,11,369,37,77,38,40,123,5,497,188,257 }, -{ 49,28,109,22,159,9,272,95,105,131,55,35,254,168,39,327,169,0,1,286,175,374,347,158,420,67,36,194,312,424,627,346 }, -{ 5,2,61,29,45,58,80,311,1,17,209,227,52,243,106,869,454,151,592,496,48,334,14,155,6,186,46,171,75,21,255,667 }, -{ 244,44,110,141,260,30,269,352,839,131,574,228,373,276,1,406,219,717,217,137,253,224,120,93,36,31,567,116,661,187,341,88 }, -{ 12,99,79,139,11,453,196,51,277,474,111,23,542,37,94,188,33,13,401,775,40,961,313,102,4,339,153,485,629,134,300,431 }, -{ 16,35,9,0,68,24,149,69,67,18,1,114,65,230,71,7,103,133,50,167,212,118,101,191,140,64,399,28,124,283,55,565 }, -{ 88,30,274,435,131,613,190,100,93,829,166,1,187,795,530,127,382,957,960,160,31,137,466,264,39,800,406,254,28,473,521,219 }, -{ 167,16,18,118,212,24,60,71,101,68,191,9,375,411,363,35,0,1,589,199,302,21,447,55,146,126,92,271,647,121,562,48 }, -{ 64,141,86,177,77,128,147,597,304,95,269,102,275,4,352,49,120,5,372,194,465,13,588,237,947,216,202,180,612,751,107,534 }, -{ 18,65,90,403,523,289,240,214,194,102,701,475,202,217,283,862,389,51,33,0,494,421,453,817,84,64,847,899,352,13,23,437 }, -{ 13,51,23,202,5,12,21,128,15,115,0,1,141,120,64,32,4,2,515,403,165,457,3,10,700,99,453,318,719,450,308,401 }, -{ 98,223,393,31,1,271,834,791,167,44,202,64,93,697,5,116,77,125,450,446,212,18,541,293,51,120,195,132,284,13,807,765 }, -{ 15,515,700,753,4,11,23,13,40,51,82,165,0,110,93,33,141,64,120,5,10,77,3,102,180,32,202,125,8,197,31,21 }, -{ 15,515,700,753,0,1,13,2,901,23,5,341,3,51,82,8,4,180,961,9,115,10,12,6,898,7,351,141,134,22,31,120 }, -{ 234,416,77,5,315,639,325,202,147,198,113,49,450,61,455,142,0,21,22,342,329,494,178,58,102,427,318,230,13,120,43,470 }, -{ 60,146,16,18,156,126,121,271,199,360,132,24,167,0,640,10,71,522,21,92,5,340,107,354,118,150,22,195,446,35,28,212 }, -{ 4,361,11,14,56,368,377,161,27,12,300,77,59,200,17,554,202,33,40,494,495,21,210,80,757,25,128,23,19,38,444,53 }, -{ 141,82,217,351,15,352,120,1,180,260,515,64,854,36,700,317,752,372,13,269,77,753,922,21,349,23,202,110,93,137,51,373 }, -{ 15,515,700,753,77,13,0,1,23,33,102,2,51,4,3,5,291,217,10,9,450,120,341,7,317,6,11,117,115,8,260,180 }, -{ 15,515,120,13,700,23,77,141,1,260,0,753,180,51,137,202,115,365,110,291,217,5,128,9,21,341,197,269,2,450,317,165 }, -{ 174,6,348,85,138,74,280,204,66,233,192,355,289,65,81,580,636,353,25,91,104,343,673,214,64,95,42,712,792,32,194,90 }, -{ 152,497,452,59,4,216,11,79,94,77,128,188,269,339,588,33,76,529,318,32,141,471,12,202,111,21,5,51,37,90,72,177 }, -{ 417,499,10,141,253,244,110,559,8,564,180,260,728,120,352,638,642,341,951,206,143,752,901,93,137,661,922,373,44,31,811,197 }, -{ 13,77,23,33,4,51,0,102,128,59,141,40,64,115,177,10,137,22,202,2,7,11,90,1,117,180,269,14,49,6,134,3 }, -{ 1,2,22,0,36,5,67,50,14,28,12,86,38,46,83,168,194,65,103,114,49,7,10,95,21,69,23,24,128,51,55,13 }, -{ 17,106,119,207,255,306,742,378,84,62,136,45,3,5,240,80,61,56,209,383,311,790,655,32,2,440,76,151,58,29,179,263 }, -{ 3,128,1,141,2,202,33,5,64,15,0,515,102,13,269,10,700,180,134,51,120,6,77,318,23,137,17,117,753,197,82,153 }, -{ 514,38,377,328,11,57,41,248,880,266,556,4,152,361,471,757,485,403,305,102,3,211,313,99,457,130,12,14,157,40,23,54 }, -{ 68,0,167,101,9,118,264,520,16,18,21,478,562,1,124,212,100,936,664,777,191,88,806,154,48,24,759,604,35,252,265,65 }, -{ 230,689,699,213,466,352,217,831,30,443,418,144,854,201,840,855,1,251,203,317,530,957,96,93,822,539,36,752,351,137,83,800 }, -{ 33,77,102,117,15,82,13,134,23,64,0,515,120,153,51,4,40,128,700,260,202,141,196,22,753,11,351,10,1,326,95,269 }, -{ 11,40,33,51,117,13,542,328,14,134,38,153,23,12,485,231,102,54,775,37,3,377,111,139,211,4,457,403,369,475,99,719 }, -{ 33,64,77,128,141,2,1,202,102,13,23,117,0,15,3,153,51,134,10,40,6,5,515,269,137,180,318,165,700,7,196,753 }, -{ 15,515,700,753,4,1,5,11,13,21,33,180,93,141,64,2,23,77,82,3,0,102,32,40,352,341,10,197,98,110,117,901 }, -{ 1,2,14,67,50,46,38,24,103,83,0,5,36,28,29,133,114,96,65,52,18,75,54,108,22,7,238,58,160,9,361,69 }, -{ 258,201,276,137,160,860,116,261,295,843,567,144,131,44,187,268,943,219,284,31,202,935,141,98,662,203,127,96,36,93,224,1 }, -{ 7,2,14,16,46,87,75,52,92,278,29,38,140,70,1,5,35,294,24,262,135,69,171,172,58,409,112,60,50,66,97,12 }, -{ 13,23,0,2,51,1,33,4,115,10,15,141,77,3,5,180,217,515,9,7,64,11,700,6,102,40,197,22,317,753,165,202 }, -{ 74,145,6,66,25,204,42,29,222,337,138,26,7,525,192,174,746,287,544,135,415,2,609,632,112,64,87,0,85,45,712,396 }, -{ 77,33,102,15,217,13,23,141,202,515,51,700,291,4,269,753,317,180,21,64,318,115,128,0,275,2,352,196,3,5,137,11 }, -{ 187,219,258,871,44,442,160,574,137,224,908,116,839,131,36,926,276,201,93,228,202,860,31,613,144,531,406,1,902,30,190,318 }, -{ 1,372,141,5,21,77,225,744,96,30,23,349,13,291,269,284,69,442,459,144,303,839,217,622,160,330,260,48,120,410,189,352 }, -{ 66,222,2,74,29,87,135,6,7,145,52,25,294,337,226,172,138,331,42,70,97,112,26,1,632,192,43,5,415,609,461,353 }, -{ 45,17,106,209,5,2,21,29,48,207,3,186,243,155,255,263,454,119,400,496,270,14,290,62,425,1,171,32,659,52,38,56 }, -{ 93,88,141,120,30,213,260,373,100,717,459,82,110,1,166,450,180,321,217,372,36,269,131,225,22,352,326,466,473,187,244,410 }, -{ 266,57,152,381,313,471,12,229,99,369,339,62,157,3,4,37,77,38,188,17,11,162,40,184,129,59,475,775,128,452,403,453 }, -{ 217,352,317,141,752,15,180,515,372,365,700,341,753,349,77,21,291,1,115,244,64,120,13,98,269,82,5,498,864,351,23,144 }, -{ 14,514,369,102,403,377,51,719,880,153,23,13,457,11,485,4,401,12,328,453,33,40,117,57,629,38,730,236,134,670,361,961 }, -{ 107,7,172,14,92,135,2,359,60,314,46,16,126,278,232,150,279,32,38,392,298,5,35,97,24,192,259,288,330,52,356,312 }, -{ 0,4,25,13,59,90,65,23,26,19,18,12,5,216,91,51,389,33,77,11,22,85,27,81,21,177,746,45,42,194,37,123 }, -{ 5,49,315,202,416,77,455,639,450,21,197,137,350,13,408,0,329,318,494,344,61,402,64,509,347,120,113,48,95,713,308,401 }, -{ 130,47,381,390,59,90,200,214,289,6,65,472,29,64,874,648,50,751,624,26,52,32,4,194,875,714,85,249,247,33,881,19 }, -{ 51,23,453,13,719,12,457,165,37,730,99,4,386,197,401,17,11,2,3,15,5,961,475,6,515,64,54,700,32,115,0,403 }, -{ 15,515,1,13,700,2,23,0,753,5,3,180,51,4,165,12,141,21,197,457,7,115,6,9,352,10,120,202,8,341,11,77 }, -{ 0,9,1,67,35,28,68,16,24,65,18,69,50,114,103,12,22,13,5,101,2,96,23,83,149,21,39,55,7,175,433,124 }, -{ 28,105,22,0,1,320,170,9,49,301,109,95,127,31,98,55,65,35,2,24,168,159,36,713,16,740,13,338,21,44,512,23 }, -{ 13,77,4,51,23,33,102,202,128,59,40,0,64,141,117,403,115,11,15,318,153,269,22,515,475,134,10,494,177,1,90,210 }, -{ 13,23,0,51,77,33,2,141,4,10,1,64,115,102,3,6,22,15,217,11,180,7,40,515,165,202,177,9,269,128,700,5 }, -{ 456,116,492,8,949,268,867,391,203,51,499,13,719,386,31,791,457,918,125,10,23,93,479,685,417,0,22,338,506,551,870,730 }, -{ 17,237,45,180,106,62,32,64,115,41,136,498,255,21,197,129,241,13,3,227,23,352,165,752,350,365,449,155,4,546,476,38 }, -{ 1,15,180,515,0,2,341,700,901,352,4,141,13,3,752,5,753,217,317,115,365,23,197,21,51,165,31,6,269,202,77,7 }, -{ 205,141,216,269,497,4,588,76,59,152,128,452,79,77,875,11,72,94,188,217,352,12,247,37,90,64,32,1,474,23,947,372 }, -{ 64,247,217,237,317,180,752,115,349,141,498,13,437,304,23,372,352,164,579,291,33,864,177,197,0,490,72,10,482,77,269,51 }, -{ 2,1,0,13,15,141,3,77,5,515,64,33,23,180,6,700,4,117,217,7,10,11,102,165,753,197,115,134,40,352,12,269 }, -{ 11,40,38,328,33,542,12,313,41,339,23,157,377,117,369,51,471,99,775,485,13,305,457,57,14,475,37,248,4,54,188,719 }, -{ 33,77,102,40,13,23,0,51,4,128,64,202,117,141,22,196,153,10,134,15,59,269,1,137,65,11,403,318,453,86,515,177 }, -{ 472,80,34,250,495,161,17,14,469,176,128,4,389,106,283,436,216,527,3,297,483,177,53,56,231,194,119,84,719,57,255,59 }, -{ 317,352,180,141,217,752,115,341,365,244,1,269,202,901,253,15,21,498,372,4,137,515,13,2,700,318,5,197,23,143,753,349 }, -{ 9,39,101,18,265,100,333,520,252,16,0,329,593,1,553,364,68,167,310,30,121,254,118,158,363,166,60,604,272,24,286,404 }, -{ 15,515,1,180,700,901,0,2,753,341,752,4,3,13,115,365,317,5,23,197,141,217,165,352,6,22,36,9,137,51,7,10 }, -{ 131,39,9,829,166,613,578,827,1,30,716,254,100,98,31,224,0,406,228,310,616,219,44,846,127,190,938,96,265,371,856,438 }, -{ 17,64,62,106,141,751,136,292,32,129,352,41,38,476,86,128,214,237,5,177,123,209,217,45,269,954,162,710,180,3,90,4 }, -{ 25,42,235,65,650,736,605,6,630,85,123,343,233,256,26,122,63,389,141,249,416,444,368,194,19,108,138,174,90,0,544,511 }, -{ 184,229,152,57,266,432,497,452,17,381,619,257,313,12,4,205,59,3,99,471,157,128,5,129,339,369,77,11,32,45,202,2 }, -{ 137,202,160,860,141,30,93,567,36,276,295,261,131,39,9,964,201,843,1,98,800,318,116,22,943,187,10,219,206,44,269,535 }, -{ 0,493,125,64,49,9,279,10,35,18,93,55,293,31,14,13,194,165,325,48,22,132,21,107,98,389,44,581,342,259,174,137 }, -{ 15,515,700,753,4,33,13,77,23,5,51,32,102,40,93,11,349,141,21,8,82,202,64,31,110,10,117,0,1,44,3,318 }, -{ 110,253,854,811,352,141,244,951,180,642,661,384,498,143,752,317,911,10,269,206,559,351,261,120,902,533,922,959,365,160,332,217 }, -{ 2,29,70,1,75,52,6,220,26,112,145,331,74,163,19,69,38,324,46,58,14,5,25,21,278,223,50,307,66,7,67,409 }, -{ 13,23,77,33,51,4,64,141,115,102,0,2,128,177,40,11,202,10,6,180,7,15,269,1,32,217,59,22,291,3,137,515 }, -{ 340,897,691,478,658,264,914,382,100,812,363,1,724,156,166,698,88,521,39,404,682,447,296,96,303,411,30,909,9,274,656,772 }, -{ 9,18,310,101,265,159,326,120,105,158,33,363,77,195,51,55,13,39,354,132,23,7,28,639,16,137,98,1,252,272,709,49 }, -{ 57,313,471,12,99,369,157,339,266,152,38,37,475,453,328,775,11,40,59,188,77,514,401,403,342,4,139,33,377,51,229,14 }, -{ 16,7,24,14,35,140,60,92,18,69,71,2,189,1,46,230,108,388,150,38,21,172,278,67,246,267,50,309,236,135,451,0 }, -{ 206,417,93,940,959,473,499,203,8,137,559,728,31,202,44,120,450,141,10,260,116,564,22,326,269,318,268,244,0,1,253,638 }, -{ 15,515,700,753,1,0,13,2,23,4,3,51,5,217,7,77,341,115,8,9,10,33,6,180,317,349,291,120,11,165,457,901 }, -{ 1,2,5,14,48,21,290,32,50,45,38,46,263,207,155,72,76,29,17,408,425,171,89,52,7,0,292,449,3,227,513,428 }, -{ 121,132,354,167,271,223,146,98,18,463,1,668,446,195,407,60,212,447,781,48,360,363,411,522,156,393,807,9,21,16,293,13 }, -{ 131,578,105,371,219,224,716,616,187,49,9,254,737,159,385,98,258,127,272,761,0,916,623,910,28,286,39,31,22,518,924,242 }, -{ 302,467,97,6,273,1,24,484,124,51,36,18,2,398,453,421,523,69,7,23,13,403,386,150,66,0,298,65,426,165,22,158 }, -{ 30,190,530,88,1,100,778,539,625,274,382,410,96,731,960,39,795,321,9,131,264,144,840,748,44,166,669,957,36,31,435,228 }, -{ 141,1,2,128,64,33,15,202,3,0,180,5,13,77,515,134,269,102,197,700,10,137,318,6,120,165,753,352,4,82,23,117 }, -{ 44,201,567,116,131,224,295,662,489,268,219,31,434,144,187,276,110,384,93,261,699,137,36,442,120,1,613,30,228,64,141,244 }, -{ 12,15,51,23,515,37,99,13,700,0,10,117,753,38,165,82,134,120,11,453,197,64,115,569,1,629,401,22,457,474,110,153 }, -{ 7,135,2,92,172,14,66,140,38,52,97,46,29,74,16,324,278,226,6,87,1,571,262,5,357,232,35,380,69,314,24,330 }, -{ 125,386,23,963,949,60,51,391,165,221,13,197,118,21,719,193,541,421,517,150,393,7,401,453,308,5,791,551,326,558,48,173 }, -{ 6,85,42,25,138,222,174,235,280,256,525,289,26,214,64,746,90,32,544,65,204,19,66,337,355,95,348,415,74,29,5,312 }, -{ 1,14,5,50,2,67,24,0,46,69,48,21,58,103,16,12,18,38,54,96,83,7,502,45,36,181,35,9,430,28,10,155 }, -{ 811,351,642,180,951,752,110,638,253,10,82,352,197,341,365,564,499,854,873,55,9,417,282,901,244,22,559,143,206,141,28,898 }, -{ 23,13,51,15,12,453,403,165,4,515,115,719,475,457,700,523,2,21,0,99,202,197,14,5,386,753,128,401,37,308,33,117 }, -{ 120,13,23,77,141,1,15,93,217,82,260,51,137,202,110,515,21,180,165,5,128,102,64,351,291,700,269,352,326,203,177,0 }, -{ 1,5,0,22,12,2,36,21,10,23,86,13,28,51,9,128,48,14,32,50,7,3,96,137,54,4,202,49,37,65,208,323 }, -{ 219,98,23,127,301,51,258,308,170,910,13,165,22,105,293,616,125,242,276,401,201,395,964,115,55,284,31,374,327,206,512,900 }, -{ 64,180,80,165,5,237,2,250,34,58,297,61,197,17,22,29,186,498,231,445,247,3,752,311,95,32,483,153,27,45,115,469 }, -{ 13,77,23,33,0,2,1,64,141,51,102,10,15,3,115,40,180,6,515,128,7,22,269,202,4,217,700,5,177,117,14,165 }, -{ 15,120,51,515,13,450,23,700,202,153,196,753,260,64,128,141,730,4,326,386,21,523,33,318,5,457,95,32,403,1,77,269 }, -{ 2,1,5,29,32,45,207,263,14,425,58,72,76,21,7,408,48,46,52,186,17,292,38,6,61,89,476,50,155,720,119,3 }, -{ 15,515,700,753,4,13,11,5,1,23,33,21,3,141,32,2,40,180,117,64,269,202,102,197,0,165,120,51,341,352,153,12 }, -{ 76,5,214,129,2,123,45,710,17,249,618,460,179,32,1,257,205,519,90,207,245,184,162,61,769,209,292,106,6,29,14,128 }, -{ 1,15,23,13,120,141,51,515,202,21,700,165,0,180,137,2,5,77,128,93,753,260,269,197,326,33,110,352,82,102,318,48 }, -{ 7,2,135,14,29,87,66,52,97,172,70,112,5,58,46,337,92,16,20,43,1,38,232,155,74,294,6,461,409,151,262,32 }, -{ 574,187,384,926,860,110,258,434,269,531,141,244,160,261,253,116,699,959,940,717,533,36,219,31,902,661,871,295,201,352,10,260 }, -{ 156,354,296,1,182,586,64,379,340,937,850,698,31,48,98,44,120,18,163,23,30,658,195,125,77,284,223,291,774,481,96,39 }, -{ 250,80,34,472,17,495,176,469,33,194,64,483,4,297,141,14,161,27,53,667,56,833,73,527,585,231,106,51,84,814,2,59 }, -{ 97,7,81,140,66,92,172,192,24,298,43,6,74,69,314,426,462,14,501,16,21,508,60,189,267,232,230,104,48,20,135,330 }, -{ 31,44,116,144,268,393,492,434,367,489,127,98,918,0,384,9,22,206,948,105,93,203,1,456,332,940,299,28,137,49,293,125 }, -{ 15,128,33,3,13,51,141,1,202,64,23,2,515,120,102,0,5,82,10,700,165,197,269,153,403,110,753,137,196,318,117,12 }, -{ 31,98,127,9,0,105,22,28,44,512,293,395,299,1,242,49,685,763,320,599,125,116,109,276,284,95,870,159,23,456,36,900 }, -{ 7,24,124,1,6,97,2,69,14,18,23,92,21,67,66,16,5,484,43,20,118,65,36,22,28,0,51,140,13,71,29,150 }, -{ 1,64,442,303,284,349,202,141,622,67,154,447,260,44,652,429,9,335,237,919,197,98,167,33,682,269,547,77,863,411,340,201 }, -{ 1,15,2,141,515,0,700,13,3,180,10,753,5,64,77,33,4,6,7,197,102,269,165,23,134,11,352,341,291,349,22,120 }, -{ 99,139,12,453,196,277,775,40,475,33,23,401,215,51,11,14,77,111,313,130,38,211,37,266,129,15,339,153,719,3,369,515 }, -{ 33,77,102,4,23,128,13,141,202,64,51,0,40,59,269,115,117,137,153,1,318,11,10,177,15,134,22,90,196,2,403,32 }, -{ 7,2,14,58,70,112,16,5,87,38,46,52,6,128,135,1,32,21,155,29,66,64,0,97,92,186,172,294,13,23,20,37 }, -{ 15,13,515,1,700,2,23,0,753,5,3,4,51,10,341,115,365,180,11,33,317,77,6,7,217,12,197,165,117,9,64,102 }, -{ 2,1,14,29,75,69,67,6,52,46,38,24,103,220,83,25,70,87,262,74,96,267,50,366,26,16,226,394,357,66,108,19 }, -{ 9,105,18,39,1,0,16,557,101,272,252,890,326,49,265,21,137,100,23,938,13,310,159,5,31,24,254,51,30,128,202,132 }, -{ 80,209,45,61,667,17,6,106,5,2,151,29,483,255,454,833,27,311,112,19,738,378,1,58,113,26,25,469,119,887,32,64 }, -{ 13,23,51,15,5,1,515,0,21,2,12,141,700,165,202,115,753,32,180,4,3,197,10,120,457,9,269,128,64,341,7,33 }, -{ 99,12,453,277,139,157,369,474,339,51,38,23,37,196,188,401,775,111,11,313,328,475,153,266,4,471,79,40,33,629,102,14 }, -{ 7,92,16,232,97,140,126,14,60,107,66,35,298,387,314,104,246,462,441,150,0,38,24,2,172,357,230,330,5,633,22,289 }, -{ 13,77,23,202,318,141,33,4,51,269,102,177,115,403,137,2,40,494,90,11,342,128,31,117,21,32,7,12,64,134,14,10 }, -{ 13,2,0,23,141,1,77,3,180,33,6,64,15,10,115,51,4,5,217,197,7,165,515,102,22,11,700,269,40,352,177,14 }, -{ 15,515,700,753,4,11,1,93,13,5,180,110,82,21,120,23,2,33,10,141,3,165,197,102,901,0,32,341,117,40,153,12 }, -{ 15,515,700,753,1,13,0,2,23,4,77,51,3,5,341,291,7,33,6,115,10,9,8,217,11,177,120,180,102,165,197,365 }, -{ 20,43,198,325,173,904,104,234,66,147,77,319,416,422,97,426,5,0,7,450,861,202,712,725,2,32,639,376,38,324,945,315 }, -{ 105,0,9,28,49,301,170,1,127,159,22,16,31,98,512,623,24,109,158,395,35,68,371,65,713,55,2,242,293,21,44,18 }, -{ 213,88,689,466,230,30,321,435,699,352,217,201,795,831,144,854,1,443,96,539,530,840,418,251,855,190,93,100,669,31,957,662 }, -{ 130,453,47,196,4,57,14,59,236,711,51,153,730,77,412,381,23,202,108,128,361,13,283,117,11,719,200,46,34,78,210,2 }, -{ 1,2,5,14,0,50,36,22,38,46,65,67,12,86,114,28,103,29,208,7,10,128,21,83,218,23,96,54,194,6,133,51 }, -{ 6,26,74,19,165,453,14,730,1,125,197,50,29,51,138,357,13,2,108,391,70,719,46,457,47,500,386,262,112,23,235,52 }, -{ 9,10,376,20,43,0,49,18,30,120,2,33,325,104,501,470,77,788,725,102,523,39,858,5,904,414,174,55,137,37,342,13 }, -{ 15,515,700,753,0,1,13,23,51,77,120,202,341,82,5,4,9,260,2,137,141,128,115,351,901,8,180,10,197,21,450,33 }, -{ 105,131,272,578,9,49,371,219,159,616,286,320,224,187,716,98,28,22,0,623,127,258,910,737,385,31,239,347,254,109,424,95 }, -{ 457,51,13,23,961,12,719,99,453,15,4,515,165,401,629,3,700,11,17,14,2,37,753,41,57,569,38,45,0,33,5,32 }, -{ 202,120,5,33,318,77,450,102,1,260,403,128,494,21,165,13,269,12,326,23,342,523,402,2,817,64,15,141,125,82,457,475 }, -{ 141,269,352,217,180,64,349,137,202,160,317,15,372,515,700,752,318,753,244,13,437,291,165,864,22,237,5,82,954,21,77,418 }, -{ 70,29,2,145,74,112,26,6,75,52,19,66,632,1,87,220,5,135,163,287,307,25,226,7,58,396,294,278,113,409,69,151 }, -{ 82,351,317,15,752,180,898,352,141,901,515,341,10,700,365,1,753,498,0,217,253,115,55,854,33,5,143,32,21,160,36,197 }, -{ 39,9,310,254,0,30,101,49,252,272,100,265,105,455,159,557,190,333,286,688,18,166,1,158,709,16,625,627,31,131,327,329 }, -{ 2,58,29,5,1,151,186,52,70,45,7,549,14,75,112,400,113,155,61,46,227,163,311,315,66,6,307,27,17,220,287,74 }, -{ 141,217,13,21,352,23,269,77,180,115,317,64,202,15,349,137,5,51,165,291,318,752,372,4,0,102,33,365,197,32,341,125 }, -{ 68,35,0,9,65,101,149,124,24,154,175,16,28,7,67,1,18,189,114,398,55,14,345,39,118,133,69,2,230,429,71,283 }, -{ 66,7,29,2,112,52,20,43,97,151,74,192,135,5,173,525,337,45,145,58,415,25,14,32,644,70,544,226,222,21,6,580 }, -{ 31,125,44,22,116,299,242,55,1,170,64,36,479,870,456,685,10,599,558,0,268,506,28,740,23,903,492,164,393,206,2,86 }, -{ 188,11,79,12,99,377,94,33,542,339,40,474,111,37,4,51,102,453,139,775,13,475,23,961,277,471,134,57,431,266,115,117 }, -{ 658,698,340,98,296,303,1,31,850,363,156,919,44,774,586,385,120,77,82,10,223,30,354,291,23,914,478,87,260,163,48,13 }, -{ 15,515,700,753,82,4,1,13,901,33,197,11,5,10,23,165,2,0,180,3,21,77,51,120,365,115,217,40,117,102,32,401 }, -{ 15,515,700,753,4,11,5,13,1,141,3,180,23,202,21,2,269,64,165,33,40,32,0,318,120,128,12,197,117,352,51,17 }, -{ 91,6,233,85,370,718,81,65,25,256,63,343,42,74,235,123,138,511,397,249,26,194,650,355,64,87,544,18,90,643,66,214 }, -{ 23,13,202,51,21,120,1,5,141,128,450,64,318,403,15,137,260,33,12,48,32,31,125,494,269,102,165,515,77,2,197,14 }, -{ 180,317,365,341,752,217,115,352,901,482,372,498,1,141,15,253,515,244,2,700,0,21,13,82,23,4,579,351,753,291,269,77 }, -{ 13,115,197,341,9,352,468,237,64,498,23,165,22,509,901,546,482,180,28,569,317,51,365,873,391,95,86,217,49,837,752,706 }, -{ 13,23,51,1,141,5,165,202,21,120,64,125,180,15,2,33,197,115,128,32,260,269,12,82,4,515,137,7,318,93,0,700 }, -{ 214,289,90,174,874,6,138,280,65,81,64,85,355,751,194,233,312,348,835,91,0,32,343,636,249,29,875,288,519,104,247,74 }, -{ 15,515,700,753,4,5,11,13,1,33,23,21,2,3,102,32,141,77,180,117,31,64,0,40,134,196,120,352,12,44,197,6 }, -{ 33,15,13,515,117,23,700,217,134,753,0,51,153,77,141,2,4,64,196,1,3,180,10,115,5,102,6,11,22,202,165,7 }, -{ 15,515,700,753,33,4,77,102,1,40,13,117,11,115,134,5,21,153,23,217,3,32,2,317,120,196,180,141,51,12,59,260 }, -{ 15,515,700,753,13,0,1,23,2,217,51,3,4,5,8,317,115,9,341,10,202,180,6,365,7,82,457,22,120,901,33,291 }, -{ 7,2,135,20,97,14,66,52,337,673,192,29,43,355,353,5,16,294,107,376,147,226,331,560,64,470,222,104,415,32,4,324 }, -{ 195,132,142,167,146,77,363,271,121,354,202,120,647,178,786,212,687,0,101,878,16,522,60,5,450,411,35,55,98,639,259,318 }, -{ 202,77,20,0,318,66,104,128,102,269,177,43,33,7,216,291,494,5,2,342,74,173,97,112,450,22,337,10,234,52,64,678 }, -{ 107,362,612,356,359,97,414,43,259,20,392,7,298,147,819,683,465,173,729,660,319,14,5,779,581,595,246,35,501,92,0,230 }, -{ 6,165,14,453,13,51,19,23,386,457,74,391,308,2,26,401,47,758,603,108,719,366,1,29,309,730,324,197,133,70,115,867 }, -{ 179,72,205,180,247,245,4,490,352,59,317,152,79,498,94,217,148,76,752,864,11,216,141,405,89,452,197,111,497,188,37,21 }, -{ 107,7,298,314,14,359,32,392,232,279,172,97,60,581,387,126,121,0,534,493,356,92,441,95,13,21,35,147,22,5,16,362 }, -{ 156,271,354,586,360,132,591,195,121,18,340,1,5,13,21,48,668,446,23,463,296,658,60,55,407,698,146,70,626,51,163,24 }, -{ 13,23,51,4,0,12,457,15,11,453,2,515,5,1,99,10,115,165,700,475,401,403,3,961,40,14,37,753,719,32,64,569 }, -{ 48,125,21,165,13,221,23,763,423,508,197,5,98,92,193,16,441,386,64,314,293,457,391,140,49,60,102,693,683,51,35,867 }, -{ 202,77,120,450,5,318,1,494,0,195,18,132,523,403,326,604,354,260,121,576,203,167,234,817,682,49,35,615,21,20,13,102 }, -{ 39,9,166,30,0,101,158,68,404,190,333,274,252,310,88,100,49,28,344,35,21,22,419,131,438,1,16,65,530,694,124,10 }, -{ 15,515,700,753,110,4,1,11,165,180,93,13,82,5,2,197,33,120,0,3,10,23,21,115,901,217,341,77,317,51,32,117 }, -{ 2,29,1,14,6,52,5,46,50,26,70,19,103,58,38,67,96,262,516,309,218,133,108,27,75,17,112,114,24,487,331,83 }, -{ 120,77,15,13,1,141,260,23,515,217,110,51,137,700,317,202,165,291,180,21,753,128,0,177,326,93,450,82,64,269,197,5 }, -{ 255,59,554,297,183,56,33,444,108,358,123,196,269,122,77,153,57,177,117,730,19,467,605,130,128,50,275,4,291,475,134,133 }, -{ 13,23,51,12,153,14,117,120,165,134,99,401,38,453,15,128,197,719,64,515,475,403,37,33,196,700,40,125,5,0,54,2 }, -{ 64,33,174,348,95,108,467,554,56,0,25,306,233,6,63,511,343,120,13,85,29,561,543,707,319,180,899,355,77,49,256,18 }, -{ 120,260,51,23,77,15,202,1,93,82,141,450,13,326,515,137,21,5,64,33,110,700,128,165,318,203,269,102,351,753,197,125 }, -{ 15,515,700,753,4,13,11,1,5,21,23,2,33,64,3,180,32,141,22,102,77,0,10,93,82,352,117,40,341,31,165,6 }, -{ 15,515,700,753,341,13,23,141,33,1,0,217,4,77,180,10,82,351,51,137,5,64,9,317,21,11,102,40,260,202,854,115 }, -{ 105,272,131,22,327,286,28,239,320,9,109,578,219,49,98,224,95,159,538,371,616,127,187,64,713,55,0,170,168,258,716,623 }, -{ 16,18,68,35,24,60,71,118,92,126,0,9,101,191,7,55,154,175,212,14,167,150,302,28,375,1,107,124,346,273,21,108 }, -{ 20,147,43,470,376,142,904,178,427,798,0,595,198,325,858,319,61,202,173,97,5,422,14,22,107,259,32,49,887,77,414,392 }, -{ 13,23,51,12,33,15,99,64,128,515,453,202,117,153,37,102,700,40,134,196,120,0,2,753,141,14,38,3,82,403,77,21 }, -{ 383,17,62,136,84,119,56,440,3,504,240,80,378,129,123,548,106,128,4,11,14,555,162,32,184,361,59,64,205,5,469,57 }, -{ 70,1,48,652,5,638,846,888,21,349,269,260,340,562,767,761,163,883,774,141,125,518,591,0,23,9,87,13,371,303,622,31 }, -{ 66,135,6,97,74,278,69,7,14,324,267,172,2,140,462,1,357,38,808,550,92,841,189,29,16,25,298,87,75,204,24,335 }, -{ 51,23,33,13,102,40,12,128,64,77,10,202,0,196,117,4,14,99,134,453,65,153,11,475,139,403,22,141,86,2,21,15 }, -{ 88,100,264,166,274,435,772,1,382,921,96,478,30,438,639,909,897,521,190,466,960,410,9,144,530,418,31,329,265,691,778,93 }, -{ 62,440,136,56,84,3,504,548,555,383,4,17,129,128,507,361,123,59,119,162,14,57,152,328,161,11,202,495,184,27,80,215 }, -{ 911,617,332,959,206,141,253,244,282,384,110,120,10,260,352,143,951,811,269,373,160,417,93,531,728,203,434,940,137,55,36,717 }, -{ 120,15,260,141,77,1,515,82,700,351,33,23,450,13,110,326,64,217,269,753,203,137,102,5,165,21,51,291,93,177,373,128 }, -{ 15,515,700,753,0,1,2,23,13,51,5,9,82,901,180,8,3,4,120,6,7,141,93,12,197,341,10,33,115,730,64,125 }, -{ 7,104,97,107,356,232,66,560,298,289,14,707,38,568,359,64,20,0,65,324,22,214,92,32,192,5,387,43,712,90,172,95 }, -{ 6,1,2,66,67,14,74,24,108,29,69,83,458,7,25,38,135,103,36,150,451,114,52,594,75,65,380,18,267,602,19,278 }, -{ 13,23,51,12,115,21,202,5,457,15,4,1,64,719,0,403,2,3,453,165,99,141,401,128,32,515,10,37,523,197,120,700 }, -{ 57,59,4,11,412,381,77,53,421,291,250,368,99,14,27,369,803,283,23,108,403,19,339,210,0,401,12,444,236,40,361,736 }, -{ 15,515,700,1,0,753,2,13,23,5,51,180,3,115,6,7,457,4,9,8,12,82,197,165,141,901,120,719,33,64,21,22 }, -{ 64,95,180,247,929,146,90,126,197,32,237,60,288,165,316,92,5,13,77,7,217,955,522,22,16,314,132,4,317,10,312,86 }, -{ 15,1,120,13,23,515,0,51,700,180,141,2,5,202,21,260,753,165,137,33,77,110,197,128,326,7,450,4,102,9,269,12 }, -{ 14,2,16,46,1,7,24,69,75,35,38,50,29,220,52,140,267,67,18,54,70,309,5,60,92,189,171,87,71,163,58,0 }, -{ 31,98,127,44,9,299,0,276,293,284,116,49,935,599,105,22,456,201,28,1,39,125,242,137,371,144,131,492,159,272,51,395 }, -{ 6,27,151,53,573,445,297,113,26,73,436,19,491,250,396,315,45,112,145,58,614,881,25,34,611,200,17,80,70,5,138,631 }, -{ 32,693,81,788,90,804,403,56,494,21,84,397,202,65,18,77,64,681,214,725,523,784,526,33,102,825,240,0,115,241,817,91 }, -{ 24,7,14,2,18,16,65,0,108,149,28,69,1,71,154,36,124,35,67,140,189,429,92,68,66,22,55,118,302,150,9,6 }, -{ 0,68,9,35,65,101,189,212,114,67,124,69,1,154,149,39,230,64,252,16,88,702,103,100,18,336,28,329,520,83,30,755 }, -{ 5,2,186,29,61,45,17,1,52,48,58,171,155,227,80,209,311,21,14,46,50,106,243,513,334,502,496,38,3,6,32,592 }, -{ 15,515,700,753,13,1,2,0,3,4,5,23,341,11,10,33,6,51,165,117,153,7,180,12,365,901,77,569,197,115,64,9 }, -{ 13,15,23,515,0,51,1,700,4,2,753,10,3,5,12,77,33,961,165,457,197,11,115,9,22,102,40,403,202,21,14,59 }, -{ 15,515,700,753,13,0,1,23,2,33,102,5,4,10,9,3,51,115,77,7,6,341,12,11,217,40,457,196,180,165,8,523 }, -{ 166,39,30,274,190,100,333,438,530,310,88,252,0,9,539,265,1,656,404,101,625,131,778,254,31,455,676,329,724,158,21,23 }, -{ 734,148,94,308,431,115,37,89,111,413,79,468,197,629,341,474,569,12,13,873,179,401,11,4,180,23,205,72,59,365,134,51 }, -{ 539,228,224,219,816,190,30,258,871,840,669,93,406,530,957,187,160,531,748,137,131,88,863,36,728,839,44,213,352,116,202,466 }, -{ 393,791,125,801,730,551,386,23,31,175,93,98,51,13,144,788,126,203,21,345,116,22,949,110,575,165,326,44,0,4,60,221 }, -{ 13,23,77,141,0,4,51,2,33,115,64,1,10,3,6,15,11,102,7,217,180,40,515,22,128,177,202,9,700,269,165,5 }, -{ 2,29,7,70,52,14,1,58,112,46,75,5,171,163,87,220,307,151,186,334,38,66,155,16,69,135,278,45,262,97,6,21 }, -{ 88,321,213,100,230,435,689,466,1,382,30,352,217,699,410,96,795,36,921,752,190,141,144,180,44,831,317,83,443,31,840,251 }, -{ 363,411,101,520,354,9,195,668,132,156,447,1,905,364,18,23,765,664,146,5,360,13,121,96,98,31,252,39,100,759,264,551 }, -{ 13,23,51,730,12,719,453,457,401,475,5,21,403,2,0,1,15,4,3,899,99,32,165,11,515,308,197,115,6,961,700,523 }, -{ 72,76,89,12,37,4,308,179,38,528,90,431,54,205,148,184,401,57,152,474,23,59,51,245,428,11,32,99,405,316,257,21 }, -{ 376,20,43,147,470,173,97,595,107,319,414,142,819,5,729,178,858,7,427,32,426,104,14,0,392,362,259,61,230,77,560,246 }, -{ 202,141,269,494,318,137,51,128,403,4,217,96,77,5,64,177,291,180,15,352,102,10,33,349,2,317,0,341,120,515,21,453 }, -{ 77,202,33,128,102,318,494,269,13,0,117,23,342,291,403,15,134,51,153,141,177,515,82,137,196,700,203,64,22,351,753,4 }, -{ 253,110,951,352,499,811,10,854,180,638,244,559,642,752,564,8,141,143,417,341,901,260,206,197,922,661,93,15,498,373,165,911 }, -{ 141,13,23,180,4,217,5,1,269,317,21,0,2,202,115,51,352,77,3,197,64,341,318,15,291,9,137,93,32,165,515,33 }, -{ 9,0,18,252,16,101,68,39,24,118,35,109,158,329,28,167,60,364,333,265,49,100,22,419,553,55,1,677,71,7,212,159 }, -{ 28,109,9,39,0,158,49,22,168,35,55,175,1,65,67,185,194,159,289,95,272,114,30,105,86,584,36,169,254,2,83,24 }, -{ 15,515,13,700,1,753,2,23,0,3,4,5,33,341,11,51,6,10,197,115,901,180,77,40,102,12,365,165,141,217,7,317 }, -{ 173,693,104,422,5,18,61,32,102,0,20,13,784,560,33,66,397,526,49,207,29,25,510,707,65,6,11,344,21,263,81,77 }, -{ 23,13,386,51,308,801,719,221,401,949,21,730,165,421,102,115,125,33,341,670,468,117,770,1,120,6,197,14,403,97,67,958 }, -{ 0,49,105,16,28,24,159,9,158,320,1,68,35,239,170,18,109,7,55,65,2,95,301,124,347,14,21,154,22,127,286,31 }, -{ 2,5,1,207,45,29,32,58,76,61,6,263,292,655,72,14,17,476,7,119,52,306,70,64,21,90,186,214,106,38,3,790 }, -{ 21,6,125,49,13,64,715,66,115,95,197,33,22,32,204,165,56,278,0,408,241,120,4,808,681,350,263,85,81,571,135,509 }, -{ 612,427,325,107,202,5,376,49,64,392,403,470,21,147,31,788,494,14,362,465,858,98,20,804,518,43,845,318,125,97,725,534 }, -{ 32,21,76,72,2,1,14,5,241,449,89,38,350,221,155,48,50,292,37,46,45,90,270,54,17,179,214,12,148,430,476,413 }, -{ 24,0,28,16,7,124,35,154,14,149,65,18,9,68,55,108,175,71,2,1,22,109,92,67,484,336,118,69,302,398,570,420 }, -{ 1,5,14,2,48,50,38,67,46,21,0,54,45,270,281,12,24,32,155,96,513,103,290,83,61,58,36,17,37,72,69,181 }, -{ 13,961,569,197,37,15,23,474,515,94,148,111,12,165,629,341,700,79,901,401,51,405,753,10,134,4,115,734,873,11,89,117 }, -{ 33,23,102,51,13,40,77,128,64,202,141,15,4,12,0,1,2,117,22,11,10,403,153,515,99,318,137,269,139,196,700,134 }, -{ 0,1,24,67,9,16,18,35,28,69,103,50,5,2,65,12,83,68,7,96,14,22,21,149,75,114,13,133,23,71,218,54 }, -{ 384,617,940,332,855,911,206,959,434,282,141,10,93,253,244,110,144,268,120,36,352,137,417,203,116,31,44,269,160,201,143,951 }, -{ 30,93,473,137,31,704,450,652,190,203,800,254,166,274,326,144,269,160,127,303,120,625,88,848,110,435,77,521,349,131,340,744 }, -{ 53,27,73,26,19,250,297,200,25,630,17,6,611,122,34,42,714,235,472,65,436,14,80,684,690,106,45,113,680,108,64,4 }, -{ 15,515,1,2,700,0,753,3,5,141,180,4,13,77,33,10,217,6,7,134,11,352,197,64,165,341,317,23,12,115,102,40 }, -{ 254,530,39,613,688,221,30,31,438,190,228,960,1,44,141,21,180,406,23,166,9,202,13,96,137,48,131,829,317,269,393,51 }, -{ 9,39,28,35,30,166,158,36,0,175,101,346,364,67,49,68,168,420,88,1,194,131,100,352,55,83,190,64,137,570,86,65 }, -{ 62,56,3,548,555,507,440,161,34,4,215,136,162,514,361,527,17,14,211,130,328,11,383,123,84,183,38,57,184,152,205,494 }, -{ 92,126,107,7,356,493,97,279,359,298,16,246,35,60,14,441,362,121,43,423,5,132,392,20,508,230,199,146,232,173,150,414 }, -{ 15,82,141,515,291,922,349,700,217,260,372,120,351,93,77,753,318,352,373,854,1,326,269,21,13,102,144,202,64,23,203,137 }, -{ 141,217,352,115,180,13,269,317,752,77,23,21,341,197,5,372,244,291,9,64,51,102,4,1,365,2,165,33,3,48,237,351 }, -{ 78,47,390,19,130,453,108,27,711,813,730,444,412,283,196,690,123,14,128,26,250,389,650,236,200,65,51,4,34,183,297,73 }, -{ 34,250,297,80,472,64,495,17,311,3,148,45,667,61,176,53,243,27,90,161,469,141,483,151,62,128,29,4,58,56,5,231 }, -{ 51,23,33,13,551,77,102,326,421,21,523,120,5,899,453,692,202,153,308,615,115,958,450,401,791,68,221,93,475,18,403,4 }, -{ 98,223,393,363,411,1,478,834,664,156,284,691,447,791,914,293,354,724,697,9,807,541,759,51,18,421,48,264,948,586,195,848 }, -{ 7,14,107,232,16,92,2,60,46,5,359,121,24,526,220,620,135,1,172,21,126,314,132,77,18,75,32,278,12,23,52,38 }, -{ 32,76,2,1,21,72,241,14,5,48,292,89,476,45,720,270,179,90,17,214,148,38,50,29,129,155,350,46,290,227,123,464 }, -{ 15,515,700,753,13,23,33,77,51,4,102,0,32,202,1,11,128,82,117,141,40,5,110,8,3,90,137,21,10,318,403,165 }, -{ 66,6,69,2,1,74,14,135,278,267,380,24,29,97,67,38,103,75,7,388,324,25,52,150,87,83,189,357,335,108,204,172 }, -{ 152,4,339,59,79,471,188,11,77,94,128,33,529,377,12,111,102,202,452,402,216,99,13,542,51,40,474,37,64,291,23,961 }, -{ 15,515,700,753,1,0,196,13,33,2,77,5,23,102,3,10,9,7,217,4,6,153,117,177,14,457,115,12,40,730,11,134 }, -{ 17,209,45,106,207,5,255,119,62,2,61,3,263,742,306,655,425,378,32,56,29,136,84,80,311,58,186,240,243,383,14,21 }, -{ 120,260,450,15,1,23,817,13,515,523,326,5,700,51,82,31,202,64,21,753,318,93,32,269,98,33,351,77,102,125,457,165 }, -{ 116,492,268,93,23,206,203,0,551,918,13,51,8,22,417,940,120,10,499,31,949,791,125,523,165,473,341,730,421,959,401,391 }, -{ 15,515,700,753,165,13,0,1,197,23,4,82,120,2,180,12,260,719,8,3,386,117,5,523,901,11,341,51,10,9,141,351 }, -{ 14,24,69,7,2,66,108,1,67,6,36,398,18,267,150,97,29,38,83,149,65,74,28,0,189,71,388,16,273,124,46,22 }, -{ 330,96,523,335,367,662,141,839,1,922,372,615,244,717,269,443,418,352,403,692,217,854,752,180,36,64,498,576,349,201,98,284 }, -{ 184,90,257,205,245,229,57,152,769,17,524,5,32,497,45,432,619,2,452,266,4,106,1,21,179,59,76,3,460,292,381,128 }, -{ 7,14,16,2,46,5,70,107,87,13,58,307,92,32,38,23,202,0,172,24,18,21,60,128,77,35,20,10,9,4,171,112 }, -{ 7,66,140,16,14,92,97,69,267,172,189,24,380,2,35,60,298,451,230,135,314,74,150,71,38,357,6,330,67,423,21,443 }, -{ 121,167,354,132,18,446,147,101,212,146,407,16,55,35,647,191,20,271,199,68,60,259,463,107,9,126,363,7,195,43,14,411 }, -{ 76,90,179,32,205,21,184,460,257,288,45,245,316,5,57,152,241,2,358,1,229,72,524,148,48,769,17,4,12,38,14,720 }, -{ 147,259,178,878,427,465,581,198,786,798,142,534,325,929,20,362,35,132,107,376,43,5,279,77,49,146,70,202,590,771,33,14 }, -{ 473,93,450,778,141,30,855,466,144,203,330,530,88,523,459,372,201,617,839,704,254,321,934,326,39,36,82,717,332,213,559,403 }, -{ 523,475,51,899,730,453,23,719,403,33,457,13,421,386,4,120,117,196,102,153,15,801,450,817,515,260,202,11,700,99,165,125 }, -{ 15,1,13,515,0,2,700,5,23,753,4,3,341,317,10,115,180,11,33,64,217,77,117,165,197,7,6,365,9,141,102,134 }, -{ 19,4,119,40,33,202,27,84,102,56,77,73,504,485,26,494,757,63,862,59,23,300,25,12,128,11,5,13,342,880,469,6 }, -{ 32,20,2,13,5,21,23,6,12,38,43,29,64,7,95,51,61,207,48,147,90,178,17,182,49,0,115,202,52,362,37,22 }, -{ 339,188,11,79,4,94,377,12,99,111,542,102,37,33,474,51,471,40,453,152,77,13,59,403,342,23,117,57,475,134,128,38 }, -{ 34,128,283,176,495,231,318,432,503,275,529,527,161,53,3,202,56,291,585,469,73,17,14,412,57,27,80,245,250,381,402,51 }, -{ 15,515,13,700,1,217,141,120,23,180,753,115,365,51,317,341,77,260,0,291,110,137,202,5,21,269,64,36,349,2,4,10 }, -{ 13,15,961,515,700,753,4,12,2,457,3,11,197,51,37,569,115,23,5,0,99,10,1,134,6,111,165,33,72,40,38,79 }, -{ 15,515,700,753,13,1,0,2,23,33,5,3,10,4,9,115,7,102,6,51,12,217,77,11,40,457,569,341,117,317,14,719 }, -{ 5,76,2,32,292,214,45,1,129,519,123,179,90,710,17,29,460,72,14,207,21,249,58,205,464,263,618,48,6,245,3,257 }, -{ 72,76,32,4,21,12,38,23,99,54,89,3,14,17,51,57,11,90,13,488,179,2,59,148,45,37,5,115,401,1,10,421 }, -{ 98,223,393,1,834,264,284,791,724,293,478,772,697,909,363,682,905,447,541,821,411,51,421,9,807,48,765,31,730,96,386,410 }, -{ 341,13,509,8,23,638,165,901,762,10,569,242,391,197,873,642,506,499,629,961,15,180,116,456,206,546,417,1,338,457,515,867 }, -{ 1,2,5,50,14,38,46,114,0,36,29,22,218,65,86,96,137,21,133,285,12,10,323,181,17,58,51,23,67,7,28,6 }, -{ 481,878,202,13,5,23,182,32,269,21,1,318,77,142,557,494,141,33,640,137,70,291,2,51,260,415,929,403,120,58,4,259 }, -{ 15,515,700,753,1,4,13,0,2,5,341,3,11,180,134,12,10,317,197,365,33,21,23,165,117,6,77,7,217,37,32,498 }, -{ 25,119,19,6,26,42,27,17,4,790,45,814,2,469,483,84,122,1,0,33,32,128,76,80,611,113,73,56,5,240,202,77 }, -{ 14,2,7,1,24,0,65,6,16,69,67,22,124,28,108,5,18,36,86,10,38,46,66,398,289,168,12,83,21,23,610,13 }, -{ 51,23,128,13,15,202,12,120,33,64,141,82,10,515,0,403,700,3,1,99,117,269,153,165,753,5,318,197,102,260,2,137 }, -{ 16,35,24,0,9,18,7,1,68,69,50,71,103,65,67,189,133,23,28,13,60,537,149,335,75,21,64,5,114,2,12,14 }, -{ 754,803,133,576,880,543,2,1,657,50,14,38,46,5,29,67,218,36,58,171,52,96,24,103,775,0,114,83,181,54,65,45 }, -{ 21,32,5,3,2,17,14,72,76,1,12,23,38,51,4,54,10,0,89,13,99,137,45,36,421,115,543,11,22,128,221,48 }, -{ 434,384,268,144,855,940,617,206,332,116,93,911,959,282,203,137,141,489,44,120,10,110,244,36,98,31,269,253,367,417,160,9 }, -{ 15,2,1,0,13,515,5,700,3,23,180,217,141,10,753,4,117,6,77,33,64,7,11,197,352,317,341,134,165,115,12,9 }, -{ 2,113,6,25,1,0,29,4,7,833,5,45,32,61,128,19,77,151,74,145,64,42,14,210,655,106,59,177,27,17,21,738 }, -{ 116,268,918,203,551,31,8,692,206,791,403,499,417,93,940,421,0,23,22,120,13,523,44,51,299,473,959,1,10,475,202,125 }, -{ 107,126,132,612,362,279,20,146,259,493,199,121,590,43,660,147,35,376,939,60,941,534,683,5,0,953,16,7,49,649,595,470 }, -{ 15,515,700,753,13,1,0,23,2,33,77,4,3,51,5,102,115,10,9,341,6,7,11,342,217,12,120,180,40,317,141,8 }, -{ 53,27,17,161,469,378,73,527,19,136,383,250,495,56,862,26,62,84,80,106,200,4,34,14,440,297,3,128,585,5,129,123 }, -{ 17,45,209,106,5,207,243,454,119,255,2,263,186,290,29,3,21,62,425,61,84,32,58,56,48,408,655,136,306,14,742,227 }, -{ 4,152,59,452,128,79,216,11,339,471,529,188,94,77,202,12,291,33,318,377,99,51,23,5,402,349,32,474,102,13,205,111 }, -{ 15,515,700,753,1,0,2,13,3,5,23,4,180,51,115,9,6,12,7,8,197,33,10,961,901,77,141,752,110,22,120,341 }, -{ 951,752,638,811,351,642,180,253,10,341,197,901,110,873,8,244,15,352,165,898,143,515,564,762,499,55,365,700,82,753,141,854 }, -{ 6,262,197,350,74,26,115,509,841,583,165,38,21,13,47,50,235,19,33,324,453,4,308,196,138,99,64,903,675,1,223,130 }, -{ 125,165,391,23,386,221,21,13,558,457,51,867,197,115,401,758,77,97,308,791,7,180,48,120,963,451,743,89,603,134,403,450 }, -{ 1,14,2,5,16,46,7,38,58,24,50,0,69,48,35,67,54,18,12,75,21,45,513,155,430,37,270,9,61,163,223,32 }, -{ 23,13,51,0,12,15,4,1,115,2,515,453,10,457,5,3,202,21,165,700,403,11,37,64,77,401,9,197,753,59,475,99 }, -{ 129,84,17,56,27,495,19,548,80,123,162,378,3,504,161,469,618,73,40,53,4,26,205,184,106,183,62,6,257,128,862,12 }, -{ 28,9,22,49,109,1,67,0,39,55,168,158,83,36,35,86,420,194,185,159,95,105,69,208,272,103,50,114,2,254,169,30 }, -{ 242,391,8,456,116,13,23,492,341,165,867,51,499,457,479,638,338,509,719,10,1,642,417,762,401,93,206,268,901,569,22,197 }, -{ 211,162,248,130,57,4,41,556,507,266,183,152,305,361,11,129,62,229,38,471,514,313,157,300,377,3,440,128,123,328,339,59 }, -{ 7,92,97,16,298,140,60,126,14,35,279,314,232,246,43,230,508,173,71,107,423,24,150,779,20,189,66,18,607,21,0,653 }, -{ 15,515,700,753,1,0,2,13,23,5,3,180,51,901,6,4,7,12,9,115,8,457,165,82,120,197,10,64,141,341,22,117 }, -{ 0,18,403,25,523,74,6,24,42,91,22,102,13,51,49,193,475,681,95,85,730,64,899,397,273,750,247,673,32,805,757,288 }, -{ 56,0,18,65,33,554,84,343,64,6,90,561,22,19,899,108,27,63,289,475,240,467,370,32,233,214,24,123,95,287,28,194 }, -{ 31,98,127,9,0,44,293,105,395,299,49,242,28,22,599,116,1,284,276,125,456,685,763,159,272,623,23,935,393,144,201,137 }, -{ 1,5,2,14,38,46,50,48,21,7,58,45,270,61,155,171,0,290,69,32,29,54,67,16,24,666,663,17,37,75,502,52 }, -{ 23,51,13,453,457,12,719,4,15,99,401,2,961,3,11,730,475,515,0,1,165,115,629,700,14,17,403,40,5,33,37,64 }, -{ 968,967,966,965,964,963,962,961,960,959,958,957,956,955,954,953,952,951,950,949,948,947,946,945,944,943,942,941,940,939,938,937 }, -{ 2,1,14,29,67,103,6,46,52,75,24,133,38,218,83,309,36,108,70,114,96,5,238,74,25,26,220,236,65,50,69,87 }, -{ 7,71,16,92,24,60,14,97,150,140,35,189,149,298,18,230,43,508,2,423,69,0,38,314,66,279,399,517,251,20,232,273 }, -{ 23,1,120,51,13,202,77,141,260,21,15,5,128,82,2,450,269,165,102,318,48,32,137,515,125,64,12,115,351,180,33,7 }, -{ 77,13,33,23,64,51,4,102,141,128,40,1,2,202,0,6,177,115,137,15,59,10,11,7,269,22,515,180,318,3,700,95 }, -{ 101,9,18,363,264,520,411,604,676,682,905,271,16,821,167,0,621,364,39,100,121,118,166,781,647,252,1,848,447,265,404,60 }, -{ 144,203,326,382,166,418,93,88,96,822,1,141,859,77,744,438,110,269,921,367,521,274,100,39,494,120,403,473,217,576,13,291 }, -{ 13,21,180,125,5,23,191,32,18,16,146,199,115,24,165,118,0,225,22,1,60,197,64,901,375,241,48,12,408,71,522,818 }, -{ 15,515,700,753,13,0,23,8,1,51,82,102,2,33,4,9,180,165,5,77,10,110,12,197,120,260,18,326,351,403,22,457 }, -{ 33,77,102,64,13,23,128,51,141,202,1,40,0,2,117,10,15,4,6,318,269,134,22,515,180,115,177,153,137,196,3,700 }, -{ 174,544,104,525,74,0,151,25,6,624,29,66,2,636,81,45,204,177,64,416,7,644,5,138,222,319,355,77,22,122,789,216 }, -{ 141,304,372,352,291,947,177,269,128,954,77,349,217,202,64,318,498,437,102,864,86,13,115,180,137,5,210,197,32,950,678,7 }, -{ 161,200,53,17,714,27,34,73,472,62,585,56,440,383,136,78,527,19,4,3,106,361,14,250,80,514,377,84,322,390,862,548 }, -{ 32,76,72,21,38,14,89,54,12,37,2,241,5,428,17,1,181,221,350,45,3,4,449,90,148,179,99,292,794,770,477,46 }, -{ 33,23,128,64,141,13,77,51,102,202,2,15,1,3,40,10,5,153,269,515,165,0,117,196,180,318,6,700,137,134,120,22 }, -{ 96,137,30,0,9,39,840,202,669,406,141,530,613,1,180,88,22,160,679,576,28,403,31,219,49,228,829,100,36,15,10,856 }, -{ 180,141,352,1,15,752,115,0,217,365,2,515,13,901,341,317,23,4,197,700,269,5,3,31,753,244,21,165,253,202,51,44 }, -{ 1,2,67,0,28,50,83,65,14,46,103,114,24,38,36,9,69,5,18,7,22,133,55,218,16,124,29,54,96,160,12,480 }, -{ 180,115,352,317,365,217,752,901,141,15,341,1,515,253,700,0,753,873,2,197,31,137,165,244,4,120,160,44,98,5,202,3 }, -{ 5,32,347,49,13,21,95,713,23,1,77,33,60,64,107,4,126,928,296,850,0,241,197,102,652,195,180,534,165,153,379,10 }, -{ 341,180,365,901,317,115,15,752,515,700,217,873,753,82,0,110,197,141,951,165,1,564,13,351,253,12,10,3,2,4,308,244 }, -{ 17,45,21,3,106,5,155,38,227,32,2,209,62,54,12,243,14,181,552,587,46,540,207,794,37,48,430,119,255,221,770,29 }, -{ 16,24,35,18,7,0,50,1,9,14,75,69,2,5,12,21,60,13,67,71,23,48,10,108,223,181,189,103,46,64,92,51 }, -{ 127,13,98,165,308,23,286,293,258,51,219,395,197,115,301,401,31,391,22,105,457,170,239,276,55,338,629,116,180,479,509,569 }, -{ 539,213,748,840,957,669,30,466,88,217,144,251,863,190,137,93,230,228,679,352,317,203,617,321,258,530,160,219,96,831,816,689 }, -{ 5,48,1,21,2,14,0,36,12,38,32,54,430,181,50,270,72,99,281,45,17,10,46,22,37,218,67,3,290,76,23,51 }, -{ 13,23,0,4,33,51,2,115,141,1,77,217,180,10,9,317,3,102,11,5,15,197,7,202,22,165,40,64,515,6,341,31 }, -{ 13,15,117,515,23,12,37,134,165,700,38,54,457,753,51,64,153,197,14,10,33,82,961,0,99,89,115,719,141,3,4,1 }, -{ 5,21,2,3,1,32,14,12,48,17,0,10,51,23,38,22,4,72,13,54,36,45,137,76,99,114,86,37,11,64,540,430 }, -{ 202,128,77,318,291,33,269,102,275,141,494,342,40,678,0,177,20,210,402,7,4,5,137,6,13,450,403,32,49,120,23,22 }, -{ 1,2,24,14,67,46,69,50,38,103,16,18,75,35,83,29,52,96,5,108,0,7,54,71,149,394,236,309,70,133,220,58 }, -{ 15,515,1,700,0,2,753,13,23,5,180,3,51,4,165,457,12,197,115,6,7,21,9,141,8,901,33,82,120,77,10,110 }, -{ 0,28,65,14,67,2,124,24,1,9,7,69,55,154,36,16,46,114,175,35,83,22,429,18,109,149,68,189,108,336,251,133 }, -{ 56,162,403,3,129,775,99,161,17,40,527,33,880,4,14,128,475,12,548,23,102,202,361,117,34,184,383,200,183,196,64,53 }, -{ 151,2,29,58,112,45,186,113,5,70,52,1,311,6,315,66,61,7,74,27,631,17,80,87,287,243,209,227,14,491,19,869 }, -{ 6,1,74,2,75,29,25,66,26,70,52,138,67,324,357,42,19,220,14,85,87,108,38,451,309,103,24,69,380,135,114,65 }, -{ 15,515,700,13,23,0,1,120,753,51,180,2,260,202,5,141,77,102,9,450,115,21,197,165,7,137,110,33,12,269,901,4 }, -{ 5,45,17,2,14,46,48,38,181,50,155,3,186,54,61,29,21,227,281,80,540,106,12,400,52,1,58,32,328,171,209,487 }, -{ 16,18,265,121,158,35,60,9,39,7,329,105,252,68,24,1,132,167,159,22,0,49,286,101,21,146,23,327,120,709,5,14 }, -{ 108,467,283,56,389,650,123,412,33,177,899,475,216,453,269,349,619,65,51,730,403,670,23,196,523,128,84,13,401,789,503,543 }, -{ 514,3,11,377,328,4,361,507,57,403,14,880,130,485,176,215,236,38,152,102,211,56,62,757,54,585,300,556,34,555,40,229 }, -{ 3,555,62,266,130,99,507,139,514,12,152,229,215,305,57,40,440,33,403,471,38,56,475,14,361,313,775,328,196,548,123,23 }, -{ 120,202,318,15,77,13,1,450,33,269,515,260,5,128,494,51,23,700,102,141,40,753,326,403,817,137,523,21,177,922,342,7 }, -{ 15,1,515,23,0,13,700,2,51,753,180,5,165,21,197,12,3,120,115,4,141,6,9,7,457,33,386,202,82,8,31,341 }, -{ 15,180,515,82,351,700,10,317,753,115,217,365,141,898,33,901,13,23,110,854,752,77,1,197,4,341,143,36,64,352,102,9 }, -{ 104,289,66,707,214,90,712,64,97,173,20,0,414,194,874,43,32,7,568,560,65,38,426,312,715,192,376,74,835,5,324,147 }, -{ 84,56,0,554,63,65,453,249,123,643,18,26,847,475,511,403,416,561,524,289,370,73,9,19,45,42,719,194,27,467,33,730 }, -{ 21,346,13,350,308,826,197,101,352,68,570,0,165,23,9,841,115,100,509,694,221,230,35,217,569,88,124,749,1,777,212,154 }, -{ 16,92,7,24,60,18,35,140,126,14,50,71,46,330,2,75,246,5,121,267,571,1,230,309,220,0,9,64,146,236,54,108 }, -{ 82,15,515,898,365,700,180,33,341,753,77,901,10,115,55,351,21,5,1,4,13,102,36,217,2,165,752,120,197,117,11,317 }, -{ 16,24,35,18,69,71,140,1,103,7,189,68,0,50,9,108,2,133,60,267,230,46,149,67,167,118,92,14,75,21,191,38 }, -{ 60,71,16,18,7,20,43,118,35,68,375,28,608,0,175,566,154,92,14,149,628,33,22,13,2,10,279,23,107,356,55,117 }, -{ 187,258,871,295,201,434,219,224,489,384,268,110,261,839,44,699,93,116,36,131,141,228,144,160,940,567,244,406,137,574,98,253 }, -{ 66,7,97,172,192,712,232,324,204,74,43,448,387,426,568,20,526,107,104,135,356,729,173,0,22,5,32,95,2,64,500,560 }, -{ 15,515,700,753,1,4,0,341,13,3,134,2,5,33,11,77,12,10,23,197,365,901,7,40,217,32,21,6,51,180,961,37 }, -{ 0,28,24,9,35,65,16,124,68,55,109,154,7,39,22,149,158,14,175,1,49,252,18,71,2,168,289,419,108,420,67,101 }, -{ 7,16,14,92,2,46,140,24,220,35,38,60,75,1,50,18,87,54,5,126,29,52,278,262,314,107,71,21,172,135,330,394 }, -{ 7,92,16,14,172,126,2,60,140,35,135,314,278,46,24,38,232,107,330,66,5,18,150,246,230,97,52,1,121,563,279,21 }, -{ 6,26,235,53,297,436,27,19,25,73,113,445,90,214,65,42,64,289,250,611,624,32,45,648,614,17,85,491,34,122,200,416 }, -{ 352,141,1,217,854,752,351,180,244,36,110,661,82,258,816,160,295,219,567,224,230,269,922,144,260,268,93,201,137,116,489,202 }, -{ 16,60,35,18,126,107,68,191,92,121,7,14,598,20,493,279,167,446,118,0,28,43,463,55,24,212,375,566,9,150,575,21 }, -{ 15,1,515,2,4,13,0,700,3,5,23,753,341,77,51,115,33,11,180,10,197,141,6,165,7,901,102,40,9,202,217,12 }, -{ 23,51,13,202,21,5,1,120,15,137,128,125,32,2,12,141,33,165,64,515,403,318,700,48,180,7,6,450,115,523,475,260 }, -{ 131,716,224,371,219,187,737,616,385,254,9,98,105,924,31,258,836,39,127,578,49,916,44,761,272,137,944,159,0,242,442,22 }, -{ 15,515,700,1,753,2,5,0,4,13,3,180,11,141,197,10,341,217,33,134,165,6,77,7,317,12,352,64,365,32,102,40 }, -{ 66,74,7,173,174,29,192,2,222,20,226,43,353,52,712,6,0,138,500,204,97,145,64,104,426,673,355,90,25,5,65,87 }, -{ 5,259,786,534,590,493,279,49,13,581,465,21,929,35,941,132,147,32,23,612,362,626,107,121,178,0,146,61,48,939,10,18 }, -{ 2,14,16,7,278,69,135,140,46,24,267,35,92,38,1,189,29,52,309,60,66,75,71,172,74,357,18,87,67,6,230,5 }, -{ 165,13,308,197,391,23,401,15,51,457,180,509,115,569,3,629,961,719,34,758,317,734,14,29,46,2,17,901,38,453,5,217 }, -{ 1,22,2,14,0,28,7,168,67,49,65,24,36,95,5,105,55,35,12,46,69,16,114,159,194,50,10,9,158,83,164,109 }, -{ 34,453,3,196,130,14,322,11,47,51,377,236,361,4,730,153,514,711,57,440,62,17,161,108,176,59,485,56,162,412,202,117 }, -{ 18,16,21,23,48,13,24,35,121,5,156,60,51,1,7,132,141,221,163,115,0,271,447,340,363,202,125,71,2,781,22,698 }, -{ 165,13,457,23,197,961,629,569,341,41,12,38,401,901,54,51,115,17,15,509,421,37,62,45,719,57,32,328,117,758,157,99 }, -{ 2,1,77,141,33,64,3,102,0,23,13,5,128,10,6,15,180,202,269,40,51,515,7,165,137,117,318,4,700,153,197,352 }, -{ 68,212,0,124,101,9,154,16,562,191,21,149,65,24,35,1,118,167,818,350,520,100,722,841,264,71,13,302,478,23,375,346 }, -{ 98,23,48,598,13,293,541,21,125,121,51,807,0,31,35,259,126,7,386,1,223,783,10,107,199,20,221,144,342,963,49,64 }, -{ 21,13,5,586,1,23,167,48,33,781,647,49,165,18,51,271,77,32,761,118,0,82,391,22,146,141,459,31,197,156,115,4 }, -{ 2,1,5,61,29,7,58,45,14,6,425,32,70,52,290,738,207,21,72,112,66,76,655,17,186,46,64,263,38,0,128,87 }, -{ 39,265,9,100,1,333,363,101,18,411,447,254,166,310,31,98,264,30,639,404,156,286,16,93,593,203,272,682,0,905,44,821 }, -{ 6,2,1,19,29,51,26,108,25,74,5,23,14,114,13,386,133,103,42,66,453,70,309,138,719,324,65,38,64,96,52,75 }, -{ 20,43,356,107,49,858,595,7,414,359,0,5,392,319,97,612,422,819,14,376,173,246,22,470,147,427,230,92,197,33,683,95 }, -{ 0,9,68,35,65,67,114,101,28,1,124,175,336,69,154,103,83,24,189,133,39,16,50,7,2,149,55,251,18,345,230,36 }, -{ 23,13,51,15,0,1,515,115,165,2,5,12,700,202,4,21,141,457,753,197,10,3,180,120,32,9,318,11,453,64,6,269 }, -{ 121,195,60,16,126,107,98,271,146,407,132,35,1,167,199,223,493,191,279,20,18,5,43,7,21,92,48,393,0,362,212,467 }, -{ 31,44,299,116,393,144,492,456,268,22,105,0,367,918,384,434,127,489,98,9,963,125,242,948,1,28,206,49,36,51,93,293 }, -{ 23,13,457,51,165,401,719,758,197,453,961,629,308,14,15,12,730,3,386,569,391,29,739,515,34,828,832,901,115,514,670,341 }, -{ 105,36,131,22,180,115,341,127,169,1,9,31,64,98,44,365,317,141,272,143,160,55,219,86,197,776,239,187,0,535,13,752 }, -{ 1,6,2,14,66,25,29,5,108,67,65,114,19,38,26,52,74,7,24,18,69,86,36,388,64,51,17,83,23,46,42,75 }, -{ 51,386,23,453,719,13,730,6,457,670,758,19,401,165,2,475,47,26,899,14,108,17,1,5,197,29,894,754,236,74,27,285 }, -{ 252,18,9,101,121,16,132,0,419,167,364,60,604,35,265,363,146,271,39,158,68,109,28,329,848,24,647,907,682,159,212,55 }, -{ 283,503,128,432,26,193,63,269,789,529,102,122,389,275,678,6,25,318,445,4,342,27,573,605,177,862,643,291,216,57,235,59 }, -{ 2,1,29,75,69,52,14,6,46,74,87,7,220,226,278,38,135,66,267,70,16,262,25,24,380,324,357,140,67,394,97,222 }, -{ 97,298,69,7,66,140,189,24,16,267,172,423,60,150,14,314,92,71,81,501,43,35,74,6,517,232,149,607,83,330,18,2 }, -{ 475,421,403,899,51,805,523,958,453,817,23,615,401,801,120,326,202,670,494,730,450,386,115,629,260,576,77,365,569,0,165,13 }, -{ 7,20,14,128,77,97,112,202,2,177,16,415,269,318,275,66,107,43,141,414,135,38,307,10,58,0,6,291,32,5,4,40 }, -{ 24,14,7,0,2,1,22,28,16,65,168,124,35,67,108,109,18,49,10,149,69,158,5,95,289,12,55,6,36,71,46,21 }, -{ 26,80,27,73,122,25,19,17,6,42,684,209,445,573,667,106,45,690,4,611,255,680,297,495,65,59,128,119,483,113,64,53 }, -{ 107,259,362,376,465,20,470,147,595,534,612,683,660,43,5,49,581,0,858,35,427,246,97,786,178,356,14,21,142,878,7,279 }, -{ 131,30,228,190,856,406,224,88,219,530,863,613,778,274,944,816,187,39,100,160,258,31,44,93,1,321,539,36,871,137,435,531 }, -{ 113,6,311,25,45,491,80,611,27,26,209,667,17,73,122,42,684,396,19,85,106,5,614,4,2,255,151,29,1,64,648,61 }, -{ 15,515,700,753,0,1,23,51,120,2,13,82,5,260,9,4,341,77,180,115,141,10,7,12,450,8,202,901,197,351,165,93 }, -{ 219,127,98,258,395,421,924,293,242,201,697,105,276,51,308,23,453,272,401,944,512,137,13,31,284,567,386,365,116,131,964,125 }, -{ 15,180,352,141,515,752,217,82,1,317,854,700,351,753,115,341,110,13,260,120,21,36,33,898,23,10,5,365,4,160,901,137 }, -{ 129,123,17,257,162,184,205,249,183,769,5,80,3,4,229,130,119,45,90,99,618,106,57,497,12,128,2,84,59,152,27,40 }, -{ 33,102,23,77,64,128,51,13,0,202,10,141,40,15,1,22,117,137,2,86,4,403,269,153,515,196,65,11,700,115,99,5 }, -{ 7,14,2,16,172,107,46,92,5,135,35,202,294,87,38,232,29,97,20,21,24,1,60,220,66,43,12,0,126,52,54,70 }, -{ 403,576,615,523,475,326,805,817,494,421,51,202,120,450,137,453,23,859,260,401,402,77,33,670,0,958,15,197,386,515,165,480 }, -{ 141,352,217,137,0,180,202,349,9,269,23,51,115,291,77,372,13,317,120,752,365,351,93,22,2,341,64,10,82,854,28,18 }, -{ 1,23,13,51,202,141,5,165,21,15,120,180,64,2,197,125,33,102,12,7,137,515,48,128,269,318,93,700,0,403,9,4 }, -{ 25,151,6,145,122,29,174,45,113,74,4,665,42,138,2,614,416,287,19,348,746,0,66,26,1,7,64,243,311,396,81,624 }, -{ 30,190,254,166,100,382,731,829,88,131,264,795,9,93,625,274,438,1,578,613,716,31,44,39,530,36,616,921,265,203,160,77 }, -{ 132,5,21,13,1,23,32,195,379,687,156,121,626,296,48,70,850,146,51,82,883,771,35,49,652,407,60,4,260,0,845,33 }, -{ 9,254,0,49,272,131,39,159,688,101,105,578,518,158,286,28,327,333,68,224,252,219,344,16,22,1,716,31,30,228,24,890 }, -{ 16,7,35,60,18,20,14,68,9,0,28,118,43,92,126,55,107,2,101,154,24,71,5,202,121,109,22,252,21,97,1,621 }, -{ 15,515,700,753,13,1,341,2,0,4,3,5,11,23,10,33,117,12,901,197,6,134,77,8,165,317,21,365,217,7,17,40 }, -{ 78,19,444,47,26,390,27,453,130,813,108,730,711,65,412,122,51,680,113,235,690,196,630,283,128,236,14,64,73,53,200,445 }, -{ 2,7,29,5,61,6,45,1,66,113,112,14,52,315,738,128,32,151,74,16,20,64,70,21,592,0,25,4,425,43,491,222 }, -{ 145,112,74,66,6,29,26,70,19,396,25,87,2,287,135,151,138,222,5,226,42,122,7,307,1,644,45,58,113,651,635,632 }, -{ 92,16,7,60,126,24,140,35,14,232,18,121,246,71,46,267,172,150,107,314,132,146,230,2,278,108,330,199,236,5,38,572 }, -{ 13,115,197,538,569,341,98,55,165,127,365,762,219,286,844,23,170,206,734,638,535,901,169,253,629,0,873,509,180,10,332,258 }, -{ 58,151,74,53,287,27,29,396,6,70,2,73,5,52,112,26,651,1,297,113,17,75,19,45,334,445,145,34,315,549,436,331 }, -{ 214,289,90,874,104,751,64,65,312,835,204,249,750,194,74,81,875,32,519,288,348,0,174,247,636,715,138,192,784,6,524,280 }, -{ 9,39,28,35,30,0,166,49,1,175,439,158,64,346,36,101,67,364,86,88,274,100,168,55,23,10,420,22,190,141,505,180 }, -{ 341,901,15,515,700,753,1,365,10,0,569,180,2,197,115,31,165,3,5,4,44,22,317,13,9,951,23,253,116,143,762,93 }, -{ 120,202,77,450,260,15,128,318,102,515,494,13,817,700,269,5,403,51,1,33,23,753,82,326,141,342,291,137,21,523,351,32 }, -{ 13,115,241,64,180,32,125,197,165,4,118,22,21,23,16,247,237,28,225,191,95,141,167,5,0,341,288,35,459,18,177,24 }, -{ 16,24,35,14,1,2,7,69,18,46,60,50,267,140,71,189,108,38,75,92,0,5,9,230,67,21,309,335,54,236,394,220 }, -{ 15,515,700,753,898,180,901,341,197,638,10,165,33,1,115,4,77,365,317,13,102,217,117,0,5,2,253,3,82,569,21,752 }, -{ 193,523,18,84,56,730,233,65,4,817,90,33,643,403,91,511,453,240,59,11,214,51,719,196,153,475,32,123,64,847,102,561 }, -{ 112,29,151,2,74,6,66,7,222,145,287,45,5,624,52,25,113,416,58,122,19,70,186,204,4,87,644,549,337,884,32,0 }, -{ 13,0,23,2,1,15,33,3,77,515,141,5,4,217,10,51,64,180,700,115,6,117,11,7,753,40,102,165,197,22,317,153 }, -{ 28,0,1,67,65,9,2,114,83,69,103,50,36,22,55,24,46,14,124,109,35,7,16,38,133,160,389,323,18,12,154,5 }, -{ 121,132,18,167,271,146,101,363,621,9,411,647,16,354,520,60,212,932,1,806,55,0,195,446,68,35,31,364,777,252,407,118 }, -{ 26,6,85,396,122,624,25,19,42,445,64,648,573,416,174,680,665,214,45,348,90,65,194,145,113,881,138,289,112,436,297,544 }, -{ 16,146,18,92,24,199,60,71,121,126,35,108,156,953,271,674,132,7,32,640,360,246,649,118,21,95,5,517,14,9,1,314 }, -{ 51,13,23,453,475,730,719,15,457,403,64,115,33,95,4,523,3,12,21,6,899,102,5,128,401,202,11,141,308,515,22,125 }, -{ 151,396,6,53,27,113,58,26,73,112,74,287,45,29,297,19,145,70,138,445,315,436,34,2,17,573,5,61,549,491,1,80 }, -{ 223,1,888,774,260,98,269,385,349,202,96,141,421,622,730,863,318,697,87,453,393,418,922,834,751,5,163,335,120,291,352,30 }, -{ 16,60,92,35,126,121,7,150,246,18,107,1,598,24,167,195,14,97,71,279,98,441,191,199,517,146,356,223,298,271,230,0 }, -{ 22,1,105,28,239,170,0,55,95,31,36,301,2,320,98,127,9,49,44,64,35,67,10,86,5,12,109,23,168,13,21,312 }, -{ 2,6,5,207,292,76,1,119,45,32,17,29,61,306,790,58,240,106,14,64,214,151,476,710,7,72,84,128,4,179,70,25 }, -{ 51,23,221,254,115,13,438,530,125,48,21,39,541,960,386,49,1,613,15,840,228,308,627,131,688,401,5,326,421,158,165,83 }, -{ 1,5,2,0,12,22,21,36,10,14,48,86,23,13,32,54,3,4,28,65,51,50,137,37,208,114,9,38,17,7,281,202 }, -{ 363,23,447,182,296,340,1,93,698,478,379,156,284,144,18,269,21,98,141,70,668,411,664,658,110,914,67,937,180,691,335,291 }, -{ 17,32,45,498,41,115,180,197,106,62,54,38,546,165,13,155,468,509,341,243,241,217,542,15,57,536,428,51,117,721,292,129 }, -{ 32,95,64,246,22,92,180,13,5,652,125,241,638,237,7,49,4,126,21,115,197,296,888,316,0,165,774,23,16,392,1,534 }, -{ 15,515,700,753,33,341,13,217,4,141,77,23,180,317,1,10,102,351,82,115,40,5,854,21,137,11,352,901,365,117,197,0 }, -{ 15,120,1,82,93,217,515,260,77,141,13,110,700,351,352,23,180,753,21,854,202,317,64,349,269,51,165,137,5,128,291,36 }, -{ 13,23,51,141,77,0,33,4,115,64,2,10,102,202,217,128,1,177,269,11,7,22,6,21,32,9,180,40,15,3,165,318 }, -{ 478,264,1,520,98,724,9,682,223,664,21,759,13,772,604,100,23,363,411,48,821,5,0,905,909,447,31,265,88,101,166,39 }, -{ 20,29,7,2,77,416,6,128,33,5,0,113,104,32,43,13,491,66,23,21,102,51,74,210,202,525,64,318,10,81,174,14 }, -{ 2,1,5,14,7,58,61,29,45,290,46,38,52,21,32,270,6,592,425,0,75,155,16,48,17,50,72,70,207,24,263,663 }, -{ 80,6,17,209,106,26,483,113,19,469,255,25,378,27,495,833,45,64,161,2,61,667,76,742,32,90,445,5,814,65,887,119 }, -{ 98,223,393,1,354,834,195,791,447,697,284,293,360,541,781,156,51,807,18,664,421,411,163,668,48,31,591,765,883,386,948,23 }, -{ 679,141,816,36,93,406,876,144,228,137,1,180,669,21,332,251,5,269,116,187,96,351,202,752,317,64,203,831,574,466,855,345 }, -{ 15,515,700,13,1,753,2,0,23,341,3,5,4,10,51,11,33,165,6,7,115,197,12,64,180,153,217,77,9,569,901,317 }, -{ 13,23,202,51,5,21,403,15,120,64,1,450,128,141,12,523,33,165,494,125,2,515,269,7,48,102,318,95,260,180,453,197 }, -{ 16,18,24,60,71,92,146,246,199,35,140,7,9,118,121,108,167,230,126,132,0,640,156,14,68,133,267,360,649,271,64,55 }, -{ 269,141,678,177,202,77,128,318,33,947,40,120,291,349,102,137,64,352,210,864,461,498,13,342,196,23,275,450,954,0,205,111 }, -{ 16,24,92,18,71,60,35,7,108,191,167,246,140,14,126,21,1,68,150,118,149,388,399,9,273,0,121,796,230,48,212,517 }, -{ 2,14,1,29,46,75,52,70,69,171,38,7,58,163,16,5,24,220,67,112,223,54,50,409,155,35,267,186,151,334,394,140 }, -{ 9,252,100,265,166,39,88,404,329,0,1,520,382,812,101,593,264,274,604,676,30,118,68,553,18,664,363,23,639,865,21,411 }, -{ 16,18,35,24,0,60,158,7,22,68,14,49,109,159,55,9,28,71,2,10,5,105,1,118,329,13,344,23,92,20,21,126 }, -{ 15,13,515,700,23,0,753,1,51,2,4,10,77,5,3,197,115,165,961,202,9,457,180,12,141,22,33,120,6,11,318,31 }, -{ 160,93,251,137,317,1,180,36,120,217,345,752,617,352,332,10,96,531,498,318,365,202,141,269,816,341,901,679,143,35,83,968 }, -{ 6,25,42,128,19,59,122,4,85,26,611,27,269,233,45,0,343,91,318,80,11,177,283,73,33,614,2,77,64,138,445,216 }, -{ 95,64,74,7,32,81,51,204,0,20,237,65,56,38,91,23,207,180,347,343,29,6,511,52,49,10,25,18,554,370,14,312 }, -{ 202,120,326,260,450,817,494,318,137,403,128,77,523,553,859,5,704,1,15,23,13,576,7,16,615,51,682,291,515,0,21,234 }, -{ 20,43,107,356,362,126,595,92,359,7,422,319,493,16,858,5,392,246,414,683,60,0,35,945,441,21,259,819,49,97,279,173 }, -{ 25,42,6,77,33,102,0,122,4,690,29,483,210,27,21,19,2,300,18,648,680,119,117,59,1,10,342,12,26,153,91,684 }, -{ 31,44,299,116,125,242,456,599,22,393,0,144,492,28,268,1,9,963,301,105,367,36,127,170,384,434,206,98,918,10,13,93 }, -{ 410,521,686,367,662,88,335,321,201,96,98,772,144,1,934,921,443,435,284,274,264,551,120,897,44,100,33,225,744,418,909,960 }, -{ 142,178,878,234,132,786,195,202,77,416,147,929,146,522,167,259,687,639,450,271,626,481,590,5,198,212,771,49,0,465,315,427 }, -{ 254,39,131,9,272,0,578,716,310,224,30,49,105,827,518,829,166,333,616,228,613,846,101,219,1,31,890,98,159,938,252,100 }, -{ 230,699,854,473,450,351,831,137,855,217,352,704,800,202,251,498,160,144,206,203,317,201,253,752,418,141,1,332,82,180,443,36 }, -{ 403,202,475,453,494,23,51,77,318,402,13,33,128,102,137,141,120,342,269,0,450,4,899,576,40,421,275,117,217,177,196,64 }, -{ 23,44,98,182,291,144,116,39,110,141,96,82,905,70,367,264,125,93,77,411,120,1,658,202,100,415,107,363,197,30,447,105 }, -{ 15,515,1,13,700,23,77,120,0,753,51,180,202,141,260,5,21,115,2,137,128,9,450,197,365,269,12,326,110,102,318,7 }, -{ 0,32,18,95,207,577,193,29,61,104,64,784,715,102,693,887,81,91,583,671,403,5,52,474,397,180,138,49,37,344,38,263 }, -{ 0,101,9,68,252,16,100,39,166,364,124,24,154,265,212,88,18,35,329,419,28,118,71,30,65,158,191,55,1,694,21,676 }, -{ 16,24,191,18,35,71,167,118,149,68,212,9,0,1,21,108,101,92,60,375,302,7,589,755,124,674,350,48,562,246,13,363 }, -{ 2,14,69,24,1,67,46,16,38,103,29,267,7,35,189,135,278,71,108,18,83,309,52,6,149,388,75,236,60,0,150,66 }, -{ 13,1,23,0,4,2,51,15,180,33,3,115,5,515,141,10,77,700,11,9,197,341,202,165,217,102,22,7,753,317,365,6 }, -{ 447,1,698,411,31,363,98,5,919,4,156,125,759,691,13,64,459,354,44,21,48,293,30,914,478,225,82,120,2,922,848,839 }, -{ 854,82,351,217,141,180,352,15,515,752,1,700,317,898,753,244,10,21,922,115,77,36,4,260,64,110,372,13,5,365,120,11 }, -{ 0,1,4,13,5,2,82,33,3,120,10,23,9,11,77,260,21,102,8,31,40,6,351,51,64,450,22,117,93,110,7,457 }, -{ 51,13,403,23,12,475,1,2,21,5,453,523,115,202,817,7,0,99,3,6,450,120,494,64,22,95,49,899,10,37,32,141 }, -{ 180,752,352,141,498,864,317,217,9,0,115,237,230,39,30,197,83,1,930,64,35,365,372,13,579,88,702,36,101,901,482,21 }, -{ 16,24,0,7,22,18,28,35,14,158,71,2,109,60,1,168,49,154,124,68,10,55,92,118,159,9,5,747,95,105,65,6 }, -{ 15,515,700,753,0,23,1,13,341,51,120,77,141,4,137,33,260,82,202,9,180,5,351,2,291,10,11,901,21,115,40,128 }, -{ 24,68,35,149,18,16,0,7,9,14,189,108,69,65,67,1,71,2,118,28,140,101,114,336,230,124,175,133,46,55,251,154 }, -{ 13,308,197,115,125,9,165,237,391,23,509,569,546,28,49,629,22,338,317,254,749,180,468,159,903,386,217,352,558,39,36,734 }, -{ 64,90,32,217,77,4,141,216,172,312,128,13,86,33,597,147,352,95,5,115,875,22,59,11,102,14,182,437,97,177,874,707 }, -{ 1,22,36,0,105,28,2,67,95,49,55,5,239,12,86,9,83,170,312,64,31,21,23,10,164,50,114,159,208,13,7,320 }, -{ 9,18,16,0,159,105,101,252,49,310,24,68,35,39,265,272,7,1,60,28,455,890,329,557,118,286,55,137,327,167,5,13 }, -{ 129,123,214,249,618,17,5,257,205,184,460,76,2,162,769,245,90,106,128,45,119,1,183,4,3,12,179,64,6,229,99,209 }, -{ 51,453,719,457,23,13,730,899,475,386,4,15,11,12,670,196,515,523,961,401,153,3,700,99,753,117,403,32,120,165,57,0 }, -{ 173,66,192,204,20,74,104,636,7,43,289,426,825,712,560,214,81,750,65,97,707,0,90,414,64,348,32,500,22,861,95,6 }, -{ 13,23,1,51,5,21,141,120,202,15,165,2,515,180,12,125,0,64,82,700,197,269,32,48,260,128,115,93,9,137,33,753 }, -{ 200,34,322,78,472,390,27,714,19,14,136,161,453,176,236,444,59,3,62,128,108,57,283,862,73,53,47,17,412,813,4,56 }, -{ 33,347,66,204,426,498,56,172,97,95,5,681,546,22,10,0,135,180,4,241,19,174,6,353,263,21,7,370,42,197,27,808 }, -{ 131,224,219,187,385,371,258,442,254,737,31,98,836,127,924,944,44,871,908,716,39,827,201,574,116,137,36,1,276,242,578,616 }, -{ 1,15,2,180,4,141,13,515,0,5,3,115,700,901,341,23,217,352,753,51,197,77,317,33,365,752,165,21,6,7,269,93 }, -{ 370,91,718,74,81,510,397,66,636,240,355,84,138,511,18,278,6,681,701,289,90,5,214,582,64,104,0,643,192,65,750,32 }, -{ 15,515,700,753,13,120,1,0,165,2,197,23,260,180,4,82,51,386,8,5,12,10,3,141,351,341,326,9,450,7,64,6 }, -{ 32,2,76,5,1,292,72,45,476,214,21,241,29,14,17,48,129,90,179,460,464,123,290,148,519,205,3,263,249,38,710,89 }, -{ 13,165,115,17,197,569,23,509,457,45,32,41,106,180,62,38,659,734,155,536,341,629,961,873,587,54,431,37,391,99,405,428 }, -{ 68,24,35,16,0,101,9,124,154,71,149,65,18,175,28,118,7,55,302,108,92,14,22,346,1,39,429,252,375,364,10,67 }, -{ 0,9,1,68,230,65,35,69,83,23,101,13,141,67,217,352,21,39,16,88,28,124,212,100,115,154,51,64,30,36,10,317 }, -{ 100,265,88,909,410,382,812,593,1,213,321,30,252,230,352,264,9,166,689,39,676,98,21,466,724,639,478,217,13,48,553,101 }, -{ 113,61,198,904,43,0,5,37,899,325,20,59,33,523,204,725,817,389,470,329,222,40,174,58,22,453,690,848,122,104,788,105 }, -{ 16,24,141,18,7,0,71,140,35,269,75,352,12,9,108,217,5,330,60,64,199,70,22,13,486,246,318,133,65,50,23,498 }, -{ 271,167,121,60,18,191,146,199,16,1,446,132,575,212,463,354,126,35,598,566,727,98,107,21,608,955,640,407,5,24,223,68 }, -{ 15,515,115,217,700,13,317,753,141,180,33,23,110,120,4,341,82,10,1,260,365,36,64,854,351,21,51,352,137,77,40,0 }, -{ 173,7,97,356,43,107,20,387,729,104,426,232,560,595,359,392,414,707,885,81,5,0,66,858,612,49,861,14,22,32,819,230 }, -{ 15,515,700,753,13,23,51,82,0,33,165,120,196,4,1,2,197,453,260,351,180,12,40,8,386,110,5,326,9,141,217,457 }, -{ 9,100,120,30,77,795,137,82,202,39,264,827,578,127,0,166,373,318,18,326,141,260,1,450,731,31,33,395,217,291,341,254 }, -{ 14,2,67,1,24,69,0,28,65,7,46,18,114,108,36,83,38,398,9,16,124,133,103,154,50,55,22,267,29,160,35,547 }, -{ 14,7,69,24,66,16,2,267,189,67,71,150,140,97,18,60,172,35,6,1,38,149,388,92,83,135,108,74,462,380,29,36 }, -{ 15,515,700,753,1,13,2,0,4,341,5,3,23,365,11,117,180,10,12,33,134,115,77,197,217,165,6,7,317,102,21,9 }, -{ 1,22,0,12,5,2,36,28,21,10,86,13,23,49,128,9,95,51,55,96,208,141,48,202,4,137,37,64,105,3,50,7 }, -{ 605,630,63,123,736,650,65,108,444,368,561,389,19,25,42,619,122,194,183,27,53,33,84,26,297,813,114,73,256,235,249,216 }, -{ 127,39,9,0,31,371,98,254,1,385,395,44,30,836,187,131,100,116,284,578,299,166,28,21,737,16,276,272,23,49,137,935 }, -{ 2,61,6,29,45,151,1,655,7,207,32,5,112,425,17,76,833,4,14,64,58,106,119,25,113,128,72,52,70,21,292,790 }, -{ 2,5,17,14,3,29,23,27,13,401,46,6,51,58,1,453,45,53,34,52,133,19,236,26,181,114,99,366,151,108,218,38 }, -{ 31,22,170,1,301,44,127,98,36,28,55,105,239,0,338,116,512,299,293,125,86,10,242,395,2,13,9,64,841,23,95,685 }, -{ 1,21,2,14,5,32,48,50,38,270,46,76,290,72,45,54,17,0,155,221,263,207,37,281,430,3,89,12,181,408,36,67 }, -{ 17,106,119,378,84,240,62,80,383,136,306,3,56,790,742,5,207,504,64,440,32,128,45,2,123,209,14,4,61,57,297,667 }, -{ 13,15,1,515,23,2,0,700,4,115,3,51,5,10,753,180,33,341,217,11,165,317,365,197,6,77,40,64,22,9,7,117 }, -{ 772,335,96,744,1,367,662,686,652,897,303,264,521,31,225,410,141,520,260,116,64,44,321,98,144,88,919,966,340,269,349,284 }, -{ 1,0,5,28,36,2,12,22,83,67,65,50,24,14,9,96,21,218,18,114,48,281,54,10,7,160,181,103,37,23,133,99 }, -{ 25,6,145,42,138,81,174,348,525,544,26,74,85,280,287,648,746,91,66,0,29,396,204,64,636,90,122,194,355,104,65,233 }, -{ 16,24,0,18,28,158,7,35,49,22,68,159,55,1,14,109,105,2,9,71,65,154,124,95,424,344,60,239,118,577,21,10 }, -{ 352,854,699,230,93,689,137,144,217,160,251,36,669,202,351,120,617,855,752,203,332,82,450,180,141,748,831,30,258,201,1,816 }, -{ 15,13,515,700,1,2,0,753,23,5,4,3,51,33,10,115,11,317,217,77,180,341,117,165,6,134,197,153,64,9,102,7 }, -{ 104,20,43,173,66,319,0,77,202,7,198,5,97,580,355,74,2,204,174,52,712,234,426,155,102,192,32,4,500,337,226,904 }, -{ 13,23,15,51,1,515,0,2,5,700,141,4,753,165,115,12,3,21,457,10,180,269,32,6,197,202,9,7,120,11,77,33 }, -{ 15,515,700,753,0,1,13,2,23,115,4,317,8,3,5,51,9,341,10,217,22,365,33,457,6,180,77,901,197,120,18,7 }, -{ 15,515,700,753,1,0,2,3,4,13,5,141,23,16,82,217,457,10,365,180,9,317,51,21,269,898,64,202,11,12,318,341 }, -{ 2,5,1,14,50,38,29,17,114,46,133,3,45,21,58,171,181,36,218,12,6,52,0,48,137,65,361,23,155,4,285,51 }, -{ 0,68,9,65,101,124,35,212,16,149,154,100,24,1,114,336,67,589,252,39,71,189,69,562,18,13,30,398,118,88,265,264 }, -{ 1,2,14,22,0,7,67,65,28,36,24,46,168,5,86,69,38,16,49,12,289,10,194,50,83,114,95,6,18,23,55,158 }, -{ 13,23,15,51,515,0,700,4,1,753,2,10,115,961,457,12,33,11,3,5,197,9,165,77,102,403,453,40,64,22,37,59 }, -{ 15,515,700,753,0,1,2,13,23,5,51,901,8,9,180,3,7,82,4,120,12,10,719,341,6,31,141,457,197,22,115,93 }, -{ 100,252,88,101,0,265,9,724,48,1,21,352,213,676,410,382,321,230,30,329,593,909,39,812,553,217,23,689,520,264,166,419 }, -{ 2,5,1,58,171,14,46,50,29,52,45,38,186,155,67,54,151,281,334,61,48,96,17,181,103,400,502,227,21,223,12,69 }, -{ 23,120,13,1,202,141,51,21,165,128,260,15,5,269,137,64,33,180,82,318,93,197,77,326,515,125,110,700,450,2,32,48 }, -{ 341,197,10,901,13,15,8,638,569,515,479,23,180,873,700,165,143,642,0,961,753,951,1,115,509,499,116,12,498,242,82,206 }, -{ 15,1,23,13,515,21,120,51,2,141,202,700,5,180,165,0,753,197,12,7,33,260,352,137,269,4,82,128,48,9,110,6 }, -{ 2,29,50,58,1,6,5,52,14,262,17,46,27,53,151,34,171,74,324,26,38,309,45,113,19,96,287,396,223,67,73,583 }, -{ 13,23,141,51,4,202,0,115,77,2,33,217,5,317,180,64,10,269,3,9,15,21,1,128,102,137,318,11,352,515,22,31 }, -{ 1,67,0,24,50,5,14,18,16,69,2,9,103,35,83,12,96,28,54,7,58,223,21,46,281,48,65,181,22,38,36,108 }, -{ 13,23,141,51,77,64,202,115,33,102,128,4,0,269,10,21,217,32,180,318,9,137,2,11,22,291,7,177,16,31,165,197 }, -{ 317,115,180,365,873,498,217,341,13,752,482,197,569,352,1,901,36,23,457,468,165,346,546,143,509,134,579,876,868,2,332,21 }, -{ 184,257,205,229,152,17,57,497,266,432,452,524,5,619,381,32,4,90,2,12,313,128,45,59,245,106,3,471,129,769,339,214 }, -{ 13,23,1,0,15,2,4,515,51,3,10,33,5,700,115,180,753,77,11,365,341,217,9,6,197,7,102,165,317,40,22,64 }, -{ 626,70,771,687,379,846,767,761,518,878,82,481,31,786,49,591,178,163,407,44,87,13,845,125,590,371,195,120,98,557,937,351 }, -{ 264,1,410,909,772,897,686,521,335,478,98,96,691,639,100,44,284,382,31,321,744,88,914,724,662,765,223,9,682,363,0,367 }, -{ 13,23,1,2,0,15,51,515,5,10,4,33,115,77,180,700,3,141,217,40,6,753,317,197,64,165,7,11,102,9,341,22 }, -{ 141,77,13,64,269,23,115,21,318,217,5,202,102,33,137,2,15,291,177,51,48,180,32,4,515,352,128,7,0,10,96,11 }, -{ 13,15,23,515,51,0,700,753,1,2,4,10,33,11,961,453,115,40,457,14,12,3,9,5,165,401,197,77,22,21,64,102 }, -{ 1,22,0,36,2,31,5,12,13,105,28,9,49,86,141,21,23,95,128,55,44,115,170,10,164,98,180,4,137,239,83,51 }, -{ 100,101,88,0,252,9,265,30,21,39,759,724,213,329,321,13,419,68,562,382,676,352,694,35,553,410,1,166,909,593,230,23 }, -{ 539,88,30,190,321,530,840,144,669,435,957,748,778,100,96,418,203,213,1,131,410,228,466,274,36,382,219,863,613,83,822,352 }, -{ 7,97,92,173,298,107,43,314,232,140,16,356,20,387,729,362,126,359,246,14,230,501,426,441,0,5,560,66,104,779,35,60 }, -{ 2,14,7,1,58,5,46,16,38,70,75,45,24,155,29,0,21,52,61,163,220,50,69,270,35,48,32,171,18,6,64,54 }, -{ 447,411,363,664,647,98,621,1,354,271,223,478,18,777,781,936,360,759,167,132,121,48,21,156,9,195,118,293,23,691,13,264 }, -{ 2,1,14,65,36,67,0,7,46,22,69,5,38,24,28,6,83,29,86,114,168,50,124,208,12,18,108,10,194,484,103,16 }, -{ 421,386,51,791,730,958,165,801,23,453,697,403,615,13,221,523,24,899,401,326,551,670,576,102,18,33,125,77,566,115,203,197 }, -{ 104,319,422,945,0,81,20,43,715,32,784,693,879,7,397,74,306,207,52,681,671,2,61,173,6,636,904,95,887,5,18,192 }, -{ 2,29,1,46,14,52,70,262,6,26,50,67,75,96,309,38,103,112,58,19,5,163,145,83,74,220,223,357,24,69,331,25 }, -{ 786,929,590,771,687,626,941,178,465,259,70,5,13,21,35,534,107,518,132,49,878,48,146,121,379,279,31,767,147,195,108,125 }, -{ 5,2,186,45,17,29,48,50,14,61,46,155,400,1,227,171,52,58,38,54,430,209,80,281,3,106,536,311,181,243,21,502 }, -{ 0,32,64,95,817,494,342,403,207,202,194,389,453,365,312,180,316,5,690,237,848,577,450,61,102,523,475,289,49,241,65,482 }, -{ 23,13,51,15,202,515,1,0,2,12,115,4,700,5,165,753,77,457,21,141,128,10,3,64,403,32,197,318,9,11,33,117 }, -{ 119,2,6,76,5,17,45,292,306,240,32,1,19,84,64,61,4,209,710,80,26,0,106,27,214,25,128,129,29,179,3,113 }, -{ 15,515,700,1,753,0,13,23,180,120,51,2,5,33,165,197,9,450,7,260,115,523,4,12,202,141,82,77,21,102,8,6 }, -{ 6,138,74,280,222,85,66,226,25,42,87,204,64,337,29,135,95,174,235,26,145,65,19,32,792,294,112,52,256,2,5,22 }, -{ 39,9,0,101,333,158,49,252,310,254,272,68,16,18,159,286,344,455,30,109,627,327,24,105,419,100,364,22,35,1,329,709 }, -{ 21,5,32,14,2,1,38,72,76,54,17,3,48,221,270,0,45,46,12,181,37,89,36,50,540,290,430,10,4,741,99,23 }, -{ 120,137,202,269,141,260,318,450,922,494,77,291,82,15,5,351,128,1,515,326,64,854,700,352,342,21,753,678,349,32,523,90 }, -{ 16,24,68,35,71,18,149,118,191,167,9,0,212,124,65,246,7,67,140,189,399,101,133,60,1,108,267,114,69,92,695,154 }, -{ 28,0,9,67,1,22,109,36,55,65,194,114,39,83,49,69,2,35,103,50,158,208,86,420,168,289,505,24,7,185,5,323 }, -{ 147,325,198,427,142,178,202,798,5,376,20,318,259,43,120,450,77,234,534,904,470,465,878,725,329,14,315,0,260,858,70,61 }, -{ 28,0,65,9,109,1,55,67,35,22,24,39,289,7,175,14,114,2,158,124,420,194,68,16,336,36,49,69,168,570,154,505 }, -{ 141,180,13,115,1,23,4,269,2,202,0,317,217,51,15,5,21,352,77,318,3,752,197,10,165,365,137,341,9,515,33,64 }, -{ 15,515,700,0,753,1,2,13,23,9,51,5,4,901,33,7,8,3,12,93,180,120,197,6,82,341,10,141,22,260,457,115 }, -{ 6,74,66,1,25,75,324,380,278,26,138,85,135,500,87,42,220,841,97,350,29,19,70,226,38,21,52,606,235,889,2,14 }, -{ 13,23,51,12,4,15,453,0,457,1,403,165,115,3,11,2,64,5,401,10,515,37,202,33,40,32,99,475,197,700,308,17 }, -{ 788,180,5,83,693,319,314,4,32,21,17,11,817,3,510,498,33,12,24,104,814,120,64,117,306,804,523,450,288,160,102,43 }, -{ 15,1,515,13,2,700,23,0,4,753,3,341,5,51,33,11,10,6,77,7,115,102,180,165,141,9,197,217,901,40,12,64 }, -{ 101,18,9,167,520,16,0,118,60,212,604,364,694,24,55,252,68,917,264,35,1,121,146,363,39,100,806,5,21,166,191,28 }, -{ 13,23,0,33,51,141,77,4,64,2,115,217,9,102,7,202,21,10,180,3,15,128,5,269,6,32,11,16,165,352,22,317 }, -{ 66,135,97,74,172,6,278,7,204,324,138,174,29,85,2,87,25,140,92,192,52,38,802,69,448,500,808,620,22,1,280,232 }, -{ 2,24,69,6,97,7,1,0,14,298,423,66,67,29,150,25,189,267,124,74,607,18,36,81,172,33,83,38,52,273,71,809 }, -{ 0,9,158,39,68,49,109,16,24,333,35,344,101,22,159,254,272,30,124,65,28,18,793,154,310,252,327,105,627,419,286,55 }, -{ 15,515,700,10,753,33,77,180,4,341,1,197,13,115,365,23,901,317,5,102,11,217,165,117,141,40,2,3,253,21,134,55 }, -{ 100,166,382,478,265,264,88,39,98,1,404,274,9,593,724,921,639,438,363,682,411,31,30,812,96,447,821,905,252,0,223,435 }, -{ 19,283,436,53,297,26,813,432,27,128,42,25,390,503,122,736,73,123,605,63,389,529,630,250,690,65,381,444,6,269,108,216 }, -{ 33,202,13,128,494,0,51,141,269,1,4,2,102,180,15,137,65,95,6,450,77,40,117,59,457,36,196,817,134,86,49,515 }, -{ 18,16,60,68,101,167,191,118,35,121,9,212,55,0,126,1,24,647,199,146,520,107,628,621,363,71,21,28,346,92,806,727 }, -{ 352,230,217,531,160,93,36,669,748,854,689,258,137,871,728,699,752,251,574,202,373,351,228,120,717,260,144,219,268,82,816,1 }, -{ 15,515,700,753,13,23,0,51,8,1,4,82,165,77,110,33,10,180,5,202,11,22,120,12,9,197,115,93,403,141,40,351 }, -{ 0,9,101,217,35,88,352,100,39,175,30,68,562,752,13,317,252,115,180,197,64,1,83,141,65,213,165,230,194,36,28,265 }, -{ 32,64,5,470,288,90,21,147,0,95,356,22,20,519,835,312,819,18,247,182,11,97,13,4,387,49,43,298,316,48,107,7 }, -{ 16,14,24,1,7,2,35,0,5,50,18,69,46,12,58,75,9,67,70,163,21,54,38,48,223,502,281,37,140,60,28,10 }, -{ 18,265,9,252,39,195,354,411,1,16,132,101,121,682,167,203,5,363,146,593,35,333,21,271,60,13,100,0,156,327,7,520 }, -{ 4,13,1,115,141,23,2,180,5,0,51,3,217,202,77,15,33,269,341,318,317,21,165,515,11,10,197,365,9,137,64,352 }, -{ 7,24,16,14,71,35,18,92,140,189,108,149,68,60,69,150,2,230,97,66,0,458,67,1,65,251,38,314,388,267,36,46 }, -{ 1,22,31,36,0,2,44,5,141,105,180,170,12,64,13,98,86,55,23,21,28,164,115,127,10,125,128,4,9,239,352,197 }, -{ 66,7,97,2,192,20,52,43,135,74,560,107,104,0,750,147,414,29,580,173,324,376,226,194,77,174,204,38,356,64,16,470 }, -{ 0,101,9,68,35,124,24,65,39,16,252,100,154,166,28,364,149,694,30,88,55,346,1,419,71,439,265,289,22,21,175,158 }, -{ 39,9,100,30,127,0,737,856,31,836,827,254,98,931,166,88,93,1,44,190,131,228,120,395,625,385,863,264,219,373,110,28 }, -{ 5,17,2,3,21,45,14,155,48,32,38,1,328,181,186,46,23,51,12,61,227,29,106,54,99,133,62,832,13,37,514,543 }, -{ 131,613,30,224,228,716,274,100,827,406,219,856,39,190,31,88,1,166,9,44,829,863,931,93,0,187,625,924,127,98,137,254 }, -{ 352,217,64,141,752,269,180,864,437,372,954,115,498,177,77,349,317,318,579,291,947,197,247,0,23,717,237,304,128,457,776,678 }, -{ 141,217,180,317,352,115,15,341,1,23,13,365,515,752,2,64,0,5,498,700,372,165,51,237,753,77,244,197,137,4,21,253 }, -{ 5,2,17,14,1,45,3,38,21,29,181,58,46,48,50,133,114,171,61,155,32,6,186,281,361,12,36,54,4,13,52,514 }, -{ 6,2,119,25,790,4,45,483,655,113,1,29,76,26,32,19,887,17,128,0,292,833,59,61,106,64,77,814,14,151,84,42 }, -{ 13,23,2,0,51,4,1,115,141,3,5,180,33,217,77,9,202,11,7,15,10,6,317,64,21,197,515,165,102,128,22,269 }, -{ 13,23,51,202,21,5,1,15,141,165,120,2,115,12,32,0,515,128,318,64,125,700,4,403,197,453,180,457,3,7,10,6 }, -{ 9,39,31,30,0,127,1,44,100,131,98,187,385,276,88,442,219,908,254,116,49,166,935,28,201,36,141,827,137,299,284,21 }, -{ 22,49,28,109,9,185,105,95,1,131,159,272,36,67,86,254,39,55,35,0,505,31,83,169,208,327,286,98,168,535,312,708 }, -{ 5,1,2,21,0,12,48,22,10,14,36,3,32,17,23,54,86,38,4,51,13,37,137,50,65,281,114,45,28,99,58,202 }, -{ 141,269,352,82,217,351,180,854,372,922,752,1,15,260,317,318,515,202,64,700,120,349,954,753,77,35,67,717,898,137,365,115 }, -{ 144,203,613,418,326,406,96,669,137,679,1,228,494,822,840,317,36,83,855,160,817,859,856,816,217,831,345,93,876,77,44,251 }, -{ 15,13,515,23,700,753,51,1,33,0,202,21,2,5,180,141,120,165,217,82,12,117,4,352,269,197,115,32,3,9,134,260 }, -{ 13,23,115,1,0,51,4,77,2,33,15,141,10,5,341,180,515,3,217,202,9,365,317,64,700,102,11,165,197,22,753,7 }, -{ 531,943,373,160,728,93,206,260,261,559,964,269,717,535,332,384,365,295,110,533,141,10,180,352,244,137,120,55,959,564,36,253 }, -{ 1,31,36,170,22,55,44,10,86,64,127,0,2,98,301,164,740,338,237,143,5,125,116,13,242,141,299,180,23,169,105,12 }, -{ 13,23,141,77,51,4,64,32,33,202,115,269,102,128,21,0,177,180,318,90,40,10,7,5,137,15,217,352,9,291,59,22 }, -{ 202,120,260,318,77,15,450,269,1,82,33,23,141,13,51,515,351,128,700,5,64,326,137,21,102,110,753,494,93,523,817,165 }, -{ 523,899,102,33,730,15,23,403,719,117,153,13,515,51,475,4,700,5,453,817,196,753,494,40,202,120,1,2,450,457,17,421 }, -{ 202,403,494,450,120,817,523,475,318,453,33,402,128,77,13,51,260,576,342,102,15,23,515,4,700,5,82,753,326,210,137,615 }, -{ 2,29,112,66,7,52,70,151,58,87,135,5,74,226,307,6,14,186,1,45,549,172,644,25,113,287,46,155,334,64,294,97 }, -{ 1,77,349,291,260,120,652,102,5,39,64,269,9,33,340,342,13,98,888,698,23,296,100,318,51,202,87,137,638,128,50,850 }, -{ 1,13,15,2,0,4,23,515,5,141,180,3,700,341,115,51,753,269,77,901,197,352,217,33,21,11,365,6,165,202,7,317 }, -{ 7,14,16,2,46,5,70,58,1,38,24,35,92,163,0,75,21,18,50,54,140,12,87,220,155,69,171,23,60,9,13,307 }, -{ 2,29,66,226,135,7,87,74,52,278,6,75,222,220,294,70,97,1,145,25,172,262,324,38,69,112,331,92,5,14,140,26 }, -{ 15,515,700,753,33,77,117,4,1,102,134,40,153,11,13,196,217,21,5,51,23,115,32,3,2,202,141,137,128,291,48,177 }, -{ 15,217,82,515,351,141,317,1,13,700,260,77,110,120,115,854,23,753,180,51,21,36,137,922,5,64,365,352,291,202,93,341 }, -{ 31,190,30,373,120,110,863,88,44,127,908,856,260,318,82,98,93,187,836,717,935,39,442,131,141,254,228,219,1,968,77,116 }, -{ 23,13,308,9,165,115,51,21,401,125,49,39,197,391,159,254,217,743,28,438,773,629,558,386,341,95,32,317,876,679,109,166 }, -{ 49,5,43,165,7,0,21,104,125,22,173,422,64,13,623,102,20,18,314,95,91,141,23,31,193,51,391,900,779,558,92,232 }, -{ 1,4,13,2,15,0,23,515,77,3,341,33,5,700,115,51,202,753,141,180,11,10,102,217,6,901,40,7,197,318,317,365 }, -{ 93,843,295,120,36,160,206,261,10,137,567,110,384,141,943,268,201,332,258,55,1,180,64,116,44,144,699,203,282,31,260,373 }, -{ 81,7,192,426,43,173,172,104,879,91,5,712,715,526,6,97,568,95,448,66,33,861,560,32,49,20,0,636,232,825,2,22 }, -{ 475,403,51,453,33,102,13,23,494,202,0,196,15,77,153,18,4,117,515,450,318,22,730,128,700,421,65,753,269,402,134,817 }, -{ 141,269,260,318,202,120,352,349,82,351,1,5,854,137,64,291,15,922,180,851,32,77,515,372,21,700,7,217,13,947,33,753 }, -{ 15,515,700,753,1,13,0,2,4,23,3,5,180,115,197,12,51,165,217,10,961,9,6,141,352,21,8,7,33,77,457,120 }, -{ 39,166,9,30,0,101,274,404,252,333,190,100,158,438,310,88,68,265,656,21,1,530,329,344,49,539,625,254,13,131,48,419 }, -{ 0,1,28,9,22,12,65,83,67,36,5,2,50,55,96,109,16,13,24,23,21,238,49,18,285,160,128,39,69,114,7,323 }, -{ 15,515,700,1,0,753,23,2,13,51,5,180,115,6,3,9,197,12,457,120,7,165,901,82,4,21,8,141,31,33,719,341 }, -{ 9,39,30,0,28,166,22,49,180,1,352,35,317,158,88,141,498,131,115,345,752,128,228,217,100,83,219,930,13,251,365,36 }, -{ 5,61,45,2,80,29,311,209,6,17,58,1,151,106,454,667,243,70,52,496,287,592,255,738,64,74,483,14,27,32,112,19 }, -{ 31,125,22,44,299,456,685,242,599,116,170,28,0,1,492,393,506,144,558,10,268,301,239,23,13,36,963,367,55,206,105,95 }, -{ 187,258,926,574,839,93,228,860,406,219,871,160,137,531,224,116,120,902,669,201,36,131,44,144,843,533,318,384,442,1,434,268 }, -{ 7,107,75,16,87,9,64,177,24,18,291,77,349,141,60,232,23,0,51,269,132,14,5,21,70,32,678,112,126,121,71,947 }, -{ 15,515,700,753,1,0,2,13,5,3,23,180,4,115,901,51,6,8,961,9,7,10,12,82,197,22,141,341,33,120,365,457 }, -{ 13,23,51,1,5,202,2,12,15,21,165,141,0,115,3,4,32,515,197,10,180,318,128,120,64,700,6,7,403,269,457,137 }, -{ 1,2,0,77,64,3,141,13,33,15,23,10,6,102,5,515,180,4,117,7,700,165,11,217,269,40,753,115,128,17,197,134 }, -{ 345,531,332,269,260,317,717,752,373,351,180,352,728,82,10,365,160,533,217,143,498,251,244,93,341,901,36,1,141,898,55,864 }, -{ 16,7,33,189,92,77,388,60,140,35,102,24,14,1,230,21,150,117,733,314,18,915,71,13,108,134,5,64,69,2,98,22 }, -{ 142,202,234,178,5,786,77,49,70,0,416,450,639,878,1,48,21,929,147,259,315,455,198,120,12,481,163,113,846,329,318,22 }, -{ 81,715,192,0,173,712,681,104,636,91,74,20,750,370,7,718,95,879,22,43,825,560,422,64,207,49,172,18,397,10,426,319 }, -{ 13,23,15,51,515,0,700,753,4,1,961,2,10,115,457,11,33,453,3,5,9,40,12,197,165,77,401,475,64,102,22,569 }, -{ 64,297,5,445,95,61,250,311,80,34,17,312,45,2,86,472,58,14,180,53,22,151,869,738,247,237,29,1,128,165,21,288 }, -{ 16,24,18,71,7,35,118,92,14,154,60,68,0,149,28,302,124,150,55,175,2,9,97,1,429,20,108,273,22,65,43,126 }, -{ 195,360,156,771,132,163,626,687,591,371,883,146,121,846,70,586,379,13,293,98,407,48,761,296,354,18,31,1,55,49,21,105 }, -{ 202,13,77,23,318,33,51,0,4,141,5,21,217,32,291,102,64,128,15,10,9,494,269,137,515,403,1,31,117,700,120,317 }, -{ 7,192,97,81,172,66,426,173,43,715,712,232,861,879,104,330,568,298,74,893,885,526,387,825,92,140,91,14,636,6,5,448 }, -{ 2,1,14,6,67,7,65,69,24,36,66,124,108,83,38,29,22,86,0,18,484,5,28,46,12,10,25,302,150,16,650,74 }, -{ 33,77,13,202,102,4,0,23,128,51,141,64,318,22,403,269,137,10,15,40,494,117,32,59,11,153,1,21,177,196,515,115 }, -{ 269,141,318,77,349,291,217,202,33,15,372,304,515,22,102,177,351,700,352,120,5,137,10,317,260,753,64,851,854,403,49,21 }, -{ 6,74,66,85,138,25,87,42,135,26,226,222,280,29,75,500,220,278,792,70,19,2,1,294,204,64,32,145,853,112,52,174 }, -{ 9,0,105,39,16,18,1,101,272,31,127,98,24,518,333,252,310,28,68,737,846,371,158,916,938,49,30,7,286,35,301,455 }, -{ 2,1,14,67,24,46,83,108,69,29,38,103,114,36,6,133,18,0,28,7,65,52,236,75,50,398,5,309,135,16,278,160 }, -{ 22,1,28,105,49,95,0,2,67,55,36,239,168,159,65,35,14,170,320,164,9,7,10,5,114,12,83,64,194,109,24,301 }, -{ 7,66,172,97,92,140,232,568,298,14,192,314,16,380,135,324,2,330,74,38,357,448,126,69,35,5,107,6,387,60,204,572 }, -{ 2,29,1,14,5,6,46,133,114,50,52,26,218,108,19,13,366,236,27,45,70,17,58,23,86,51,137,65,112,38,25,12 }, -{ 6,1,74,25,2,26,29,66,42,19,75,14,388,67,108,70,52,85,103,65,38,138,357,133,114,594,324,516,603,96,309,69 }, -{ 22,9,28,1,36,49,109,105,86,95,131,31,169,39,0,141,272,159,44,55,98,180,13,30,185,115,83,128,352,137,64,208 }, -{ 203,822,326,23,77,859,403,494,576,39,473,182,33,1,691,100,18,217,13,817,411,447,363,102,93,966,96,478,291,704,310,120 }, -{ 15,515,700,753,33,77,117,4,102,134,115,153,13,1,40,217,11,196,341,2,5,3,23,317,365,0,21,291,32,51,12,569 }, -{ 15,515,700,753,13,0,23,1,8,82,51,165,197,120,180,2,9,33,4,110,5,12,10,260,351,386,141,7,457,475,93,901 }, -{ 1,23,13,15,51,0,21,2,515,5,141,180,120,165,700,202,197,4,753,12,33,9,7,82,115,93,3,352,260,6,110,48 }, -{ 15,515,700,753,0,1,2,5,3,4,8,13,180,341,10,23,7,6,9,51,77,197,961,115,165,82,120,31,22,202,457,217 }, -{ 559,661,922,564,141,533,10,317,373,110,143,269,244,260,332,261,93,642,752,295,351,876,531,843,180,206,728,384,352,1,434,120 }, -{ 15,515,1,0,700,2,13,23,753,5,51,180,3,165,12,6,197,115,4,9,7,21,719,8,457,82,141,120,33,22,901,10 }, -{ 198,234,0,325,5,77,202,416,20,147,32,43,639,315,49,61,450,455,142,21,113,230,22,318,725,342,207,13,95,904,494,10 }, -{ 447,264,363,9,411,676,682,1,156,664,821,478,166,354,812,39,100,905,382,897,98,18,759,404,31,101,724,5,265,223,88,13 }, -{ 24,14,69,16,35,18,2,7,108,189,71,67,267,149,1,46,68,83,38,140,0,236,251,9,388,60,133,103,65,28,29,50 }, -{ 16,7,35,20,14,18,109,2,43,120,107,60,1,121,326,907,553,77,13,147,23,82,68,260,0,403,5,24,202,126,265,199 }, -{ 30,131,187,276,31,44,613,442,39,9,190,228,1,839,116,935,908,219,127,88,244,224,110,137,93,201,98,141,36,567,0,856 }, -{ 98,223,1,393,812,265,100,421,593,834,697,48,51,410,791,382,21,88,31,284,9,125,96,293,230,23,213,217,656,689,541,5 }, -{ 98,51,127,219,616,258,105,293,395,421,924,512,31,308,23,201,116,44,301,272,763,276,125,13,453,170,401,295,261,944,115,567 }, -{ 253,110,951,352,811,206,332,180,141,244,282,10,854,417,642,638,559,752,143,911,260,55,93,533,499,498,661,120,351,959,564,341 }, -{ 49,9,159,254,272,158,0,131,28,39,627,105,327,286,22,518,688,578,68,347,374,101,224,424,95,35,219,24,16,364,65,344 }, -{ 105,22,131,272,98,286,327,109,374,239,28,95,320,219,9,224,55,127,187,36,578,169,64,185,538,1,159,10,371,634,49,616 }, -{ 691,478,340,1,658,914,724,363,744,698,156,772,411,296,682,447,9,284,335,98,264,303,909,21,354,410,225,13,664,686,88,919 }, -{ 16,24,35,18,71,7,140,108,189,267,92,60,14,230,68,69,9,1,149,46,246,191,388,167,2,0,118,236,133,21,674,5 }, -{ 9,0,127,31,98,371,395,39,737,49,1,44,385,272,512,28,293,242,836,761,254,299,101,16,187,22,116,158,159,131,18,21 }, -{ 16,18,68,0,60,35,9,101,252,28,118,24,419,55,7,109,604,71,39,121,22,364,14,158,191,167,925,126,329,21,92,49 }, -{ 116,268,203,93,206,692,551,31,417,940,499,8,473,44,202,523,959,0,120,137,559,22,450,403,576,10,728,299,13,326,51,1 }, -{ 225,459,744,1,919,914,691,330,622,21,141,223,5,284,934,335,88,538,340,82,385,839,363,120,478,98,48,30,64,32,686,166 }, -{ 5,1,2,0,14,36,21,281,12,48,50,67,22,28,54,83,24,218,38,10,181,9,32,18,65,58,45,114,430,17,99,37 }, -{ 137,450,202,704,120,260,326,318,968,269,851,403,291,77,23,141,182,310,494,373,351,457,82,890,349,110,60,128,817,678,105,96 }, -{ 15,515,700,753,4,33,13,23,77,5,40,11,102,93,1,21,110,51,82,117,141,2,10,8,32,64,120,31,202,3,217,115 }, -{ 15,13,1,23,515,0,51,2,700,5,753,21,180,141,165,3,12,115,197,4,7,6,457,9,352,202,33,8,719,120,77,341 }, -{ 219,127,258,98,276,201,131,395,944,293,116,284,567,31,242,105,137,935,295,44,403,860,51,224,576,456,9,371,578,475,202,512 }, -{ 16,7,18,35,60,0,14,20,118,28,68,22,2,24,1,92,158,107,5,49,154,126,109,12,43,10,55,6,677,71,21,168 }, -{ 93,728,531,160,559,373,574,120,295,860,533,269,717,260,926,902,258,318,36,201,261,434,851,137,617,141,187,352,843,384,332,251 }, -{ 28,0,1,9,22,109,83,39,49,12,36,67,55,5,96,2,128,30,158,69,21,23,160,208,35,13,65,323,50,141,194,238 }, -{ 23,13,51,1,5,15,141,21,0,2,165,515,202,700,12,197,180,120,32,115,4,753,64,9,7,269,6,3,125,386,48,453 }, -{ 5,21,13,49,14,20,7,23,43,32,1,0,652,48,713,22,38,2,16,132,955,107,12,279,24,888,197,640,70,303,18,638 }, -{ 9,0,28,39,1,30,35,101,22,67,83,141,49,175,36,68,55,88,13,251,10,69,23,158,180,115,64,100,217,65,345,166 }, -{ 260,120,82,269,5,450,351,1,202,141,854,13,77,922,32,33,137,4,23,125,291,21,15,515,165,349,177,700,318,326,180,753 }, -{ 121,16,18,35,363,101,60,20,107,14,68,259,621,55,604,43,7,252,9,364,126,0,167,191,5,407,132,28,199,419,146,10 }, -{ 13,23,51,1,15,0,2,141,5,515,12,21,700,115,165,180,4,753,3,197,202,32,9,120,7,8,6,11,37,10,457,269 }, -{ 201,144,206,443,418,203,435,96,335,459,187,1,88,332,330,321,269,934,30,372,822,521,268,326,44,523,382,141,410,264,494,473 }, -{ 31,44,276,201,116,131,284,662,567,144,9,489,98,295,268,434,0,30,137,39,93,1,187,22,219,918,110,299,141,36,224,384 }, -{ 520,478,664,1,264,604,9,167,777,759,411,0,806,724,48,21,101,68,647,936,363,223,118,682,410,18,100,16,252,98,265,13 }, -{ 23,70,21,87,60,75,120,182,163,379,92,18,7,937,71,121,446,132,24,98,931,126,107,77,795,195,115,44,411,146,51,850 }, -{ 39,9,0,737,127,31,846,98,1,827,105,310,371,30,254,100,44,18,395,242,272,101,385,916,836,16,265,131,938,93,166,557 }, -{ 279,20,43,126,107,7,92,16,356,362,60,595,246,359,598,35,0,683,939,653,121,97,125,441,399,392,150,199,48,230,14,649 }, -{ 206,417,93,959,499,728,8,559,120,473,137,141,10,564,31,260,44,450,203,341,253,244,373,116,143,638,268,180,352,110,318,940 }, -{ 259,465,147,132,590,687,534,199,581,146,941,427,107,640,279,178,121,5,195,150,522,955,198,35,786,929,798,142,1,21,325,626 }, -{ 523,15,120,450,202,515,403,51,817,700,13,753,23,457,33,899,128,64,730,102,494,342,115,719,453,196,49,99,318,421,308,5 }, -{ 141,559,10,244,365,564,661,180,253,143,752,110,55,317,533,341,901,93,373,206,535,160,82,922,260,36,531,964,352,332,261,197 }, -{ 219,258,98,127,276,964,943,137,843,535,201,935,131,860,261,295,284,567,206,44,116,31,253,492,203,332,160,615,36,93,55,692 }, -{ 0,319,422,207,945,693,577,887,32,804,95,344,104,904,61,20,5,43,7,725,113,510,306,102,49,263,153,426,33,83,22,9 }, -{ 1,22,0,5,12,2,36,21,28,86,49,105,9,10,23,13,141,95,31,55,128,37,51,4,83,202,3,64,96,7,32,44 }, -{ 15,515,13,700,1,0,753,2,23,3,4,5,51,10,115,197,6,33,12,9,165,7,8,77,11,961,180,269,141,22,120,457 }, -{ 15,1,23,120,77,13,515,51,141,202,700,180,110,137,260,753,326,5,128,102,0,21,2,165,269,33,197,450,318,217,93,115 }, -{ 15,515,1,700,0,753,13,2,23,180,51,5,120,4,9,115,197,12,7,165,21,33,6,82,3,8,523,901,31,141,457,260 }, -{ 16,18,24,7,92,35,60,75,9,13,71,14,0,108,50,21,126,121,1,140,23,5,132,146,2,12,128,10,64,141,70,87 }, -{ 180,341,901,15,515,1,365,700,0,2,197,753,115,4,10,13,752,5,3,8,165,317,141,23,143,873,44,31,569,55,93,6 }, -{ 9,0,175,35,101,28,39,67,68,1,65,83,30,69,364,336,22,114,55,124,194,158,100,289,252,166,64,345,103,36,50,88 }, -{ 64,165,180,197,115,247,217,237,21,13,32,316,22,141,352,72,288,304,95,225,76,391,386,16,468,90,49,35,365,640,372,23 }, -{ 15,515,700,13,753,1,0,2,23,4,5,3,115,51,141,197,12,10,180,961,7,9,21,33,217,6,8,165,457,11,77,341 }, -{ 132,121,199,146,60,279,493,640,407,598,126,195,534,581,955,590,107,5,150,35,522,49,259,16,18,360,156,0,147,362,21,167 }, -{ 0,1,28,9,22,5,36,12,65,24,67,96,2,83,18,50,114,55,21,16,7,10,23,14,13,160,137,51,48,218,103,69 }, -{ 93,120,957,77,30,968,459,110,137,160,613,102,202,352,373,141,31,372,217,330,190,318,269,260,203,44,28,473,228,177,863,704 }, -{ 15,515,700,0,753,1,13,23,2,51,5,9,120,82,4,7,901,197,10,8,260,180,341,12,33,6,3,523,165,102,115,141 }, -{ 206,417,8,141,499,44,244,93,31,10,137,253,559,116,728,144,120,564,269,638,203,352,143,260,341,752,268,717,951,180,160,110 }, -{ 530,254,228,1,96,21,406,39,827,31,669,840,613,829,137,679,166,98,23,51,960,438,131,93,48,224,219,317,310,36,876,190 }, -{ 15,515,700,753,13,457,0,197,719,1,165,82,23,8,120,730,2,10,12,180,134,5,9,141,260,4,351,51,115,3,341,899 }, -{ 0,16,68,9,24,28,18,35,252,109,39,419,124,158,154,55,101,71,22,118,60,7,49,65,333,14,1,10,329,364,677,346 }, -{ 1,15,13,23,515,51,120,0,700,180,2,165,5,753,141,197,21,33,202,102,260,4,9,12,7,326,137,450,115,6,82,110 }, -{ 535,253,352,564,110,365,82,180,341,10,854,533,55,898,244,901,873,141,752,143,642,559,498,317,36,951,115,964,638,282,661,197 }, -{ 31,44,125,338,116,64,242,36,1,10,55,22,456,237,180,13,299,164,506,86,23,165,558,143,0,762,492,479,844,546,93,8 }, -{ 13,23,4,1,202,2,0,51,115,77,141,180,5,15,217,3,33,11,515,317,9,10,102,21,700,341,365,318,269,64,32,128 }, -{ 9,39,0,166,68,101,28,364,30,158,562,35,175,65,333,154,49,404,706,124,21,252,274,168,190,289,100,570,16,1,310,346 }, -{ 15,515,700,753,341,13,0,23,1,33,141,4,260,82,77,51,351,180,9,5,115,137,10,217,11,120,102,40,349,269,202,854 } diff --git a/src/ktx.imageio/etcdec.h b/src/ktx.imageio/etcdec.h deleted file mode 100644 index b90ac59d3e..0000000000 --- a/src/ktx.imageio/etcdec.h +++ /dev/null @@ -1,597 +0,0 @@ -// clang-format off -/* etcdec.h - v0.91 - provides functions to decompress blocks of ETC/EAC compressed images - written by Sergii "iOrange" Kudlai in 2022 - - This library does not allocate memory and is trying to use as less stack as possible - - The library was never optimized specifically for speed but for the overall size - it has zero external dependencies and is not using any runtime functions - - Supported ETC formats: - ETC1 - ETC2_RGB - ETC2_RGB_A1 ("punchthrough" alpha) - ETC2_RGBA - EAC_R11 - EAC_RG11 - - ETC1/ETC2_RGB/ETC2_RGB_A1/ETC2_RGBA are expected to decompress into 4*4 RGBA blocks 8bit per component (32bit pixel) - EAC_R11/EAC_RG11 are expected to decompress into 4*4 R/RG blocks of either 32bit float or 16bit unsigned int16 per - component (32/16bit (R11) and 64/32bit (RG11) pixel) - - For more info, issues and suggestions please visit https://github.com/iOrange/etcdec - - CREDITS: - Vladimir Vondrus (@mosra) - fixes for platforms that define char as unsigned type - - LICENSE: See end of file for license information. -*/ - -#ifndef ETCDEC_HEADER_INCLUDED -#define ETCDEC_HEADER_INCLUDED - -/* if ETCDEC_STATIC causes problems, try defining ETCDECDEF to 'inline' or 'static inline' */ -#ifndef ETCDECDEF -#ifdef ETCDEC_STATIC -#define ETCDECDEF static -#else -#ifdef __cplusplus -#define ETCDECDEF extern "C" -#else -#define ETCDECDEF extern -#endif -#endif -#endif - - -/* Used information sources: - - ETC1 compression - https://registry.khronos.org/OpenGL/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt - http://www.jacobstrom.com/publications/packman_sketch.pdf - - ETC2/EAC compression - https://registry.khronos.org/OpenGL/specs/gl/glspec43.core.pdf -*/ - - -#define ETCDEC_ETC_RGB_BLOCK_SIZE 8 -#define ETCDEC_ETC_RGB_A1_BLOCK_SIZE 8 -#define ETCDEC_EAC_RGBA_BLOCK_SIZE 16 -#define ETCDEC_EAC_R11_BLOCK_SIZE 8 -#define ETCDEC_EAC_RG11_BLOCK_SIZE 16 - -#define ETCDEC_ETC_RGB_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*ETCDEC_ETC_RGB_BLOCK_SIZE) -#define ETCDEC_ETC_RGB_A1_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*ETCDEC_ETC_RGB_A1_BLOCK_SIZE) -#define ETCDEC_EAC_RGBA_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*ETCDEC_EAC_RGBA_BLOCK_SIZE) -#define ETCDEC_EAC_R11_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*ETCDEC_EAC_R11_BLOCK_SIZE) -#define ETCDEC_EAC_RG11_COMPRESSED_SIZE(w, h) ((((w)>>2)*((h)>>2))*ETCDEC_EAC_RG11_BLOCK_SIZE) - -ETCDECDEF void etcdec_etc_rgb(const void* compressedBlock, void* decompressedBlock, int destinationPitch); -ETCDECDEF void etcdec_etc_rgb_a1(const void* compressedBlock, void* decompressedBlock, int destinationPitch); -ETCDECDEF void etcdec_eac_rgba(const void* compressedBlock, void* decompressedBlock, int destinationPitch); -ETCDECDEF void etcdec_eac_r11_u16(const void* compressedBlock, void* decompressedBlock, int destinationPitch); -ETCDECDEF void etcdec_eac_rg11_u16(const void* compressedBlock, void* decompressedBlock, int destinationPitch); -ETCDECDEF void etcdec_eac_r11_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned); -ETCDECDEF void etcdec_eac_rg11_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned); - - -#ifdef ETCDEC_IMPLEMENTATION - -/* http://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend */ -static int etcdec__extend_sign(int val, int bits) { - return (val << (32 - bits)) >> (32 - bits); -} - -static int etcdec__clamp_255(int value) { - return value < 0 ? 0 : (value > 255 ? 255 : value); -} -static int etcdec__clamp_2047(int value) { - return value < 0 ? 0 : (value > 2047 ? 2047 : value); -} - - -#ifndef ETCDEC_BSWAP64 -static unsigned long long etcdec__bswap(unsigned long long x) { - return (((x) & 0xFF00000000000000ull) >> 56) | - (((x) & 0x00FF000000000000ull) >> 40) | - (((x) & 0x0000FF0000000000ull) >> 24) | - (((x) & 0x000000FF00000000ull) >> 8) | - (((x) & 0x00000000FF000000ull) << 8) | - (((x) & 0x0000000000FF0000ull) << 24) | - (((x) & 0x000000000000FF00ull) << 40) | - (((x) & 0x00000000000000FFull) << 56); -} - -#define ETCDEC_BSWAP64(x) etcdec__bswap(x) -#endif /* ETCDEC_BSWAP64 */ - -static void etcdec__decompress_legacy_etc_mode(unsigned long long block, - int r0, int g0, int b0, - int r1, int g1, int b1, - unsigned char* decompressed, - int isOpaque, - int destinationPitch) { - int flipBit, codeWord0, codeWord1; - int i, j, x0, y0, x1, y1, m, idx; - const int (*modifiersTablePtr)[4]; - - /* already remapped so we can just use pixel indices "as-is" */ - static int modifierTableRemappedOpaque[8][4] = { - { 2, 8, -2, -8 }, - { 5, 17, -5, -17 }, - { 9, 29, -9, -29 }, - { 13, 42, -13, -42 }, - { 18, 60, -18, -60 }, - { 24, 80, -24, -80 }, - { 33, 106, -33, -106 }, - { 47, 183, -47, -183 } - }; - - static int modifierTableRemappedTransparent[8][4] = { - { 0, 8, 0, -8 }, - { 0, 17, 0, -17 }, - { 0, 29, 0, -29 }, - { 0, 42, 0, -42 }, - { 0, 60, 0, -60 }, - { 0, 80, 0, -80 }, - { 0, 106, 0, -106 }, - { 0, 183, 0, -183 } - }; - - flipBit = (block & 0x100000000ull) != 0; - - codeWord0 = (block >> 37) & 0x7; - codeWord1 = (block >> 34) & 0x7; - - modifiersTablePtr = isOpaque ? modifierTableRemappedOpaque : modifierTableRemappedTransparent; - - /* now decode both blocks, using proper orientation based on flipBit */ - for (i = 0; i < 2; ++i) { - for (j = 0; j < 4; ++j) { - x0 = flipBit ? i : j; - x1 = flipBit ? (i + 2) : j; - y0 = flipBit ? j : i; - y1 = flipBit ? j : (i + 2); - - /* if isOpaque == 0 and idx is "msb=1 & lsb=0" (== 2) -> pixel is completely transparent */ - - /* block A */ - m = x0 + y0 * 4; - idx = (((block >> (m + 16)) & 1) << 1) | ((block >> m) & 1); - m = (x0 * destinationPitch) + (y0 * 4); - if (isOpaque || idx != 2) { - decompressed[m + 0] = (unsigned char)etcdec__clamp_255(r0 + modifiersTablePtr[codeWord0][idx]); - decompressed[m + 1] = (unsigned char)etcdec__clamp_255(g0 + modifiersTablePtr[codeWord0][idx]); - decompressed[m + 2] = (unsigned char)etcdec__clamp_255(b0 + modifiersTablePtr[codeWord0][idx]); - decompressed[m + 3] = 0xFF; - } else { - *((unsigned int*)(decompressed + m)) = 0u; - } - - // block B - m = x1 + y1 * 4; - idx = (((block >> (m + 16)) & 1) << 1) | ((block >> m) & 1); - m = (x1 * destinationPitch) + (y1 * 4); - if (isOpaque || idx != 2) { - decompressed[m + 0] = (unsigned char)etcdec__clamp_255(r1 + modifiersTablePtr[codeWord1][idx]); - decompressed[m + 1] = (unsigned char)etcdec__clamp_255(g1 + modifiersTablePtr[codeWord1][idx]); - decompressed[m + 2] = (unsigned char)etcdec__clamp_255(b1 + modifiersTablePtr[codeWord1][idx]); - decompressed[m + 3] = 0xFF; - } else { - *((unsigned int*)(decompressed + m)) = 0u; - } - } - } -} - -static void etcdec__decompress_etc_mode_t_h(unsigned long long block, - int mode, - unsigned char* decompressed, - int isOpaque, - int destinationPitch) { - int r0, g0, b0, r1, g1, b1; - int ra, rb, ga, gb, ba, bb, da, db, dist; - int i, j, k, idx; - unsigned int paintColors[4]; /* 0xAABBGGRR */ - - static char distanceTable[8] = { 3, 6, 11, 16, 23, 32, 41, 64 }; - - if (mode == 1) { /* "T" mode */ - ra = (block >> 59) & 0x3; - rb = (block >> 56) & 0x3; - g0 = (block >> 52) & 0xF; - b0 = (block >> 48) & 0xF; - r1 = (block >> 44) & 0xF; - g1 = (block >> 40) & 0xF; - b1 = (block >> 36) & 0xF; - da = (block >> 34) & 0x3; - db = (block >> 32) & 0x1; - - r0 = (ra << 2) | rb; - } else { /* "H" mode */ - r0 = (block >> 59) & 0xF; - ga = (block >> 56) & 0x7; - gb = (block >> 52) & 0x1; - ba = (block >> 51) & 0x1; - bb = (block >> 47) & 0x7; - r1 = (block >> 43) & 0xF; - g1 = (block >> 39) & 0xF; - b1 = (block >> 35) & 0xF; - da = (block >> 34) & 0x1; - db = (block >> 32) & 0x1; - - g0 = (ga << 1) | gb; - b0 = (ba << 3) | bb; - } - - /* These four bit values are extended to RGB888 by replicating - the four higher order bits in the four lower order bits. */ - r0 = (r0 << 4) | r0; - g0 = (g0 << 4) | g0; - b0 = (b0 << 4) | b0; - r1 = (r1 << 4) | r1; - g1 = (g1 << 4) | g1; - b1 = (b1 << 4) | b1; - - if (mode == 1) { /* "T" mode */ - dist = (da << 1) | db; - dist = distanceTable[dist]; - - paintColors[0] = 0xFF000000 | (b0 << 16) | (g0 << 8) | r0; - paintColors[2] = 0xFF000000 | (b1 << 16) | (g1 << 8) | r1; - paintColors[1] = 0xFF000000 | (etcdec__clamp_255(b1 + dist) << 16) - | (etcdec__clamp_255(g1 + dist) << 8) - | etcdec__clamp_255(r1 + dist); - paintColors[3] = 0xFF000000 | (etcdec__clamp_255(b1 - dist) << 16) - | (etcdec__clamp_255(g1 - dist) << 8) - | etcdec__clamp_255(r1 - dist); - } else { /* "H" mode */ - dist = ((r0 << 16) | (g0 << 8) | b0) >= ((r1 << 16) | (g1 << 8) | b1) ? 1 : 0; - dist |= (da << 2) | (db << 1); - dist = distanceTable[dist]; - - paintColors[0] = 0xFF000000 | (etcdec__clamp_255(b0 + dist) << 16) - | (etcdec__clamp_255(g0 + dist) << 8) - | etcdec__clamp_255(r0 + dist); - paintColors[1] = 0xFF000000 | (etcdec__clamp_255(b0 - dist) << 16) - | (etcdec__clamp_255(g0 - dist) << 8) - | etcdec__clamp_255(r0 - dist); - paintColors[2] = 0xFF000000 | (etcdec__clamp_255(b1 + dist) << 16) - | (etcdec__clamp_255(g1 + dist) << 8) - | etcdec__clamp_255(r1 + dist); - paintColors[3] = 0xFF000000 | (etcdec__clamp_255(b1 - dist) << 16) - | (etcdec__clamp_255(g1 - dist) << 8) - | etcdec__clamp_255(r1 - dist); - } - - for (i = 0; i < 4; ++i) { - for (j = 0; j < 4; ++j) { - k = i + j * 4; - idx = (((block >> (k + 16)) & 1) << 1) | ((block >> k) & 1); - /* if isOpaque == 0 and idx is "msb=1 & lsb=0" (== 2) -> pixel is completely transparent */ - if (isOpaque || idx != 2) { - ((unsigned int*)decompressed)[j] = paintColors[idx]; - } else { - ((unsigned int*)decompressed)[j] = 0u; - } - } - - decompressed += destinationPitch; - } -} - -static void etcdec__decompress_etc_mode_planar(unsigned long long block, unsigned char* decompressed, int destinationPitch) { - int ro, go, bo, rh, gh, bh, rv, gv, bv; - int go1, go2, bo1, bo2, bo3, rh1, rh2; - int i, j; - - ro = (block >> 57) & 0x3F; - go1 = (block >> 56) & 0x01; - go2 = (block >> 49) & 0x3F; - bo1 = (block >> 48) & 0x01; - bo2 = (block >> 43) & 0x03; - bo3 = (block >> 39) & 0x07; - rh1 = (block >> 34) & 0x1F; - rh2 = (block >> 32) & 0x01; - gh = (block >> 25) & 0x7F; - bh = (block >> 19) & 0x3F; - rv = (block >> 13) & 0x3F; - gv = (block >> 6) & 0x7F; - bv = (block >> 0) & 0x3F; - - go = (go1 << 6) | go2; - bo = (bo1 << 5) | (bo2 << 3) | bo3; - rh = (rh1 << 1) | rh2; - - ro = (ro << 2) | (ro >> 4); - rh = (rh << 2) | (rh >> 4); - rv = (rv << 2) | (rv >> 4); - go = (go << 1) | (go >> 6); - gh = (gh << 1) | (gh >> 6); - gv = (gv << 1) | (gv >> 6); - bo = (bo << 2) | (bo >> 4); - bh = (bh << 2) | (bh >> 4); - bv = (bv << 2) | (bv >> 4); - - /* With three base colors in RGB888 format, the color of each - pixel can then be determined as: - R(x, y) = x * (RH − RO) / 4.0 + y * (RV − RO) / 4.0 + RO - G(x, y) = x * (GH − GO) / 4.0 + y * (GV − GO) / 4.0 + GO - B(x, y) = x * (BH − BO) / 4.0 + y * (BV − BO) / 4.0 + BO */ - for (i = 0; i < 4; ++i) { - for (j = 0; j < 4; ++j) { - decompressed[(j * 4) + 0] = (unsigned char)etcdec__clamp_255((j * (rh - ro) + i * (rv - ro) + (ro << 2) + 2) >> 2); - decompressed[(j * 4) + 1] = (unsigned char)etcdec__clamp_255((j * (gh - go) + i * (gv - go) + (go << 2) + 2) >> 2); - decompressed[(j * 4) + 2] = (unsigned char)etcdec__clamp_255((j * (bh - bo) + i * (bv - bo) + (bo << 2) + 2) >> 2); - decompressed[(j * 4) + 3] = 0xFF; - } - - decompressed += destinationPitch; - } -} - -static void etcdec__decompress_etc_block(const void* compressedBlock, void* decompressedBlock, int isPunchthrough, int destinationPitch) { - unsigned long long block; - int diffBit, newMode; - int r0, g0, b0, r1, g1, b1; - unsigned char* decompressed; - - block = ETCDEC_BSWAP64(((unsigned long long*)compressedBlock)[0]); - decompressed = (unsigned char*)decompressedBlock; - - /* if isPunchthrough == TRUE -> this is actually an 'opaque' bit */ - diffBit = (block & 0x200000000ull) != 0; - - newMode = 0; /* assume legacy mode by default */ - - if (!isPunchthrough && !diffBit) { /* "individual" mode */ - r0 = (block >> 60) & 0xF; - r1 = (block >> 56) & 0xF; - g0 = (block >> 52) & 0xF; - g1 = (block >> 48) & 0xF; - b0 = (block >> 44) & 0xF; - b1 = (block >> 40) & 0xF; - - /* These four bit values are extended to RGB888 by replicating - the four higher order bits in the four lower order bits. */ - r0 = (r0 << 4) | r0; - g0 = (g0 << 4) | g0; - b0 = (b0 << 4) | b0; - r1 = (r1 << 4) | r1; - g1 = (g1 << 4) | g1; - b1 = (b1 << 4) | b1; - } else { /* "differential" mode */ - r0 = (block >> 59) & 0x1F; - r1 = r0 + etcdec__extend_sign((block >> 56) & 0x7, 3); - g0 = (block >> 51) & 0x1F; - g1 = g0 + etcdec__extend_sign((block >> 48) & 0x7, 3); - b0 = (block >> 43) & 0x1F; - b1 = b0 + etcdec__extend_sign((block >> 40) & 0x7, 3); - - if (r1 < 0 || r1 > 31) { - /* First, R and dR are added, and if the sum is not - within the interval[0, 31], the "T" mode is selected */ - newMode = 1; - } else if (g1 < 0 || g1 > 31) { - /* Otherwise, if the sum of Gand dG is outside - the interval[0, 31], the "H" mode is selected */ - newMode = 2; - } else if (b1 < 0 || b1 > 31) { - /* Otherwise, if the sum of Band dB is outside - of the interval[0, 31], the "planar" mode is selected */ - newMode = 3; - } else { - /* Finally the "differential" mode is selected */ - - /* These five-bit codewords are extended to RGB888 by replicating - the top three highest order bits to the three lowest order bits. */ - r0 = (r0 << 3) | (r0 >> 2); - g0 = (g0 << 3) | (g0 >> 2); - b0 = (b0 << 3) | (b0 >> 2); - r1 = (r1 << 3) | (r1 >> 2); - g1 = (g1 << 3) | (g1 >> 2); - b1 = (b1 << 3) | (b1 >> 2); - } - } - - if (!newMode) { /* legacy mode: ETC1 & ETC2 */ - etcdec__decompress_legacy_etc_mode(block, r0, g0, b0, r1, g1, b1, decompressed, !isPunchthrough || diffBit, destinationPitch); - } else if (newMode < 3) { /* ETC2 "T" and "H" modes */ - etcdec__decompress_etc_mode_t_h(block, newMode, decompressed, !isPunchthrough || diffBit, destinationPitch); - } else { /* ETC2 "planar" mode */ - etcdec__decompress_etc_mode_planar(block, decompressed, destinationPitch); - } -} - -static void etcdec__decompress_eac_block(const void* compressedBlock, void* decompressedBlock, int is11Bit, int destinationPitch, int pixelSize) { - unsigned long long block; - unsigned char* decompressed; - int baseCodeword, multiplier, modifier, idx; - int i, j, k; - const signed char* modifiersPtr; - - static signed char modifierTable[16][8] = { - { -3, -6, -9, -15, 2, 5, 8, 14 }, - { -3, -7, -10, -13, 2, 6, 9, 12 }, - { -2, -5, -8, -13, 1, 4, 7, 12 }, - { -2, -4, -6, -13, 1, 3, 5, 12 }, - { -3, -6, -8, -12, 2, 5, 7, 11 }, - { -3, -7, -9, -11, 2, 6, 8, 10 }, - { -4, -7, -8, -11, 3, 6, 7, 10 }, - { -3, -5, -8, -11, 2, 4, 7, 10 }, - { -2, -6, -8, -10, 1, 5, 7, 9 }, - { -2, -5, -8, -10, 1, 4, 7, 9 }, - { -2, -4, -8, -10, 1, 3, 7, 9 }, - { -2, -5, -7, -10, 1, 4, 6, 9 }, - { -3, -4, -7, -10, 2, 3, 6, 9 }, - { -1, -2, -3, -10, 0, 1, 2, 9 }, - { -4, -6, -8, -9, 3, 5, 7, 8 }, - { -3, -5, -7, -9, 2, 4, 6, 8 } - }; - - block = ETCDEC_BSWAP64(((unsigned long long*)compressedBlock)[0]); - decompressed = (unsigned char*)decompressedBlock; - baseCodeword = (block >> 56) & 0xFF; - multiplier = (block >> 52) & 0xF; - modifiersPtr = modifierTable[(block >> 48) & 0xF]; - - for (i = 0; i < 4; ++i) { - for (j = 0; j < 4; ++j) { - idx = (block >> ((15 - (j * 4 + i)) * 3)) & 0x7; - modifier = modifiersPtr[idx]; - - if (is11Bit) { - /* EAC R11/RG11 */ - /* If the multiplier value is zero, we should set the multiplier to 1.0/8.0 */ - /* so that the "multiplier * 8" will resolve to 1 */ - k = etcdec__clamp_2047((baseCodeword * 8 + 4) + (modifier * (multiplier ? multiplier * 8 : 1))); - - /* Now just extending the 11-bits value to 16-bits for convenience */ - *((unsigned short*)(decompressed + (j * pixelSize))) = (unsigned short)((k << 5) | (k >> 6)); - } else { - /* EAC ETC2 Alpha channel */ - decompressed[j * pixelSize] = (unsigned char)etcdec__clamp_255(baseCodeword + (modifier * multiplier)); - } - } - - decompressed += destinationPitch; - } -} - - -ETCDECDEF void etcdec_etc_rgb(const void* compressedBlock, void* decompressedBlock, int destinationPitch) { - etcdec__decompress_etc_block(compressedBlock, decompressedBlock, 0, destinationPitch); -} - -ETCDECDEF void etcdec_etc_rgb_a1(const void* compressedBlock, void* decompressedBlock, int destinationPitch) { - etcdec__decompress_etc_block(compressedBlock, decompressedBlock, 1, destinationPitch); -} - -ETCDECDEF void etcdec_eac_rgba(const void* compressedBlock, void* decompressedBlock, int destinationPitch) { - /* first half of the block (64 bits) is an Alpha (EAC 8 bits) compressed data */ - /* second half of the block (64 bits) is just an ETC2_RGB compressed data */ - etcdec__decompress_etc_block(((char*)compressedBlock) + 8, decompressedBlock, 0, destinationPitch); - etcdec__decompress_eac_block(compressedBlock, ((char*)decompressedBlock) + 3, 0, destinationPitch, 4); -} - -ETCDECDEF void etcdec_eac_r11_u16(const void* compressedBlock, void* decompressedBlock, int destinationPitch) { - etcdec__decompress_eac_block(compressedBlock, decompressedBlock, 1, destinationPitch, 2); -} - -ETCDECDEF void etcdec_eac_rg11_u16(const void* compressedBlock, void* decompressedBlock, int destinationPitch) { - etcdec__decompress_eac_block(compressedBlock, decompressedBlock, 1, destinationPitch, 4); - etcdec__decompress_eac_block(((char*)compressedBlock) + 8, ((char*)decompressedBlock) + 2, 1, destinationPitch, 4); -} - -ETCDECDEF void etcdec_eac_r11_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) { - unsigned short block[16]; - unsigned char* decompressed; - const unsigned short* b; - int i, j; - short s; - - etcdec_eac_r11_u16(compressedBlock, block, 4 * 2); - b = block; - decompressed = (unsigned char*)decompressedBlock; - for (i = 0; i < 4; ++i) { - for (j = 0; j < 4; ++j, ++b) { - if (isSigned) { - s = (short)b[0]; - *((float*)(decompressed + j * 4)) = (s < 0 ? ((float)s / 32768.0f) : ((float)s / 32767.0f)); - } else { - *((float*)(decompressed + j * 4)) = (float)b[0] / 65535.0f; - } - } - decompressed += destinationPitch; - } -} - -ETCDECDEF void etcdec_eac_rg11_float(const void* compressedBlock, void* decompressedBlock, int destinationPitch, int isSigned) { - unsigned short block[16*2]; - unsigned char* decompressed; - const unsigned short* b; - int i, j; - short sr, sg; - - etcdec_eac_rg11_u16(compressedBlock, block, 4 * 4); - b = block; - decompressed = (unsigned char*)decompressedBlock; - for (i = 0; i < 4; ++i) { - for (j = 0; j < 4; ++j, b += 2) { - if (isSigned) { - sr = (short)b[0]; - sg = (short)b[1]; - *((float*)(decompressed + j * 8 + 0)) = (sr < 0 ? ((float)sr / 32768.0f) : ((float)sr / 32767.0f)); - *((float*)(decompressed + j * 8 + 4)) = (sg < 0 ? ((float)sg / 32768.0f) : ((float)sg / 32767.0f)); - } - else { - *((float*)(decompressed + j * 8 + 0)) = (float)b[0] / 65535.0f; - *((float*)(decompressed + j * 8 + 4)) = (float)b[1] / 65535.0f; - } - } - decompressed += destinationPitch; - } -} - -#endif /* ETCDEC_IMPLEMENTATION */ - -#endif /* ETCDEC_HEADER_INCLUDED */ - -/* LICENSE: - -This software is available under 2 licenses -- choose whichever you prefer. - ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License - -Copyright (c) 2022 Sergii Kudlai - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ------------------------------------------------------------------------------- -ALTERNATIVE B - The Unlicense - -This is free and unencumbered software released into the public domain. - -Anyone is free to copy, modify, publish, use, compile, sell, or -distribute this software, either in source code form or as a compiled -binary, for any purpose, commercial or non-commercial, and by any -means. - -In jurisdictions that recognize copyright laws, the author or authors -of this software dedicate any and all copyright interest in the -software to the public domain. We make this dedication for the benefit -of the public at large and to the detriment of our heirs and -successors. We intend this dedication to be an overt act of -relinquishment in perpetuity of all present and future rights to this -software under copyright law. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. - -For more information, please refer to - -*/ diff --git a/src/ktx.imageio/ktx_pvt.h b/src/ktx.imageio/ktx_pvt.h index d5ab75cfa7..eb7f193950 100644 --- a/src/ktx.imageio/ktx_pvt.h +++ b/src/ktx.imageio/ktx_pvt.h @@ -9,31 +9,6 @@ OIIO_PLUGIN_NAMESPACE_BEGIN -#define BCN_BLOCK_SIZE 4 -#define ETC_BLOCK_SIZE 4 - -#define BC1_BLOCK_SIZE 8 -#define BC2_BLOCK_SIZE 16 -#define BC3_BLOCK_SIZE 16 -#define BC4_BLOCK_SIZE 8 -#define BC5_BLOCK_SIZE 16 -#define BC6H_BLOCK_SIZE 16 -#define BC7_BLOCK_SIZE 16 - -#define BC1_OUTPUT_NCHANNELS 4 -#define BC3_OUTPUT_NCHANNELS 4 -#define BC4_OUTPUT_NCHANNELS 1 -#define BC5_OUTPUT_NCHANNELS 2 -#define BC6H_OUTPUT_NCHANNELS 4 -#define BC7_OUTPUT_NCHANNELS 4 - -// ETC2_RGB decompresses to RGBA (otherwise another inner loop with memcpy) -#define ETC2_RGB_OUTPUT_NCHANNELS 4 -#define ETC2_RGBA_OUTPUT_NCHANNELS 4 -#define ETC2_RGB_A1_OUTPUT_NCHANNELS 4 - - - // this is: "«KTX 20»\r\n\x1A\n" static const uint8_t KTX2_IDENTIFIER[12] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index f0ca2bd057..b6058b1e39 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -17,17 +17,11 @@ #include #include -// #include "ConvectionKernels/ConvectionKernels_BC67.h" /* for BC6HS/BC6HU decoders */ -#define ETCDEC_IMPLEMENTATION -#include "bc7enc-rdo/bc7decomp.h" /* for BC7 decoder */ -#include "bc7enc-rdo/rgbcx.h" /* for BC1-BC5 decoders */ -#include "etcdec.h" /* for ETC2 decoders */ - OIIO_PLUGIN_NAMESPACE_BEGIN class KtxInput final : public ImageInput { public: - KtxInput() {} + KtxInput() { } ~KtxInput() override { close(); } @@ -135,32 +129,6 @@ class KtxInput final : public ImageInput { TextureKind get_texture_kind() const; std::string get_colorspace() const; - - inline void cpy_decoded_block(const uint8_t* pSrc, uint8_t* dst, size_t x, - size_t y, size_t width, size_t height, - size_t nchannels) const; - - template - inline bool check_bcn_spans(cspan src, span

dst, int level, - BlockCompression cmp) const; - - void decode_bc1(cspan src, span dst, int level) const; - void decode_bc3(cspan src, span dst, int level) const; - void decode_bc4(cspan src, span dst, int level) const; - void decode_bc5(cspan src, span dst, int level) const; - // void decode_bc6h(cspan src, span dst, int level, - // bool is_signed) const; - void decode_bc7(cspan src, span dst, int level) const; - - inline bool check_etc_spans(cspan src, span dst, - int level, BlockCompression cmp) const; - - void decode_etc2_rgb(cspan src, span dst, - int level) const; - void decode_etc2_rgba(cspan src, span dst, - int level) const; - void decode_etc2_rgb_a1(cspan src, span dst, - int level) const; }; @@ -172,14 +140,10 @@ OIIO_EXPORT int ktx_imageio_version = OIIO_PLUGIN_VERSION; OIIO_EXPORT const char* ktx_imageio_library_version() -{ - return "ktx v5.0.0-rc1"; -} // hardcoded because I couldn't expose KTX_VERSION +{ return "ktx v5.0.0-rc1"; } // hardcoded because I couldn't expose KTX_VERSION OIIO_EXPORT ImageInput* ktx_input_imageio_create() -{ - return new KtxInput; -} +{ return new KtxInput; } OIIO_EXPORT const char* ktx_input_extensions[] = { "ktx2", nullptr }; OIIO_PLUGIN_EXPORTS_END @@ -271,8 +235,8 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // worse, 3D array textures. // // TODO: - // Implementing the per-subimage allocation approach requires a significant - // effort. For the moment, let's make sure this approach is working (i.e., + // Implementing the per-subimage allocation approach requires some effort. + // For the moment, let's make sure this approach is working (i.e., // all tests are passing) then let's profile and see what more experienced // users might say about this. // @@ -619,11 +583,6 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) if (!check_open(m_spec, { 0, 65535, 0, 65535, 0, 65535, 0, 4 })) return false; - // Initialize BC1, BC3, BC4 and BC5 decoder library - if (m_cmp == BlockCompression::BC1 || m_cmp == BlockCompression::BC3 - || m_cmp == BlockCompression::BC4 || m_cmp == BlockCompression::BC5) - rgbcx::init(rgbcx::bc1_approx_mode::cBC1Ideal); - if (!seek_subimage(0, 0)) // errorfmt is set via seek_subimage return false; @@ -723,19 +682,17 @@ KtxInput::seek_subimage(int subimage, int miplevel) // ktxTexture2_DecodeAstc call. // This currently decodes the whole texture (all miplevels, all // slices, etc.) into memory. - // TODO: implement decode_astc for per-miplvl/subimage decoding. + // TODO: wait for my PR in libktx to implement decode_astc for + // per-miplvl/subimage decoding. // - // BCn: we implement decode_bcn using 3rd party BCn decoder - // via bcdec (at https://github.com/iOrange/bcdec). This allows us - // to do a per-miplvl and per-subimage decode hence why we don't - // decode here but rather in seek_subimage. + // BCn: libktx will provide decoders/encoders for BCn block compression + // via ktxTexture2_DecodeBCn. TODO: wait for my RP in libktx to get + // merged then add BCn support. // - // ETC2: we implement decode_etc using 3rd party ETC decoder - // via etcdec (https://github.com/iOrange/etcdec). This allows us - // to do a per-miplvl and per-subimage decode hence why we don't - // decode here but rather in seek_subimage. + // ETC2: TODO: some licensing clarification is needed from the part of + // etcunpack usage in libktx. // - // PVRTC: TODO + // PVRTC: TODO: wait for libktx PR. // if (m_tex2->isCompressed /* i.e., is GPU block compressed? */) { ktx_size_t offset; @@ -752,91 +709,46 @@ KtxInput::seek_subimage(int subimage, int miplevel) miplevel)); switch (m_cmp) { - /* BCn formats */ - case BlockCompression::BC1: { - // TODO: is std::vector<>::resize() a nop when the vector already has that exact size? - m_buf.resize(width * height * BC1_OUTPUT_NCHANNELS); - span dst_span(m_buf.data(), m_buf.size()); - if (!check_bcn_spans(src_span, dst_span, miplevel, m_cmp)) - return false; - decode_bc1(src_span, dst_span, miplevel); - break; - } - - case BlockCompression::BC3: { - m_buf.resize(width * height * BC3_OUTPUT_NCHANNELS); - span dst_span(m_buf.data(), m_buf.size()); - if (!check_bcn_spans(src_span, dst_span, miplevel, m_cmp)) - return false; - decode_bc3(src_span, dst_span, miplevel); - break; - } - - case BlockCompression::BC4: { - m_buf.resize(width * height * BC4_OUTPUT_NCHANNELS); - span dst_span(m_buf.data(), m_buf.size()); - if (!check_bcn_spans(src_span, dst_span, miplevel, m_cmp)) - return false; - decode_bc4(src_span, dst_span, miplevel); - break; - } - - case BlockCompression::BC5: { - m_buf.resize(width * height * BC5_OUTPUT_NCHANNELS); - span dst_span(m_buf.data(), m_buf.size()); - if (!check_bcn_spans(src_span, dst_span, miplevel, m_cmp)) - return false; - decode_bc5(src_span, dst_span, miplevel); - break; - } - - /* HDR format */ - // case BlockCompression::BC6HS: { - // m_buf.resize(width * height * BC6H_OUTPUT_NCHANNELS * 2); - // span dst_span(reinterpret_cast(m_buf.data()), - // m_buf.size() / 2); - // if (!check_bcn_spans(src_span, dst_span, miplevel, m_cmp)) - // return false; - // decode_bc6h(src_span, dst_span, miplevel, /* is_signed */ true); - // break; + /* BCn LDR formats */ + case BlockCompression::BC1: + case BlockCompression::BC2: + case BlockCompression::BC3: + case BlockCompression::BC4: + case BlockCompression::BC5: + case BlockCompression::BC7: + // + // TODO: wait for my PR in libktx to be merged + // + // Note: + // ktxTexture2_DecodeBCn internally creates a new ktxTexture2 texture + // and populates it with decoded data from the originally provided + // texture. At the end, it moves the decoded data to m_tex and + // destroys the temporarily created texture. + // + // This operation is expensive (both in memory and CPU cycles). + // After this, m_tex2->isCompressed will be false => this will only + // be called once. + // + // if (auto status = ktxTexture2_DecodeBCn(m_tex2); + // status != KTX_SUCCESS) { + // errorfmt("failed to decode BCn-compressed texture. " + // "ktxTexture2_DecodeBCn returned Ktx error code: {}", + // static_cast(status)); + // return false; // } + // break; + return false; - case BlockCompression::BC7: { - m_buf.resize(width * height * BC7_OUTPUT_NCHANNELS); - span dst_span(m_buf.data(), m_buf.size()); - if (!check_bcn_spans(src_span, dst_span, miplevel, m_cmp)) - return false; - decode_bc7(src_span, dst_span, miplevel); - break; - } + /* BCn HDR formats - TODO */ + case BlockCompression::BC6HU: + case BlockCompression::BC6HS: + return false; /* ETC formats */ - case BlockCompression::ETC2_RGB: { - m_buf.resize(width * height * ETC2_RGB_OUTPUT_NCHANNELS); - span dst_span(m_buf.data(), m_buf.size()); - if (!check_etc_spans(src_span, dst_span, miplevel, m_cmp)) - return false; - decode_etc2_rgb(src_span, dst_span, miplevel); - break; - } - - case BlockCompression::ETC2_RGB_A1: { - m_buf.resize(width * height * ETC2_RGB_A1_OUTPUT_NCHANNELS); - span dst_span(m_buf.data(), m_buf.size()); - if (!check_etc_spans(src_span, dst_span, miplevel, m_cmp)) - return false; - decode_etc2_rgb_a1(src_span, dst_span, miplevel); - break; - } - - case BlockCompression::ETC2_RGBA: { - m_buf.resize(width * height * ETC2_RGBA_OUTPUT_NCHANNELS); - span dst_span(m_buf.data(), m_buf.size()); - if (!check_etc_spans(src_span, dst_span, miplevel, m_cmp)) - return false; - decode_etc2_rgba(src_span, dst_span, miplevel); - break; - } + case BlockCompression::ETC2_RGB: + case BlockCompression::ETC2_RGB_A1: + case BlockCompression::ETC2_RGBA: + return false; /* ASTC formats */ case BlockCompression::ASTC: @@ -847,7 +759,7 @@ KtxInput::seek_subimage(int subimage, int miplevel) // texture. At the end, it moves the decoded data to m_tex and // destroys the temporarily created texture. // - // This operation is very expensive (both in memory and CPU cycles). + // This operation is expensive (both in memory and CPU cycles). // After this, m_tex2->isCompressed will be false => this will only // be called once. // @@ -866,13 +778,13 @@ KtxInput::seek_subimage(int subimage, int miplevel) return false; } - m_pitch = width * m_spec.nchannels - * m_spec.format.size() /* 1 for LDR, 2 for HDR formats */; + m_pitch = width * m_spec.nchannels + * m_spec.format.size() /* 1 for LDR, 2 for HDR formats */; m_data_ptr = m_buf.data(); } - // Do NOT change this to `else` statement because this handles the ASTC case - // above (which, again, sets m_tex2->isCompressed to `false`) + // Do NOT change this to `else` statement because this handles the ASTC and + // BCn cases above (which, again, sets m_tex2->isCompressed to `false`) if (!m_tex2->isCompressed) { // // GetImageOffset implements internal checks depending on texture kind (e.g., @@ -965,7 +877,7 @@ OpenImageIO::KtxInput::valid_file(Filesystem::IOProxy* ioproxy) const return false; // per KTX2 specs: the first 12 bytes of a KTX2 file are used to identify it - uint8_t magic[12] {}; + uint8_t magic[12] { }; const size_t numRead = ioproxy->pread(magic, sizeof(magic), 0); return (numRead == sizeof(magic)) @@ -1040,446 +952,4 @@ KtxInput::get_colorspace() const return "unknown"; } -// -// Copies a decoded block (e.g., a decoded BC7 block) from provided pSrc to -// provided pDst. For each row of the decoded block, performs a memcpy to the -// destination block while accounting for potential non-multiple-of-block-size -// destination dimensions. -// -// Source: -// -// <-------------- block size -------------> -// +---------------------------------------+ -// | pSrc + 0 | ... | pSrc + src_pitch - 1 | <-- row: 0 -// | | -// | | ... | | -// | | -// | | ... | | -// | | -// | | ... | | -// | | -// | | ... | | -// | | -// | | ... | | <-- row: block_size - 1 -// +---------------------------------------+ -// -// -// Destination: -// -// <-------------------- width -----------------> -// +--------------------------------------------+ -// | pDst | ... | pDst + dst_pitch - 1 | <-- row: 0 -// | | -// | | -// | pDst + y * dst_pitch * nchannels * x -> +--|---+ <- destination block -// | | |xxx| -// | | |xxx| -// | +--|---+ -// | | -// | | -// | | -// | | <-- row: height - 1 -// +--------------------------------------------+ -// -// Source and destination SHOULD have the same stride (i.e., nchannels). -// -inline void -KtxInput::cpy_decoded_block(const uint8_t* pSrc, uint8_t* dst, size_t x, - size_t y, size_t width, size_t height, - size_t nchannels /* stride */) const -{ - // TODO: expose this as param - constexpr size_t kBlockSize { 4 }; - const size_t src_pitch = kBlockSize * nchannels; - const size_t dst_pitch = width * nchannels; - int cols = std::min(kBlockSize, width - x); - uint8_t* pDst = dst + y * dst_pitch + nchannels * x; - for (size_t py { 0 }; py < kBlockSize && y + py < height; ++py) { - memcpy(pDst, pSrc, cols * nchannels); - pSrc += src_pitch; - pDst += dst_pitch; - } -} - - -// -// Makes sure that provided source BCn blocks span and target span (where blocks -// will be decoded into) are of sufficient sizes. -// You should call this before any decode_bcn() functions. -// -template -inline bool -KtxInput::check_bcn_spans(cspan src, span

dst, int level, - BlockCompression cmp) const -{ - const size_t nchannels { static_cast(m_spec.nchannels) }; - const size_t width = std::max(m_tex->baseWidth >> level, 1u); - const size_t height = std::max(m_tex->baseHeight >> level, 1u); - size_t expected_nchannels; - size_t expected_dst_size; - size_t expected_src_size; - const int nblocks_x { static_cast( - std::ceil(width / (float)BCN_BLOCK_SIZE)) }; - const int nblocks_y { static_cast( - std::ceil(height / (float)BCN_BLOCK_SIZE)) }; - - switch (cmp) { - case BlockCompression::BC1: - expected_nchannels = BC1_OUTPUT_NCHANNELS; - expected_dst_size = width * height * BC1_OUTPUT_NCHANNELS; - expected_src_size = nblocks_x * nblocks_y * BC1_BLOCK_SIZE; - break; - - case BlockCompression::BC3: - expected_nchannels = BC3_OUTPUT_NCHANNELS; - expected_dst_size = width * height * BC3_OUTPUT_NCHANNELS; - expected_src_size = nblocks_x * nblocks_y * BC3_BLOCK_SIZE; - break; - - case BlockCompression::BC4: - expected_nchannels = BC4_OUTPUT_NCHANNELS; - expected_dst_size = width * height * BC4_OUTPUT_NCHANNELS; - expected_src_size = nblocks_x * nblocks_y * BC4_BLOCK_SIZE; - break; - - case BlockCompression::BC5: - expected_nchannels = BC5_OUTPUT_NCHANNELS; - expected_dst_size = width * height * BC5_OUTPUT_NCHANNELS; - expected_src_size = nblocks_x * nblocks_y * BC5_BLOCK_SIZE; - break; - - // case KHR_DF_MODEL_BC6H: - // expected_nchannels = BC6H_OUTPUT_NCHANNELS; - // expected_dst_size = width * height * BC6H_OUTPUT_NCHANNELS; - // expected_src_size = nblocks_x * nblocks_y * BC6H_BLOCK_SIZE; - // break; - - case BlockCompression::BC7: - expected_nchannels = BC7_OUTPUT_NCHANNELS; - expected_dst_size = width * height * BC7_OUTPUT_NCHANNELS; - expected_src_size = nblocks_x * nblocks_y * BC7_BLOCK_SIZE; - break; - - default: return false; - } - - if (nchannels != expected_nchannels) { - errorfmt("Current BCn scheme is expected to decode into " - "{}-channel-images but target image got: {} channels.", - expected_nchannels, nchannels); - return false; - } - - if (src.size() < expected_src_size) { - errorfmt("The source data buffer's size is smaller than expected. " - "Expected {} bytes but provided buffer only has {} bytes.", - expected_src_size, src.size()); - return false; - } - - if (dst.size() < expected_dst_size) { - errorfmt( - "The size of the destination buffer to hold decoded BCn " - "blocks is smaller than expected. Expected {} bytes but provided " - "buffer only has {} bytes.", - expected_dst_size, dst.size()); - return false; - } - return true; -} - - -void -KtxInput::decode_bc1(cspan src, span dst, int level) const -{ - const size_t width = std::max(m_tex2->baseWidth >> level, 1u); - const size_t height = std::max(m_tex2->baseHeight >> level, 1u); - - const size_t rgba_pitch = BCN_BLOCK_SIZE * BC1_OUTPUT_NCHANNELS; - uint8_t rgba[BCN_BLOCK_SIZE * rgba_pitch]; /* 64 bytes */ - const uint8_t* src_blocks = src.data(); - - for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { - // BC1: 8 bytes -> 4 x 4 x 4 = 64 bytes - rgbcx::unpack_bc1(src_blocks, rgba, true); - src_blocks += BC1_BLOCK_SIZE; - cpy_decoded_block(rgba, dst.data(), x, y, width, height, - BC1_OUTPUT_NCHANNELS); - } - } -} - - -void -KtxInput::decode_bc3(cspan src, span dst, int level) const -{ - const size_t width = std::max(m_tex2->baseWidth >> level, 1u); - const size_t height = std::max(m_tex2->baseHeight >> level, 1u); - - const size_t rgba_pitch = BCN_BLOCK_SIZE * BC3_OUTPUT_NCHANNELS; - uint8_t rgba[BCN_BLOCK_SIZE * rgba_pitch]; /* 64 bytes */ - const uint8_t* src_blocks = src.data(); - - for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { - // BC3: 16 bytes -> 4 x 4 x 4 = 64 bytes - rgbcx::unpack_bc3(src_blocks, rgba); - src_blocks += BC3_BLOCK_SIZE; - cpy_decoded_block(rgba, dst.data(), x, y, width, height, - BC3_OUTPUT_NCHANNELS); - } - } -} - - -void -KtxInput::decode_bc4(cspan src, span dst, int level) const -{ - const size_t width = std::max(m_tex2->baseWidth >> level, 1u); - const size_t height = std::max(m_tex2->baseHeight >> level, 1u); - - const size_t r_pitch = BCN_BLOCK_SIZE * BC4_OUTPUT_NCHANNELS; - uint8_t r[BCN_BLOCK_SIZE * r_pitch]; /* 16 bytes */ - const uint8_t* src_blocks = src.data(); - - for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { - // BC4: 8 bytes -> 4 x 4 x 1 = 16 bytes - rgbcx::unpack_bc4(src_blocks, r, /* stride */ BC4_OUTPUT_NCHANNELS); - src_blocks += BC4_BLOCK_SIZE; - cpy_decoded_block(r, dst.data(), x, y, width, height, - BC4_OUTPUT_NCHANNELS); - } - } -} - - -void -KtxInput::decode_bc5(cspan src, span dst, int level) const -{ - const size_t width = std::max(m_tex2->baseWidth >> level, 1u); - const size_t height = std::max(m_tex2->baseHeight >> level, 1u); - - const size_t rg_pitch = BCN_BLOCK_SIZE * BC5_OUTPUT_NCHANNELS; - uint8_t rg[BCN_BLOCK_SIZE * rg_pitch]; /* 32 bytes */ - const uint8_t* src_blocks = src.data(); - - for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { - // BC5: 16 bytes -> 4 x 4 x 2 = 32 bytes - rgbcx::unpack_bc5(src_blocks, rg, 0, 1, - /* stride */ BC5_OUTPUT_NCHANNELS); - src_blocks += BC5_BLOCK_SIZE; - cpy_decoded_block(rg, dst.data(), x, y, width, height, - BC5_OUTPUT_NCHANNELS); - } - } -} - -// TODO: this is not yet tested because apparently I can't (or ktx can't) -// generate a BC6HS/BC6HU KTX file... -// void -// KtxInput::decode_bc6h(cspan src, span dst, int level, -// bool is_signed) const -// { -// constexpr size_t kBlockSize { 4 }; -// constexpr size_t nchannels { BC6H_OUTPUT_NCHANNELS }; -// const size_t width = std::max(m_tex2->baseWidth >> level, 1u); -// const size_t height = std::max(m_tex2->baseHeight >> level, 1u); -// const size_t pitch = width * nchannels; -// -// cvtt::PixelBlockF16 rgbx; /* int16_t m_pixels[16][4]; */ -// constexpr int rgbx_pitch { kBlockSize * 4 }; -// const uint8_t* src_blocks = src.data(); -// -// for (size_t y { 0 }; y < height; y += kBlockSize) { -// for (size_t x { 0 }; x < width; x += kBlockSize) { -// // BC6: 16 bytes -> 4 x 4 x 3 x 2 = 32 bytes -// -// cvtt::Internal::BC6HComputer::UnpackOne(rgbx, src_blocks, -// is_signed); -// src_blocks += BC6H_BLOCK_SIZE; -// -// // copy HDR block into destination -// const int16_t* pSrc = rgbx.m_pixels[0]; -// int16_t* pDst = dst.data() + y * pitch + x * nchannels; -// const int cols = std::min(kBlockSize, width - x); -// for (size_t py { 0 }; py < kBlockSize && y + py < height; ++py) { -// memcpy(pDst, pSrc, cols * nchannels * 2); -// pSrc += rgbx_pitch; -// pDst += pitch; -// } -// } -// } -// } - - -void -KtxInput::decode_bc7(cspan src, span dst, int level) const -{ - const size_t width = std::max(m_tex2->baseWidth >> level, 1u); - const size_t height = std::max(m_tex2->baseHeight >> level, 1u); - - const size_t rgba_pitch = BCN_BLOCK_SIZE * BC7_OUTPUT_NCHANNELS; - uint8_t rgba[BCN_BLOCK_SIZE * rgba_pitch]; /* 64 bytes */ - const uint8_t* src_blocks = src.data(); - - for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { - // BC7: 16 bytes -> 4 x 4 x 4 = 64 bytes - bc7decomp::unpack_bc7(src_blocks, - reinterpret_cast( - rgba)); - src_blocks += BC7_BLOCK_SIZE; - cpy_decoded_block(rgba, dst.data(), x, y, width, height, - BC7_OUTPUT_NCHANNELS); - } - } -} - - -// -// Makes sure that provided source ETC blocks span and target span (where blocks -// will be decoded into) are of sufficient sizes. -// You should call this before any decode_etc() functions. -// -inline bool -KtxInput::check_etc_spans(cspan src, span dst, int level, - BlockCompression cmp) const -{ - const size_t nchannels { static_cast(m_spec.nchannels) }; - const size_t width = std::max(m_tex->baseWidth >> level, 1u); - const size_t height = std::max(m_tex->baseHeight >> level, 1u); - size_t expected_nchannels; - size_t expected_dst_size; - size_t expected_src_size; - const int nblocks_x { static_cast( - std::ceil(width / (float)ETC_BLOCK_SIZE)) }; - const int nblocks_y { static_cast( - std::ceil(height / (float)ETC_BLOCK_SIZE)) }; - - switch (cmp) { - case BlockCompression::ETC2_RGB: - expected_nchannels = ETC2_RGB_OUTPUT_NCHANNELS; - expected_dst_size = width * height * ETC2_RGB_OUTPUT_NCHANNELS; - expected_src_size = nblocks_x * nblocks_y * ETCDEC_ETC_RGB_BLOCK_SIZE; - break; - - case BlockCompression::ETC2_RGB_A1: - expected_nchannels = ETC2_RGB_A1_OUTPUT_NCHANNELS; - expected_dst_size = width * height * ETC2_RGB_A1_OUTPUT_NCHANNELS; - expected_src_size = nblocks_x * nblocks_y - * ETCDEC_ETC_RGB_A1_BLOCK_SIZE; - break; - - case BlockCompression::ETC2_RGBA: - expected_nchannels = ETC2_RGBA_OUTPUT_NCHANNELS; - expected_dst_size = width * height * ETC2_RGBA_OUTPUT_NCHANNELS; - expected_src_size = nblocks_x * nblocks_y * ETCDEC_EAC_RGBA_BLOCK_SIZE; - break; - - default: - errorfmt("Unsupported ETCO compression format: {}", - static_cast(cmp)); - return false; - } - - if (nchannels != expected_nchannels) { - errorfmt("Current ETC scheme is expected to decode into " - "{}-channel-images but target image got: {} channels.", - expected_nchannels, nchannels); - return false; - } - - if (src.size() < expected_src_size) { - errorfmt("The source data buffer's size is smaller than expected. " - "Expected {} bytes but provided buffer only has {} bytes.", - expected_src_size, src.size()); - return false; - } - - if (dst.size() < expected_dst_size) { - errorfmt( - "The size of the destination buffer to hold decoded ETC " - "blocks is smaller than expected. Expected {} bytes but provided " - "buffer only has {} bytes.", - expected_dst_size, dst.size()); - return false; - } - return true; -} - - -void -KtxInput::decode_etc2_rgb(cspan src, span dst, - int level) const -{ - const size_t width = std::max(m_tex2->baseWidth >> level, 1u); - const size_t height = std::max(m_tex2->baseHeight >> level, 1u); - - const size_t rgbx_pitch = ETC_BLOCK_SIZE * ETC2_RGB_OUTPUT_NCHANNELS; - uint8_t rgbx[ETC_BLOCK_SIZE * rgbx_pitch]; /* 64 bytes */ - const uint8_t* src_blocks = src.data(); - - for (size_t y { 0 }; y < height; y += ETC_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += ETC_BLOCK_SIZE) { - // ETC2_RGB: 8 bytes -> 4 x 4 x (3 + 1) = 64 bytes (alpha ignored) - etcdec_etc_rgb(src_blocks, rgbx, - ETC_BLOCK_SIZE * ETC2_RGB_OUTPUT_NCHANNELS); - src_blocks += ETCDEC_ETC_RGB_BLOCK_SIZE; - cpy_decoded_block(rgbx, dst.data(), x, y, width, height, - ETC2_RGB_OUTPUT_NCHANNELS); - } - } -} - - -void -KtxInput::decode_etc2_rgba(cspan src, span dst, - int level) const -{ - const size_t width = std::max(m_tex2->baseWidth >> level, 1u); - const size_t height = std::max(m_tex2->baseHeight >> level, 1u); - - const size_t rgba_pitch = ETC_BLOCK_SIZE * ETC2_RGBA_OUTPUT_NCHANNELS; - uint8_t rgba[ETC_BLOCK_SIZE * rgba_pitch]; /* 64 bytes */ - const uint8_t* src_blocks = src.data(); - - for (size_t y { 0 }; y < height; y += ETC_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += ETC_BLOCK_SIZE) { - // ETC2_RGBA: 16 bytes -> 4 x 4 x 4 = 64 bytes - etcdec_eac_rgba(src_blocks, rgba, - ETC_BLOCK_SIZE * ETC2_RGBA_OUTPUT_NCHANNELS); - src_blocks += ETCDEC_EAC_RGBA_BLOCK_SIZE; - cpy_decoded_block(rgba, dst.data(), x, y, width, height, - ETC2_RGBA_OUTPUT_NCHANNELS); - } - } -} - - -void -KtxInput::decode_etc2_rgb_a1(cspan src, span dst, - int level) const -{ - const size_t width = std::max(m_tex2->baseWidth >> level, 1u); - const size_t height = std::max(m_tex2->baseHeight >> level, 1u); - - const size_t rgba_pitch = ETC_BLOCK_SIZE * ETC2_RGB_A1_OUTPUT_NCHANNELS; - uint8_t rgba[ETC_BLOCK_SIZE * rgba_pitch]; /* 64 bytes */ - const uint8_t* src_blocks = src.data(); - - for (size_t y { 0 }; y < height; y += ETC_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += ETC_BLOCK_SIZE) { - // ETC2_RGB_A1: 8 bytes -> 4 x 4 x 4 = 64 bytes - etcdec_etc_rgb_a1(src_blocks, rgba, - ETC_BLOCK_SIZE * ETC2_RGB_A1_OUTPUT_NCHANNELS); - src_blocks += ETCDEC_ETC_RGB_A1_BLOCK_SIZE; - cpy_decoded_block(rgba, dst.data(), x, y, width, height, - ETC2_RGB_A1_OUTPUT_NCHANNELS); - } - } -} - OIIO_PLUGIN_NAMESPACE_END diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp index df7554f219..fc28b1101a 100644 --- a/src/ktx.imageio/ktxoutput.cpp +++ b/src/ktx.imageio/ktxoutput.cpp @@ -15,11 +15,6 @@ #include #include -#include "bc7enc-rdo/bc7enc.h" /* for BC7 encoder */ -#include "bc7enc-rdo/rgbcx.h" /* for BC1-BC5 encoders */ -// TODO: ETC encoders -// TODO: BC6HS/BC6HU encoders - OIIO_PLUGIN_NAMESPACE_BEGIN class KtxOutput final : public ImageOutput { @@ -87,25 +82,6 @@ class KtxOutput final : public ImageOutput { bool write_ktx2(); - inline bool check_bcn_spans(cspan src, span dst, - size_t width, size_t height, - BlockCompression cmp) const; - - void encode_bc1(cspan src, span dst, size_t width, - size_t height); - - void encode_bc3(cspan src, span dst, size_t width, - size_t height); - - void encode_bc4(cspan src, span dst, size_t width, - size_t height); - - void encode_bc5(cspan src, span dst, size_t width, - size_t height); - - void encode_bc7(cspan src, span dst, size_t width, - size_t height, const bc7enc_compress_block_params* params); - // void generate_mip_levels(const image_span& base_lvl_image, // ImageInput& inputFile, uint32_t numMipLevels, // uint32_t layerIndex, uint32_t faceIndex, @@ -351,7 +327,6 @@ KtxOutput::open(const std::string& name, const ImageSpec& newspec, // size from the set raw VkFormat: // (e.g., VK_FORMAT_R8G8_SRGB => width * height * 3 ) m_img.resize(m_spec.scanline_bytes() * m_spec.height); - rgbcx::init(rgbcx::bc1_approx_mode::cBC1Ideal); } return true; @@ -516,10 +491,6 @@ KtxOutput::write_ktx2() (ktx_uint32_t)strlen(writer) + 1, writer); // std::cout << "KTXwrite: " << writer << '\n'; - // dimensions for current mip level - const size_t width = std::max(m_tex->baseWidth >> 0, 1u); - const size_t height = std::max(m_tex->baseHeight >> 0, 1u); - // // In case data was read from an input KTX2 file with mipmaps, we have to // write the base level then generate mipmaps up to the specified level @@ -556,65 +527,27 @@ KtxOutput::write_ktx2() || m_cmp == BlockCompression::BC4 || m_cmp == BlockCompression::BC5 || m_cmp == BlockCompression::BC7) { - // - // Is this a BCn GPU block compressed texture? If so, we need to encode - // to target block compression. To do so, we create another intermediate - // std::vector (is there a better alternative?) - // - std::vector img_compressed; - img_compressed.resize(ktxTexture_GetImageSize(ktxTexture(m_tex), 0)); - switch (m_cmp) { - case BlockCompression::BC1: - if (!check_bcn_spans(m_img, img_compressed, width, height, m_cmp)) - return false; - encode_bc1(m_img, img_compressed, width, height); - break; - - case BlockCompression::BC3: - if (!check_bcn_spans(m_img, img_compressed, width, height, m_cmp)) - return false; - encode_bc3(m_img, img_compressed, width, height); - break; - - case BlockCompression::BC4: - if (!check_bcn_spans(m_img, img_compressed, width, height, m_cmp)) - return false; - encode_bc4(m_img, img_compressed, width, height); - break; - - case BlockCompression::BC5: - if (!check_bcn_spans(m_img, img_compressed, width, height, m_cmp)) - return false; - encode_bc5(m_img, img_compressed, width, height); - break; - - case BlockCompression::BC7: { - // MUST be called before calling bc7enc_compress_block() (or you'll get artifacts). - bc7enc_compress_block_init(); - - // Make sure to init BC7 block compression params to default. There is - // simply no way to figure out the originally used params from KTX2 file so - // we use defaults. - bc7enc_compress_block_params cmp_params; - bc7enc_compress_block_params_init(&cmp_params); - if (!check_bcn_spans(m_img, img_compressed, width, height, m_cmp)) - return false; - encode_bc7(m_img, img_compressed, width, height, &cmp_params); - break; - } - default: return false; - } - - if (auto status = ktxTexture_SetImageFromMemory(ktxTexture(m_tex), 0, 0, - 0, - img_compressed.data(), - img_compressed.size()); - status != KTX_SUCCESS) { - errorfmt( - "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", - static_cast(status)); - return false; - } + // First set uncompressed texture + // if (auto status = ktxTexture_SetImageFromMemory(ktxTexture(m_tex), 0, 0, + // 0, m_img.data(), + // m_img.size()); + // status != KTX_SUCCESS) { + // has_error(); + // errorfmt( + // "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", + // static_cast(status)); + // return false; + // } + + // Then compress the whole texture to BCn format + // TODO: expose BCn compression quality parameter as spec attribute + // if (auto status = ktxTexture2_CompressBCn(m_tex, nullptr); + // status != KTX_SUCCESS) { + // errorfmt("ktxTexture2_CompressBCn returned KTX exit error code: {}", + // static_cast(status)); + // return false; + // } + return false; } else if (m_cmp == BlockCompression::ASTC) { // First set uncompressed images if (auto status = ktxTexture_SetImageFromMemory(ktxTexture(m_tex), 0, 0, @@ -704,228 +637,6 @@ KtxOutput::write_ktx2() -// -// Makes sure that provided source BCn blocks span and target span (where blocks -// will be decoded into) are of sufficient sizes. -// This should be called before any decode_bcn() functions. -// -inline bool -KtxOutput::check_bcn_spans(cspan src, span dst, size_t width, - size_t height, BlockCompression cmp) const -{ - const size_t nchannels { static_cast(m_spec.nchannels) }; - const int nblocks_x { static_cast( - std::ceil(width / (float)BCN_BLOCK_SIZE)) }; - const int nblocks_y { static_cast( - std::ceil(height / (float)BCN_BLOCK_SIZE)) }; - size_t expected_nchannels; - size_t expected_dst_size; - - /* Safety checks */ - if ((width % BCN_BLOCK_SIZE != 0) || (height % BCN_BLOCK_SIZE != 0)) { - errorfmt("To use BCn block compression, the image's dimensions have " - "to be a multiple of: {}", - BCN_BLOCK_SIZE); - return false; - } - - switch (cmp) { - case BlockCompression::BC1: - expected_nchannels = BC1_OUTPUT_NCHANNELS; - expected_dst_size = nblocks_x * nblocks_y * BC1_BLOCK_SIZE; - break; - - case BlockCompression::BC3: - expected_nchannels = BC3_OUTPUT_NCHANNELS; - expected_dst_size = nblocks_x * nblocks_y * BC3_BLOCK_SIZE; - break; - - case BlockCompression::BC4: - expected_nchannels = BC4_OUTPUT_NCHANNELS; - expected_dst_size = nblocks_x * nblocks_y * BC4_BLOCK_SIZE; - break; - - case BlockCompression::BC5: - expected_nchannels = BC5_OUTPUT_NCHANNELS; - expected_dst_size = nblocks_x * nblocks_y * BC5_BLOCK_SIZE; - break; - - case BlockCompression::BC7: - expected_nchannels = BC7_OUTPUT_NCHANNELS; - expected_dst_size = nblocks_x * nblocks_y * BC7_BLOCK_SIZE; - break; - - default: - errorfmt("Unsupported block compression format: {}", - static_cast(m_cmp)); - return false; - } - - if (nchannels != expected_nchannels) { - errorfmt("Current BCn scheme is expected to encode {}-channel-images " - "but provided image got: {} channels.", - nchannels); - return false; - } - - const size_t expected_src_size { width * height * expected_nchannels }; - if (src.size() < expected_src_size) { - errorfmt("The source data buffer's size is smaller than expected. " - "Expected {} bytes but provided buffer only has {} bytes.", - expected_src_size, src.size()); - return false; - } - - if (dst.size() < expected_dst_size) { - errorfmt("The size of the destination buffer to hold current BCn " - "scheme's compressed blocks is smaller than expected. " - "Expected {} bytes but provided buffer only has {} bytes.", - expected_dst_size, dst.size()); - return false; - } - return true; -} - - - -void -KtxOutput::encode_bc1(cspan src, span dst, size_t width, - size_t height) -{ - uint8_t* dst_ptr { dst.data() }; - const size_t pixels_pitch { BCN_BLOCK_SIZE - * BC1_OUTPUT_NCHANNELS }; // 4 x 4 - uint8_t pPixels[BCN_BLOCK_SIZE * pixels_pitch]; // 4 x 4 x 4 - - // Row-major loop over blocks - for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { - // extract/copy source block - for (size_t i { 0 }; i < BCN_BLOCK_SIZE; ++i) { - // copy 4 pixels (32bpp) to pPixels - memcpy(pPixels + i * pixels_pitch, - src.data() + (y + i) * width * BC1_OUTPUT_NCHANNELS - + x * BC1_OUTPUT_NCHANNELS, - pixels_pitch); - } - // BC1: 8 bytes -> 4 x 4 x 4 = 64 bytes - rgbcx::encode_bc1(10, dst_ptr, - reinterpret_cast(pPixels), true, - false); - dst_ptr += BC1_BLOCK_SIZE; - } - } -} - - - -void -KtxOutput::encode_bc3(cspan src, span dst, size_t width, - size_t height) -{ - uint8_t* dst_ptr { dst.data() }; - const size_t pixels_pitch { BCN_BLOCK_SIZE - * BC3_OUTPUT_NCHANNELS }; // 4 x 4 - uint8_t pPixels[BCN_BLOCK_SIZE * pixels_pitch]; // 4 x 4 x 4 - - for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { - for (size_t i { 0 }; i < BCN_BLOCK_SIZE; ++i) - memcpy(pPixels + i * pixels_pitch, - src.data() + (y + i) * width * BC3_OUTPUT_NCHANNELS - + x * BC3_OUTPUT_NCHANNELS, - pixels_pitch); - // BC3: 16 bytes -> 4 x 4 x 4 = 64 bytes - rgbcx::encode_bc3(10, dst_ptr, - reinterpret_cast(pPixels)); - dst_ptr += BC3_BLOCK_SIZE; - } - } -} - - - -void -KtxOutput::encode_bc4(cspan src, span dst, size_t width, - size_t height) -{ - uint8_t* dst_ptr { dst.data() }; - const size_t pixels_pitch { BCN_BLOCK_SIZE - * BC4_OUTPUT_NCHANNELS }; // 4 x 1 - uint8_t pPixels[BCN_BLOCK_SIZE * pixels_pitch]; // 4 x 4 x 1 - - for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { - for (size_t i { 0 }; i < BCN_BLOCK_SIZE; ++i) - memcpy(pPixels + i * pixels_pitch, - src.data() + (y + i) * width * BC4_OUTPUT_NCHANNELS - + x * BC4_OUTPUT_NCHANNELS, - pixels_pitch); - // BC4: 8 bytes -> 4 x 4 x 1 = 16 bytes - rgbcx::encode_bc4(dst_ptr, - reinterpret_cast(pPixels), - /* stride */ BC4_OUTPUT_NCHANNELS); - dst_ptr += BC4_BLOCK_SIZE; - } - } -} - - - -void -KtxOutput::encode_bc5(cspan src, span dst, size_t width, - size_t height) -{ - uint8_t* dst_ptr { dst.data() }; - const size_t pixels_pitch { BCN_BLOCK_SIZE - * BC5_OUTPUT_NCHANNELS }; // 4 x 2 - uint8_t pPixels[BCN_BLOCK_SIZE * pixels_pitch]; // 4 x 4 x 2 - - for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { - for (size_t i { 0 }; i < BCN_BLOCK_SIZE; ++i) - memcpy(pPixels + i * pixels_pitch, - src.data() + (y + i) * width * BC5_OUTPUT_NCHANNELS - + x * BC5_OUTPUT_NCHANNELS, - pixels_pitch); - // BC5: 16 bytes -> 4 x 4 x 2 = 32 bytes - rgbcx::encode_bc5(dst_ptr, - reinterpret_cast(pPixels), 0, 1, - /* stride */ BC5_OUTPUT_NCHANNELS); - dst_ptr += BC5_BLOCK_SIZE; - } - } -} - - - -void -KtxOutput::encode_bc7(cspan src, span dst, size_t width, - size_t height, const bc7enc_compress_block_params* params) -{ - uint8_t* dst_ptr { dst.data() }; - const size_t pixels_pitch { BCN_BLOCK_SIZE - * BC7_OUTPUT_NCHANNELS }; // 4 x 4 - uint8_t pPixels[BCN_BLOCK_SIZE * pixels_pitch]; // 4 x 4 x 4 - - for (size_t y { 0 }; y < height; y += BCN_BLOCK_SIZE) { - for (size_t x { 0 }; x < width; x += BCN_BLOCK_SIZE) { - for (size_t i { 0 }; i < BCN_BLOCK_SIZE; ++i) - memcpy(pPixels + i * pixels_pitch, - src.data() + (y + i) * width * BC7_OUTPUT_NCHANNELS - + x * BC7_OUTPUT_NCHANNELS, - pixels_pitch); - // BC7: 16 bytes -> 4 x 4 x 4 = 64 bytes - bc7enc_compress_block(dst_ptr, - reinterpret_cast(pPixels), - params); - dst_ptr += BC7_BLOCK_SIZE; - } - } -} - - - // void // KtxOutput::generate_mip_levels(const image_span& base_lvl_image, // ImageInput& inputFile, uint32_t numMipLevels, From dd28a48e0fecda36bc312c8f2a0add9fc46819e3 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Mon, 22 Jun 2026 15:20:54 +0200 Subject: [PATCH 03/42] Only build or find libktx on MSVC, Clang, or GCC *IntelLLVM is simply not supported by libktx. Signed-off-by: Walid Chtioui --- src/cmake/externalpackages.cmake | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index 4df337e5cf..c1d2123b48 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -250,10 +250,19 @@ checked_find_package (fmt REQUIRED get_target_property(FMT_INCLUDE_DIR fmt::fmt-header-only INTERFACE_INCLUDE_DIRECTORIES) # Ktx for KTX textures -checked_find_package (Ktx - VERSION_MIN 5.0.0 - BUILD_LOCAL missing -) +# Note for KTX developers: set VERSION_MIN to 0.0.0 if you have a locally-built +# tracking HEAD:main of KTX-Software because building non-tagged libktx sets the +# version to 0.0.0 +# +# libktx doesn't support IntelLLVM and compilers other than gcc, clang, and +# MSVC. +if (MSVC OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" + OR ${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") + checked_find_package (Ktx + VERSION_MIN 5.0.0 + BUILD_LOCAL missing + ) +endif() ########################################################################### From 3453812222383d8f6a1003b1e72df6a33b66a9fe Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Mon, 29 Jun 2026 23:04:50 +0200 Subject: [PATCH 04/42] Add to-native-data-format conversion *Add to_native* conversion to KTX2 output similar to how JPEG and PNG OIIO outputs are written. *Apply clang-format-17 as opposed to the previously applied clang-format v18 which caused the CI to fail. *Update CMAKE_VERSION in CIs to minimum version required by libktx (i.e., 3.23). *Fix uninitialized structs passed to libktx. *Other misc clean-ups and minor refactoring. Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 7 +- src/cmake/testing.cmake | 2 +- src/ktx.imageio/README.md | 54 ++++++++++++++++ src/ktx.imageio/ktx_pvt.h | 28 +++++++- src/ktx.imageio/ktxinput.cpp | 24 ++++--- src/ktx.imageio/ktxoutput.cpp | 117 ++++++++++++++++++++++------------ testsuite/ktx/run.py | 7 -- 7 files changed, 177 insertions(+), 62 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42bd8fa383..3fc3578610 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -455,7 +455,8 @@ jobs: python_ver: "3.9" python_action_ver: "3.9" gcc_action_ver: 9 - setenvs: export CMAKE_VERSION=3.18.2 + # CMAKE_VERSION changed from 3.18.2 so that libktx can be built + setenvs: export CMAKE_VERSION=3.23 PTEX_VERSION=v2.3.2 WEBP_VERSION=v1.1.0 PUGIXML_VERSION=v1.8 @@ -483,7 +484,7 @@ jobs: python_ver: "3.9" python_action_ver: "3.9" llvm_action_ver: "11" - setenvs: export CMAKE_VERSION=3.18.2 + setenvs: export CMAKE_VERSION=3.23 PTEX_VERSION=v2.3.2 WEBP_VERSION=v1.1.0 PUGIXML_VERSION=v1.8 @@ -520,7 +521,7 @@ jobs: gcc_action_ver: 9 simd: 0 setenvs: export EMBEDPLUGINS=0 - CMAKE_VERSION=3.18.2 + CMAKE_VERSION=3.23 PTEX_VERSION=v2.3.2 WEBP_VERSION=v1.1.0 USE_JPEGTURBO=0 diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index c0a7a85606..0900c688a5 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -544,7 +544,7 @@ endfunction() function (oiio_setup_test_data) # TODO: revert this after accepting OpenImageIO-images PR and before merging (just so that the CI passes) oiio_get_test_data (oiio-images - REPO https://github.com/AcademySoftwareFoundation/OpenImageIO-images.git + REPO https://github.com/walcht/OpenImageIO-images.git BRANCH main) oiio_get_test_data (openexr-images REPO https://github.com/AcademySoftwareFoundation/openexr-images.git diff --git a/src/ktx.imageio/README.md b/src/ktx.imageio/README.md index adfd9e3df4..0319c2458d 100644 --- a/src/ktx.imageio/README.md +++ b/src/ktx.imageio/README.md @@ -197,6 +197,60 @@ transcoding support, supercompression decompression support, etc.). - Commit hash: see `OpenImageIO/src/cmake/build_Ktx.cmake` - License: Many subresources. +# Building OIIO + +To build (with required dependencies and fetch tests): + + 1. add REQUIRED to force local build: + + ```cmake + checked_find_package (Ktx REQUIRED VERSION_MIN 5.0.0 BUILD_LOCAL missing) + ``` + + or if you have a HEAD:main Ktx locally installed, change the above to: + + ```cmake + checked_find_package (Ktx REQUIRED VERSION_MIN 0.0.0) + ``` + + 1. pystring dependency for OpenColor dependency will probably fail (just + change the tag commit hash and add required as such: + `checked_find_package(pystring REQUIRED VERSION_MIN 1.1.4)`) + + 1. and force libjpeg-Turbo local build by adding `REQUIRED`: + + ```cmake + checked_find_package (libjpeg-turbo REQUIRED + VERSION_MIN 2.1 + DEFINITIONS USE_JPEG_TURBO=1) + ``` + + 1. build using CMake + + ```bash + cmake -S . -B build/ -DCMAKE_BUILD_TYPE=DEBUG -DOIIO_DOWNLOAD_MISSING_TESTDATA=ON -DOpenImageIO_BUILD_MISSING_DEPS=required + ``` + + 1. build will probably because of 'pystring not found' error, so run it again. + +To test (of course, make sure image assets are downloaded - see the CMake flag above): + + 1. Invoke ctest (just see how top-level Makefile invokes it) + ```bash + cd build/ + ctest -V --force-new-ctest-process --output-on-failure + ``` + + +## Misc Notes for Developers + +Use `clang-format-17` for formatting and not whatever newest version you have +on your machine (in my case, clang-format v18 caused clang-format CI to fail): + +```bash +find . -regex '.*\.\(cpp\|hpp\|cc\|cxx\)' -exec clang-format-17 -style=file -i {} \; +``` + ## Resources - [KTX2 Specs](https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html) diff --git a/src/ktx.imageio/ktx_pvt.h b/src/ktx.imageio/ktx_pvt.h index eb7f193950..14fcd16c21 100644 --- a/src/ktx.imageio/ktx_pvt.h +++ b/src/ktx.imageio/ktx_pvt.h @@ -298,7 +298,7 @@ struct FormatInfo { // test file with the same VkFormat). // inline bool -extract_info_from_format(VkFormat vkformat, FormatInfo& formatinfo) +get_info_from_vkformat(VkFormat vkformat, FormatInfo& formatinfo) { // clang-format off switch (vkformat) { @@ -328,6 +328,32 @@ extract_info_from_format(VkFormat vkformat, FormatInfo& formatinfo) +inline VkFormat +get_vkformat_from_info(int nchannels, TypeDesc format, bool srgb_colorspace) +{ + if (format != TypeDesc::UINT8) + return VK_FORMAT_UNDEFINED; + + switch (nchannels) { + case 1: + return srgb_colorspace ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM; + break; + case 2: + return srgb_colorspace ? VK_FORMAT_R8G8_SRGB : VK_FORMAT_R8G8_UNORM; + break; + case 3: + return srgb_colorspace ? VK_FORMAT_R8G8B8_SRGB : VK_FORMAT_R8G8B8_UNORM; + break; + case 4: + return srgb_colorspace ? VK_FORMAT_R8G8B8A8_SRGB + : VK_FORMAT_R8G8B8A8_UNORM; + break; + default: break; + } + + return VK_FORMAT_UNDEFINED; +} + // TODO inline void gl_to_vkformat() diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index b6058b1e39..4785b3a880 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -21,7 +21,7 @@ OIIO_PLUGIN_NAMESPACE_BEGIN class KtxInput final : public ImageInput { public: - KtxInput() { } + KtxInput() {} ~KtxInput() override { close(); } @@ -140,10 +140,14 @@ OIIO_EXPORT int ktx_imageio_version = OIIO_PLUGIN_VERSION; OIIO_EXPORT const char* ktx_imageio_library_version() -{ return "ktx v5.0.0-rc1"; } // hardcoded because I couldn't expose KTX_VERSION +{ + return "ktx v5.0.0-rc1"; +} // hardcoded because I couldn't expose KTX_VERSION OIIO_EXPORT ImageInput* ktx_input_imageio_create() -{ return new KtxInput; } +{ + return new KtxInput; +} OIIO_EXPORT const char* ktx_input_extensions[] = { "ktx2", nullptr }; OIIO_PLUGIN_EXPORTS_END @@ -541,8 +545,8 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) auto format = static_cast(m_tex2->vkFormat); if (m_tex2->isCompressed) { FormatInfo format_info; - if (!extract_info_from_format(static_cast(format), - format_info)) { + if (!get_info_from_vkformat(static_cast(format), + format_info)) { errorfmt( "Failed to extract info (e.g., nchannels, typedesc, etc.) from VkFormat: {}", static_cast(format)); @@ -567,8 +571,8 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // { FormatInfo format_info; - if (!extract_info_from_format(static_cast(format), - format_info)) { + if (!get_info_from_vkformat(static_cast(format), + format_info)) { errorfmt( "Failed to extract info (e.g., nchannels, typedesc, etc.) from VkFormat: {}", static_cast(format)); @@ -778,8 +782,8 @@ KtxInput::seek_subimage(int subimage, int miplevel) return false; } - m_pitch = width * m_spec.nchannels - * m_spec.format.size() /* 1 for LDR, 2 for HDR formats */; + m_pitch = width * m_spec.nchannels + * m_spec.format.size() /* 1 for LDR, 2 for HDR formats */; m_data_ptr = m_buf.data(); } @@ -877,7 +881,7 @@ OpenImageIO::KtxInput::valid_file(Filesystem::IOProxy* ioproxy) const return false; // per KTX2 specs: the first 12 bytes of a KTX2 file are used to identify it - uint8_t magic[12] { }; + uint8_t magic[12] {}; const size_t numRead = ioproxy->pread(magic, sizeof(magic), 0); return (numRead == sizeof(magic)) diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp index fc28b1101a..d8e93b0197 100644 --- a/src/ktx.imageio/ktxoutput.cpp +++ b/src/ktx.imageio/ktxoutput.cpp @@ -64,6 +64,8 @@ class KtxOutput final : public ImageOutput { BlockCompression m_cmp { BlockCompression::NONE }; + std::vector m_scratch; + /// libktx only supports writing whole images (i.e., (miplevel, layer, face_slice/depth) /// hence why we keep a large std::vector at all times. This is also needed because we /// apply compression upon file closure and not each time on write_scanline(s). @@ -74,8 +76,6 @@ class KtxOutput final : public ImageOutput { void init(); - VkFormat figure_vkformat_from_spec() const; - bool basisu_basislz_compress(); bool basisu_uastc_compress(); @@ -108,16 +108,26 @@ KtxOutput::open(const std::string& name, const ImageSpec& newspec, OpenMode mode) { // TODO: verify the x, y, z limits (probably not 65535) + // This does: m_spec = newspec if (!check_open(mode, newspec, { 0, 65535, 0, 65535, 0, 65535, 0, 4 })) return false; // Save name and spec for later use m_filename = name; + // If not uint8, default to uint8 (HDR not yet supported) + if (m_spec.format + != TypeDesc::UINT8 /* && m_spec.format != TypeDesc::UINT16 */) + m_spec.set_format(TypeDesc::UINT8); + ioproxy_retrieve_from_config(m_spec); if (!ioproxy_use_or_open(m_filename)) return false; + std::string colorspace = m_spec.get_string_attribute("oiio:ColorSpace", + "srgb_rec709_scene"); + bool is_srgb = colorspace == "srgb_rec709_scene"; + // TODO: get_int_attribute causes a segfault and I have no idea why ... // Weirdly, calling find_attribute directly (and checking the resulting // pointer) works, but not get_int_attribute ... @@ -187,8 +197,10 @@ KtxOutput::open(const std::string& name, const ImageSpec& newspec, return false; } - // Get original VkFormat (i.e., before potential decompression/transcoding) - // or the vkformat explicitly set via the "ktx:vkformat" attribute. + // + // If provided, get target VkFormat explicitly set via the "ktx:vkformat" + // attribute. + // auto vkFormat = VkFormat::VK_FORMAT_UNDEFINED; ParamValue* vkFormatQ = m_spec.find_attribute("ktx:vkformat", TypeDesc::UINT32); @@ -198,7 +210,7 @@ KtxOutput::open(const std::string& name, const ImageSpec& newspec, // Get GPU-block-compression from provided VkFormat if (vkFormat != VK_FORMAT_UNDEFINED) { FormatInfo format_info; - if (!extract_info_from_format(vkFormat, format_info)) { + if (!get_info_from_vkformat(vkFormat, format_info)) { close(); errorfmt("Could not extract format info from provided " "VkFormat: {}. This format is probably unsupported.", @@ -210,6 +222,23 @@ KtxOutput::open(const std::string& name, const ImageSpec& newspec, // std::cout << "[ktxoutput] found vkformat: " << vkFormat << '\n'; } + // + // User provided nothing about neither the target VkFormat nor the target + // super-compression scheme. Choose a sane default from provided spec (i.e., + // nchannels + bit depth). Two choices: compress to raw format and nullify + // the benefit of writing a KTX2 at the benefit of a losseless write. Or, + // use UASTC format which is what is typically used within KTX. + // + if (vkFormat == VK_FORMAT_UNDEFINED + && m_colormodel == KHR_DF_MODEL_UNSPECIFIED) { + if (m_spec.format != TypeDesc::UINT8) { + close(); + errorfmt("Non-LDR input is not yet supported."); + return false; + } + m_colormodel = KHR_DF_MODEL_UASTC; + } + // // Since we are using libktx's ktxTexture2_CompressAstc, the format has to // be set to an uncompressed VkFormat otherwise we get KTX_INVALID_OPERATION @@ -218,18 +247,18 @@ KtxOutput::open(const std::string& name, const ImageSpec& newspec, if (m_cmp == BlockCompression::ASTC) vkFormat = VK_FORMAT_R8G8B8A8_SRGB; - // Id a basis universal format compression is not requested and + // + // If a Basis Universal format compression is not requested and // "ktx:vkformat" is VK_FORMAT_UNDEFINED, then we error out. The user has to // set the vkformat so that we know in which format we write the texture to. + // if ((m_colormodel != KHR_DF_MODEL_ETC1S && m_colormodel != KHR_DF_MODEL_UASTC) && vkFormat == VK_FORMAT_UNDEFINED) { - // TODO: maybe don't error out and set the format depending on the nchannels? - // (e.g., 4 + srgb_rec709_scene colorspace => VK_FORMAT_R8G8B8A8_SRGB) close(); errorfmt( "VkFormat is set to VK_FORMAT_UNDEFINED even though the " - "supercompressionscheme is not Basis LZ. You have to set the " + "supercompression scheme is not BasisLZ. You have to set the " "target VkFormat by setting the ImageSpec's attribute 'ktx:vkformat'."); return false; } @@ -244,7 +273,8 @@ KtxOutput::open(const std::string& name, const ImageSpec& newspec, if ((m_colormodel == KHR_DF_MODEL_ETC1S || m_colormodel == KHR_DF_MODEL_UASTC) && vkFormat == VK_FORMAT_UNDEFINED) { - vkFormat = figure_vkformat_from_spec(); + vkFormat = get_vkformat_from_info(m_spec.nchannels, m_spec.format, + is_srgb); } else if (m_colormodel == KHR_DF_MODEL_ETC1S || m_colormodel == KHR_DF_MODEL_UASTC) { // TODO: It could be that the user explicitly provided a vkformat - in which @@ -347,18 +377,38 @@ KtxOutput::write_scanline(int y, int z, TypeDesc format, const void* data, // depth parameter (i.e., z). bool KtxOutput::write_scanlines(int ybegin, int yend, - int _ /* slice or face or layer */, TypeDesc format, + int z /* slice or face or layer */, TypeDesc format, const void* data, stride_t xstride, stride_t ystride) { // std::cout << "write_scanlines called with: ybegin=" << ybegin // << "; yend=" << yend << "; z=" << z << "; format=" << format // << "; xstride=" << xstride << '\n'; - m_spec.auto_stride(xstride, format, spec().nchannels); + stride_t zstride = AutoStride; + m_spec.auto_stride(xstride, ystride, zstride, format, spec().nchannels, + m_spec.width, m_spec.height); // const void* origdata = data; + + // to_native_rectangle will do this check and assignment. Keep it here for + // consistency with JPEG writer if (format == TypeUnknown) format = m_spec.format; + // + // Convert to the native format the current specs expects. This is needed, + // for instance, to convert a given TypeDesc::FLOAT into native format that + // this KTX2 writer expects (i.e., TypeDesc::UINT8 or TypeDesc::UINT16). + // + // Returned data pointer may be the same as the provided pointer (i.e., no + // conversion is needed because supplied data is already in native format). + // + data = to_native_rectangle(m_spec.x, m_spec.x + m_spec.width, ybegin, yend, + z, z + 1, format, data, xstride, ystride, + zstride, m_scratch, false, 0, ybegin, z); + + // data should now be contiguous and of the expected format (UINT8 or + // UINT16) so simply memcpy into internal buffer that will be written on + // close(). const size_t pitch = m_spec.scanline_bytes(); auto pSrc = reinterpret_cast(data); size_t offset = ybegin * pitch; @@ -384,8 +434,7 @@ KtxOutput::close() if (m_tex) { // Apparently we can't do (or I don't know yet how to) partial writes // using libktx. We can only write whole ktxTextures all together. - if (result) - result = write_ktx2(); // TODO: can this throw? (prob not) + result = write_ktx2(); // TODO: can this throw? (prob not) ktxTexture_Destroy(ktxTexture(m_tex)); } init(); @@ -407,22 +456,6 @@ KtxOutput::init() -VkFormat -KtxOutput::figure_vkformat_from_spec() const -{ - // TODO: check colorspace and return VkFormat accordingly - // TODO: check format (TypeDesc) and return VkFormat accordingly - switch (m_spec.nchannels) { - case 1: return VK_FORMAT_R8_SRGB; - case 2: return VK_FORMAT_R8G8_SRGB; - case 3: return VK_FORMAT_R8G8B8_SRGB; - case 4: return VK_FORMAT_R8G8B8A8_SRGB; - } - return VK_FORMAT_R8G8B8A8_SRGB; -} - - - // // Applies BasisLZ/ETC1S supercompression to this KTX2 texture. The ImageSpec is // queried (searched) for attribute that determine the BasisLZ/ETC1S compression @@ -436,11 +469,14 @@ KtxOutput::basisu_basislz_compress() // what params the original data was compressed with so that we can reproduce // it. // TODO: expose as "ktx:" attribute(s) - ktxBasisParams params; + ktxBasisParams params = { 0 }; params.structSize = sizeof(ktxBasisParams); params.codec = ktx_basis_codec_e::KTX_BASIS_CODEC_ETC1S; + params.verbose = false; + params.noSSE = false; params.threadCount = 1; params.etc1sCompressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL; + // TODO: expose RDO support for ETC1S if (auto status = ktxTexture2_CompressBasisEx(m_tex, ¶ms); status != KTX_SUCCESS) { errorfmt("ktxTexture2_CompressBasisEx returned error code: ", @@ -461,12 +497,15 @@ bool KtxOutput::basisu_uastc_compress() { // TODO: expose parameters - ktxBasisParams params; - params.structSize = sizeof(ktxBasisParams); - params.codec = ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_LDR_4x4; - params.threadCount = 1; - // .uastcFlags = KTX_PACK_UASTC_LEVEL_DEFAULT, - // TODO: set uastcRDONoMultithreading for testing + ktxBasisParams params = { 0 }; + params.structSize = sizeof(ktxBasisParams); + params.codec = ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_LDR_4x4; + params.verbose = false; + params.noSSE = false; + params.threadCount = 1; + params.uastcFlags = KTX_PACK_UASTC_LEVEL_DEFAULT; + params.uastcRDO = false; + // TODO: expose RDO support for UASTC if (auto status = ktxTexture2_CompressBasisEx(m_tex, ¶ms); status != KTX_SUCCESS) { errorfmt("ktxTexture2_CompressBasisEx returned error code: ", @@ -517,7 +556,6 @@ KtxOutput::write_ktx2() 0, m_img.data(), m_img.size()); status != KTX_SUCCESS) { - has_error(); errorfmt( "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", static_cast(status)); @@ -532,7 +570,6 @@ KtxOutput::write_ktx2() // 0, m_img.data(), // m_img.size()); // status != KTX_SUCCESS) { - // has_error(); // errorfmt( // "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", // static_cast(status)); @@ -547,6 +584,7 @@ KtxOutput::write_ktx2() // static_cast(status)); // return false; // } + errorfmt("Writing/Encoding BCn compression is not yet supported."); return false; } else if (m_cmp == BlockCompression::ASTC) { // First set uncompressed images @@ -554,7 +592,6 @@ KtxOutput::write_ktx2() 0, m_img.data(), m_img.size()); status != KTX_SUCCESS) { - has_error(); errorfmt( "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", static_cast(status)); diff --git a/testsuite/ktx/run.py b/testsuite/ktx/run.py index 3a97ceddfa..2b982b30e5 100644 --- a/testsuite/ktx/run.py +++ b/testsuite/ktx/run.py @@ -15,13 +15,6 @@ "2d_etc1s.ktx2", # GPU-block-compressed formats "2d_astc4x4.ktx2", - "2d_bc1.ktx2", - "2d_bc3.ktx2", - "2d_bc4.ktx2", - "2d_bc5.ktx2", - "2d_bc7.ktx2", - "2d_etc1.ktx2", - "2d_etc2.ktx2", ] for f in files: From 1e608680dfb5380e50b9be336bff3a74ea966baa Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sat, 4 Jul 2026 17:46:37 +0200 Subject: [PATCH 05/42] Override C/C++ compiler for KTX; revert CMAKE_VERSION in CI; misc refactoring *KTX does not support compilation with Intel's C++ compiler ICX hence why an override option is provided and is used in ICX CI. *Do all decompression/decoding/deflation in open() rather than in seek_subimage() Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 11 +- src/cmake/build_Ktx.cmake | 6 + src/ktx.imageio/ktx_pvt.h | 505 +++++++++++++++++++++-------------- src/ktx.imageio/ktxinput.cpp | 274 +++++++++---------- 4 files changed, 438 insertions(+), 358 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fc3578610..5374bfdb0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -179,7 +179,9 @@ jobs: setenvs: export USE_OPENVDB=0 USE_OPENCV=0 UHDR_CMAKE_C_COMPILER=gcc UHDR_CMAKE_CXX_COMPILER=g++ - # Building libuhdr with icx results in test failures + KTX_CMAKE_C_COMPILER=gcc + KTX_CMAKE_CXX_COMPILER=g++ + # Building libuhdr and libktx with icx results in test failures optional_deps_append: "LibRaw;Ptex;openjph;Qt6" - desc: VFX2026 gcc14/C++20 py3.13 exr3.4 ocio2.5 nametag: linux-vfx2026 @@ -455,8 +457,7 @@ jobs: python_ver: "3.9" python_action_ver: "3.9" gcc_action_ver: 9 - # CMAKE_VERSION changed from 3.18.2 so that libktx can be built - setenvs: export CMAKE_VERSION=3.23 + setenvs: export CMAKE_VERSION=3.18.2 PTEX_VERSION=v2.3.2 WEBP_VERSION=v1.1.0 PUGIXML_VERSION=v1.8 @@ -484,7 +485,7 @@ jobs: python_ver: "3.9" python_action_ver: "3.9" llvm_action_ver: "11" - setenvs: export CMAKE_VERSION=3.23 + setenvs: export CMAKE_VERSION=3.18.2 PTEX_VERSION=v2.3.2 WEBP_VERSION=v1.1.0 PUGIXML_VERSION=v1.8 @@ -521,7 +522,7 @@ jobs: gcc_action_ver: 9 simd: 0 setenvs: export EMBEDPLUGINS=0 - CMAKE_VERSION=3.23 + CMAKE_VERSION=3.18.2 PTEX_VERSION=v2.3.2 WEBP_VERSION=v1.1.0 USE_JPEGTURBO=0 diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index 70d8735c7c..017d86e255 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -11,6 +11,10 @@ set_cache (Ktx_BUILD_SHARED_LIBS OFF ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} string (MAKE_C_IDENTIFIER ${Ktx_BUILD_VERSION} Ktx_VERSION_IDENT) +# Override C/C++ compiler (useful when running CI with unsupported Intel's compiler ICX) +set_cache (KTX_CMAKE_C_COMPILER ${CMAKE_C_COMPILER} "libktx build C compiler override" ADVANCED) +set_cache (KTX_CMAKE_CXX_COMPILER ${CMAKE_CXX_COMPILER} "libktx build C++ compiler override" ADVANCED) + # for detailed build instructions, see: # https://github.com/KhronosGroup/KTX-Software/blob/main/BUILDING.md # KTX-Software not only provides Ktx but also a set of cli tools and load @@ -34,6 +38,8 @@ build_dependency_with_cmake(Ktx -D LIBKTX_FEATURE_VK_UPLOAD=OFF -D LIBKTX_FEATURE_GL_UPLOAD=OFF -D LIBKTX_FEATURE_ETC_UNPACK=OFF # This has some weird licensing and I don't feel comfortable including it ... + -D CMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER} + -D CMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER} # as per KTX-Software: # > Intel Macs have support for SSE, but if you're building universal # > binaries, you have to disable SSE or the build will fail. diff --git a/src/ktx.imageio/ktx_pvt.h b/src/ktx.imageio/ktx_pvt.h index 14fcd16c21..edbd07cbdf 100644 --- a/src/ktx.imageio/ktx_pvt.h +++ b/src/ktx.imageio/ktx_pvt.h @@ -9,12 +9,6 @@ OIIO_PLUGIN_NAMESPACE_BEGIN -// this is: "«KTX 20»\r\n\x1A\n" -static const uint8_t KTX2_IDENTIFIER[12] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, - 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; - - - enum class TextureKind : uint32_t { SINGLE_TEXTURE_1D, SINGLE_TEXTURE_2D, @@ -84,195 +78,294 @@ struct KTXglFormat { -// TODO: VkFormat struct should be autogenerated via CMake and the Vulkan -// version -// Provided by VK_VERSION_1_0 -typedef enum VkFormat : uint32_t { - VK_FORMAT_UNDEFINED = 0, - VK_FORMAT_R4G4_UNORM_PACK8 = 1, - VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2, - VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3, - VK_FORMAT_R5G6B5_UNORM_PACK16 = 4, - VK_FORMAT_B5G6R5_UNORM_PACK16 = 5, - VK_FORMAT_R5G5B5A1_UNORM_PACK16 = 6, - VK_FORMAT_B5G5R5A1_UNORM_PACK16 = 7, - VK_FORMAT_A1R5G5B5_UNORM_PACK16 = 8, - VK_FORMAT_R8_UNORM = 9, - VK_FORMAT_R8_SNORM = 10, - VK_FORMAT_R8_USCALED = 11, - VK_FORMAT_R8_SSCALED = 12, - VK_FORMAT_R8_UINT = 13, - VK_FORMAT_R8_SINT = 14, - VK_FORMAT_R8_SRGB = 15, - VK_FORMAT_R8G8_UNORM = 16, - VK_FORMAT_R8G8_SNORM = 17, - VK_FORMAT_R8G8_USCALED = 18, - VK_FORMAT_R8G8_SSCALED = 19, - VK_FORMAT_R8G8_UINT = 20, - VK_FORMAT_R8G8_SINT = 21, - VK_FORMAT_R8G8_SRGB = 22, - VK_FORMAT_R8G8B8_UNORM = 23, - VK_FORMAT_R8G8B8_SNORM = 24, - VK_FORMAT_R8G8B8_USCALED = 25, - VK_FORMAT_R8G8B8_SSCALED = 26, - VK_FORMAT_R8G8B8_UINT = 27, - VK_FORMAT_R8G8B8_SINT = 28, - VK_FORMAT_R8G8B8_SRGB = 29, - VK_FORMAT_B8G8R8_UNORM = 30, - VK_FORMAT_B8G8R8_SNORM = 31, - VK_FORMAT_B8G8R8_USCALED = 32, - VK_FORMAT_B8G8R8_SSCALED = 33, - VK_FORMAT_B8G8R8_UINT = 34, - VK_FORMAT_B8G8R8_SINT = 35, - VK_FORMAT_B8G8R8_SRGB = 36, - VK_FORMAT_R8G8B8A8_UNORM = 37, - VK_FORMAT_R8G8B8A8_SNORM = 38, - VK_FORMAT_R8G8B8A8_USCALED = 39, - VK_FORMAT_R8G8B8A8_SSCALED = 40, - VK_FORMAT_R8G8B8A8_UINT = 41, - VK_FORMAT_R8G8B8A8_SINT = 42, - VK_FORMAT_R8G8B8A8_SRGB = 43, - VK_FORMAT_B8G8R8A8_UNORM = 44, - VK_FORMAT_B8G8R8A8_SNORM = 45, - VK_FORMAT_B8G8R8A8_USCALED = 46, - VK_FORMAT_B8G8R8A8_SSCALED = 47, - VK_FORMAT_B8G8R8A8_UINT = 48, - VK_FORMAT_B8G8R8A8_SINT = 49, - VK_FORMAT_B8G8R8A8_SRGB = 50, - VK_FORMAT_A8B8G8R8_UNORM_PACK32 = 51, - VK_FORMAT_A8B8G8R8_SNORM_PACK32 = 52, - VK_FORMAT_A8B8G8R8_USCALED_PACK32 = 53, - VK_FORMAT_A8B8G8R8_SSCALED_PACK32 = 54, - VK_FORMAT_A8B8G8R8_UINT_PACK32 = 55, - VK_FORMAT_A8B8G8R8_SINT_PACK32 = 56, - VK_FORMAT_A8B8G8R8_SRGB_PACK32 = 57, - VK_FORMAT_A2R10G10B10_UNORM_PACK32 = 58, - VK_FORMAT_A2R10G10B10_SNORM_PACK32 = 59, - VK_FORMAT_A2R10G10B10_USCALED_PACK32 = 60, - VK_FORMAT_A2R10G10B10_SSCALED_PACK32 = 61, - VK_FORMAT_A2R10G10B10_UINT_PACK32 = 62, - VK_FORMAT_A2R10G10B10_SINT_PACK32 = 63, - VK_FORMAT_A2B10G10R10_UNORM_PACK32 = 64, - VK_FORMAT_A2B10G10R10_SNORM_PACK32 = 65, - VK_FORMAT_A2B10G10R10_USCALED_PACK32 = 66, - VK_FORMAT_A2B10G10R10_SSCALED_PACK32 = 67, - VK_FORMAT_A2B10G10R10_UINT_PACK32 = 68, - VK_FORMAT_A2B10G10R10_SINT_PACK32 = 69, - VK_FORMAT_R16_UNORM = 70, - VK_FORMAT_R16_SNORM = 71, - VK_FORMAT_R16_USCALED = 72, - VK_FORMAT_R16_SSCALED = 73, - VK_FORMAT_R16_UINT = 74, - VK_FORMAT_R16_SINT = 75, - VK_FORMAT_R16_SFLOAT = 76, - VK_FORMAT_R16G16_UNORM = 77, - VK_FORMAT_R16G16_SNORM = 78, - VK_FORMAT_R16G16_USCALED = 79, - VK_FORMAT_R16G16_SSCALED = 80, - VK_FORMAT_R16G16_UINT = 81, - VK_FORMAT_R16G16_SINT = 82, - VK_FORMAT_R16G16_SFLOAT = 83, - VK_FORMAT_R16G16B16_UNORM = 84, - VK_FORMAT_R16G16B16_SNORM = 85, - VK_FORMAT_R16G16B16_USCALED = 86, - VK_FORMAT_R16G16B16_SSCALED = 87, - VK_FORMAT_R16G16B16_UINT = 88, - VK_FORMAT_R16G16B16_SINT = 89, - VK_FORMAT_R16G16B16_SFLOAT = 90, - VK_FORMAT_R16G16B16A16_UNORM = 91, - VK_FORMAT_R16G16B16A16_SNORM = 92, - VK_FORMAT_R16G16B16A16_USCALED = 93, - VK_FORMAT_R16G16B16A16_SSCALED = 94, - VK_FORMAT_R16G16B16A16_UINT = 95, - VK_FORMAT_R16G16B16A16_SINT = 96, - VK_FORMAT_R16G16B16A16_SFLOAT = 97, - VK_FORMAT_R32_UINT = 98, - VK_FORMAT_R32_SINT = 99, - VK_FORMAT_R32_SFLOAT = 100, - VK_FORMAT_R32G32_UINT = 101, - VK_FORMAT_R32G32_SINT = 102, - VK_FORMAT_R32G32_SFLOAT = 103, - VK_FORMAT_R32G32B32_UINT = 104, - VK_FORMAT_R32G32B32_SINT = 105, - VK_FORMAT_R32G32B32_SFLOAT = 106, - VK_FORMAT_R32G32B32A32_UINT = 107, - VK_FORMAT_R32G32B32A32_SINT = 108, - VK_FORMAT_R32G32B32A32_SFLOAT = 109, - VK_FORMAT_R64_UINT = 110, - VK_FORMAT_R64_SINT = 111, - VK_FORMAT_R64_SFLOAT = 112, - VK_FORMAT_R64G64_UINT = 113, - VK_FORMAT_R64G64_SINT = 114, - VK_FORMAT_R64G64_SFLOAT = 115, - VK_FORMAT_R64G64B64_UINT = 116, - VK_FORMAT_R64G64B64_SINT = 117, - VK_FORMAT_R64G64B64_SFLOAT = 118, - VK_FORMAT_R64G64B64A64_UINT = 119, - VK_FORMAT_R64G64B64A64_SINT = 120, - VK_FORMAT_R64G64B64A64_SFLOAT = 121, - VK_FORMAT_B10G11R11_UFLOAT_PACK32 = 122, - VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 = 123, - VK_FORMAT_D16_UNORM = 124, - VK_FORMAT_X8_D24_UNORM_PACK32 = 125, - VK_FORMAT_D32_SFLOAT = 126, - VK_FORMAT_S8_UINT = 127, - VK_FORMAT_D16_UNORM_S8_UINT = 128, - VK_FORMAT_D24_UNORM_S8_UINT = 129, - VK_FORMAT_D32_SFLOAT_S8_UINT = 130, - VK_FORMAT_BC1_RGB_UNORM_BLOCK = 131, - VK_FORMAT_BC1_RGB_SRGB_BLOCK = 132, - VK_FORMAT_BC1_RGBA_UNORM_BLOCK = 133, - VK_FORMAT_BC1_RGBA_SRGB_BLOCK = 134, - VK_FORMAT_BC2_UNORM_BLOCK = 135, - VK_FORMAT_BC2_SRGB_BLOCK = 136, - VK_FORMAT_BC3_UNORM_BLOCK = 137, - VK_FORMAT_BC3_SRGB_BLOCK = 138, - VK_FORMAT_BC4_UNORM_BLOCK = 139, - VK_FORMAT_BC4_SNORM_BLOCK = 140, - VK_FORMAT_BC5_UNORM_BLOCK = 141, - VK_FORMAT_BC5_SNORM_BLOCK = 142, - VK_FORMAT_BC6H_UFLOAT_BLOCK = 143, - VK_FORMAT_BC6H_SFLOAT_BLOCK = 144, - VK_FORMAT_BC7_UNORM_BLOCK = 145, - VK_FORMAT_BC7_SRGB_BLOCK = 146, - VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK = 147, - VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK = 148, - VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK = 149, - VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK = 150, - VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK = 151, - VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK = 152, - VK_FORMAT_EAC_R11_UNORM_BLOCK = 153, - VK_FORMAT_EAC_R11_SNORM_BLOCK = 154, - VK_FORMAT_EAC_R11G11_UNORM_BLOCK = 155, - VK_FORMAT_EAC_R11G11_SNORM_BLOCK = 156, - VK_FORMAT_ASTC_4x4_UNORM_BLOCK = 157, - VK_FORMAT_ASTC_4x4_SRGB_BLOCK = 158, - VK_FORMAT_ASTC_5x4_UNORM_BLOCK = 159, - VK_FORMAT_ASTC_5x4_SRGB_BLOCK = 160, - VK_FORMAT_ASTC_5x5_UNORM_BLOCK = 161, - VK_FORMAT_ASTC_5x5_SRGB_BLOCK = 162, - VK_FORMAT_ASTC_6x5_UNORM_BLOCK = 163, - VK_FORMAT_ASTC_6x5_SRGB_BLOCK = 164, - VK_FORMAT_ASTC_6x6_UNORM_BLOCK = 165, - VK_FORMAT_ASTC_6x6_SRGB_BLOCK = 166, - VK_FORMAT_ASTC_8x5_UNORM_BLOCK = 167, - VK_FORMAT_ASTC_8x5_SRGB_BLOCK = 168, - VK_FORMAT_ASTC_8x6_UNORM_BLOCK = 169, - VK_FORMAT_ASTC_8x6_SRGB_BLOCK = 170, - VK_FORMAT_ASTC_8x8_UNORM_BLOCK = 171, - VK_FORMAT_ASTC_8x8_SRGB_BLOCK = 172, - VK_FORMAT_ASTC_10x5_UNORM_BLOCK = 173, - VK_FORMAT_ASTC_10x5_SRGB_BLOCK = 174, - VK_FORMAT_ASTC_10x6_UNORM_BLOCK = 175, - VK_FORMAT_ASTC_10x6_SRGB_BLOCK = 176, - VK_FORMAT_ASTC_10x8_UNORM_BLOCK = 177, - VK_FORMAT_ASTC_10x8_SRGB_BLOCK = 178, - VK_FORMAT_ASTC_10x10_UNORM_BLOCK = 179, - VK_FORMAT_ASTC_10x10_SRGB_BLOCK = 180, - VK_FORMAT_ASTC_12x10_UNORM_BLOCK = 181, - VK_FORMAT_ASTC_12x10_SRGB_BLOCK = 182, - VK_FORMAT_ASTC_12x12_UNORM_BLOCK = 183, - VK_FORMAT_ASTC_12x12_SRGB_BLOCK = 184, +/// +/// Copied from KTX-Software/lib/src/vkformat_enum.h +/// KTX-Software commit: b9bcfeb1df982f38b11d460cdd9878aa748ebafb +/// Autogenerating this proved to be a bigger headache that simply copying and +/// pasting the autogenerated enum from libktx. +/// +typedef enum VkFormat { + VK_FORMAT_UNDEFINED = 0, + VK_FORMAT_R4G4_UNORM_PACK8 = 1, + VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2, + VK_FORMAT_B4G4R4A4_UNORM_PACK16 = 3, + VK_FORMAT_R5G6B5_UNORM_PACK16 = 4, + VK_FORMAT_B5G6R5_UNORM_PACK16 = 5, + VK_FORMAT_R5G5B5A1_UNORM_PACK16 = 6, + VK_FORMAT_B5G5R5A1_UNORM_PACK16 = 7, + VK_FORMAT_A1R5G5B5_UNORM_PACK16 = 8, + VK_FORMAT_R8_UNORM = 9, + VK_FORMAT_R8_SNORM = 10, + VK_FORMAT_R8_USCALED = 11, + VK_FORMAT_R8_SSCALED = 12, + VK_FORMAT_R8_UINT = 13, + VK_FORMAT_R8_SINT = 14, + VK_FORMAT_R8_SRGB = 15, + VK_FORMAT_R8G8_UNORM = 16, + VK_FORMAT_R8G8_SNORM = 17, + VK_FORMAT_R8G8_USCALED = 18, + VK_FORMAT_R8G8_SSCALED = 19, + VK_FORMAT_R8G8_UINT = 20, + VK_FORMAT_R8G8_SINT = 21, + VK_FORMAT_R8G8_SRGB = 22, + VK_FORMAT_R8G8B8_UNORM = 23, + VK_FORMAT_R8G8B8_SNORM = 24, + VK_FORMAT_R8G8B8_USCALED = 25, + VK_FORMAT_R8G8B8_SSCALED = 26, + VK_FORMAT_R8G8B8_UINT = 27, + VK_FORMAT_R8G8B8_SINT = 28, + VK_FORMAT_R8G8B8_SRGB = 29, + VK_FORMAT_B8G8R8_UNORM = 30, + VK_FORMAT_B8G8R8_SNORM = 31, + VK_FORMAT_B8G8R8_USCALED = 32, + VK_FORMAT_B8G8R8_SSCALED = 33, + VK_FORMAT_B8G8R8_UINT = 34, + VK_FORMAT_B8G8R8_SINT = 35, + VK_FORMAT_B8G8R8_SRGB = 36, + VK_FORMAT_R8G8B8A8_UNORM = 37, + VK_FORMAT_R8G8B8A8_SNORM = 38, + VK_FORMAT_R8G8B8A8_USCALED = 39, + VK_FORMAT_R8G8B8A8_SSCALED = 40, + VK_FORMAT_R8G8B8A8_UINT = 41, + VK_FORMAT_R8G8B8A8_SINT = 42, + VK_FORMAT_R8G8B8A8_SRGB = 43, + VK_FORMAT_B8G8R8A8_UNORM = 44, + VK_FORMAT_B8G8R8A8_SNORM = 45, + VK_FORMAT_B8G8R8A8_USCALED = 46, + VK_FORMAT_B8G8R8A8_SSCALED = 47, + VK_FORMAT_B8G8R8A8_UINT = 48, + VK_FORMAT_B8G8R8A8_SINT = 49, + VK_FORMAT_B8G8R8A8_SRGB = 50, + VK_FORMAT_A8B8G8R8_UNORM_PACK32 = 51, + VK_FORMAT_A8B8G8R8_SNORM_PACK32 = 52, + VK_FORMAT_A8B8G8R8_USCALED_PACK32 = 53, + VK_FORMAT_A8B8G8R8_SSCALED_PACK32 = 54, + VK_FORMAT_A8B8G8R8_UINT_PACK32 = 55, + VK_FORMAT_A8B8G8R8_SINT_PACK32 = 56, + VK_FORMAT_A8B8G8R8_SRGB_PACK32 = 57, + VK_FORMAT_A2R10G10B10_UNORM_PACK32 = 58, + VK_FORMAT_A2R10G10B10_SNORM_PACK32 = 59, + VK_FORMAT_A2R10G10B10_USCALED_PACK32 = 60, + VK_FORMAT_A2R10G10B10_SSCALED_PACK32 = 61, + VK_FORMAT_A2R10G10B10_UINT_PACK32 = 62, + VK_FORMAT_A2R10G10B10_SINT_PACK32 = 63, + VK_FORMAT_A2B10G10R10_UNORM_PACK32 = 64, + VK_FORMAT_A2B10G10R10_SNORM_PACK32 = 65, + VK_FORMAT_A2B10G10R10_USCALED_PACK32 = 66, + VK_FORMAT_A2B10G10R10_SSCALED_PACK32 = 67, + VK_FORMAT_A2B10G10R10_UINT_PACK32 = 68, + VK_FORMAT_A2B10G10R10_SINT_PACK32 = 69, + VK_FORMAT_R16_UNORM = 70, + VK_FORMAT_R16_SNORM = 71, + VK_FORMAT_R16_USCALED = 72, + VK_FORMAT_R16_SSCALED = 73, + VK_FORMAT_R16_UINT = 74, + VK_FORMAT_R16_SINT = 75, + VK_FORMAT_R16_SFLOAT = 76, + VK_FORMAT_R16G16_UNORM = 77, + VK_FORMAT_R16G16_SNORM = 78, + VK_FORMAT_R16G16_USCALED = 79, + VK_FORMAT_R16G16_SSCALED = 80, + VK_FORMAT_R16G16_UINT = 81, + VK_FORMAT_R16G16_SINT = 82, + VK_FORMAT_R16G16_SFLOAT = 83, + VK_FORMAT_R16G16B16_UNORM = 84, + VK_FORMAT_R16G16B16_SNORM = 85, + VK_FORMAT_R16G16B16_USCALED = 86, + VK_FORMAT_R16G16B16_SSCALED = 87, + VK_FORMAT_R16G16B16_UINT = 88, + VK_FORMAT_R16G16B16_SINT = 89, + VK_FORMAT_R16G16B16_SFLOAT = 90, + VK_FORMAT_R16G16B16A16_UNORM = 91, + VK_FORMAT_R16G16B16A16_SNORM = 92, + VK_FORMAT_R16G16B16A16_USCALED = 93, + VK_FORMAT_R16G16B16A16_SSCALED = 94, + VK_FORMAT_R16G16B16A16_UINT = 95, + VK_FORMAT_R16G16B16A16_SINT = 96, + VK_FORMAT_R16G16B16A16_SFLOAT = 97, + VK_FORMAT_R32_UINT = 98, + VK_FORMAT_R32_SINT = 99, + VK_FORMAT_R32_SFLOAT = 100, + VK_FORMAT_R32G32_UINT = 101, + VK_FORMAT_R32G32_SINT = 102, + VK_FORMAT_R32G32_SFLOAT = 103, + VK_FORMAT_R32G32B32_UINT = 104, + VK_FORMAT_R32G32B32_SINT = 105, + VK_FORMAT_R32G32B32_SFLOAT = 106, + VK_FORMAT_R32G32B32A32_UINT = 107, + VK_FORMAT_R32G32B32A32_SINT = 108, + VK_FORMAT_R32G32B32A32_SFLOAT = 109, + VK_FORMAT_R64_UINT = 110, + VK_FORMAT_R64_SINT = 111, + VK_FORMAT_R64_SFLOAT = 112, + VK_FORMAT_R64G64_UINT = 113, + VK_FORMAT_R64G64_SINT = 114, + VK_FORMAT_R64G64_SFLOAT = 115, + VK_FORMAT_R64G64B64_UINT = 116, + VK_FORMAT_R64G64B64_SINT = 117, + VK_FORMAT_R64G64B64_SFLOAT = 118, + VK_FORMAT_R64G64B64A64_UINT = 119, + VK_FORMAT_R64G64B64A64_SINT = 120, + VK_FORMAT_R64G64B64A64_SFLOAT = 121, + VK_FORMAT_B10G11R11_UFLOAT_PACK32 = 122, + VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 = 123, + VK_FORMAT_D16_UNORM = 124, + VK_FORMAT_X8_D24_UNORM_PACK32 = 125, + VK_FORMAT_D32_SFLOAT = 126, + VK_FORMAT_S8_UINT = 127, + VK_FORMAT_D16_UNORM_S8_UINT = 128, + VK_FORMAT_D24_UNORM_S8_UINT = 129, + VK_FORMAT_D32_SFLOAT_S8_UINT = 130, + VK_FORMAT_BC1_RGB_UNORM_BLOCK = 131, + VK_FORMAT_BC1_RGB_SRGB_BLOCK = 132, + VK_FORMAT_BC1_RGBA_UNORM_BLOCK = 133, + VK_FORMAT_BC1_RGBA_SRGB_BLOCK = 134, + VK_FORMAT_BC2_UNORM_BLOCK = 135, + VK_FORMAT_BC2_SRGB_BLOCK = 136, + VK_FORMAT_BC3_UNORM_BLOCK = 137, + VK_FORMAT_BC3_SRGB_BLOCK = 138, + VK_FORMAT_BC4_UNORM_BLOCK = 139, + VK_FORMAT_BC4_SNORM_BLOCK = 140, + VK_FORMAT_BC5_UNORM_BLOCK = 141, + VK_FORMAT_BC5_SNORM_BLOCK = 142, + VK_FORMAT_BC6H_UFLOAT_BLOCK = 143, + VK_FORMAT_BC6H_SFLOAT_BLOCK = 144, + VK_FORMAT_BC7_UNORM_BLOCK = 145, + VK_FORMAT_BC7_SRGB_BLOCK = 146, + VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK = 147, + VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK = 148, + VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK = 149, + VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK = 150, + VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK = 151, + VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK = 152, + VK_FORMAT_EAC_R11_UNORM_BLOCK = 153, + VK_FORMAT_EAC_R11_SNORM_BLOCK = 154, + VK_FORMAT_EAC_R11G11_UNORM_BLOCK = 155, + VK_FORMAT_EAC_R11G11_SNORM_BLOCK = 156, + VK_FORMAT_ASTC_4x4_UNORM_BLOCK = 157, + VK_FORMAT_ASTC_4x4_SRGB_BLOCK = 158, + VK_FORMAT_ASTC_5x4_UNORM_BLOCK = 159, + VK_FORMAT_ASTC_5x4_SRGB_BLOCK = 160, + VK_FORMAT_ASTC_5x5_UNORM_BLOCK = 161, + VK_FORMAT_ASTC_5x5_SRGB_BLOCK = 162, + VK_FORMAT_ASTC_6x5_UNORM_BLOCK = 163, + VK_FORMAT_ASTC_6x5_SRGB_BLOCK = 164, + VK_FORMAT_ASTC_6x6_UNORM_BLOCK = 165, + VK_FORMAT_ASTC_6x6_SRGB_BLOCK = 166, + VK_FORMAT_ASTC_8x5_UNORM_BLOCK = 167, + VK_FORMAT_ASTC_8x5_SRGB_BLOCK = 168, + VK_FORMAT_ASTC_8x6_UNORM_BLOCK = 169, + VK_FORMAT_ASTC_8x6_SRGB_BLOCK = 170, + VK_FORMAT_ASTC_8x8_UNORM_BLOCK = 171, + VK_FORMAT_ASTC_8x8_SRGB_BLOCK = 172, + VK_FORMAT_ASTC_10x5_UNORM_BLOCK = 173, + VK_FORMAT_ASTC_10x5_SRGB_BLOCK = 174, + VK_FORMAT_ASTC_10x6_UNORM_BLOCK = 175, + VK_FORMAT_ASTC_10x6_SRGB_BLOCK = 176, + VK_FORMAT_ASTC_10x8_UNORM_BLOCK = 177, + VK_FORMAT_ASTC_10x8_SRGB_BLOCK = 178, + VK_FORMAT_ASTC_10x10_UNORM_BLOCK = 179, + VK_FORMAT_ASTC_10x10_SRGB_BLOCK = 180, + VK_FORMAT_ASTC_12x10_UNORM_BLOCK = 181, + VK_FORMAT_ASTC_12x10_SRGB_BLOCK = 182, + VK_FORMAT_ASTC_12x12_UNORM_BLOCK = 183, + VK_FORMAT_ASTC_12x12_SRGB_BLOCK = 184, + VK_FORMAT_G8B8G8R8_422_UNORM = 1000156000, + VK_FORMAT_B8G8R8G8_422_UNORM = 1000156001, + VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM = 1000156002, + VK_FORMAT_G8_B8R8_2PLANE_420_UNORM = 1000156003, + VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM = 1000156004, + VK_FORMAT_G8_B8R8_2PLANE_422_UNORM = 1000156005, + VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM = 1000156006, + VK_FORMAT_R10X6_UNORM_PACK16 = 1000156007, + VK_FORMAT_R10X6G10X6_UNORM_2PACK16 = 1000156008, + VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16 = 1000156009, + VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16 = 1000156010, + VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16 = 1000156011, + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16 = 1000156012, + VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16 = 1000156013, + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16 = 1000156014, + VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16 = 1000156015, + VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16 = 1000156016, + VK_FORMAT_R12X4_UNORM_PACK16 = 1000156017, + VK_FORMAT_R12X4G12X4_UNORM_2PACK16 = 1000156018, + VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16 = 1000156019, + VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16 = 1000156020, + VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16 = 1000156021, + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16 = 1000156022, + VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16 = 1000156023, + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16 = 1000156024, + VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16 = 1000156025, + VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16 = 1000156026, + VK_FORMAT_G16B16G16R16_422_UNORM = 1000156027, + VK_FORMAT_B16G16R16G16_422_UNORM = 1000156028, + VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM = 1000156029, + VK_FORMAT_G16_B16R16_2PLANE_420_UNORM = 1000156030, + VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM = 1000156031, + VK_FORMAT_G16_B16R16_2PLANE_422_UNORM = 1000156032, + VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM = 1000156033, + VK_FORMAT_G8_B8R8_2PLANE_444_UNORM = 1000330000, + VK_FORMAT_G10X6_B10X6R10X6_2PLANE_444_UNORM_3PACK16 = 1000330001, + VK_FORMAT_G12X4_B12X4R12X4_2PLANE_444_UNORM_3PACK16 = 1000330002, + VK_FORMAT_G16_B16R16_2PLANE_444_UNORM = 1000330003, + VK_FORMAT_A4R4G4B4_UNORM_PACK16 = 1000340000, + VK_FORMAT_A4B4G4R4_UNORM_PACK16 = 1000340001, + VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK = 1000066000, + VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK = 1000066001, + VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK = 1000066002, + VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK = 1000066003, + VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK = 1000066004, + VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK = 1000066005, + VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK = 1000066006, + VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK = 1000066007, + VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK = 1000066008, + VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK = 1000066009, + VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK = 1000066010, + VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK = 1000066011, + VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK = 1000066012, + VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK = 1000066013, + VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG = 1000054000, + VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG = 1000054001, + VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG = 1000054002, + VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG = 1000054003, + VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG = 1000054004, + VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG = 1000054005, + VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG = 1000054006, + VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG = 1000054007, + VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT = 1000288000, + VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT = 1000288001, + VK_FORMAT_ASTC_3x3x3_SFLOAT_BLOCK_EXT = 1000288002, + VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT = 1000288003, + VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT = 1000288004, + VK_FORMAT_ASTC_4x3x3_SFLOAT_BLOCK_EXT = 1000288005, + VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT = 1000288006, + VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT = 1000288007, + VK_FORMAT_ASTC_4x4x3_SFLOAT_BLOCK_EXT = 1000288008, + VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT = 1000288009, + VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT = 1000288010, + VK_FORMAT_ASTC_4x4x4_SFLOAT_BLOCK_EXT = 1000288011, + VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT = 1000288012, + VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT = 1000288013, + VK_FORMAT_ASTC_5x4x4_SFLOAT_BLOCK_EXT = 1000288014, + VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT = 1000288015, + VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT = 1000288016, + VK_FORMAT_ASTC_5x5x4_SFLOAT_BLOCK_EXT = 1000288017, + VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT = 1000288018, + VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT = 1000288019, + VK_FORMAT_ASTC_5x5x5_SFLOAT_BLOCK_EXT = 1000288020, + VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT = 1000288021, + VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT = 1000288022, + VK_FORMAT_ASTC_6x5x5_SFLOAT_BLOCK_EXT = 1000288023, + VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT = 1000288024, + VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT = 1000288025, + VK_FORMAT_ASTC_6x6x5_SFLOAT_BLOCK_EXT = 1000288026, + VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT = 1000288027, + VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT = 1000288028, + VK_FORMAT_ASTC_6x6x6_SFLOAT_BLOCK_EXT = 1000288029, + VK_FORMAT_R16G16_SFIXED5_NV = 1000464000, + VK_FORMAT_A1B5G5R5_UNORM_PACK16_KHR = 1000470000, + VK_FORMAT_A8_UNORM_KHR = 1000470001, + VK_FORMAT_MAX_ENUM = 0x7FFFFFFF } VkFormat; @@ -286,17 +379,15 @@ struct FormatInfo { -// -// Note: -// Colorspace detection from VkFormat is simply wrong/not complete. You should -// do colorspace detection via the color model and transfer function. -// See: -// https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_data_format_descriptor -// -// TODO: there are a lot of formats to test so currently only "widely" used -// formats are supported (every format included here has to have a corresponding -// test file with the same VkFormat). -// +/// +/// Colorspace detection should be done via the color model and transfer function +/// and not via VkFormat. See: +/// https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_data_format_descriptor +/// +/// TODO: there are a lot of formats to test so currently only "widely" used +/// formats are supported (every format included here has to have a corresponding +/// test file with the same VkFormat). +/// inline bool get_info_from_vkformat(VkFormat vkformat, FormatInfo& formatinfo) { @@ -354,6 +445,8 @@ get_vkformat_from_info(int nchannels, TypeDesc format, bool srgb_colorspace) return VK_FORMAT_UNDEFINED; } + + // TODO inline void gl_to_vkformat() diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index 4785b3a880..756edba213 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -16,6 +16,7 @@ #include "ktx_pvt.h" #include #include +#include OIIO_PLUGIN_NAMESPACE_BEGIN @@ -76,11 +77,6 @@ class KtxInput final : public ImageInput { private: std::string m_filename; - /// Buffer to hold the decoded GPU-block-compressed format. This is only - /// used to hold BCn or ECT decompressed data for a particular - /// miplevel/subimage. - std::vector m_buf; - /// Non-owning pointer to KTX2 texture. The texture is managed by libktx /// and should be destroyed via a 'ktxTexture_Destroy()' call. ktxTexture* m_tex { nullptr }; @@ -89,14 +85,7 @@ class KtxInput final : public ImageInput { ktxTexture2* m_tex2 { nullptr }; /// Non-owning pointer to first byte of the requested (miplevel, slice). - /// - /// For non-GPU-compressed formats, this points to first byte of the whole - /// texture data. - /// - /// For GPU-compressed formats: - /// - BCn: this is simply m_buf.data() - /// - ETC: this is simply m_buf.data() - /// - ASTC: this points to first byte of the whole decompressed texture + /// This points to first byte of the whole texture data. uint8_t* m_data_ptr { nullptr }; ktx_uint32_t m_pitch { 0 }; ///< Row pitch for current mip level. @@ -108,10 +97,10 @@ class KtxInput final : public ImageInput { /// GPU block compression kind (only set in case of GPU-block-compressed KTX /// textures). - BlockCompression m_cmp = BlockCompression::NONE; + BlockCompression m_cmp { BlockCompression::NONE }; /// Original VkFormat (i.e., before applying any decompression or transcoding). - VkFormat m_vkformat; + VkFormat m_vkformat { VK_FORMAT_UNDEFINED }; std::unique_ptr m_config; ///< Saved copy of configuration spec @@ -123,8 +112,8 @@ class KtxInput final : public ImageInput { /// Helper function: performs the actual pixel decoding. bool internal_readimg(unsigned char* dst, int w, int h, int d); - bool ktx_magic_cmp(const uint8_t* KTX_MAGIC, const uint8_t* sig, - size_t start) const; + /// Checks the magic + bool ktx_magic_cmp(const uint8_t* sig, size_t start) const; TextureKind get_texture_kind() const; @@ -142,7 +131,7 @@ OIIO_EXPORT const char* ktx_imageio_library_version() { return "ktx v5.0.0-rc1"; -} // hardcoded because I couldn't expose KTX_VERSION +} // TODO: hardcoded because I couldn't expose KTX_VERSION OIIO_EXPORT ImageInput* ktx_input_imageio_create() { @@ -476,6 +465,95 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) } } + // + // Decode GPU-compression if any (using libktx). Supported formats: + // + // ASTC: libktx provides decoders for ASTC block compression via the + // ktxTexture2_DecodeAstc call. + // This currently decodes the whole texture (all miplevels, all + // slices, etc.) into memory. + // + // BCn: libktx will provide decoders/encoders for BCn block compression + // via ktxTexture2_DecodeBCn. + // TODO: wait for my RP in libktx to get merged then add BCn + // support. + // + // ETC2: TODO: some licensing clarification is needed from the part of + // etcunpack usage in libktx. + // + // PVRTC: TODO: wait for libktx PR. + // + if (m_tex2->isCompressed /* i.e., is GPU block compressed? */) { + switch (m_cmp) { + /* BCn GPU formats */ + case BlockCompression::BC1: + case BlockCompression::BC2: + case BlockCompression::BC3: + case BlockCompression::BC4: + case BlockCompression::BC5: + case BlockCompression::BC6HU: + case BlockCompression::BC6HS: + case BlockCompression::BC7: + // + // TODO: wait for my PR in libktx to be merged + // + // Note: + // ktxTexture2_DecodeBCn internally creates a new ktxTexture2 texture + // and populates it with decoded data from the originally provided + // texture. At the end, it moves the decoded data to m_tex and + // destroys the temporarily created texture. + // + // This operation is expensive (both in memory and CPU cycles). + // After this, m_tex2->isCompressed will be false => this will only + // be called once. + // + // if (auto status = ktxTexture2_DecodeBCn(m_tex2); + // status != KTX_SUCCESS) { + // errorfmt("failed to decode BCn-compressed texture. " + // "ktxTexture2_DecodeBCn returned Ktx error code: {}", + // static_cast(status)); + // return false; + // } + // break; + errorfmt("BCn GPU-compressed formats are not yet supported."); + return false; + + /* ETC formats */ + case BlockCompression::ETC2_RGB: + case BlockCompression::ETC2_RGB_A1: + case BlockCompression::ETC2_RGBA: + errorfmt("ETC GPU-compressed formats are not yet supported."); + return false; + + /* ASTC formats */ + case BlockCompression::ASTC: + // + // Note: + // ktxTexture2_DecodeAstc internally creates a new ktxTexture2 texture + // and populates it with decoded data from the originally provided + // texture. At the end, it moves the decoded data to m_tex and + // destroys the temporarily created texture. + // + // This operation is expensive (both in memory and CPU cycles). + // After this, m_tex2->isCompressed will be false => this will only + // be called once. + // + if (auto status = ktxTexture2_DecodeAstc(m_tex2); + status != KTX_SUCCESS) { + errorfmt("failed to decode ASTC-compressed texture. " + "ktxTexture2_DecodeAstc returned Ktx error code: {}", + static_cast(status)); + return false; + } + break; + + default: + errorfmt("Unknown/unsupported GPU block compression kind: {}", + static_cast(m_cmp)); + return false; + } + } + // // This could mean one of the following as per the specs at: // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_use_of_vk_format_undefined @@ -679,133 +757,30 @@ KtxInput::seek_subimage(int subimage, int miplevel) m_spec.height = height; m_spec.depth = depth; + // Should never be true (because the texture is decompressed in open()) + if (m_tex2->isCompressed) { + OIIO_ASSERT( + false + && "KTX2 texture should be decompressed before a call to subimage."); + return false; + } + // - // Decode GPU-compression if any. Supported formats: - // - // ASTC: libktx provides decoders for ASTC block compression via the - // ktxTexture2_DecodeAstc call. - // This currently decodes the whole texture (all miplevels, all - // slices, etc.) into memory. - // TODO: wait for my PR in libktx to implement decode_astc for - // per-miplvl/subimage decoding. - // - // BCn: libktx will provide decoders/encoders for BCn block compression - // via ktxTexture2_DecodeBCn. TODO: wait for my RP in libktx to get - // merged then add BCn support. - // - // ETC2: TODO: some licensing clarification is needed from the part of - // etcunpack usage in libktx. - // - // PVRTC: TODO: wait for libktx PR. + // GetImageOffset implements internal checks depending on texture kind (e.g., + // 3D, cubemap, etc.) and incase of invalid input, KTX_INVALID_OPERATION is + // returned. // - if (m_tex2->isCompressed /* i.e., is GPU block compressed? */) { - ktx_size_t offset; - if (auto status = ktxTexture2_GetImageOffset(m_tex2, miplevel, - arr_layer, face_slice, - &offset); - status != KTX_SUCCESS) { - return status; - } - // TODO: Are pointer indices [offset, offset + size[ safe? - // Encoded blocks - cspan src_span(m_tex2->pData + offset, - ktxTexture_GetImageSize(ktxTexture(m_tex), - miplevel)); - - switch (m_cmp) { - /* BCn LDR formats */ - case BlockCompression::BC1: - case BlockCompression::BC2: - case BlockCompression::BC3: - case BlockCompression::BC4: - case BlockCompression::BC5: - case BlockCompression::BC7: - // - // TODO: wait for my PR in libktx to be merged - // - // Note: - // ktxTexture2_DecodeBCn internally creates a new ktxTexture2 texture - // and populates it with decoded data from the originally provided - // texture. At the end, it moves the decoded data to m_tex and - // destroys the temporarily created texture. - // - // This operation is expensive (both in memory and CPU cycles). - // After this, m_tex2->isCompressed will be false => this will only - // be called once. - // - // if (auto status = ktxTexture2_DecodeBCn(m_tex2); - // status != KTX_SUCCESS) { - // errorfmt("failed to decode BCn-compressed texture. " - // "ktxTexture2_DecodeBCn returned Ktx error code: {}", - // static_cast(status)); - // return false; - // } - // break; - return false; - - /* BCn HDR formats - TODO */ - case BlockCompression::BC6HU: - case BlockCompression::BC6HS: - return false; - - /* ETC formats */ - case BlockCompression::ETC2_RGB: - case BlockCompression::ETC2_RGB_A1: - case BlockCompression::ETC2_RGBA: - return false; - - /* ASTC formats */ - case BlockCompression::ASTC: - // - // Note: - // ktxTexture2_DecodeAstc internally creates a new ktxTexture2 texture - // and populates it with decoded data from the originally provided - // texture. At the end, it moves the decoded data to m_tex and - // destroys the temporarily created texture. - // - // This operation is expensive (both in memory and CPU cycles). - // After this, m_tex2->isCompressed will be false => this will only - // be called once. - // - if (auto status = ktxTexture2_DecodeAstc(m_tex2); - status != KTX_SUCCESS) { - errorfmt("failed to decode ASTC-compressed texture. " - "ktxTexture2_DecodeAstc returned Ktx error code: {}", - static_cast(status)); - return false; - } - break; - - default: - errorfmt("Unknown/unsupported GPU block compression kind: {}", - static_cast(m_cmp)); - return false; - } - - m_pitch = width * m_spec.nchannels - * m_spec.format.size() /* 1 for LDR, 2 for HDR formats */; - m_data_ptr = m_buf.data(); + ktx_size_t offset; + if (auto status = ktxTexture_GetImageOffset(m_tex, miplevel, arr_layer, + face_slice, &offset); + status != KTX_SUCCESS) { + errorfmt("ktxTexture_GetImageOffset failed with exit code: {}", + static_cast(status)); + return false; } + m_data_ptr = m_tex2->pData + offset; + m_pitch = ktxTexture_GetRowPitch(m_tex, miplevel); - // Do NOT change this to `else` statement because this handles the ASTC and - // BCn cases above (which, again, sets m_tex2->isCompressed to `false`) - if (!m_tex2->isCompressed) { - // - // GetImageOffset implements internal checks depending on texture kind (e.g., - // 3D, cubemap, etc.) and incase of invalid input, KTX_INVALID_OPERATION is - // returned. - // - ktx_size_t offset; - if (auto status = ktxTexture_GetImageOffset(m_tex, miplevel, arr_layer, - face_slice, &offset); - status != KTX_SUCCESS) { - errorfmt("ktxTexture_GetImageOffset failed with exit code: {}", - static_cast(status)); - return false; - } - m_pitch = ktxTexture_GetRowPitch(m_tex, miplevel); - m_data_ptr = m_tex2->pData + offset; - } return true; } @@ -873,6 +848,7 @@ KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, } + bool OpenImageIO::KtxInput::valid_file(Filesystem::IOProxy* ioproxy) const { @@ -884,22 +860,25 @@ OpenImageIO::KtxInput::valid_file(Filesystem::IOProxy* ioproxy) const uint8_t magic[12] {}; const size_t numRead = ioproxy->pread(magic, sizeof(magic), 0); - return (numRead == sizeof(magic)) - && this->ktx_magic_cmp(KTX2_IDENTIFIER, magic, 0); + return (numRead == sizeof(magic)) && this->ktx_magic_cmp(magic, 0); } + bool -KtxInput::ktx_magic_cmp(const uint8_t* KTX_MAGIC, const uint8_t* sig, - size_t start) const +KtxInput::ktx_magic_cmp(const uint8_t* sig, size_t start) const { - for (size_t i = start; (i - start) < sizeof(KTX_MAGIC); ++i) - if (sig[i] != KTX_MAGIC[i]) + // this is: "«KTX 20»\r\n\x1A\n" + const uint8_t KTX2_IDENTIFIER[12] { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, + 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A }; + for (size_t i = start; (i - start) < sizeof(KTX2_IDENTIFIER); ++i) + if (sig[i] != KTX2_IDENTIFIER[i]) return false; return true; } + TextureKind KtxInput::get_texture_kind() const { @@ -923,6 +902,7 @@ KtxInput::get_texture_kind() const } + std::string KtxInput::get_colorspace() const { From bef354ede2cdc1fc9c5dabea585b35eda3f30b5d Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sat, 4 Jul 2026 18:03:38 +0200 Subject: [PATCH 06/42] Remove include Signed-off-by: Walid Chtioui --- src/ktx.imageio/ktxinput.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index 756edba213..b548010f7a 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -16,7 +16,6 @@ #include "ktx_pvt.h" #include #include -#include OIIO_PLUGIN_NAMESPACE_BEGIN From ae63741ea9780d2e1d1b285e01cb5683cf958d92 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sun, 5 Jul 2026 17:06:34 +0200 Subject: [PATCH 07/42] Add alpha and ioproxy support to ktxinput/ktxoutput *Due to missing 'alpha' support (which obviously exists in libktx), unit testing was failing (due to unexpected number of channels). *Misc cleanup; removal of ktxTexture* member field and only using ktxTexture2*. *Remove MSVC/GNU/Clang checks on libktx dependency in externalpackages. Signed-off-by: Walid Chtioui --- src/cmake/externalpackages.cmake | 22 +++--- src/ktx.imageio/ktxinput.cpp | 127 +++++++++++++++++-------------- src/ktx.imageio/ktxoutput.cpp | 1 + 3 files changed, 79 insertions(+), 71 deletions(-) diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index af65e9465c..8ae790c733 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -259,20 +259,16 @@ else () get_target_property(FMT_INCLUDE_DIR fmt::fmt-header-only INTERFACE_INCLUDE_DIRECTORIES) endif () -# Ktx for KTX textures -# Note for KTX developers: set VERSION_MIN to 0.0.0 if you have a locally-built -# tracking HEAD:main of KTX-Software because building non-tagged libktx sets the -# version to 0.0.0 +# Ktx for KTX2 textures # -# libktx doesn't support IntelLLVM and compilers other than gcc, clang, and -# MSVC. -if (MSVC OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" - OR ${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") - checked_find_package (Ktx - VERSION_MIN 5.0.0 - BUILD_LOCAL missing - ) -endif() +# Note for KTX plugin developers: +# set VERSION_MIN to 0.0.0 if you have a locally-built tracking HEAD:main of +# KTX-Software because building non-tagged libktx sets the version to 0.0.0. +# +checked_find_package (Ktx + VERSION_MIN 5.0.0 + BUILD_LOCAL missing +) ########################################################################### diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index b548010f7a..a7ef64136f 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -30,6 +30,7 @@ class KtxInput final : public ImageInput { int supports(string_view feature) const override { return ( + feature == "ioproxy" || // as per the KTX1/2 specs: // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_keyvalue_data feature == "arbitrary_metadata" || @@ -78,21 +79,17 @@ class KtxInput final : public ImageInput { /// Non-owning pointer to KTX2 texture. The texture is managed by libktx /// and should be destroyed via a 'ktxTexture_Destroy()' call. - ktxTexture* m_tex { nullptr }; - - /// m_tex2 reinterpret_cast'ed to KtxTexture2* for convenience. - ktxTexture2* m_tex2 { nullptr }; + ktxTexture2* m_tex { nullptr }; /// Non-owning pointer to first byte of the requested (miplevel, slice). /// This points to first byte of the whole texture data. uint8_t* m_data_ptr { nullptr }; - ktx_uint32_t m_pitch { 0 }; ///< Row pitch for current mip level. - ktx_size_t m_offset { 0 }; ///< Current offset from subimage call. - int m_subimage { -1 }; ///< What subimage are we looking at? - int m_nbrsubimages { -1 }; ///< Number of slices/faces in texture - int m_miplevel { -1 }; ///< What mip level are we looking at? - int m_nbrmiplevels { -1 }; ///< Number of mip levels + ktx_size_t m_offset { 0 }; ///< Current offset from subimage call. + int m_subimage { -1 }; ///< What subimage are we looking at? + int m_nbrsubimages { -1 }; ///< Number of slices/faces in texture + int m_miplevel { -1 }; ///< What mip level are we looking at? + int m_nbrmiplevels { -1 }; ///< Number of mip levels /// GPU block compression kind (only set in case of GPU-block-compressed KTX /// textures). @@ -190,10 +187,8 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) { m_filename = name; - if (!ioproxy_use_or_open(name)) { - errorfmt("ioproxy_use_or_open(\"{}\") failed", name); + if (!ioproxy_use_or_open(name)) return false; - } // If an IOProxy was passed, it had better be a File or a MemReader Filesystem::IOProxy* m_io = ioproxy(); @@ -237,7 +232,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // if (proxytype == "file") { auto fd = reinterpret_cast(m_io)->handle(); - auto res = ktxTexture_CreateFromStdioStream( + auto res = ktxTexture2_CreateFromStdioStream( fd, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &m_tex); if (KTX_SUCCESS != res) { errorfmt("Failed to create ktx texture using " @@ -247,7 +242,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) } else /* (proxytype == "memreader") */ { OIIO_ASSERT(proxytype == "memreader"); auto buff = reinterpret_cast(m_io)->buffer(); - auto res = ktxTexture_CreateFromMemory( + auto res = ktxTexture2_CreateFromMemory( buff.data(), buff.size(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &m_tex); if (KTX_SUCCESS != res) { @@ -257,13 +252,13 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) } } - m_tex2 = reinterpret_cast(m_tex); - m_nbrmiplevels = m_tex2->numLevels; - m_nbrsubimages = m_tex2->numFaces; + m_nbrmiplevels = m_tex->numLevels; + m_nbrsubimages = m_tex->numFaces; - m_spec = ImageSpec(m_tex2->baseWidth, m_tex2->baseHeight, - 4 /* dummy value - will be overwritten */); - m_spec.depth = m_spec.full_depth = m_tex2->baseDepth; + m_spec = ImageSpec(m_tex->baseWidth, m_tex->baseHeight, + 4 /* dummy value - will be overwritten */, + TypeDesc::UINT8); + m_spec.depth = m_spec.full_depth = m_tex->baseDepth; std::string colorspace = get_colorspace(); m_spec.set_colorspace(colorspace); @@ -281,7 +276,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) m_spec.extra_attribs.attribute("ktx:supercompressionscheme", TypeDesc::UINT32, 1, cspan( - m_tex2->supercompressionScheme)); + m_tex->supercompressionScheme)); m_spec.extra_attribs.attribute("ktx:texturekind", TypeDesc::UINT32, 1, cspan(static_cast( @@ -305,16 +300,16 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // if a Basis Universal scheme has to be applied. m_spec.extra_attribs.attribute("ktx:colormodel", TypeDesc::UINT32, 1, cspan( - ktxTexture2_GetColorModel_e(m_tex2))); + ktxTexture2_GetColorModel_e(m_tex))); // m_spec.extra_attribs.attribute("ktx:transferfunction", TypeDesc::UINT32, 1, // cspan(transfer_function)); // TODO: do we actually need the dfd data to re-generate the same KTX2 file? - // uint32_t dfdTotalSize = *m_tex2->pDfd; + // uint32_t dfdTotalSize = *m_tex->pDfd; // m_spec.extra_attribs.attribute("ktx:dfd", TypeDesc::UINT8, dfdTotalSize, // make_cspan(reinterpret_cast( - // m_tex2->pDfd), + // m_tex->pDfd), // dfdTotalSize)); // @@ -420,14 +415,14 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // KTX_SS_BASIS_LZ supercompression schemes. New schemes may be added to the // spec hence why we do a strict if check. // - if (m_tex2->supercompressionScheme != KTX_SS_NONE - && m_tex2->supercompressionScheme != KTX_SS_ZSTD - && m_tex2->supercompressionScheme != KTX_SS_ZLIB - && m_tex2->supercompressionScheme != KTX_SS_BASIS_LZ) { + if (m_tex->supercompressionScheme != KTX_SS_NONE + && m_tex->supercompressionScheme != KTX_SS_ZSTD + && m_tex->supercompressionScheme != KTX_SS_ZLIB + && m_tex->supercompressionScheme != KTX_SS_BASIS_LZ) { // vendor-specific or newly introduced supercompression schemes (not // supported) errorfmt("unsuppoted supercompression scheme: {}", - static_cast(m_tex2->supercompressionScheme)); + static_cast(m_tex->supercompressionScheme)); return false; } @@ -439,8 +434,8 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // Call this BEFORE (potential) ktxTexture2_TranscodeBasis call // m_spec.extra_attribs.attribute("ktx:vkformat", TypeDesc::UINT32, 1, - cspan(static_cast( - m_tex2->vkFormat))); + cspan( + static_cast(m_tex->vkFormat))); // // Do we need to transcode this texture (i.e., is this a Basis Universal @@ -453,9 +448,9 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // This modifies the KtxTexture2 (m_tex) therefore make sure to save // essential properties for proper KTX2 regeneration. // - if (ktxTexture2_NeedsTranscoding(m_tex2)) { + if (ktxTexture2_NeedsTranscoding(m_tex)) { if (auto status = ktxTexture2_TranscodeBasis( - m_tex2, ktx_transcode_fmt_e::KTX_TTF_RGBA32, 0); + m_tex, ktx_transcode_fmt_e::KTX_TTF_RGBA32, 0); status != KTX_SUCCESS) { errorfmt("failed to transcode KTX2 texture to raw pixels. " "ktxTexture2_TranscodeBasis returned Ktx error code: {}", @@ -482,7 +477,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // // PVRTC: TODO: wait for libktx PR. // - if (m_tex2->isCompressed /* i.e., is GPU block compressed? */) { + if (m_tex->isCompressed /* i.e., is GPU block compressed? */) { switch (m_cmp) { /* BCn GPU formats */ case BlockCompression::BC1: @@ -503,10 +498,10 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // destroys the temporarily created texture. // // This operation is expensive (both in memory and CPU cycles). - // After this, m_tex2->isCompressed will be false => this will only + // After this, m_tex->isCompressed will be false => this will only // be called once. // - // if (auto status = ktxTexture2_DecodeBCn(m_tex2); + // if (auto status = ktxTexture2_DecodeBCn(m_tex); // status != KTX_SUCCESS) { // errorfmt("failed to decode BCn-compressed texture. " // "ktxTexture2_DecodeBCn returned Ktx error code: {}", @@ -534,10 +529,10 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // destroys the temporarily created texture. // // This operation is expensive (both in memory and CPU cycles). - // After this, m_tex2->isCompressed will be false => this will only + // After this, m_tex->isCompressed will be false => this will only // be called once. // - if (auto status = ktxTexture2_DecodeAstc(m_tex2); + if (auto status = ktxTexture2_DecodeAstc(m_tex); status != KTX_SUCCESS) { errorfmt("failed to decode ASTC-compressed texture. " "ktxTexture2_DecodeAstc returned Ktx error code: {}", @@ -576,7 +571,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // do not have corresponding Vulkan formats. // TODO // - if (m_tex2->vkFormat == VK_FORMAT_UNDEFINED) { + if (m_tex->vkFormat == VK_FORMAT_UNDEFINED) { // TODO: check case (4) - color model // check case (3) - non-Vulkan GPU formats (here we simply map these @@ -619,8 +614,8 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // state of data in OIIO is always decompressed (i.e., we never return // block-compressed data from read_native_scanline(s) functions). // - auto format = static_cast(m_tex2->vkFormat); - if (m_tex2->isCompressed) { + auto format = static_cast(m_tex->vkFormat); + if (m_tex->isCompressed) { FormatInfo format_info; if (!get_info_from_vkformat(static_cast(format), format_info)) { @@ -668,6 +663,8 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // errorfmt is set via seek_subimage return false; + OIIO_ASSERT(m_data_ptr != nullptr); + newspec = m_spec; return true; } @@ -681,7 +678,7 @@ KtxInput::close() if (!ioproxy_opened()) return true; if (m_tex) { - ktxTexture_Destroy(m_tex); + ktxTexture2_Destroy(m_tex); m_tex = nullptr; } ioproxy_clear(); @@ -748,16 +745,16 @@ KtxInput::seek_subimage(int subimage, int miplevel) // dimensions of a miplevel. See: // https://github.com/KhronosGroup/KTX-Software/lib/src/texture.c // - const size_t width = std::max(m_tex2->baseWidth >> miplevel, 1u); - const size_t height = std::max(m_tex2->baseHeight >> miplevel, 1u); - const size_t depth = std::max(m_tex2->baseDepth >> miplevel, 1u); + const size_t width = std::max(m_tex->baseWidth >> miplevel, 1u); + const size_t height = std::max(m_tex->baseHeight >> miplevel, 1u); + const size_t depth = std::max(m_tex->baseDepth >> miplevel, 1u); m_spec.width = width; m_spec.height = height; m_spec.depth = depth; // Should never be true (because the texture is decompressed in open()) - if (m_tex2->isCompressed) { + if (m_tex->isCompressed) { OIIO_ASSERT( false && "KTX2 texture should be decompressed before a call to subimage."); @@ -770,15 +767,18 @@ KtxInput::seek_subimage(int subimage, int miplevel) // returned. // ktx_size_t offset; - if (auto status = ktxTexture_GetImageOffset(m_tex, miplevel, arr_layer, - face_slice, &offset); + if (auto status = ktxTexture2_GetImageOffset(m_tex, miplevel, arr_layer, + face_slice, &offset); status != KTX_SUCCESS) { - errorfmt("ktxTexture_GetImageOffset failed with exit code: {}", + errorfmt("ktxTexture2_GetImageOffset failed with exit code: {}", static_cast(status)); return false; } - m_data_ptr = m_tex2->pData + offset; - m_pitch = ktxTexture_GetRowPitch(m_tex, miplevel); + m_data_ptr = m_tex->pData + offset; + const uint32_t pitch + = ktxTexture_GetRowPitch(reinterpret_cast(m_tex), + miplevel); + OIIO_ASSERT(pitch == m_spec.scanline_bytes()); return true; } @@ -790,6 +790,11 @@ KtxInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, void* data) { lock_guard lock(*this); + + // Since we will access m_spec + if (!seek_subimage(subimage, miplevel)) + return false; + return read_native_scanlines(subimage, miplevel, y, y + 1, as_writable_bytes(data, m_spec.scanline_bytes( true))); @@ -808,9 +813,7 @@ KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, return false; } - // avoid calling seek_subimage because this will NOT be thread-safe and - // we have to introduce a lock which will make this slower (read note above - // about how libktx inflates all data in open()). + // Since we will access m_spec if (!seek_subimage(subimage, miplevel)) return false; @@ -825,6 +828,10 @@ KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, int yend, span data) { lock_guard lock(*this); + + if (!seek_subimage(subimage, miplevel)) + return false; + // is provided [ybegin, yend[ valid? if (ybegin < 0 || ybegin >= yend || yend > m_spec.height) { // out of range scanlines @@ -839,8 +846,12 @@ KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, // errorfmt is set within valid_raw_span_size return false; + OIIO_ASSERT(m_data_ptr != nullptr); + + const size_t pitch = m_spec.scanline_bytes(); + // since miplevel is valid => get number of bytes in a row for this mip - memcpy(data.data(), m_data_ptr, m_pitch * (yend - ybegin)); + memcpy(data.data(), m_data_ptr, pitch * (yend - ybegin)); // std::cout << "read_native_scanlines(" << subimage << ", " << miplevel // << ", " << ybegin << ", " << yend << ")" << '\n'; return true; @@ -910,8 +921,8 @@ KtxInput::get_colorspace() const // for OIIO colorspaces, see: // https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/src/libOpenImageIO/color_ocio.cpp khr_df_transfer_e transfer_function = ktxTexture2_GetTransferFunction_e( - m_tex2); - khr_df_primaries_e primaries = ktxTexture2_GetPrimaries_e(m_tex2); + m_tex); + khr_df_primaries_e primaries = ktxTexture2_GetPrimaries_e(m_tex); // std::cout << "tf: " << transfer_function << "; primaries: " << primaries // << '\n'; switch (transfer_function) { diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp index d8e93b0197..5d5b5259b2 100644 --- a/src/ktx.imageio/ktxoutput.cpp +++ b/src/ktx.imageio/ktxoutput.cpp @@ -28,6 +28,7 @@ class KtxOutput final : public ImageOutput { int supports(string_view feature) const override { return ( + feature == "alpha" || feature == "ioproxy" || // as per the KTX1/2 specs: // registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_keyvalue_data feature == "arbitrary_metadata" || From 8b68531f25e9eb291b20316d8e08c1edde26aec8 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sun, 5 Jul 2026 18:32:44 +0200 Subject: [PATCH 08/42] Fix mismatching deallocation *Previously, a buffer allocated via a malloc by libktx was freed using RAII via std::unique_ptr's delete[] which doesn't match the allocator. Signed-off-by: Walid Chtioui --- src/ktx.imageio/ktxoutput.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp index 5d5b5259b2..338e81a0b8 100644 --- a/src/ktx.imageio/ktxoutput.cpp +++ b/src/ktx.imageio/ktxoutput.cpp @@ -650,7 +650,6 @@ KtxOutput::write_ktx2() return true; } - // TODO: this hasn't been tested yet if (!strcmp(m_io->proxytype(), "vecoutput")) { auto proxy = reinterpret_cast(m_io); ktx_uint8_t* buff; @@ -662,8 +661,10 @@ KtxOutput::write_ktx2() static_cast(status)); return false; } - /* Cleanup when we go out of scope or on exception */ - auto _ = std::unique_ptr(buff); + // Cleanup when we go out of scope or on exception + // Do not do an array style destruction (via unique_ptr) as this does + // not match how the data was originally allocated (via malloc) + auto _ = std::unique_ptr(buff); proxy->write(buff, buff_size); return true; } From f1c19a9301217dc5db15a56a975518d5e465e915 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sun, 5 Jul 2026 19:33:40 +0200 Subject: [PATCH 09/42] CI: lower libktx version for 'oldest' CI runners *libktx > 4.3.2 requires CMake >= 3.22. The code changes required for this will also be applied accordingly (e.g., older libktx versions don't support direct BCn encoding/decoding). Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5374bfdb0a..6d2e2a32cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -468,8 +468,11 @@ jobs: Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 + Ktx_GIT_TAG=v4.3.2 + Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd # OpenJPEG_BUILD_VERSION=2.2.0 # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e + # libktx > 4.3.2 requires CMake >= 3.22 optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' build_local_deps: 'TIFF' - desc: oldest clang11/C++17 py3.9 exr3.1 ocio2.3 @@ -498,8 +501,11 @@ jobs: Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 + Ktx_GIT_TAG=v4.3.2 + Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd # OpenJPEG_BUILD_VERSION=2.2.0 # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e + # libktx > 4.3.2 requires CMake >= 3.22 optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' build_local_deps: 'TIFF' @@ -537,6 +543,9 @@ jobs: Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 + Ktx_GIT_TAG=v4.3.2 + Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd + # libktx > 4.3.2 requires CMake >= 3.22 required_deps: none build_local_deps: 'TIFF' From 759350186c880d2cccd27c3225d788993b4e2cc6 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sun, 5 Jul 2026 19:52:55 +0200 Subject: [PATCH 10/42] Add Ktx_VERSION and use it in ktxinput *Previously, libktx version was hardcoded to v5.0.0 now it is based on the CMake-set variable Ktx_VERSION. Signed-off-by: Walid Chtioui --- src/ktx.imageio/CMakeLists.txt | 5 ++++- src/ktx.imageio/ktxinput.cpp | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ktx.imageio/CMakeLists.txt b/src/ktx.imageio/CMakeLists.txt index a035da5584..0e3139df6d 100644 --- a/src/ktx.imageio/CMakeLists.txt +++ b/src/ktx.imageio/CMakeLists.txt @@ -3,7 +3,10 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO if (Ktx_FOUND) - add_oiio_plugin (ktxinput.cpp ktxoutput.cpp LINK_LIBRARIES KTX::ktx) + add_oiio_plugin (ktxinput.cpp ktxoutput.cpp + LINK_LIBRARIES KTX::ktx + DEFINITIONS "-DKtx_VERSION=\"${Ktx_VERSION}\"") else () message (WARNING "KTX plugin will not be built, no libktx") + set (format_plugin_definitions ${format_plugin_definitions} DISABLE_KTX=1 PARENT_SCOPE) endif () diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index a7ef64136f..a69e74e4c9 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -126,8 +126,8 @@ OIIO_EXPORT int ktx_imageio_version = OIIO_PLUGIN_VERSION; OIIO_EXPORT const char* ktx_imageio_library_version() { - return "ktx v5.0.0-rc1"; -} // TODO: hardcoded because I couldn't expose KTX_VERSION + return "ktx v" Ktx_VERSION; +} OIIO_EXPORT ImageInput* ktx_input_imageio_create() { From e7c1fe324970835d4435e4680af5a0c9861604ed Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sun, 5 Jul 2026 20:09:04 +0200 Subject: [PATCH 11/42] Fix buffer cleanup using unique_ptr *malloc has to be matched by a call to free not a call to delete. Signed-off-by: Walid Chtioui --- src/ktx.imageio/ktxoutput.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp index 338e81a0b8..26999d2c43 100644 --- a/src/ktx.imageio/ktxoutput.cpp +++ b/src/ktx.imageio/ktxoutput.cpp @@ -661,10 +661,10 @@ KtxOutput::write_ktx2() static_cast(status)); return false; } - // Cleanup when we go out of scope or on exception - // Do not do an array style destruction (via unique_ptr) as this does - // not match how the data was originally allocated (via malloc) - auto _ = std::unique_ptr(buff); + // Cleanup when we go out of scope or on exception (make sure to use + // matching deallocator, i.e., free()) + auto _ = std::unique_ptr(buff, + std::free); proxy->write(buff, buff_size); return true; } From 415b36d4da0a60ec38c005c1f09e14c43bbd1bda Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sun, 5 Jul 2026 20:10:07 +0200 Subject: [PATCH 12/42] Replace Ktx_GIT_TAG with Ktx_BUILD_VERSION *Attempting to address old CMake version with 'oldest' CIs. Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 6 +++--- src/cmake/build_Ktx.cmake | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d2e2a32cc..e971aabc4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -468,7 +468,7 @@ jobs: Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 - Ktx_GIT_TAG=v4.3.2 + Ktx_BUILD_VERSION=v4.3.2 Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd # OpenJPEG_BUILD_VERSION=2.2.0 # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e @@ -501,7 +501,7 @@ jobs: Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 - Ktx_GIT_TAG=v4.3.2 + Ktx_BUILD_VERSION=v4.3.2 Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd # OpenJPEG_BUILD_VERSION=2.2.0 # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e @@ -543,7 +543,7 @@ jobs: Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 - Ktx_GIT_TAG=v4.3.2 + Ktx_BUILD_VERSION=v4.3.2 Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd # libktx > 4.3.2 requires CMake >= 3.22 required_deps: none diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index 017d86e255..dc4570182d 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -4,7 +4,7 @@ set_cache (Ktx_BUILD_VERSION v5.0.0-rc1 "Ktx version for local builds") set (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") -set (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}") +set (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") set (Ktx_GIT_COMMIT "6269d2752ed04446c2d4749f54f3aad4f94555b5") set_cache (Ktx_BUILD_SHARED_LIBS OFF ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) From 29d45c6650def8670bf76bba686f063cd07ea2f1 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sun, 5 Jul 2026 20:22:34 +0200 Subject: [PATCH 13/42] Fix Intel-based MacOS libktx build issues *Point to not-yet-merged PR in libktx only for Intel-based MacOS runner. Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e971aabc4e..7928c8eb49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -607,6 +607,11 @@ jobs: ctest_test_timeout: 1200 setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 INSTALL_QT=0 INSTALL_OPENCV=0 INSTALL_OPENVDB=0 + Ktx_GIT_REPOSITORY=https://github.com/atteneder/KTX-Software.git + Ktx_BUILD_VERSION=fix/macOS-universal-5.0.0 + Ktx_GIT_COMMIT=0ad1fc3b3f0c0403f25186829bccc81aee9a6921 + # Temporarily link to the PR that addresses libktx build issues + # TODO: revert this before merging, don't forget!!! optional_deps_append: 'OpenCV;OpenVDB;Qt5;Qt6' benchmark: 1 - desc: MacOS-14-ARM aclang15/C++20/py3.13 From a133b1f8fb11198132250e92706194dbd12ac672 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sun, 5 Jul 2026 20:23:31 +0200 Subject: [PATCH 14/42] Use set_cache() instead of set() to expose vars to CI runners Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index dc4570182d..71a830f032 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -3,9 +3,9 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO set_cache (Ktx_BUILD_VERSION v5.0.0-rc1 "Ktx version for local builds") -set (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") -set (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") -set (Ktx_GIT_COMMIT "6269d2752ed04446c2d4749f54f3aad4f94555b5") +set_cache (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") +set_cache (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") +set_cache (Ktx_GIT_COMMIT "6269d2752ed04446c2d4749f54f3aad4f94555b5") set_cache (Ktx_BUILD_SHARED_LIBS OFF ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) From 05e3dde93ec2b0bd87a08be15650790abb692bfc Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sun, 5 Jul 2026 20:33:31 +0200 Subject: [PATCH 15/42] Add description to set_cache() Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index 71a830f032..344277b547 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -3,9 +3,11 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO set_cache (Ktx_BUILD_VERSION v5.0.0-rc1 "Ktx version for local builds") -set_cache (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") +set_cache (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git" + "git repository from where to fetch libktx") set_cache (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") -set_cache (Ktx_GIT_COMMIT "6269d2752ed04446c2d4749f54f3aad4f94555b5") +set_cache (Ktx_GIT_COMMIT "6269d2752ed04446c2d4749f54f3aad4f94555b5" + "commit hash to verify tag/branch against") set_cache (Ktx_BUILD_SHARED_LIBS OFF ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) From a7f95f6f7d3e16fc989335d80e48eae70586d33a Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Wed, 8 Jul 2026 17:07:16 +0200 Subject: [PATCH 16/42] Add support for older ktx build (v4.3.2) Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 90 +++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 28 deletions(-) diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index 344277b547..30070373e0 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -17,35 +17,69 @@ string (MAKE_C_IDENTIFIER ${Ktx_BUILD_VERSION} Ktx_VERSION_IDENT) set_cache (KTX_CMAKE_C_COMPILER ${CMAKE_C_COMPILER} "libktx build C compiler override" ADVANCED) set_cache (KTX_CMAKE_CXX_COMPILER ${CMAKE_CXX_COMPILER} "libktx build C++ compiler override" ADVANCED) +# +# The only two tested libktx versions are: +# - v5.0.0-rc1 (newest supported) +# - v4.3.2 (newest version with minimum CMake required <= 3.18) +# libktx v4.3.2 requires a different setup than newer libktx versions +# # for detailed build instructions, see: -# https://github.com/KhronosGroup/KTX-Software/blob/main/BUILDING.md -# KTX-Software not only provides Ktx but also a set of cli tools and load -# test applications that we do not need. -build_dependency_with_cmake(Ktx - VERSION ${Ktx_BUILD_VERSION} - GIT_REPOSITORY ${Ktx_GIT_REPOSITORY} - GIT_TAG ${Ktx_GIT_TAG} - GIT_COMMIT ${Ktx_GIT_COMMIT} - # lib only contains CMakeLists.txt from tag v5.0.0 but that requires CMake min version 3.23 - # which in turn causes the CI to fail. Just give up and build the whole thing... - SOURCE_SUBDIR lib # To only build Ktx, cmake has to point to: KTX-Software/lib - CMAKE_ARGS - -D BUILD_SHARED_LIBS=${Ktx_BUILD_SHARED_LIBS} - -D CMAKE_INSTALL_LIBDIR=lib - -D CMAKE_POSITION_INDEPENDENT_CODE=ON - -D LIBKTX_VERSION_READ_ONLY=OFF - -D LIBKTX_VERSION_FULL=ON - -D LIBKTX_FEATURE_KTX1=ON # Setting this to OFF causes linker issues - -D LIBKTX_FEATURE_KTX2=ON - -D LIBKTX_FEATURE_VK_UPLOAD=OFF - -D LIBKTX_FEATURE_GL_UPLOAD=OFF - -D LIBKTX_FEATURE_ETC_UNPACK=OFF # This has some weird licensing and I don't feel comfortable including it ... - -D CMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER} - -D CMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER} - # as per KTX-Software: - # > Intel Macs have support for SSE, but if you're building universal - # > binaries, you have to disable SSE or the build will fail. - ) +# https://github.com/KhronosGroup/KTX-Software/blob/main/BUILDING.md +# +# KTX-Software not only provides Ktx but also a set of CLI tools and load +# test applications that we do not need. We only need the libktx component +# without any GPU texture loading functionalities. +# +if (Ktx_BUILD_VERSION STREQUAL "v4.3.2") + # Because you can't negate variables in CMake ... + if(Ktx_BUILD_SHARED_LIBS) + set(Ktx_BUILD_STATIC_LIBS OFF) + else() + set(Ktx_BUILD_STATIC_LIBS ON) + endif() + build_dependency_with_cmake(Ktx + VERSION ${Ktx_BUILD_VERSION} + GIT_REPOSITORY ${Ktx_GIT_REPOSITORY} + GIT_TAG ${Ktx_GIT_TAG} + GIT_COMMIT ${Ktx_GIT_COMMIT} + CMAKE_ARGS + -D KTX_FEATURE_STATIC_LIBRARY=${Ktx_BUILD_STATIC_LIBS} # no BUILD_SHARED_LIBS in older libktx versions ... + -D CMAKE_POSITION_INDEPENDENT_CODE=ON + -D KTX_FEATURE_TOOLS=OFF + -D KTX_FEATURE_TESTS=OFF + -D KTX_FEATURE_KTX1=ON # TODO: test with OFF + -D KTX_FEATURE_KTX2=ON + -D KTX_FEATURE_VK_UPLOAD=OFF + -D KTX_FEATURE_GL_UPLOAD=OFF + -D CMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER} + -D CMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER} + ) +else() # v5.0.0-rc1 or a branch with similar CMake setup + build_dependency_with_cmake(Ktx + VERSION ${Ktx_BUILD_VERSION} + GIT_REPOSITORY ${Ktx_GIT_REPOSITORY} + GIT_TAG ${Ktx_GIT_TAG} + GIT_COMMIT ${Ktx_GIT_COMMIT} + SOURCE_SUBDIR lib # To only build Ktx, cmake has to point to: KTX-Software/lib + CMAKE_ARGS + -D BUILD_SHARED_LIBS=${Ktx_BUILD_SHARED_LIBS} + -D CMAKE_INSTALL_LIBDIR=lib + -D CMAKE_POSITION_INDEPENDENT_CODE=ON + -D LIBKTX_VERSION_READ_ONLY=OFF + -D LIBKTX_VERSION_FULL=ON + -D LIBKTX_FEATURE_KTX1=ON # Setting this to OFF causes linker issues + -D LIBKTX_FEATURE_KTX2=ON + -D LIBKTX_FEATURE_VK_UPLOAD=OFF + -D LIBKTX_FEATURE_GL_UPLOAD=OFF + -D LIBKTX_FEATURE_ETC_UNPACK=OFF # This has some weird licensing and I don't feel comfortable including it ... + -D CMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER} + -D CMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER} + # as per KTX-Software: + # > Intel Macs have support for SSE, but if you're building universal + # > binaries, you have to disable SSE or the build will fail. + ) +endif() + # Set some things up that we'll need for a subsequent find_package to work set (Ktx_ROOT ${Ktx_LOCAL_INSTALL_DIR}) From 403f7f33a5e94d94c87a4799c64e23ae037d33ee Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Wed, 8 Jul 2026 19:41:49 +0200 Subject: [PATCH 17/42] Add support for libktx 4.3.2 This is added so that CIs on older runners/compilers/cmake can pass. Newer version of libktx require CMake >= 3.22 which these runners cannot install. Signed-off-by: Walid Chtioui --- src/cmake/externalpackages.cmake | 2 +- src/ktx.imageio/CMakeLists.txt | 12 +++++++++++- src/ktx.imageio/ktxinput.cpp | 23 +++++++++++++++++++---- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index 8ae790c733..c72a57cb04 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -266,7 +266,7 @@ endif () # KTX-Software because building non-tagged libktx sets the version to 0.0.0. # checked_find_package (Ktx - VERSION_MIN 5.0.0 + VERSION_MIN 4.3.2 BUILD_LOCAL missing ) diff --git a/src/ktx.imageio/CMakeLists.txt b/src/ktx.imageio/CMakeLists.txt index 0e3139df6d..18b49079af 100644 --- a/src/ktx.imageio/CMakeLists.txt +++ b/src/ktx.imageio/CMakeLists.txt @@ -3,9 +3,19 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO if (Ktx_FOUND) + # Get MAJOR.MINOR.PATCH from set Ktx version + string(REPLACE "." ";" version_list "${Ktx_VERSION}") + list(GET version_list 0 Ktx_VERSION_MAJOR) + list(GET version_list 1 Ktx_VERSION_MINOR) + list(GET version_list 2 Ktx_VERSION_PATCH) + add_oiio_plugin (ktxinput.cpp ktxoutput.cpp LINK_LIBRARIES KTX::ktx - DEFINITIONS "-DKtx_VERSION=\"${Ktx_VERSION}\"") + DEFINITIONS + "-DKtx_VERSION_MAJOR=${Ktx_VERSION_MAJOR}" + "-DKtx_VERSION_MINOR=${Ktx_VERSION_MINOR}" + "-DKtx_VERSION_PATCH=${Ktx_VERSION_PATCH}" + ) else () message (WARNING "KTX plugin will not be built, no libktx") set (format_plugin_definitions ${format_plugin_definitions} DISABLE_KTX=1 PARENT_SCOPE) diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index a69e74e4c9..d8977ff2c2 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -13,6 +13,9 @@ # define KHRONOS_STATIC 1 #endif +#define Ktx_VERSION \ + OIIO_MAKE_VERSION(Ktx_VERSION_MAJOR, Ktx_VERSION_MINOR, Ktx_VERSION_PATCH) + #include "ktx_pvt.h" #include #include @@ -126,7 +129,8 @@ OIIO_EXPORT int ktx_imageio_version = OIIO_PLUGIN_VERSION; OIIO_EXPORT const char* ktx_imageio_library_version() { - return "ktx v" Ktx_VERSION; + return "ktx v" OIIO_STRINGIZE(Ktx_VERSION_MAJOR) "." OIIO_STRINGIZE( + Ktx_VERSION_MINOR) "." OIIO_STRINGIZE(Ktx_VERSION_PATCH); } OIIO_EXPORT ImageInput* ktx_input_imageio_create() @@ -521,6 +525,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) /* ASTC formats */ case BlockCompression::ASTC: +#if Ktx_VERSION > OIIO_MAKE_VERSION(4, 3, 2) // // Note: // ktxTexture2_DecodeAstc internally creates a new ktxTexture2 texture @@ -540,6 +545,12 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) return false; } break; +#else + errorfmt( + "ASTC decoding is not available in libktx v{}. Consider using libktx > 4.3.2", + Ktx_VERSION); + return false; +#endif default: errorfmt("Unknown/unsupported GPU block compression kind: {}", @@ -916,13 +927,17 @@ KtxInput::get_texture_kind() const std::string KtxInput::get_colorspace() const { + // // for set of, see: // https://github.com/KhronosGroup/KTX-Software/blob/main/external/dfdutils/KHR/khr_df.h // for OIIO colorspaces, see: // https://github.com/AcademySoftwareFoundation/OpenImageIO/blob/main/src/libOpenImageIO/color_ocio.cpp - khr_df_transfer_e transfer_function = ktxTexture2_GetTransferFunction_e( - m_tex); - khr_df_primaries_e primaries = ktxTexture2_GetPrimaries_e(m_tex); + // + // Don't use ktxTexture2_GetPrimaries_e/ktxTexture2_GetTransferFunction_e as these are only + // available in newer versions of libktx (>= 5.0.0, I think) + // + const auto transfer_function = static_cast(KHR_DFDVAL(m_tex->pDfd+1, TRANSFER)); + const auto primaries = static_cast(KHR_DFDVAL(m_tex->pDfd+1, PRIMARIES)); // std::cout << "tf: " << transfer_function << "; primaries: " << primaries // << '\n'; switch (transfer_function) { From f07af6e334ba4c8bfe88c26e9b9b55a7bc19cf53 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Wed, 8 Jul 2026 21:30:26 +0200 Subject: [PATCH 18/42] Fix libktx 4.3.2 support ktxBasisParams is vastly different starting from libktx >= 5.0.0. Use smart pointers to handle ktxTexture resources and clean them up. Signed-off-by: Walid Chtioui --- src/ktx.imageio/ktx_pvt.h | 11 +++++ src/ktx.imageio/ktxinput.cpp | 46 ++++++++++---------- src/ktx.imageio/ktxoutput.cpp | 79 +++++++++++++++++++++-------------- 3 files changed, 84 insertions(+), 52 deletions(-) diff --git a/src/ktx.imageio/ktx_pvt.h b/src/ktx.imageio/ktx_pvt.h index edbd07cbdf..27472fb8cd 100644 --- a/src/ktx.imageio/ktx_pvt.h +++ b/src/ktx.imageio/ktx_pvt.h @@ -5,6 +5,7 @@ #include #include #include +#include #include OIIO_PLUGIN_NAMESPACE_BEGIN @@ -379,6 +380,16 @@ struct FormatInfo { +/// Wrapper around ktxTexture_Destroy (which is a macro) to be passed as a +/// deleter to smart pointers +static void +ktxTexture_Deleter(ktxTexture2* tex) +{ + ktxTexture_Destroy((ktxTexture*)tex); +} + + + /// /// Colorspace detection should be done via the color model and transfer function /// and not via VkFormat. See: diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index d8977ff2c2..d6f0aae739 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -80,9 +80,10 @@ class KtxInput final : public ImageInput { private: std::string m_filename; - /// Non-owning pointer to KTX2 texture. The texture is managed by libktx - /// and should be destroyed via a 'ktxTexture_Destroy()' call. - ktxTexture2* m_tex { nullptr }; + /// KTX2 texture. + std::unique_ptr m_tex { + nullptr, ktxTexture_Deleter + }; /// Non-owning pointer to first byte of the requested (miplevel, slice). /// This points to first byte of the whole texture data. @@ -235,9 +236,11 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // https://github.com/KhronosGroup/KTX-Software/blob/main/lib/src/texture.c // if (proxytype == "file") { - auto fd = reinterpret_cast(m_io)->handle(); - auto res = ktxTexture2_CreateFromStdioStream( - fd, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &m_tex); + auto fd = reinterpret_cast(m_io)->handle(); + ktxTexture2* p_tex = nullptr; + auto res = ktxTexture2_CreateFromStdioStream( + fd, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &p_tex); + m_tex.reset(p_tex); if (KTX_SUCCESS != res) { errorfmt("Failed to create ktx texture using " "ktxTexture_CreateFromStdioStream"); @@ -246,9 +249,11 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) } else /* (proxytype == "memreader") */ { OIIO_ASSERT(proxytype == "memreader"); auto buff = reinterpret_cast(m_io)->buffer(); - auto res = ktxTexture2_CreateFromMemory( + ktxTexture2* p_tex = nullptr; + auto res = ktxTexture2_CreateFromMemory( buff.data(), buff.size(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, - &m_tex); + &p_tex); + m_tex.reset(p_tex); if (KTX_SUCCESS != res) { errorfmt( "Failed to create ktx texture using ktxTexture_CreateFromMemory"); @@ -303,8 +308,8 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // Store colormodel so that if a KTX2 is requested to be generated, we know // if a Basis Universal scheme has to be applied. m_spec.extra_attribs.attribute("ktx:colormodel", TypeDesc::UINT32, 1, - cspan( - ktxTexture2_GetColorModel_e(m_tex))); + cspan(ktxTexture2_GetColorModel_e( + m_tex.get()))); // m_spec.extra_attribs.attribute("ktx:transferfunction", TypeDesc::UINT32, 1, // cspan(transfer_function)); @@ -452,9 +457,9 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // This modifies the KtxTexture2 (m_tex) therefore make sure to save // essential properties for proper KTX2 regeneration. // - if (ktxTexture2_NeedsTranscoding(m_tex)) { + if (ktxTexture2_NeedsTranscoding(m_tex.get())) { if (auto status = ktxTexture2_TranscodeBasis( - m_tex, ktx_transcode_fmt_e::KTX_TTF_RGBA32, 0); + m_tex.get(), ktx_transcode_fmt_e::KTX_TTF_RGBA32, 0); status != KTX_SUCCESS) { errorfmt("failed to transcode KTX2 texture to raw pixels. " "ktxTexture2_TranscodeBasis returned Ktx error code: {}", @@ -688,10 +693,6 @@ KtxInput::close() // Check if already closed if (!ioproxy_opened()) return true; - if (m_tex) { - ktxTexture2_Destroy(m_tex); - m_tex = nullptr; - } ioproxy_clear(); return true; }; @@ -778,8 +779,9 @@ KtxInput::seek_subimage(int subimage, int miplevel) // returned. // ktx_size_t offset; - if (auto status = ktxTexture2_GetImageOffset(m_tex, miplevel, arr_layer, - face_slice, &offset); + if (auto status + = ktxTexture_GetImageOffset(reinterpret_cast(m_tex.get()), + miplevel, arr_layer, face_slice, &offset); status != KTX_SUCCESS) { errorfmt("ktxTexture2_GetImageOffset failed with exit code: {}", static_cast(status)); @@ -787,7 +789,7 @@ KtxInput::seek_subimage(int subimage, int miplevel) } m_data_ptr = m_tex->pData + offset; const uint32_t pitch - = ktxTexture_GetRowPitch(reinterpret_cast(m_tex), + = ktxTexture_GetRowPitch(reinterpret_cast(m_tex.get()), miplevel); OIIO_ASSERT(pitch == m_spec.scanline_bytes()); @@ -936,8 +938,10 @@ KtxInput::get_colorspace() const // Don't use ktxTexture2_GetPrimaries_e/ktxTexture2_GetTransferFunction_e as these are only // available in newer versions of libktx (>= 5.0.0, I think) // - const auto transfer_function = static_cast(KHR_DFDVAL(m_tex->pDfd+1, TRANSFER)); - const auto primaries = static_cast(KHR_DFDVAL(m_tex->pDfd+1, PRIMARIES)); + const auto transfer_function = static_cast( + KHR_DFDVAL(m_tex->pDfd + 1, TRANSFER)); + const auto primaries = static_cast( + KHR_DFDVAL(m_tex->pDfd + 1, PRIMARIES)); // std::cout << "tf: " << transfer_function << "; primaries: " << primaries // << '\n'; switch (transfer_function) { diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp index 26999d2c43..676978715e 100644 --- a/src/ktx.imageio/ktxoutput.cpp +++ b/src/ktx.imageio/ktxoutput.cpp @@ -53,7 +53,10 @@ class KtxOutput final : public ImageOutput { private: std::string m_filename; - ktxTexture2* m_tex { nullptr }; + /// KTX2 texture. + std::unique_ptr m_tex { + nullptr, ktxTexture_Deleter + }; uint32_t m_nlayers { 1 }; @@ -339,19 +342,23 @@ KtxOutput::open(const std::string& name, const ImageSpec& newspec, create_info.isArray = KTX_FALSE; create_info.generateMipmaps = KTX_FALSE; - if (auto status = ktxTexture2_Create(&create_info, - KTX_TEXTURE_CREATE_ALLOC_STORAGE, - &m_tex); - status != KTX_SUCCESS) { + ktxTexture2* p_tex = nullptr; + auto result = ktxTexture2_Create(&create_info, + KTX_TEXTURE_CREATE_ALLOC_STORAGE, &p_tex); + m_tex.reset(p_tex); + + if (result != KTX_SUCCESS) { close(); errorfmt("ktxTexture_Create return KTX exit error code: {}", - static_cast(status)); + static_cast(result)); return false; } // Reserve space for base level mipmap if (!m_tex->isCompressed) { - m_img.resize(ktxTexture_GetImageSize(ktxTexture(m_tex), 0)); + m_img.resize( + ktxTexture_GetImageSize(reinterpret_cast(m_tex.get()), + 0)); } else { // TODO: // Not compressed => make sure that vector's size matches the expected @@ -436,7 +443,6 @@ KtxOutput::close() // Apparently we can't do (or I don't know yet how to) partial writes // using libktx. We can only write whole ktxTextures all together. result = write_ktx2(); // TODO: can this throw? (prob not) - ktxTexture_Destroy(ktxTexture(m_tex)); } init(); return result; @@ -470,15 +476,20 @@ KtxOutput::basisu_basislz_compress() // what params the original data was compressed with so that we can reproduce // it. // TODO: expose as "ktx:" attribute(s) - ktxBasisParams params = { 0 }; - params.structSize = sizeof(ktxBasisParams); + ktxBasisParams params = { 0 }; + params.structSize = sizeof(ktxBasisParams); +#if Ktx_VERSION >= OIIO_MAKE_VERSION(5, 0, 0) params.codec = ktx_basis_codec_e::KTX_BASIS_CODEC_ETC1S; - params.verbose = false; - params.noSSE = false; - params.threadCount = 1; params.etc1sCompressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL; +#else + params.uastc = false; + params.compressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL; +#endif + params.verbose = false; + params.noSSE = false; + params.threadCount = 1; // TODO: expose RDO support for ETC1S - if (auto status = ktxTexture2_CompressBasisEx(m_tex, ¶ms); + if (auto status = ktxTexture2_CompressBasisEx(m_tex.get(), ¶ms); status != KTX_SUCCESS) { errorfmt("ktxTexture2_CompressBasisEx returned error code: ", static_cast(status)); @@ -500,14 +511,18 @@ KtxOutput::basisu_uastc_compress() // TODO: expose parameters ktxBasisParams params = { 0 }; params.structSize = sizeof(ktxBasisParams); - params.codec = ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_LDR_4x4; - params.verbose = false; - params.noSSE = false; - params.threadCount = 1; - params.uastcFlags = KTX_PACK_UASTC_LEVEL_DEFAULT; - params.uastcRDO = false; +#if Ktx_VERSION >= OIIO_MAKE_VERSION(5, 0, 0) + params.codec = ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_LDR_4x4; +#else + params.uastc = true; +#endif + params.verbose = false; + params.noSSE = false; + params.threadCount = 1; + params.uastcFlags = KTX_PACK_UASTC_LEVEL_DEFAULT; + params.uastcRDO = false; // TODO: expose RDO support for UASTC - if (auto status = ktxTexture2_CompressBasisEx(m_tex, ¶ms); + if (auto status = ktxTexture2_CompressBasisEx(m_tex.get(), ¶ms); status != KTX_SUCCESS) { errorfmt("ktxTexture2_CompressBasisEx returned error code: ", static_cast(status)); @@ -553,8 +568,8 @@ KtxOutput::write_ktx2() // will cause a segfault! // if (!m_tex->isCompressed) { - if (auto status = ktxTexture_SetImageFromMemory(ktxTexture(m_tex), 0, 0, - 0, m_img.data(), + if (auto status = ktxTexture_SetImageFromMemory(ktxTexture(m_tex.get()), + 0, 0, 0, m_img.data(), m_img.size()); status != KTX_SUCCESS) { errorfmt( @@ -589,9 +604,9 @@ KtxOutput::write_ktx2() return false; } else if (m_cmp == BlockCompression::ASTC) { // First set uncompressed images - if (auto status = ktxTexture_SetImageFromMemory(ktxTexture(m_tex), 0, 0, - 0, m_img.data(), - m_img.size()); + if (auto status = ktxTexture_SetImageFromMemory( + reinterpret_cast(m_tex.get()), 0, 0, 0, + m_img.data(), m_img.size()); status != KTX_SUCCESS) { errorfmt( "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", @@ -601,7 +616,7 @@ KtxOutput::write_ktx2() // Then compress the whole texture to ASTC format // TODO: expose ASTC compression quality parameter as spec attribute - if (auto status = ktxTexture2_CompressAstc(m_tex, 0); + if (auto status = ktxTexture2_CompressAstc(m_tex.get(), 0); status != KTX_SUCCESS) { errorfmt("ktxTexture2_CompressAstc returned KTX exit error code: {}", static_cast(status)); @@ -621,7 +636,7 @@ KtxOutput::write_ktx2() // Finally, apply the supercompression scheme (if any) if (m_superCmp == KTX_SS_ZLIB) { - if (auto status = ktxTexture2_DeflateZLIB(m_tex, 0); + if (auto status = ktxTexture2_DeflateZLIB(m_tex.get(), 0); status != KTX_SUCCESS) { errorfmt("ktxTexture2_DeflateZLIB returned KTX exit error code: {}", static_cast(status)); @@ -629,7 +644,7 @@ KtxOutput::write_ktx2() } } else if (m_superCmp == KTX_SS_ZSTD) { - if (auto status = ktxTexture2_DeflateZstd(m_tex, 0); + if (auto status = ktxTexture2_DeflateZstd(m_tex.get(), 0); status != KTX_SUCCESS) { errorfmt("ktxTexture2_DeflateZstd returned KTX exit error code: {}", static_cast(status)); @@ -640,7 +655,8 @@ KtxOutput::write_ktx2() Filesystem::IOProxy* m_io = ioproxy(); if (!strcmp(m_io->proxytype(), "file")) { auto fd = reinterpret_cast(m_io)->handle(); - if (auto status = ktxTexture2_WriteToStdioStream(m_tex, fd); + if (auto status = ktxTexture_WriteToStdioStream( + reinterpret_cast(m_tex.get()), fd); status != KTX_SUCCESS) { errorfmt( "ktxTexture2_WriteToStdioStream returned KTX exit error code: {}", @@ -654,7 +670,8 @@ KtxOutput::write_ktx2() auto proxy = reinterpret_cast(m_io); ktx_uint8_t* buff; ktx_size_t buff_size; - if (auto status = ktxTexture2_WriteToMemory(m_tex, &buff, &buff_size); + if (auto status = ktxTexture_WriteToMemory( + reinterpret_cast(m_tex.get()), &buff, &buff_size); status != KTX_SUCCESS) { errorfmt( "ktxTexture2_WriteToMemory returned KTX exit error code: {}", From 9c918fc761050c4e730d58cbb38dd3abe4bf5838 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Thu, 9 Jul 2026 18:14:17 +0200 Subject: [PATCH 19/42] Update libktx commit hash Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index 30070373e0..9ad92172b8 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -6,7 +6,7 @@ set_cache (Ktx_BUILD_VERSION v5.0.0-rc1 "Ktx version for local builds") set_cache (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git" "git repository from where to fetch libktx") set_cache (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") -set_cache (Ktx_GIT_COMMIT "6269d2752ed04446c2d4749f54f3aad4f94555b5" +set_cache (Ktx_GIT_COMMIT "2ca7d54109f4c23298a969f22b68769e94138de5" "commit hash to verify tag/branch against") set_cache (Ktx_BUILD_SHARED_LIBS OFF ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) From 17e8f8d61428cda0b37f2b1c9f8cd69251963fae Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Thu, 9 Jul 2026 18:32:40 +0200 Subject: [PATCH 20/42] Fix Ktx_VERSION and unique_ptr ktxTexture2 access Signed-off-by: Walid Chtioui --- src/ktx.imageio/ktx_pvt.h | 3 +++ src/ktx.imageio/ktxinput.cpp | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ktx.imageio/ktx_pvt.h b/src/ktx.imageio/ktx_pvt.h index 27472fb8cd..34d21cc2a7 100644 --- a/src/ktx.imageio/ktx_pvt.h +++ b/src/ktx.imageio/ktx_pvt.h @@ -8,6 +8,9 @@ #include #include +#define Ktx_VERSION \ + OIIO_MAKE_VERSION(Ktx_VERSION_MAJOR, Ktx_VERSION_MINOR, Ktx_VERSION_PATCH) + OIIO_PLUGIN_NAMESPACE_BEGIN enum class TextureKind : uint32_t { diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index d6f0aae739..dd6f5a4891 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -13,9 +13,6 @@ # define KHRONOS_STATIC 1 #endif -#define Ktx_VERSION \ - OIIO_MAKE_VERSION(Ktx_VERSION_MAJOR, Ktx_VERSION_MINOR, Ktx_VERSION_PATCH) - #include "ktx_pvt.h" #include #include @@ -542,7 +539,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // After this, m_tex->isCompressed will be false => this will only // be called once. // - if (auto status = ktxTexture2_DecodeAstc(m_tex); + if (auto status = ktxTexture2_DecodeAstc(m_tex.get()); status != KTX_SUCCESS) { errorfmt("failed to decode ASTC-compressed texture. " "ktxTexture2_DecodeAstc returned Ktx error code: {}", From 534be09950159dcbc2b5cb9309940fa81b196fbc Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Thu, 9 Jul 2026 18:38:12 +0200 Subject: [PATCH 21/42] Attempt to fix linkage issues with Intel-MacOS CI Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7928c8eb49..4978bdf9dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -610,6 +610,7 @@ jobs: Ktx_GIT_REPOSITORY=https://github.com/atteneder/KTX-Software.git Ktx_BUILD_VERSION=fix/macOS-universal-5.0.0 Ktx_GIT_COMMIT=0ad1fc3b3f0c0403f25186829bccc81aee9a6921 + CMAKE_OSX_ARCHITECTURES=x86_64 # Temporarily link to the PR that addresses libktx build issues # TODO: revert this before merging, don't forget!!! optional_deps_append: 'OpenCV;OpenVDB;Qt5;Qt6' From 4822d66176c39f455812afdd2eb4acc577aaf873 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Thu, 9 Jul 2026 18:50:41 +0200 Subject: [PATCH 22/42] Add support for libktx v0.0.0 to target main:HEAD of libktx Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 2 +- src/cmake/externalpackages.cmake | 3 ++- src/ktx.imageio/ktx_pvt.h | 3 +++ src/ktx.imageio/ktxinput.cpp | 2 +- src/ktx.imageio/ktxoutput.cpp | 4 ++-- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index 9ad92172b8..b1aa10a001 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO -set_cache (Ktx_BUILD_VERSION v5.0.0-rc1 "Ktx version for local builds") +set_cache (Ktx_BUILD_VERSION main "Ktx version for local builds") set_cache (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git" "git repository from where to fetch libktx") set_cache (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index c72a57cb04..b476cdc56d 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -266,7 +266,8 @@ endif () # KTX-Software because building non-tagged libktx sets the version to 0.0.0. # checked_find_package (Ktx - VERSION_MIN 4.3.2 + # VERSION_MIN 4.3.2 + VERSION_MIN 0.0.0 # If you target main branch of KTX-Software which provides no version BUILD_LOCAL missing ) diff --git a/src/ktx.imageio/ktx_pvt.h b/src/ktx.imageio/ktx_pvt.h index 34d21cc2a7..daa472e053 100644 --- a/src/ktx.imageio/ktx_pvt.h +++ b/src/ktx.imageio/ktx_pvt.h @@ -11,6 +11,9 @@ #define Ktx_VERSION \ OIIO_MAKE_VERSION(Ktx_VERSION_MAJOR, Ktx_VERSION_MINOR, Ktx_VERSION_PATCH) +// main branch of KTX-Software (libktx) does not set the version of libktx +#define Ktx_VERSIONLESS OIIO_MAKE_VERSION(0, 0, 0) + OIIO_PLUGIN_NAMESPACE_BEGIN enum class TextureKind : uint32_t { diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index dd6f5a4891..54bfe6253a 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -527,7 +527,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) /* ASTC formats */ case BlockCompression::ASTC: -#if Ktx_VERSION > OIIO_MAKE_VERSION(4, 3, 2) +#if Ktx_VERSION > OIIO_MAKE_VERSION(4, 3, 2) || Ktx_VERSION == Ktx_VERSIONLESS // // Note: // ktxTexture2_DecodeAstc internally creates a new ktxTexture2 texture diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp index 676978715e..482c7f7db3 100644 --- a/src/ktx.imageio/ktxoutput.cpp +++ b/src/ktx.imageio/ktxoutput.cpp @@ -478,7 +478,7 @@ KtxOutput::basisu_basislz_compress() // TODO: expose as "ktx:" attribute(s) ktxBasisParams params = { 0 }; params.structSize = sizeof(ktxBasisParams); -#if Ktx_VERSION >= OIIO_MAKE_VERSION(5, 0, 0) +#if Ktx_VERSION >= OIIO_MAKE_VERSION(5, 0, 0) || Ktx_VERSION == Ktx_VERSIONLESS params.codec = ktx_basis_codec_e::KTX_BASIS_CODEC_ETC1S; params.etc1sCompressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL; #else @@ -511,7 +511,7 @@ KtxOutput::basisu_uastc_compress() // TODO: expose parameters ktxBasisParams params = { 0 }; params.structSize = sizeof(ktxBasisParams); -#if Ktx_VERSION >= OIIO_MAKE_VERSION(5, 0, 0) +#if Ktx_VERSION >= OIIO_MAKE_VERSION(5, 0, 0) || Ktx_VERSION == Ktx_VERSIONLESS params.codec = ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_LDR_4x4; #else params.uastc = true; From 7219ddfd9f952f8d235e46542a3ebca386802b5d Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Fri, 10 Jul 2026 17:15:47 +0200 Subject: [PATCH 23/42] Disable SSE4.2 for Intel-based MacOS CI To be reverted once the linkage issue for ASTCENC is solved. Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4978bdf9dc..264e5a3c5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -603,7 +603,7 @@ jobs: cxx_compiler: /usr/bin/clang++ cxx_std: 17 python_ver: "3.14" - simd: sse4.2,avx2 + simd: avx2 ctest_test_timeout: 1200 setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 INSTALL_QT=0 INSTALL_OPENCV=0 INSTALL_OPENVDB=0 From a1ed2d3f933f4b21b32bc0ff63191842b94b4ea3 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Fri, 10 Jul 2026 18:00:42 +0200 Subject: [PATCH 24/42] TMP: disbable simd on Intel-MacOS and disable all other jobs Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 1159 +++++++++++++++++++------------------- 1 file changed, 579 insertions(+), 580 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 264e5a3c5b..54b96a364c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,511 +43,511 @@ jobs: # # Linux Tests using ASWF-docker containers # - linux-aswf: - if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} - name: "${{matrix.desc}}" - uses: ./.github/workflows/build-steps.yml - with: - nametag: ${{ matrix.nametag || 'unnamed!' }} - runner: ${{ matrix.runner || 'ubuntu-latest' }} - container: ${{ matrix.container }} - container_volumes: ${{ matrix.container_volumes || '[]' }} - cc_compiler: ${{ matrix.cc_compiler }} - cxx_compiler: ${{ matrix.cxx_compiler }} - cxx_std: ${{ matrix.cxx_std || '17' }} - build_type: ${{ matrix.build_type || 'Release' }} - depcmds: ${{ matrix.depcmds }} - extra_artifacts: ${{ matrix.extra_artifacts }} - fmt_ver: ${{ matrix.fmt_ver }} - fmt_commit: ${{ matrix.fmt_commit }} - opencolorio_ver: ${{ matrix.opencolorio_ver }} - openexr_ver: ${{ matrix.openexr_ver }} - pybind11_ver: ${{ matrix.pybind11_ver }} - python_ver: ${{ matrix.python_ver }} - setenvs: ${{ matrix.setenvs }} - simd: ${{ matrix.simd }} - skip_build: ${{ matrix.skip_build }} - skip_tests: ${{ matrix.skip_tests }} - abi_check: ${{ matrix.abi_check }} - benchmark: ${{ matrix.benchmark }} - build_docs: ${{ matrix.build_docs }} - clang_format: ${{ matrix.clang_format }} - generator: ${{ matrix.generator }} - ctest_args: ${{ matrix.ctest_args }} - ctest_test_timeout: ${{ matrix.ctest_test_timeout }} - coverage: ${{ matrix.coverage || 0 }} - sonar: ${{ matrix.sonar || 0 }} - # Override required_deps to be 'all' and explicitly list as optional - # only the ones we are intentionally not testing for those jobs. - required_deps: ${{ matrix.required_deps || 'all' }} - optional_deps: ${{ matrix.optional_deps || 'DCMTK;FFmpeg;JXL;Libheif;Nuke;OpenCV;openjph;OpenVDB;Qt5;R3DSDK;'}}${{matrix.optional_deps_append}} - build_local_deps: ${{ matrix.build_local_deps }} - strategy: - fail-fast: false - matrix: - include: - - desc: VFX2023 gcc11/C++17 py3.10 exr3.1 ocio2.3 - nametag: linux-vfx2023 - runner: ubuntu-latest - container: aswf/ci-osl:2023-clang15.2 - opencolorio_ver: v2.3.0 - python_ver: "3.10" - simd: "avx2,f16c" - fmt_ver: 10.1.1 - fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 - pybind11_ver: v2.10.0 - setenvs: export PUGIXML_VERSION=v1.13 - optional_deps_append: 'LibRaw;Ptex;Qt6' - - desc: VFX2024 gcc11/C++17 py3.11 exr3.2 ocio2.3 - nametag: linux-vfx2024 - runner: ubuntu-latest - container: aswf/ci-oiio:2024.6 - opencolorio_ver: v2.3.2 - python_ver: "3.11" - simd: "avx2,f16c" - fmt_ver: 10.1.1 - fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 - pybind11_ver: v2.12.0 - setenvs: export PUGIXML_VERSION=v1.14 - optional_deps_append: "LibRaw" - - desc: VFX2024 clang/C++17 py3.11 exr3.2 ocio2.3 - nametag: linux-vfx2024.clang - runner: ubuntu-latest - container: aswf/ci-oiio:2024.6 - cc_compiler: clang - cxx_compiler: clang++ - opencolorio_ver: v2.3.2 - python_ver: "3.11" - simd: "avx2,f16c" - fmt_ver: 10.1.1 - fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 - pybind11_ver: v2.12.0 - setenvs: export PUGIXML_VERSION=v1.14 - optional_deps_append: "LibRaw" - - desc: VFX2025 gcc11/C++17 py3.11 exr3.3 ocio2.4 - nametag: linux-vfx2025 - runner: ubuntu-latest - container: aswf/ci-oiio:2025.5 - cxx_std: 17 - python_ver: "3.11" - simd: "avx2,f16c" - fmt_ver: 11.2.0 - fmt_commit: 40626af88bd7df9a5fb80be7b25ac85b122d6c21 - pybind11_ver: v2.13.6 - benchmark: 1 - setenvs: export PUGIXML_VERSION=v1.15 - optional_deps_append: "openjph;Qt6" - - desc: VFX2025 Debug gcc11/C++17 py3.11 exr3.3 ocio2.4 - nametag: linux-vfx2025-debug - runner: ubuntu-latest - container: aswf/ci-oiio:2025.5 - cxx_std: 17 - build_type: Debug - ctest_test_timeout: "300" - python_ver: "3.11" - simd: "avx2,f16c" - fmt_ver: 11.2.0 - fmt_commit: 40626af88bd7df9a5fb80be7b25ac85b122d6c21 - pybind11_ver: v2.13.6 - setenvs: export PUGIXML_VERSION=v1.15 - optional_deps_append: "openjph;Qt6" - # - desc: VFX2025 Static gcc11/C++17 py3.11 exr3.3 ocio2.4 - # nametag: linux-vfx2025-static - # runner: ubuntu-latest - # container: aswf/ci-oiio:2025.5 - # cxx_std: 17 - # python_ver: "3.11" - # simd: "avx2,f16c" - # fmt_ver: 11.2.0 - # pybind11_ver: v2.13.6 - # benchmark: 1 - # setenvs: export PUGIXML_VERSION=v1.15 - # BUILD_SHARED_LIBS=OFF - # optional_deps_append: "openjph;Qt6" - - desc: VFX2025 icx/C++17 py3.11 exr3.3 ocio2.4 qt5.15 - nametag: linux-vfx2025.icx - runner: ubuntu-latest - container: aswf/ci-oiio:2025.5 - cc_compiler: icx - cxx_compiler: icpx - fmt_ver: 11.2.0 - fmt_commit: 40626af88bd7df9a5fb80be7b25ac85b122d6c21 - python_ver: "3.11" - pybind11_ver: v2.13.6 - simd: "avx2,f16c" - benchmark: 1 - setenvs: export USE_OPENVDB=0 USE_OPENCV=0 - UHDR_CMAKE_C_COMPILER=gcc - UHDR_CMAKE_CXX_COMPILER=g++ - KTX_CMAKE_C_COMPILER=gcc - KTX_CMAKE_CXX_COMPILER=g++ - # Building libuhdr and libktx with icx results in test failures - optional_deps_append: "LibRaw;Ptex;openjph;Qt6" - - desc: VFX2026 gcc14/C++20 py3.13 exr3.4 ocio2.5 - nametag: linux-vfx2026 - runner: ubuntu-latest - container: aswf/ci-oiio:2026.3 - cxx_std: 20 - python_ver: "3.13" - simd: "avx2,f16c" - pybind11_ver: v3.0.0 - benchmark: 1 - # setenvs: export - optional_deps_append: "Qt5;Qt6" - - desc: Sanitizers - nametag: sanitizer - runner: ubuntu-latest - container: aswf/ci-oiio:2026.3 - cc_compiler: clang - cxx_compiler: clang++ - build_type: Debug - python_ver: "3.13" - ctest_test_timeout: "1200" - setenvs: export SANITIZE=address,undefined - OIIO_CMAKE_FLAGS="-DSANITIZE=address,undefined -DOIIO_HARDENING=3 -DUSE_PYTHON=0" - CTEST_EXCLUSIONS="broken|png-damaged" - OpenImageIO_BUILD_LOCAL_DEPS=PNG - optional_deps_append: "LibRaw" + # linux-aswf: + # if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} + # name: "${{matrix.desc}}" + # uses: ./.github/workflows/build-steps.yml + # with: + # nametag: ${{ matrix.nametag || 'unnamed!' }} + # runner: ${{ matrix.runner || 'ubuntu-latest' }} + # container: ${{ matrix.container }} + # container_volumes: ${{ matrix.container_volumes || '[]' }} + # cc_compiler: ${{ matrix.cc_compiler }} + # cxx_compiler: ${{ matrix.cxx_compiler }} + # cxx_std: ${{ matrix.cxx_std || '17' }} + # build_type: ${{ matrix.build_type || 'Release' }} + # depcmds: ${{ matrix.depcmds }} + # extra_artifacts: ${{ matrix.extra_artifacts }} + # fmt_ver: ${{ matrix.fmt_ver }} + # fmt_commit: ${{ matrix.fmt_commit }} + # opencolorio_ver: ${{ matrix.opencolorio_ver }} + # openexr_ver: ${{ matrix.openexr_ver }} + # pybind11_ver: ${{ matrix.pybind11_ver }} + # python_ver: ${{ matrix.python_ver }} + # setenvs: ${{ matrix.setenvs }} + # simd: ${{ matrix.simd }} + # skip_build: ${{ matrix.skip_build }} + # skip_tests: ${{ matrix.skip_tests }} + # abi_check: ${{ matrix.abi_check }} + # benchmark: ${{ matrix.benchmark }} + # build_docs: ${{ matrix.build_docs }} + # clang_format: ${{ matrix.clang_format }} + # generator: ${{ matrix.generator }} + # ctest_args: ${{ matrix.ctest_args }} + # ctest_test_timeout: ${{ matrix.ctest_test_timeout }} + # coverage: ${{ matrix.coverage || 0 }} + # sonar: ${{ matrix.sonar || 0 }} + # # Override required_deps to be 'all' and explicitly list as optional + # # only the ones we are intentionally not testing for those jobs. + # required_deps: ${{ matrix.required_deps || 'all' }} + # optional_deps: ${{ matrix.optional_deps || 'DCMTK;FFmpeg;JXL;Libheif;Nuke;OpenCV;openjph;OpenVDB;Qt5;R3DSDK;'}}${{matrix.optional_deps_append}} + # build_local_deps: ${{ matrix.build_local_deps }} + # strategy: + # fail-fast: false + # matrix: + # include: + # - desc: VFX2023 gcc11/C++17 py3.10 exr3.1 ocio2.3 + # nametag: linux-vfx2023 + # runner: ubuntu-latest + # container: aswf/ci-osl:2023-clang15.2 + # opencolorio_ver: v2.3.0 + # python_ver: "3.10" + # simd: "avx2,f16c" + # fmt_ver: 10.1.1 + # fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 + # pybind11_ver: v2.10.0 + # setenvs: export PUGIXML_VERSION=v1.13 + # optional_deps_append: 'LibRaw;Ptex;Qt6' + # - desc: VFX2024 gcc11/C++17 py3.11 exr3.2 ocio2.3 + # nametag: linux-vfx2024 + # runner: ubuntu-latest + # container: aswf/ci-oiio:2024.6 + # opencolorio_ver: v2.3.2 + # python_ver: "3.11" + # simd: "avx2,f16c" + # fmt_ver: 10.1.1 + # fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 + # pybind11_ver: v2.12.0 + # setenvs: export PUGIXML_VERSION=v1.14 + # optional_deps_append: "LibRaw" + # - desc: VFX2024 clang/C++17 py3.11 exr3.2 ocio2.3 + # nametag: linux-vfx2024.clang + # runner: ubuntu-latest + # container: aswf/ci-oiio:2024.6 + # cc_compiler: clang + # cxx_compiler: clang++ + # opencolorio_ver: v2.3.2 + # python_ver: "3.11" + # simd: "avx2,f16c" + # fmt_ver: 10.1.1 + # fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 + # pybind11_ver: v2.12.0 + # setenvs: export PUGIXML_VERSION=v1.14 + # optional_deps_append: "LibRaw" + # - desc: VFX2025 gcc11/C++17 py3.11 exr3.3 ocio2.4 + # nametag: linux-vfx2025 + # runner: ubuntu-latest + # container: aswf/ci-oiio:2025.5 + # cxx_std: 17 + # python_ver: "3.11" + # simd: "avx2,f16c" + # fmt_ver: 11.2.0 + # fmt_commit: 40626af88bd7df9a5fb80be7b25ac85b122d6c21 + # pybind11_ver: v2.13.6 + # benchmark: 1 + # setenvs: export PUGIXML_VERSION=v1.15 + # optional_deps_append: "openjph;Qt6" + # - desc: VFX2025 Debug gcc11/C++17 py3.11 exr3.3 ocio2.4 + # nametag: linux-vfx2025-debug + # runner: ubuntu-latest + # container: aswf/ci-oiio:2025.5 + # cxx_std: 17 + # build_type: Debug + # ctest_test_timeout: "300" + # python_ver: "3.11" + # simd: "avx2,f16c" + # fmt_ver: 11.2.0 + # fmt_commit: 40626af88bd7df9a5fb80be7b25ac85b122d6c21 + # pybind11_ver: v2.13.6 + # setenvs: export PUGIXML_VERSION=v1.15 + # optional_deps_append: "openjph;Qt6" + # # - desc: VFX2025 Static gcc11/C++17 py3.11 exr3.3 ocio2.4 + # # nametag: linux-vfx2025-static + # # runner: ubuntu-latest + # # container: aswf/ci-oiio:2025.5 + # # cxx_std: 17 + # # python_ver: "3.11" + # # simd: "avx2,f16c" + # # fmt_ver: 11.2.0 + # # pybind11_ver: v2.13.6 + # # benchmark: 1 + # # setenvs: export PUGIXML_VERSION=v1.15 + # # BUILD_SHARED_LIBS=OFF + # # optional_deps_append: "openjph;Qt6" + # - desc: VFX2025 icx/C++17 py3.11 exr3.3 ocio2.4 qt5.15 + # nametag: linux-vfx2025.icx + # runner: ubuntu-latest + # container: aswf/ci-oiio:2025.5 + # cc_compiler: icx + # cxx_compiler: icpx + # fmt_ver: 11.2.0 + # fmt_commit: 40626af88bd7df9a5fb80be7b25ac85b122d6c21 + # python_ver: "3.11" + # pybind11_ver: v2.13.6 + # simd: "avx2,f16c" + # benchmark: 1 + # setenvs: export USE_OPENVDB=0 USE_OPENCV=0 + # UHDR_CMAKE_C_COMPILER=gcc + # UHDR_CMAKE_CXX_COMPILER=g++ + # KTX_CMAKE_C_COMPILER=gcc + # KTX_CMAKE_CXX_COMPILER=g++ + # # Building libuhdr and libktx with icx results in test failures + # optional_deps_append: "LibRaw;Ptex;openjph;Qt6" + # - desc: VFX2026 gcc14/C++20 py3.13 exr3.4 ocio2.5 + # nametag: linux-vfx2026 + # runner: ubuntu-latest + # container: aswf/ci-oiio:2026.3 + # cxx_std: 20 + # python_ver: "3.13" + # simd: "avx2,f16c" + # pybind11_ver: v3.0.0 + # benchmark: 1 + # # setenvs: export + # optional_deps_append: "Qt5;Qt6" + # - desc: Sanitizers + # nametag: sanitizer + # runner: ubuntu-latest + # container: aswf/ci-oiio:2026.3 + # cc_compiler: clang + # cxx_compiler: clang++ + # build_type: Debug + # python_ver: "3.13" + # ctest_test_timeout: "1200" + # setenvs: export SANITIZE=address,undefined + # OIIO_CMAKE_FLAGS="-DSANITIZE=address,undefined -DOIIO_HARDENING=3 -DUSE_PYTHON=0" + # CTEST_EXCLUSIONS="broken|png-damaged" + # OpenImageIO_BUILD_LOCAL_DEPS=PNG + # optional_deps_append: "LibRaw" - # Test ABI stability. `abi_check` is the version or commit that we - # believe is the current standard against which we don't want to - # break the ABI. Basically, we will build that version as well as - # the current one, and compare the resulting libraries. - - desc: ABI check - nametag: abi-check - runner: ubuntu-latest - container: aswf/ci-oiio:2025.5 - build_type: RelWithDebInfo - fmt_ver: 11.1.4 - fmt_commit: 123913715afeb8a437e6388b4473fcc4753e1c9a - python_ver: "3.11" - pybind11_ver: v3.0.0 - simd: "avx2,f16c" - skip_tests: 1 - # abi_check: v3.1.6.0 - abi_check: d4c8024633dba8bb3c01d22b65ce9bc7a1ae215e - setenvs: export OIIO_CMAKE_FLAGS="-DOIIO_BUILD_TOOLS=0 -DOIIO_BUILD_TESTS=0 -DUSE_PYTHON=0" - USE_OPENCV=0 USE_FFMPEG=0 USE_PYTHON=0 USE_FREETYPE=0 - optional_deps_append: "openjph;Qt6" + # # Test ABI stability. `abi_check` is the version or commit that we + # # believe is the current standard against which we don't want to + # # break the ABI. Basically, we will build that version as well as + # # the current one, and compare the resulting libraries. + # - desc: ABI check + # nametag: abi-check + # runner: ubuntu-latest + # container: aswf/ci-oiio:2025.5 + # build_type: RelWithDebInfo + # fmt_ver: 11.1.4 + # fmt_commit: 123913715afeb8a437e6388b4473fcc4753e1c9a + # python_ver: "3.11" + # pybind11_ver: v3.0.0 + # simd: "avx2,f16c" + # skip_tests: 1 + # # abi_check: v3.1.6.0 + # abi_check: d4c8024633dba8bb3c01d22b65ce9bc7a1ae215e + # setenvs: export OIIO_CMAKE_FLAGS="-DOIIO_BUILD_TOOLS=0 -DOIIO_BUILD_TESTS=0 -DUSE_PYTHON=0" + # USE_OPENCV=0 USE_FFMPEG=0 USE_PYTHON=0 USE_FREETYPE=0 + # optional_deps_append: "openjph;Qt6" # # Linux Tests using GHA Ubuntu runners directly # - linux-ubuntu: - if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} - name: "${{matrix.desc}}" - uses: ./.github/workflows/build-steps.yml - with: - nametag: ${{ matrix.nametag || 'unnamed!' }} - runner: ${{ matrix.runner || 'ubuntu-latest' }} - container: ${{ matrix.container }} - cc_compiler: ${{ matrix.cc_compiler }} - cxx_compiler: ${{ matrix.cxx_compiler }} - cxx_std: ${{ matrix.cxx_std || '17' }} - build_type: ${{ matrix.build_type || 'Release' }} - depcmds: ${{ matrix.depcmds }} - extra_artifacts: ${{ matrix.extra_artifacts }} - fmt_ver: ${{ matrix.fmt_ver }} - fmt_commit: ${{ matrix.fmt_commit }} - opencolorio_ver: ${{ matrix.opencolorio_ver }} - openexr_ver: ${{ matrix.openexr_ver }} - pybind11_ver: ${{ matrix.pybind11_ver }} - python_ver: ${{ matrix.python_ver }} - setenvs: ${{ matrix.setenvs }} - simd: ${{ matrix.simd }} - skip_build: ${{ matrix.skip_build }} - skip_tests: ${{ matrix.skip_tests }} - abi_check: ${{ matrix.abi_check }} - benchmark: ${{ matrix.benchmark }} - build_docs: ${{ matrix.build_docs }} - clang_format: ${{ matrix.clang_format }} - generator: ${{ matrix.generator }} - ctest_args: ${{ matrix.ctest_args }} - ctest_test_timeout: ${{ matrix.ctest_test_timeout }} - coverage: ${{ matrix.coverage || 0 }} - sonar: ${{ matrix.sonar || 0 }} - gcc_action_ver: ${{ matrix.gcc_action_ver }} - llvm_action_ver: ${{ matrix.llvm_action_ver }} - python_action_ver: ${{ matrix.python_action_ver }} - # Override required_deps to be 'all' and explicitly list as optional - # only the ones we are intentionally not testing for those jobs. - required_deps: ${{ matrix.required_deps || 'all' }} - optional_deps: ${{ matrix.optional_deps || 'CUDAToolkit;DCMTK;JXL;Nuke;OpenGL;openjph;OpenVDB;Ptex;pystring;Qt5;R3DSDK;Libheif;' }}${{matrix.optional_deps_append}} - build_local_deps: ${{ matrix.build_local_deps }} - oiio_python_bindings_backend: ${{ matrix.oiio_python_bindings_backend || '' }} - strategy: - fail-fast: false - matrix: - include: - # Test formatting. This test entry doesn't build at all, it - # just runs clang-format on everything, and passes if nothing is - # misformatted. Upon failure, the build artifact will be the full - # source code with the formatting fixed (diffs will also appear in - # the console output). - - desc: "clang-format" - nametag: clang-format - runner: ubuntu-24.04 - cxx_std: 17 - extra_artifacts: "src/*.*" - python_ver: "3.10" - pybind11_ver: "0" - clang_format: 1 - skip_build: 1 - skip_tests: 1 - setenvs: export SKIP_SYSTEM_DEPS_INSTALL=1 SKIP_APT_GET_UPDATE=1 - INSTALL_OPENCV=0 QT_VERSION=0 USE_LIBHEIF=0 - EXTRA_DEP_PACKAGES="clang-format-17" - CLANG_FORMAT_EXE=clang-format-17 + # linux-ubuntu: + # if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} + # name: "${{matrix.desc}}" + # uses: ./.github/workflows/build-steps.yml + # with: + # nametag: ${{ matrix.nametag || 'unnamed!' }} + # runner: ${{ matrix.runner || 'ubuntu-latest' }} + # container: ${{ matrix.container }} + # cc_compiler: ${{ matrix.cc_compiler }} + # cxx_compiler: ${{ matrix.cxx_compiler }} + # cxx_std: ${{ matrix.cxx_std || '17' }} + # build_type: ${{ matrix.build_type || 'Release' }} + # depcmds: ${{ matrix.depcmds }} + # extra_artifacts: ${{ matrix.extra_artifacts }} + # fmt_ver: ${{ matrix.fmt_ver }} + # fmt_commit: ${{ matrix.fmt_commit }} + # opencolorio_ver: ${{ matrix.opencolorio_ver }} + # openexr_ver: ${{ matrix.openexr_ver }} + # pybind11_ver: ${{ matrix.pybind11_ver }} + # python_ver: ${{ matrix.python_ver }} + # setenvs: ${{ matrix.setenvs }} + # simd: ${{ matrix.simd }} + # skip_build: ${{ matrix.skip_build }} + # skip_tests: ${{ matrix.skip_tests }} + # abi_check: ${{ matrix.abi_check }} + # benchmark: ${{ matrix.benchmark }} + # build_docs: ${{ matrix.build_docs }} + # clang_format: ${{ matrix.clang_format }} + # generator: ${{ matrix.generator }} + # ctest_args: ${{ matrix.ctest_args }} + # ctest_test_timeout: ${{ matrix.ctest_test_timeout }} + # coverage: ${{ matrix.coverage || 0 }} + # sonar: ${{ matrix.sonar || 0 }} + # gcc_action_ver: ${{ matrix.gcc_action_ver }} + # llvm_action_ver: ${{ matrix.llvm_action_ver }} + # python_action_ver: ${{ matrix.python_action_ver }} + # # Override required_deps to be 'all' and explicitly list as optional + # # only the ones we are intentionally not testing for those jobs. + # required_deps: ${{ matrix.required_deps || 'all' }} + # optional_deps: ${{ matrix.optional_deps || 'CUDAToolkit;DCMTK;JXL;Nuke;OpenGL;openjph;OpenVDB;Ptex;pystring;Qt5;R3DSDK;Libheif;' }}${{matrix.optional_deps_append}} + # build_local_deps: ${{ matrix.build_local_deps }} + # oiio_python_bindings_backend: ${{ matrix.oiio_python_bindings_backend || '' }} + # strategy: + # fail-fast: false + # matrix: + # include: + # # Test formatting. This test entry doesn't build at all, it + # # just runs clang-format on everything, and passes if nothing is + # # misformatted. Upon failure, the build artifact will be the full + # # source code with the formatting fixed (diffs will also appear in + # # the console output). + # - desc: "clang-format" + # nametag: clang-format + # runner: ubuntu-24.04 + # cxx_std: 17 + # extra_artifacts: "src/*.*" + # python_ver: "3.10" + # pybind11_ver: "0" + # clang_format: 1 + # skip_build: 1 + # skip_tests: 1 + # setenvs: export SKIP_SYSTEM_DEPS_INSTALL=1 SKIP_APT_GET_UPDATE=1 + # INSTALL_OPENCV=0 QT_VERSION=0 USE_LIBHEIF=0 + # EXTRA_DEP_PACKAGES="clang-format-17" + # CLANG_FORMAT_EXE=clang-format-17 - - desc: latest releases gcc15 C++23 py3.12 avx2 exr3.4 ocio2.4 - nametag: linux-latest-releases - oiio_python_bindings_backend: both - runner: ubuntu-24.04 - # cc_compiler: gcc-13 - # cxx_compiler: g++-13 - cxx_std: 23 - fmt_ver: 12.1.0 - fmt_commit: 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f - opencolorio_ver: v2.5.0 - openexr_ver: v3.4.3 - pybind11_ver: v3.0.2 - python_ver: "3.12" - simd: avx2,f16c - gcc_action_ver: 15 - setenvs: export OIIO_CC=gcc-15 OIIO_CXX=g++-15 - LIBJPEGTURBO_VERSION=3.1.2 - LIBPNG_VERSION=v1.6.50 - LIBRAW_VERSION=0.22.0 - LIBTIFF_VERSION=v4.7.1 - OPENJPEG_VERSION=v2.5.4 - PTEX_VERSION=v2.5.0 - PUGIXML_VERSION=v1.15 - WEBP_VERSION=v1.6.0 - FREETYPE_VERSION=VER-2-14-1 - USE_OPENVDB=0 - UHDR_CMAKE_C_COMPILER=gcc - UHDR_CMAKE_CXX_COMPILER=g++ - OPENCOLORIO_CXX=c++ - # Ensure we are testing all the deps we think we are. We would - # like this test to have minimal missing dependencies. - required_deps: all - optional_deps: 'CUDAToolkit;DCMTK;JXL;Nuke;OpenCV;OpenGL;OpenVDB;R3DSDK' - - desc: bleeding edge gcc16 C++26 py3.12 OCIO/libtiff/exr-main avx2 - nametag: linux-bleeding-edge - runner: ubuntu-24.04 - # cc_compiler: gcc-15 - # cxx_compiler: g++-15 - cxx_std: 26 - fmt_ver: main - opencolorio_ver: main - openexr_ver: main - pybind11_ver: master - python_ver: "3.12" - simd: avx2,f16c - benchmark: 1 - gcc_action_ver: 16 - setenvs: export OIIO_CC=gcc-16 OIIO_CXX=g++-16 - LIBJPEGTURBO_VERSION=main - LIBPNG_VERSION=master - LIBRAW_VERSION=master - LIBTIFF_VERSION=master - OPENJPEG_VERSION=master - PTEX_VERSION=main - PUGIXML_VERSION=master - WEBP_VERSION=main - OIIO_HARDENING=2 - EXTRA_DEP_PACKAGES="python3.12-dev python3-numpy" - FREETYPE_VERSION=master - OpenImageIO_DEPENDENCY_BUILD_ALLOW_UNVERIFIED_TAGS=ON - QT_VERSION=0 INSTALL_OPENCV=0 USE_OPENVDB=0 - UHDR_CMAKE_C_COMPILER=gcc - UHDR_CMAKE_CXX_COMPILER=g++ - OPENCOLORIO_CXX=c++ - # Ensure we are testing all the deps we think we are. We would - # like this test to have minimal missing dependencies. - required_deps: all - optional_deps: 'CUDAToolkit;DCMTK;JXL;libuhdr;Nuke;OpenCV;OpenGL;openjph;R3DSDK;' - - desc: all local builds gcc12 C++17 avx2 exr3.2 ocio2.3 - nametag: linux-local-builds - runner: ubuntu-22.04 - cc_compiler: gcc-12 - cxx_compiler: g++-12 - cxx_std: 17 - python_ver: "3.10" - simd: avx2,f16c - build_local_deps: all - setenvs: export OpenImageIO_DEPENDENCY_BUILD_VERBOSE=ON - LIBRAW_VERSION=0.22.0 - PTEX_VERSION=v2.4.2 - PUGIXML_VERSION=v1.14 - WEBP_VERSION=v1.4.0 - OIIO_HARDENING=3 - - desc: clang18 C++17 avx2 exr3.1 ocio2.3 - nametag: linux-clang18 - runner: ubuntu-24.04 - cxx_compiler: clang++ - cc_compiler: clang - cxx_std: 17 - fmt_ver: 10.1.1 - fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 - opencolorio_ver: v2.3.0 - openexr_ver: v3.1.13 - pybind11_ver: v2.12.0 - python_ver: "3.12" - simd: avx2,f16c - setenvs: export USE_OPENVDB=0 - - desc: Linux ARM latest releases gcc14 C++20 py3.12 exr3.4 ocio2.4 - nametag: linux-arm-latest-releases - runner: ubuntu-24.04-arm - cc_compiler: gcc-14 - cxx_compiler: g++-14 - cxx_std: 20 - fmt_ver: 12.1.0 - fmt_commit: 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f - opencolorio_ver: v2.5.0 - openexr_ver: v3.4.3 - pybind11_ver: v3.0.2 - python_ver: "3.12" - setenvs: export LIBJPEGTURBO_VERSION=3.1.2 - LIBPNG_VERSION=v1.6.50 - LIBRAW_VERSION=0.22.0 - LIBTIFF_VERSION=v4.7.1 - OPENJPEG_VERSION=v2.5.4 - PTEX_VERSION=v2.4.3 - PUGIXML_VERSION=v1.15 - WEBP_VERSION=v1.6.0 - FREETYPE_VERSION=VER-2-14-1 - USE_OPENVDB=0 - - desc: Linux ARM latest releases clang18 C++20 py3.12 exr3.4 ocio2.4 - nametag: linux-arm-latest-releases-clang - runner: ubuntu-24.04-arm - cc_compiler: clang-18 - cxx_compiler: clang++-18 - cxx_std: 20 - fmt_ver: 12.1.0 - fmt_commit: 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f - opencolorio_ver: v2.5.0 - openexr_ver: v3.4.3 - pybind11_ver: v3.0.2 - python_ver: "3.12" - setenvs: export LIBJPEGTURBO_VERSION=3.1.2 - LIBPNG_VERSION=v1.6.50 - LIBRAW_VERSION=0.22.0 - LIBTIFF_VERSION=v4.7.1 - OPENJPEG_VERSION=v2.5.4 - PTEX_VERSION=v2.4.3 - PUGIXML_VERSION=v1.15 - WEBP_VERSION=v1.6.0 - FREETYPE_VERSION=VER-2-14-1 - USE_OPENVDB=0 + # - desc: latest releases gcc15 C++23 py3.12 avx2 exr3.4 ocio2.4 + # nametag: linux-latest-releases + # oiio_python_bindings_backend: both + # runner: ubuntu-24.04 + # # cc_compiler: gcc-13 + # # cxx_compiler: g++-13 + # cxx_std: 23 + # fmt_ver: 12.1.0 + # fmt_commit: 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f + # opencolorio_ver: v2.5.0 + # openexr_ver: v3.4.3 + # pybind11_ver: v3.0.2 + # python_ver: "3.12" + # simd: avx2,f16c + # gcc_action_ver: 15 + # setenvs: export OIIO_CC=gcc-15 OIIO_CXX=g++-15 + # LIBJPEGTURBO_VERSION=3.1.2 + # LIBPNG_VERSION=v1.6.50 + # LIBRAW_VERSION=0.22.0 + # LIBTIFF_VERSION=v4.7.1 + # OPENJPEG_VERSION=v2.5.4 + # PTEX_VERSION=v2.5.0 + # PUGIXML_VERSION=v1.15 + # WEBP_VERSION=v1.6.0 + # FREETYPE_VERSION=VER-2-14-1 + # USE_OPENVDB=0 + # UHDR_CMAKE_C_COMPILER=gcc + # UHDR_CMAKE_CXX_COMPILER=g++ + # OPENCOLORIO_CXX=c++ + # # Ensure we are testing all the deps we think we are. We would + # # like this test to have minimal missing dependencies. + # required_deps: all + # optional_deps: 'CUDAToolkit;DCMTK;JXL;Nuke;OpenCV;OpenGL;OpenVDB;R3DSDK' + # - desc: bleeding edge gcc16 C++26 py3.12 OCIO/libtiff/exr-main avx2 + # nametag: linux-bleeding-edge + # runner: ubuntu-24.04 + # # cc_compiler: gcc-15 + # # cxx_compiler: g++-15 + # cxx_std: 26 + # fmt_ver: main + # opencolorio_ver: main + # openexr_ver: main + # pybind11_ver: master + # python_ver: "3.12" + # simd: avx2,f16c + # benchmark: 1 + # gcc_action_ver: 16 + # setenvs: export OIIO_CC=gcc-16 OIIO_CXX=g++-16 + # LIBJPEGTURBO_VERSION=main + # LIBPNG_VERSION=master + # LIBRAW_VERSION=master + # LIBTIFF_VERSION=master + # OPENJPEG_VERSION=master + # PTEX_VERSION=main + # PUGIXML_VERSION=master + # WEBP_VERSION=main + # OIIO_HARDENING=2 + # EXTRA_DEP_PACKAGES="python3.12-dev python3-numpy" + # FREETYPE_VERSION=master + # OpenImageIO_DEPENDENCY_BUILD_ALLOW_UNVERIFIED_TAGS=ON + # QT_VERSION=0 INSTALL_OPENCV=0 USE_OPENVDB=0 + # UHDR_CMAKE_C_COMPILER=gcc + # UHDR_CMAKE_CXX_COMPILER=g++ + # OPENCOLORIO_CXX=c++ + # # Ensure we are testing all the deps we think we are. We would + # # like this test to have minimal missing dependencies. + # required_deps: all + # optional_deps: 'CUDAToolkit;DCMTK;JXL;libuhdr;Nuke;OpenCV;OpenGL;openjph;R3DSDK;' + # - desc: all local builds gcc12 C++17 avx2 exr3.2 ocio2.3 + # nametag: linux-local-builds + # runner: ubuntu-22.04 + # cc_compiler: gcc-12 + # cxx_compiler: g++-12 + # cxx_std: 17 + # python_ver: "3.10" + # simd: avx2,f16c + # build_local_deps: all + # setenvs: export OpenImageIO_DEPENDENCY_BUILD_VERBOSE=ON + # LIBRAW_VERSION=0.22.0 + # PTEX_VERSION=v2.4.2 + # PUGIXML_VERSION=v1.14 + # WEBP_VERSION=v1.4.0 + # OIIO_HARDENING=3 + # - desc: clang18 C++17 avx2 exr3.1 ocio2.3 + # nametag: linux-clang18 + # runner: ubuntu-24.04 + # cxx_compiler: clang++ + # cc_compiler: clang + # cxx_std: 17 + # fmt_ver: 10.1.1 + # fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 + # opencolorio_ver: v2.3.0 + # openexr_ver: v3.1.13 + # pybind11_ver: v2.12.0 + # python_ver: "3.12" + # simd: avx2,f16c + # setenvs: export USE_OPENVDB=0 + # - desc: Linux ARM latest releases gcc14 C++20 py3.12 exr3.4 ocio2.4 + # nametag: linux-arm-latest-releases + # runner: ubuntu-24.04-arm + # cc_compiler: gcc-14 + # cxx_compiler: g++-14 + # cxx_std: 20 + # fmt_ver: 12.1.0 + # fmt_commit: 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f + # opencolorio_ver: v2.5.0 + # openexr_ver: v3.4.3 + # pybind11_ver: v3.0.2 + # python_ver: "3.12" + # setenvs: export LIBJPEGTURBO_VERSION=3.1.2 + # LIBPNG_VERSION=v1.6.50 + # LIBRAW_VERSION=0.22.0 + # LIBTIFF_VERSION=v4.7.1 + # OPENJPEG_VERSION=v2.5.4 + # PTEX_VERSION=v2.4.3 + # PUGIXML_VERSION=v1.15 + # WEBP_VERSION=v1.6.0 + # FREETYPE_VERSION=VER-2-14-1 + # USE_OPENVDB=0 + # - desc: Linux ARM latest releases clang18 C++20 py3.12 exr3.4 ocio2.4 + # nametag: linux-arm-latest-releases-clang + # runner: ubuntu-24.04-arm + # cc_compiler: clang-18 + # cxx_compiler: clang++-18 + # cxx_std: 20 + # fmt_ver: 12.1.0 + # fmt_commit: 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f + # opencolorio_ver: v2.5.0 + # openexr_ver: v3.4.3 + # pybind11_ver: v3.0.2 + # python_ver: "3.12" + # setenvs: export LIBJPEGTURBO_VERSION=3.1.2 + # LIBPNG_VERSION=v1.6.50 + # LIBRAW_VERSION=0.22.0 + # LIBTIFF_VERSION=v4.7.1 + # OPENJPEG_VERSION=v2.5.4 + # PTEX_VERSION=v2.4.3 + # PUGIXML_VERSION=v1.15 + # WEBP_VERSION=v1.6.0 + # FREETYPE_VERSION=VER-2-14-1 + # USE_OPENVDB=0 - - desc: oldest gcc9/C++17 py3.9 exr3.1 ocio2.3 - # Oldest gcc and versions of the dependencies that we support. - nametag: linux-oldest-ubuntu - runner: ubuntu-22.04 - cc_compiler: gcc-9 - cxx_compiler: g++-9 - cxx_std: 17 - fmt_ver: 9.0.0 - fmt_commit: c4ee726532178e556d923372f29163bd206d7732 - opencolorio_ver: v2.3.0 - openexr_ver: v3.1.0 - pybind11_ver: v2.7.0 - python_ver: "3.9" - python_action_ver: "3.9" - gcc_action_ver: 9 - setenvs: export CMAKE_VERSION=3.18.2 - PTEX_VERSION=v2.3.2 - WEBP_VERSION=v1.1.0 - PUGIXML_VERSION=v1.8 - BUILD_PNG_VERSION=1.6.0 - PIP_SUFFIX=.9 - PIP_INSTALLS=numpy - Robinmap_BUILD_VERSION=1.2.0 - Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d - TIFF_BUILD_VERSION=4.0.0 - TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 - Ktx_BUILD_VERSION=v4.3.2 - Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd - # OpenJPEG_BUILD_VERSION=2.2.0 - # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e - # libktx > 4.3.2 requires CMake >= 3.22 - optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' - build_local_deps: 'TIFF' - - desc: oldest clang11/C++17 py3.9 exr3.1 ocio2.3 - # Oldest clang and versions of the dependencies that we support. - nametag: linux-oldest-clang-ubuntu - runner: ubuntu-22.04 - cxx_std: 17 - fmt_ver: 9.0.0 - fmt_commit: c4ee726532178e556d923372f29163bd206d7732 - opencolorio_ver: v2.3.0 - openexr_ver: v3.1.0 - pybind11_ver: v2.7.0 - python_ver: "3.9" - python_action_ver: "3.9" - llvm_action_ver: "11" - setenvs: export CMAKE_VERSION=3.18.2 - PTEX_VERSION=v2.3.2 - WEBP_VERSION=v1.1.0 - PUGIXML_VERSION=v1.8 - BUILD_PNG_VERSION=1.6.0 - PIP_SUFFIX=.9 - PIP_INSTALLS=numpy - OIIO_CC=clang - OIIO_CXX=clang++ - Robinmap_BUILD_VERSION=1.2.0 - Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d - TIFF_BUILD_VERSION=4.0.0 - TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 - Ktx_BUILD_VERSION=v4.3.2 - Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd - # OpenJPEG_BUILD_VERSION=2.2.0 - # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e - # libktx > 4.3.2 requires CMake >= 3.22 - optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' - build_local_deps: 'TIFF' + # - desc: oldest gcc9/C++17 py3.9 exr3.1 ocio2.3 + # # Oldest gcc and versions of the dependencies that we support. + # nametag: linux-oldest-ubuntu + # runner: ubuntu-22.04 + # cc_compiler: gcc-9 + # cxx_compiler: g++-9 + # cxx_std: 17 + # fmt_ver: 9.0.0 + # fmt_commit: c4ee726532178e556d923372f29163bd206d7732 + # opencolorio_ver: v2.3.0 + # openexr_ver: v3.1.0 + # pybind11_ver: v2.7.0 + # python_ver: "3.9" + # python_action_ver: "3.9" + # gcc_action_ver: 9 + # setenvs: export CMAKE_VERSION=3.18.2 + # PTEX_VERSION=v2.3.2 + # WEBP_VERSION=v1.1.0 + # PUGIXML_VERSION=v1.8 + # BUILD_PNG_VERSION=1.6.0 + # PIP_SUFFIX=.9 + # PIP_INSTALLS=numpy + # Robinmap_BUILD_VERSION=1.2.0 + # Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d + # TIFF_BUILD_VERSION=4.0.0 + # TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 + # Ktx_BUILD_VERSION=v4.3.2 + # Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd + # # OpenJPEG_BUILD_VERSION=2.2.0 + # # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e + # # libktx > 4.3.2 requires CMake >= 3.22 + # optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' + # build_local_deps: 'TIFF' + # - desc: oldest clang11/C++17 py3.9 exr3.1 ocio2.3 + # # Oldest clang and versions of the dependencies that we support. + # nametag: linux-oldest-clang-ubuntu + # runner: ubuntu-22.04 + # cxx_std: 17 + # fmt_ver: 9.0.0 + # fmt_commit: c4ee726532178e556d923372f29163bd206d7732 + # opencolorio_ver: v2.3.0 + # openexr_ver: v3.1.0 + # pybind11_ver: v2.7.0 + # python_ver: "3.9" + # python_action_ver: "3.9" + # llvm_action_ver: "11" + # setenvs: export CMAKE_VERSION=3.18.2 + # PTEX_VERSION=v2.3.2 + # WEBP_VERSION=v1.1.0 + # PUGIXML_VERSION=v1.8 + # BUILD_PNG_VERSION=1.6.0 + # PIP_SUFFIX=.9 + # PIP_INSTALLS=numpy + # OIIO_CC=clang + # OIIO_CXX=clang++ + # Robinmap_BUILD_VERSION=1.2.0 + # Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d + # TIFF_BUILD_VERSION=4.0.0 + # TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 + # Ktx_BUILD_VERSION=v4.3.2 + # Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd + # # OpenJPEG_BUILD_VERSION=2.2.0 + # # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e + # # libktx > 4.3.2 requires CMake >= 3.22 + # optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' + # build_local_deps: 'TIFF' - - desc: hobbled gcc9/C++17 py3.9 exr-3.1 no-sse - # Use the oldest supported versions of required dependencies, and - # disable most optional dependencies and features (no SSE or - # OpenCV, don't embed plugins). - nametag: linux-disabled-ubuntu - runner: ubuntu-22.04 - cc_compiler: gcc-9 - cxx_compiler: g++-9 - cxx_std: 17 - fmt_ver: 9.0.0 - fmt_commit: c4ee726532178e556d923372f29163bd206d7732 - opencolorio_ver: v2.3.0 - openexr_ver: v3.1.0 - pybind11_ver: v2.7.0 - python_ver: "3.9" - python_action_ver: "3.9" - gcc_action_ver: 9 - simd: 0 - setenvs: export EMBEDPLUGINS=0 - CMAKE_VERSION=3.18.2 - PTEX_VERSION=v2.3.2 - WEBP_VERSION=v1.1.0 - USE_JPEGTURBO=0 - USE_OPENCV=0 - USE_LIBUHDR=0 - FREETYPE_VERSION=VER-2-10-0 - PUGIXML_VERSION=v1.8 - BUILD_PNG_VERSION=1.6.0 - PIP_SUFFIX=.9 - PIP_INSTALLS=numpy - Robinmap_BUILD_VERSION=1.2.0 - Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d - TIFF_BUILD_VERSION=4.0.0 - TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 - Ktx_BUILD_VERSION=v4.3.2 - Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd - # libktx > 4.3.2 requires CMake >= 3.22 - required_deps: none - build_local_deps: 'TIFF' + # - desc: hobbled gcc9/C++17 py3.9 exr-3.1 no-sse + # # Use the oldest supported versions of required dependencies, and + # # disable most optional dependencies and features (no SSE or + # # OpenCV, don't embed plugins). + # nametag: linux-disabled-ubuntu + # runner: ubuntu-22.04 + # cc_compiler: gcc-9 + # cxx_compiler: g++-9 + # cxx_std: 17 + # fmt_ver: 9.0.0 + # fmt_commit: c4ee726532178e556d923372f29163bd206d7732 + # opencolorio_ver: v2.3.0 + # openexr_ver: v3.1.0 + # pybind11_ver: v2.7.0 + # python_ver: "3.9" + # python_action_ver: "3.9" + # gcc_action_ver: 9 + # simd: 0 + # setenvs: export EMBEDPLUGINS=0 + # CMAKE_VERSION=3.18.2 + # PTEX_VERSION=v2.3.2 + # WEBP_VERSION=v1.1.0 + # USE_JPEGTURBO=0 + # USE_OPENCV=0 + # USE_LIBUHDR=0 + # FREETYPE_VERSION=VER-2-10-0 + # PUGIXML_VERSION=v1.8 + # BUILD_PNG_VERSION=1.6.0 + # PIP_SUFFIX=.9 + # PIP_INSTALLS=numpy + # Robinmap_BUILD_VERSION=1.2.0 + # Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d + # TIFF_BUILD_VERSION=4.0.0 + # TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 + # Ktx_BUILD_VERSION=v4.3.2 + # Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd + # # libktx > 4.3.2 requires CMake >= 3.22 + # required_deps: none + # build_local_deps: 'TIFF' # @@ -603,7 +603,6 @@ jobs: cxx_compiler: /usr/bin/clang++ cxx_std: 17 python_ver: "3.14" - simd: avx2 ctest_test_timeout: 1200 setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 INSTALL_QT=0 INSTALL_OPENCV=0 INSTALL_OPENVDB=0 @@ -615,91 +614,91 @@ jobs: # TODO: revert this before merging, don't forget!!! optional_deps_append: 'OpenCV;OpenVDB;Qt5;Qt6' benchmark: 1 - - desc: MacOS-14-ARM aclang15/C++20/py3.13 - runner: macos-14 - nametag: macos14-arm-py313 - oiio_python_bindings_backend: both - cc_compiler: /usr/bin/clang - cxx_compiler: /usr/bin/clang++ - cxx_std: 20 - python_ver: "3.13" - - desc: MacOS-15-ARM aclang16/C++20/py3.13 - runner: macos-15 - nametag: macos15-arm-py313 - cc_compiler: /usr/bin/clang - cxx_compiler: /usr/bin/clang++ - cxx_std: 20 - python_ver: "3.13" - benchmark: 1 - - desc: MacOS-26-ARM aclang16/C++20/py3.13 - runner: macos-26 - nametag: macos26-arm-py313 - cc_compiler: /usr/bin/clang - cxx_compiler: /usr/bin/clang++ - cxx_std: 20 - python_ver: "3.14" - benchmark: 1 + # - desc: MacOS-14-ARM aclang15/C++20/py3.13 + # runner: macos-14 + # nametag: macos14-arm-py313 + # oiio_python_bindings_backend: both + # cc_compiler: /usr/bin/clang + # cxx_compiler: /usr/bin/clang++ + # cxx_std: 20 + # python_ver: "3.13" + # - desc: MacOS-15-ARM aclang16/C++20/py3.13 + # runner: macos-15 + # nametag: macos15-arm-py313 + # cc_compiler: /usr/bin/clang + # cxx_compiler: /usr/bin/clang++ + # cxx_std: 20 + # python_ver: "3.13" + # benchmark: 1 + # - desc: MacOS-26-ARM aclang16/C++20/py3.13 + # runner: macos-26 + # nametag: macos26-arm-py313 + # cc_compiler: /usr/bin/clang + # cxx_compiler: /usr/bin/clang++ + # cxx_std: 20 + # python_ver: "3.14" + # benchmark: 1 # # Windows Tests # - windows: - if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'linux-only') && ! contains(github.ref, 'macos-only') }} - name: "${{matrix.desc}}" - uses: ./.github/workflows/build-steps.yml - with: - nametag: ${{ matrix.nametag || 'unnamed!' }} - runner: ${{ matrix.runner || 'ubuntu-latest' }} - container: ${{ matrix.container }} - cc_compiler: ${{ matrix.cc_compiler }} - cxx_compiler: ${{ matrix.cxx_compiler }} - cxx_std: ${{ matrix.cxx_std || '17' }} - build_type: ${{ matrix.build_type || 'Release' }} - depcmds: ${{ matrix.depcmds }} - extra_artifacts: ${{ matrix.extra_artifacts }} - fmt_ver: ${{ matrix.fmt_ver }} - fmt_commit: ${{ matrix.fmt_commit }} - opencolorio_ver: ${{ matrix.opencolorio_ver }} - openexr_ver: ${{ matrix.openexr_ver }} - pybind11_ver: ${{ matrix.pybind11_ver }} - python_ver: ${{ matrix.python_ver }} - setenvs: ${{ matrix.setenvs }} - simd: ${{ matrix.simd }} - skip_build: ${{ matrix.skip_build }} - skip_tests: ${{ matrix.skip_tests }} - benchmark: ${{ matrix.benchmark }} - abi_check: ${{ matrix.abi_check }} - build_docs: ${{ matrix.build_docs }} - generator: ${{ matrix.generator }} - ctest_args: ${{ matrix.ctest_args }} - ctest_test_timeout: ${{ matrix.ctest_test_timeout }} - coverage: ${{ matrix.coverage || 0 }} - sonar: ${{ matrix.sonar || 0 }} - # Windows is a PITA, so we expect very few dependencies to be present or - # built. But we would like to add more dependencies and reduce this list - # of exceptions in the future. - required_deps: ${{ matrix.required_deps || 'all' }} - optional_deps: ${{ matrix.optional_deps || 'BZip2;CUDAToolkit;DCMTK;FFmpeg;GIF;JXL;Libheif;LibRaw;Nuke;OpenCV;OpenGL;OpenJPEG;openjph;OpenCV;OpenVDB;Ptex;pystring;Qt5;Qt6;TBB;R3DSDK;${{matrix.optional_deps_append}}' }} - build_local_deps: ${{ matrix.build_local_deps }} - oiio_python_bindings_backend: ${{ matrix.oiio_python_bindings_backend || '' }} - strategy: - fail-fast: false - matrix: - include: - - desc: Windows-2022 VS2022 - runner: windows-2022 - nametag: windows-2022 - generator: "Visual Studio 17 2022" - python_ver: "3.12" - ctest_test_timeout: "240" - setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 - - desc: Windows-2025 VS2026 - runner: windows-2025-vs2026 - nametag: windows-2025-vs2026 - generator: "Visual Studio 18 2026" - python_ver: "3.12" - ctest_test_timeout: "240" - setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 - benchmark: 1 - oiio_python_bindings_backend: both + # windows: + # if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'linux-only') && ! contains(github.ref, 'macos-only') }} + # name: "${{matrix.desc}}" + # uses: ./.github/workflows/build-steps.yml + # with: + # nametag: ${{ matrix.nametag || 'unnamed!' }} + # runner: ${{ matrix.runner || 'ubuntu-latest' }} + # container: ${{ matrix.container }} + # cc_compiler: ${{ matrix.cc_compiler }} + # cxx_compiler: ${{ matrix.cxx_compiler }} + # cxx_std: ${{ matrix.cxx_std || '17' }} + # build_type: ${{ matrix.build_type || 'Release' }} + # depcmds: ${{ matrix.depcmds }} + # extra_artifacts: ${{ matrix.extra_artifacts }} + # fmt_ver: ${{ matrix.fmt_ver }} + # fmt_commit: ${{ matrix.fmt_commit }} + # opencolorio_ver: ${{ matrix.opencolorio_ver }} + # openexr_ver: ${{ matrix.openexr_ver }} + # pybind11_ver: ${{ matrix.pybind11_ver }} + # python_ver: ${{ matrix.python_ver }} + # setenvs: ${{ matrix.setenvs }} + # simd: ${{ matrix.simd }} + # skip_build: ${{ matrix.skip_build }} + # skip_tests: ${{ matrix.skip_tests }} + # benchmark: ${{ matrix.benchmark }} + # abi_check: ${{ matrix.abi_check }} + # build_docs: ${{ matrix.build_docs }} + # generator: ${{ matrix.generator }} + # ctest_args: ${{ matrix.ctest_args }} + # ctest_test_timeout: ${{ matrix.ctest_test_timeout }} + # coverage: ${{ matrix.coverage || 0 }} + # sonar: ${{ matrix.sonar || 0 }} + # # Windows is a PITA, so we expect very few dependencies to be present or + # # built. But we would like to add more dependencies and reduce this list + # # of exceptions in the future. + # required_deps: ${{ matrix.required_deps || 'all' }} + # optional_deps: ${{ matrix.optional_deps || 'BZip2;CUDAToolkit;DCMTK;FFmpeg;GIF;JXL;Libheif;LibRaw;Nuke;OpenCV;OpenGL;OpenJPEG;openjph;OpenCV;OpenVDB;Ptex;pystring;Qt5;Qt6;TBB;R3DSDK;${{matrix.optional_deps_append}}' }} + # build_local_deps: ${{ matrix.build_local_deps }} + # oiio_python_bindings_backend: ${{ matrix.oiio_python_bindings_backend || '' }} + # strategy: + # fail-fast: false + # matrix: + # include: + # - desc: Windows-2022 VS2022 + # runner: windows-2022 + # nametag: windows-2022 + # generator: "Visual Studio 17 2022" + # python_ver: "3.12" + # ctest_test_timeout: "240" + # setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 + # - desc: Windows-2025 VS2026 + # runner: windows-2025-vs2026 + # nametag: windows-2025-vs2026 + # generator: "Visual Studio 18 2026" + # python_ver: "3.12" + # ctest_test_timeout: "240" + # setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 + # benchmark: 1 + # oiio_python_bindings_backend: both From 61bee60ea6b7c0e18f26a2e2e3f253fb640c8107 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Fri, 10 Jul 2026 18:12:03 +0200 Subject: [PATCH 25/42] Remove custom libktx repository for Intel-MacOS Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54b96a364c..513b95de71 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -606,10 +606,6 @@ jobs: ctest_test_timeout: 1200 setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 INSTALL_QT=0 INSTALL_OPENCV=0 INSTALL_OPENVDB=0 - Ktx_GIT_REPOSITORY=https://github.com/atteneder/KTX-Software.git - Ktx_BUILD_VERSION=fix/macOS-universal-5.0.0 - Ktx_GIT_COMMIT=0ad1fc3b3f0c0403f25186829bccc81aee9a6921 - CMAKE_OSX_ARCHITECTURES=x86_64 # Temporarily link to the PR that addresses libktx build issues # TODO: revert this before merging, don't forget!!! optional_deps_append: 'OpenCV;OpenVDB;Qt5;Qt6' From e2b0c1003544a272ff1d02778bf329ad0130693a Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Fri, 10 Jul 2026 18:26:51 +0200 Subject: [PATCH 26/42] tmp Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 513b95de71..3a0a63bfd3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -606,6 +606,9 @@ jobs: ctest_test_timeout: 1200 setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 INSTALL_QT=0 INSTALL_OPENCV=0 INSTALL_OPENVDB=0 + ASTCENC_ISA_SSE41=ON + CMAKE_OSX_ARCHITECTURES=x86_64 + BASISU_SSE=ON BASISU_OPENCL=OFF # Temporarily link to the PR that addresses libktx build issues # TODO: revert this before merging, don't forget!!! optional_deps_append: 'OpenCV;OpenVDB;Qt5;Qt6' From f87f443678439eae9e7c9fe267859379d4a73b5d Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Fri, 10 Jul 2026 20:46:43 +0200 Subject: [PATCH 27/42] tmp Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a0a63bfd3..29e695b043 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -606,7 +606,7 @@ jobs: ctest_test_timeout: 1200 setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 INSTALL_QT=0 INSTALL_OPENCV=0 INSTALL_OPENVDB=0 - ASTCENC_ISA_SSE41=ON + ASTCENC_ISA_NONE=ON CMAKE_OSX_ARCHITECTURES=x86_64 BASISU_SSE=ON BASISU_OPENCL=OFF # Temporarily link to the PR that addresses libktx build issues From 01490b63cabd2b572a140cb216fe17e8e16bf127 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sat, 11 Jul 2026 00:13:59 +0200 Subject: [PATCH 28/42] tmp Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 29e695b043..7b961a087b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -604,11 +604,10 @@ jobs: cxx_std: 17 python_ver: "3.14" ctest_test_timeout: 1200 - setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 + setenvs: export MACOSX_DEPLOYMENT_TARGET=13.3 INSTALL_QT=0 INSTALL_OPENCV=0 INSTALL_OPENVDB=0 - ASTCENC_ISA_NONE=ON + ASTCENC_ISA_SSE41=ON CMAKE_OSX_ARCHITECTURES=x86_64 - BASISU_SSE=ON BASISU_OPENCL=OFF # Temporarily link to the PR that addresses libktx build issues # TODO: revert this before merging, don't forget!!! optional_deps_append: 'OpenCV;OpenVDB;Qt5;Qt6' From a773a4ca7f7e5293835bde0fb8813a7fa2c03065 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sat, 11 Jul 2026 01:59:24 +0200 Subject: [PATCH 29/42] tmp Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index b1aa10a001..28533a2aa5 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -72,6 +72,7 @@ else() # v5.0.0-rc1 or a branch with similar CMake setup -D LIBKTX_FEATURE_VK_UPLOAD=OFF -D LIBKTX_FEATURE_GL_UPLOAD=OFF -D LIBKTX_FEATURE_ETC_UNPACK=OFF # This has some weird licensing and I don't feel comfortable including it ... + -D ASTCENC_ISA_SSE41=ON -D CMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER} -D CMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER} # as per KTX-Software: From 4bd07f95a25d8b035d92a8db0c811aa34a988bc1 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sat, 11 Jul 2026 03:18:46 +0200 Subject: [PATCH 30/42] Fix Intel-MacOS libktx linkage issues Prior to this commit, libktx defaulted to using AVX2 on Intel-based MacOS runners which do not support that SIMD. This caused hard-to-debug linkage error messages related to ASTCENC whose source files were not integrated in libktx. For more details, see: https://github.com/KhronosGroup/KTX-Software/issues/1210 Revert previous temporary commits that were used to figure out hard-to-locally-reproduce linkage errors with libktx.a. Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 1168 ++++++++++++++++++------------------- src/cmake/build_Ktx.cmake | 30 +- 2 files changed, 610 insertions(+), 588 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b961a087b..ec1a2cfcfb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,511 +43,511 @@ jobs: # # Linux Tests using ASWF-docker containers # - # linux-aswf: - # if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} - # name: "${{matrix.desc}}" - # uses: ./.github/workflows/build-steps.yml - # with: - # nametag: ${{ matrix.nametag || 'unnamed!' }} - # runner: ${{ matrix.runner || 'ubuntu-latest' }} - # container: ${{ matrix.container }} - # container_volumes: ${{ matrix.container_volumes || '[]' }} - # cc_compiler: ${{ matrix.cc_compiler }} - # cxx_compiler: ${{ matrix.cxx_compiler }} - # cxx_std: ${{ matrix.cxx_std || '17' }} - # build_type: ${{ matrix.build_type || 'Release' }} - # depcmds: ${{ matrix.depcmds }} - # extra_artifacts: ${{ matrix.extra_artifacts }} - # fmt_ver: ${{ matrix.fmt_ver }} - # fmt_commit: ${{ matrix.fmt_commit }} - # opencolorio_ver: ${{ matrix.opencolorio_ver }} - # openexr_ver: ${{ matrix.openexr_ver }} - # pybind11_ver: ${{ matrix.pybind11_ver }} - # python_ver: ${{ matrix.python_ver }} - # setenvs: ${{ matrix.setenvs }} - # simd: ${{ matrix.simd }} - # skip_build: ${{ matrix.skip_build }} - # skip_tests: ${{ matrix.skip_tests }} - # abi_check: ${{ matrix.abi_check }} - # benchmark: ${{ matrix.benchmark }} - # build_docs: ${{ matrix.build_docs }} - # clang_format: ${{ matrix.clang_format }} - # generator: ${{ matrix.generator }} - # ctest_args: ${{ matrix.ctest_args }} - # ctest_test_timeout: ${{ matrix.ctest_test_timeout }} - # coverage: ${{ matrix.coverage || 0 }} - # sonar: ${{ matrix.sonar || 0 }} - # # Override required_deps to be 'all' and explicitly list as optional - # # only the ones we are intentionally not testing for those jobs. - # required_deps: ${{ matrix.required_deps || 'all' }} - # optional_deps: ${{ matrix.optional_deps || 'DCMTK;FFmpeg;JXL;Libheif;Nuke;OpenCV;openjph;OpenVDB;Qt5;R3DSDK;'}}${{matrix.optional_deps_append}} - # build_local_deps: ${{ matrix.build_local_deps }} - # strategy: - # fail-fast: false - # matrix: - # include: - # - desc: VFX2023 gcc11/C++17 py3.10 exr3.1 ocio2.3 - # nametag: linux-vfx2023 - # runner: ubuntu-latest - # container: aswf/ci-osl:2023-clang15.2 - # opencolorio_ver: v2.3.0 - # python_ver: "3.10" - # simd: "avx2,f16c" - # fmt_ver: 10.1.1 - # fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 - # pybind11_ver: v2.10.0 - # setenvs: export PUGIXML_VERSION=v1.13 - # optional_deps_append: 'LibRaw;Ptex;Qt6' - # - desc: VFX2024 gcc11/C++17 py3.11 exr3.2 ocio2.3 - # nametag: linux-vfx2024 - # runner: ubuntu-latest - # container: aswf/ci-oiio:2024.6 - # opencolorio_ver: v2.3.2 - # python_ver: "3.11" - # simd: "avx2,f16c" - # fmt_ver: 10.1.1 - # fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 - # pybind11_ver: v2.12.0 - # setenvs: export PUGIXML_VERSION=v1.14 - # optional_deps_append: "LibRaw" - # - desc: VFX2024 clang/C++17 py3.11 exr3.2 ocio2.3 - # nametag: linux-vfx2024.clang - # runner: ubuntu-latest - # container: aswf/ci-oiio:2024.6 - # cc_compiler: clang - # cxx_compiler: clang++ - # opencolorio_ver: v2.3.2 - # python_ver: "3.11" - # simd: "avx2,f16c" - # fmt_ver: 10.1.1 - # fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 - # pybind11_ver: v2.12.0 - # setenvs: export PUGIXML_VERSION=v1.14 - # optional_deps_append: "LibRaw" - # - desc: VFX2025 gcc11/C++17 py3.11 exr3.3 ocio2.4 - # nametag: linux-vfx2025 - # runner: ubuntu-latest - # container: aswf/ci-oiio:2025.5 - # cxx_std: 17 - # python_ver: "3.11" - # simd: "avx2,f16c" - # fmt_ver: 11.2.0 - # fmt_commit: 40626af88bd7df9a5fb80be7b25ac85b122d6c21 - # pybind11_ver: v2.13.6 - # benchmark: 1 - # setenvs: export PUGIXML_VERSION=v1.15 - # optional_deps_append: "openjph;Qt6" - # - desc: VFX2025 Debug gcc11/C++17 py3.11 exr3.3 ocio2.4 - # nametag: linux-vfx2025-debug - # runner: ubuntu-latest - # container: aswf/ci-oiio:2025.5 - # cxx_std: 17 - # build_type: Debug - # ctest_test_timeout: "300" - # python_ver: "3.11" - # simd: "avx2,f16c" - # fmt_ver: 11.2.0 - # fmt_commit: 40626af88bd7df9a5fb80be7b25ac85b122d6c21 - # pybind11_ver: v2.13.6 - # setenvs: export PUGIXML_VERSION=v1.15 - # optional_deps_append: "openjph;Qt6" - # # - desc: VFX2025 Static gcc11/C++17 py3.11 exr3.3 ocio2.4 - # # nametag: linux-vfx2025-static - # # runner: ubuntu-latest - # # container: aswf/ci-oiio:2025.5 - # # cxx_std: 17 - # # python_ver: "3.11" - # # simd: "avx2,f16c" - # # fmt_ver: 11.2.0 - # # pybind11_ver: v2.13.6 - # # benchmark: 1 - # # setenvs: export PUGIXML_VERSION=v1.15 - # # BUILD_SHARED_LIBS=OFF - # # optional_deps_append: "openjph;Qt6" - # - desc: VFX2025 icx/C++17 py3.11 exr3.3 ocio2.4 qt5.15 - # nametag: linux-vfx2025.icx - # runner: ubuntu-latest - # container: aswf/ci-oiio:2025.5 - # cc_compiler: icx - # cxx_compiler: icpx - # fmt_ver: 11.2.0 - # fmt_commit: 40626af88bd7df9a5fb80be7b25ac85b122d6c21 - # python_ver: "3.11" - # pybind11_ver: v2.13.6 - # simd: "avx2,f16c" - # benchmark: 1 - # setenvs: export USE_OPENVDB=0 USE_OPENCV=0 - # UHDR_CMAKE_C_COMPILER=gcc - # UHDR_CMAKE_CXX_COMPILER=g++ - # KTX_CMAKE_C_COMPILER=gcc - # KTX_CMAKE_CXX_COMPILER=g++ - # # Building libuhdr and libktx with icx results in test failures - # optional_deps_append: "LibRaw;Ptex;openjph;Qt6" - # - desc: VFX2026 gcc14/C++20 py3.13 exr3.4 ocio2.5 - # nametag: linux-vfx2026 - # runner: ubuntu-latest - # container: aswf/ci-oiio:2026.3 - # cxx_std: 20 - # python_ver: "3.13" - # simd: "avx2,f16c" - # pybind11_ver: v3.0.0 - # benchmark: 1 - # # setenvs: export - # optional_deps_append: "Qt5;Qt6" - # - desc: Sanitizers - # nametag: sanitizer - # runner: ubuntu-latest - # container: aswf/ci-oiio:2026.3 - # cc_compiler: clang - # cxx_compiler: clang++ - # build_type: Debug - # python_ver: "3.13" - # ctest_test_timeout: "1200" - # setenvs: export SANITIZE=address,undefined - # OIIO_CMAKE_FLAGS="-DSANITIZE=address,undefined -DOIIO_HARDENING=3 -DUSE_PYTHON=0" - # CTEST_EXCLUSIONS="broken|png-damaged" - # OpenImageIO_BUILD_LOCAL_DEPS=PNG - # optional_deps_append: "LibRaw" + linux-aswf: + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} + name: "${{matrix.desc}}" + uses: ./.github/workflows/build-steps.yml + with: + nametag: ${{ matrix.nametag || 'unnamed!' }} + runner: ${{ matrix.runner || 'ubuntu-latest' }} + container: ${{ matrix.container }} + container_volumes: ${{ matrix.container_volumes || '[]' }} + cc_compiler: ${{ matrix.cc_compiler }} + cxx_compiler: ${{ matrix.cxx_compiler }} + cxx_std: ${{ matrix.cxx_std || '17' }} + build_type: ${{ matrix.build_type || 'Release' }} + depcmds: ${{ matrix.depcmds }} + extra_artifacts: ${{ matrix.extra_artifacts }} + fmt_ver: ${{ matrix.fmt_ver }} + fmt_commit: ${{ matrix.fmt_commit }} + opencolorio_ver: ${{ matrix.opencolorio_ver }} + openexr_ver: ${{ matrix.openexr_ver }} + pybind11_ver: ${{ matrix.pybind11_ver }} + python_ver: ${{ matrix.python_ver }} + setenvs: ${{ matrix.setenvs }} + simd: ${{ matrix.simd }} + skip_build: ${{ matrix.skip_build }} + skip_tests: ${{ matrix.skip_tests }} + abi_check: ${{ matrix.abi_check }} + benchmark: ${{ matrix.benchmark }} + build_docs: ${{ matrix.build_docs }} + clang_format: ${{ matrix.clang_format }} + generator: ${{ matrix.generator }} + ctest_args: ${{ matrix.ctest_args }} + ctest_test_timeout: ${{ matrix.ctest_test_timeout }} + coverage: ${{ matrix.coverage || 0 }} + sonar: ${{ matrix.sonar || 0 }} + # Override required_deps to be 'all' and explicitly list as optional + # only the ones we are intentionally not testing for those jobs. + required_deps: ${{ matrix.required_deps || 'all' }} + optional_deps: ${{ matrix.optional_deps || 'DCMTK;FFmpeg;JXL;Libheif;Nuke;OpenCV;openjph;OpenVDB;Qt5;R3DSDK;'}}${{matrix.optional_deps_append}} + build_local_deps: ${{ matrix.build_local_deps }} + strategy: + fail-fast: false + matrix: + include: + - desc: VFX2023 gcc11/C++17 py3.10 exr3.1 ocio2.3 + nametag: linux-vfx2023 + runner: ubuntu-latest + container: aswf/ci-osl:2023-clang15.2 + opencolorio_ver: v2.3.0 + python_ver: "3.10" + simd: "avx2,f16c" + fmt_ver: 10.1.1 + fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 + pybind11_ver: v2.10.0 + setenvs: export PUGIXML_VERSION=v1.13 + optional_deps_append: 'LibRaw;Ptex;Qt6' + - desc: VFX2024 gcc11/C++17 py3.11 exr3.2 ocio2.3 + nametag: linux-vfx2024 + runner: ubuntu-latest + container: aswf/ci-oiio:2024.6 + opencolorio_ver: v2.3.2 + python_ver: "3.11" + simd: "avx2,f16c" + fmt_ver: 10.1.1 + fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 + pybind11_ver: v2.12.0 + setenvs: export PUGIXML_VERSION=v1.14 + optional_deps_append: "LibRaw" + - desc: VFX2024 clang/C++17 py3.11 exr3.2 ocio2.3 + nametag: linux-vfx2024.clang + runner: ubuntu-latest + container: aswf/ci-oiio:2024.6 + cc_compiler: clang + cxx_compiler: clang++ + opencolorio_ver: v2.3.2 + python_ver: "3.11" + simd: "avx2,f16c" + fmt_ver: 10.1.1 + fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 + pybind11_ver: v2.12.0 + setenvs: export PUGIXML_VERSION=v1.14 + optional_deps_append: "LibRaw" + - desc: VFX2025 gcc11/C++17 py3.11 exr3.3 ocio2.4 + nametag: linux-vfx2025 + runner: ubuntu-latest + container: aswf/ci-oiio:2025.5 + cxx_std: 17 + python_ver: "3.11" + simd: "avx2,f16c" + fmt_ver: 11.2.0 + fmt_commit: 40626af88bd7df9a5fb80be7b25ac85b122d6c21 + pybind11_ver: v2.13.6 + benchmark: 1 + setenvs: export PUGIXML_VERSION=v1.15 + optional_deps_append: "openjph;Qt6" + - desc: VFX2025 Debug gcc11/C++17 py3.11 exr3.3 ocio2.4 + nametag: linux-vfx2025-debug + runner: ubuntu-latest + container: aswf/ci-oiio:2025.5 + cxx_std: 17 + build_type: Debug + ctest_test_timeout: "300" + python_ver: "3.11" + simd: "avx2,f16c" + fmt_ver: 11.2.0 + fmt_commit: 40626af88bd7df9a5fb80be7b25ac85b122d6c21 + pybind11_ver: v2.13.6 + setenvs: export PUGIXML_VERSION=v1.15 + optional_deps_append: "openjph;Qt6" + # - desc: VFX2025 Static gcc11/C++17 py3.11 exr3.3 ocio2.4 + # nametag: linux-vfx2025-static + # runner: ubuntu-latest + # container: aswf/ci-oiio:2025.5 + # cxx_std: 17 + # python_ver: "3.11" + # simd: "avx2,f16c" + # fmt_ver: 11.2.0 + # pybind11_ver: v2.13.6 + # benchmark: 1 + # setenvs: export PUGIXML_VERSION=v1.15 + # BUILD_SHARED_LIBS=OFF + # optional_deps_append: "openjph;Qt6" + - desc: VFX2025 icx/C++17 py3.11 exr3.3 ocio2.4 qt5.15 + nametag: linux-vfx2025.icx + runner: ubuntu-latest + container: aswf/ci-oiio:2025.5 + cc_compiler: icx + cxx_compiler: icpx + fmt_ver: 11.2.0 + fmt_commit: 40626af88bd7df9a5fb80be7b25ac85b122d6c21 + python_ver: "3.11" + pybind11_ver: v2.13.6 + simd: "avx2,f16c" + benchmark: 1 + setenvs: export USE_OPENVDB=0 USE_OPENCV=0 + UHDR_CMAKE_C_COMPILER=gcc + UHDR_CMAKE_CXX_COMPILER=g++ + KTX_CMAKE_C_COMPILER=gcc + KTX_CMAKE_CXX_COMPILER=g++ + # Building libuhdr and libktx with icx results in test failures + optional_deps_append: "LibRaw;Ptex;openjph;Qt6" + - desc: VFX2026 gcc14/C++20 py3.13 exr3.4 ocio2.5 + nametag: linux-vfx2026 + runner: ubuntu-latest + container: aswf/ci-oiio:2026.3 + cxx_std: 20 + python_ver: "3.13" + simd: "avx2,f16c" + pybind11_ver: v3.0.0 + benchmark: 1 + # setenvs: export + optional_deps_append: "Qt5;Qt6" + - desc: Sanitizers + nametag: sanitizer + runner: ubuntu-latest + container: aswf/ci-oiio:2026.3 + cc_compiler: clang + cxx_compiler: clang++ + build_type: Debug + python_ver: "3.13" + ctest_test_timeout: "1200" + setenvs: export SANITIZE=address,undefined + OIIO_CMAKE_FLAGS="-DSANITIZE=address,undefined -DOIIO_HARDENING=3 -DUSE_PYTHON=0" + CTEST_EXCLUSIONS="broken|png-damaged" + OpenImageIO_BUILD_LOCAL_DEPS=PNG + optional_deps_append: "LibRaw" - # # Test ABI stability. `abi_check` is the version or commit that we - # # believe is the current standard against which we don't want to - # # break the ABI. Basically, we will build that version as well as - # # the current one, and compare the resulting libraries. - # - desc: ABI check - # nametag: abi-check - # runner: ubuntu-latest - # container: aswf/ci-oiio:2025.5 - # build_type: RelWithDebInfo - # fmt_ver: 11.1.4 - # fmt_commit: 123913715afeb8a437e6388b4473fcc4753e1c9a - # python_ver: "3.11" - # pybind11_ver: v3.0.0 - # simd: "avx2,f16c" - # skip_tests: 1 - # # abi_check: v3.1.6.0 - # abi_check: d4c8024633dba8bb3c01d22b65ce9bc7a1ae215e - # setenvs: export OIIO_CMAKE_FLAGS="-DOIIO_BUILD_TOOLS=0 -DOIIO_BUILD_TESTS=0 -DUSE_PYTHON=0" - # USE_OPENCV=0 USE_FFMPEG=0 USE_PYTHON=0 USE_FREETYPE=0 - # optional_deps_append: "openjph;Qt6" + # Test ABI stability. `abi_check` is the version or commit that we + # believe is the current standard against which we don't want to + # break the ABI. Basically, we will build that version as well as + # the current one, and compare the resulting libraries. + - desc: ABI check + nametag: abi-check + runner: ubuntu-latest + container: aswf/ci-oiio:2025.5 + build_type: RelWithDebInfo + fmt_ver: 11.1.4 + fmt_commit: 123913715afeb8a437e6388b4473fcc4753e1c9a + python_ver: "3.11" + pybind11_ver: v3.0.0 + simd: "avx2,f16c" + skip_tests: 1 + # abi_check: v3.1.6.0 + abi_check: d4c8024633dba8bb3c01d22b65ce9bc7a1ae215e + setenvs: export OIIO_CMAKE_FLAGS="-DOIIO_BUILD_TOOLS=0 -DOIIO_BUILD_TESTS=0 -DUSE_PYTHON=0" + USE_OPENCV=0 USE_FFMPEG=0 USE_PYTHON=0 USE_FREETYPE=0 + optional_deps_append: "openjph;Qt6" # # Linux Tests using GHA Ubuntu runners directly # - # linux-ubuntu: - # if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} - # name: "${{matrix.desc}}" - # uses: ./.github/workflows/build-steps.yml - # with: - # nametag: ${{ matrix.nametag || 'unnamed!' }} - # runner: ${{ matrix.runner || 'ubuntu-latest' }} - # container: ${{ matrix.container }} - # cc_compiler: ${{ matrix.cc_compiler }} - # cxx_compiler: ${{ matrix.cxx_compiler }} - # cxx_std: ${{ matrix.cxx_std || '17' }} - # build_type: ${{ matrix.build_type || 'Release' }} - # depcmds: ${{ matrix.depcmds }} - # extra_artifacts: ${{ matrix.extra_artifacts }} - # fmt_ver: ${{ matrix.fmt_ver }} - # fmt_commit: ${{ matrix.fmt_commit }} - # opencolorio_ver: ${{ matrix.opencolorio_ver }} - # openexr_ver: ${{ matrix.openexr_ver }} - # pybind11_ver: ${{ matrix.pybind11_ver }} - # python_ver: ${{ matrix.python_ver }} - # setenvs: ${{ matrix.setenvs }} - # simd: ${{ matrix.simd }} - # skip_build: ${{ matrix.skip_build }} - # skip_tests: ${{ matrix.skip_tests }} - # abi_check: ${{ matrix.abi_check }} - # benchmark: ${{ matrix.benchmark }} - # build_docs: ${{ matrix.build_docs }} - # clang_format: ${{ matrix.clang_format }} - # generator: ${{ matrix.generator }} - # ctest_args: ${{ matrix.ctest_args }} - # ctest_test_timeout: ${{ matrix.ctest_test_timeout }} - # coverage: ${{ matrix.coverage || 0 }} - # sonar: ${{ matrix.sonar || 0 }} - # gcc_action_ver: ${{ matrix.gcc_action_ver }} - # llvm_action_ver: ${{ matrix.llvm_action_ver }} - # python_action_ver: ${{ matrix.python_action_ver }} - # # Override required_deps to be 'all' and explicitly list as optional - # # only the ones we are intentionally not testing for those jobs. - # required_deps: ${{ matrix.required_deps || 'all' }} - # optional_deps: ${{ matrix.optional_deps || 'CUDAToolkit;DCMTK;JXL;Nuke;OpenGL;openjph;OpenVDB;Ptex;pystring;Qt5;R3DSDK;Libheif;' }}${{matrix.optional_deps_append}} - # build_local_deps: ${{ matrix.build_local_deps }} - # oiio_python_bindings_backend: ${{ matrix.oiio_python_bindings_backend || '' }} - # strategy: - # fail-fast: false - # matrix: - # include: - # # Test formatting. This test entry doesn't build at all, it - # # just runs clang-format on everything, and passes if nothing is - # # misformatted. Upon failure, the build artifact will be the full - # # source code with the formatting fixed (diffs will also appear in - # # the console output). - # - desc: "clang-format" - # nametag: clang-format - # runner: ubuntu-24.04 - # cxx_std: 17 - # extra_artifacts: "src/*.*" - # python_ver: "3.10" - # pybind11_ver: "0" - # clang_format: 1 - # skip_build: 1 - # skip_tests: 1 - # setenvs: export SKIP_SYSTEM_DEPS_INSTALL=1 SKIP_APT_GET_UPDATE=1 - # INSTALL_OPENCV=0 QT_VERSION=0 USE_LIBHEIF=0 - # EXTRA_DEP_PACKAGES="clang-format-17" - # CLANG_FORMAT_EXE=clang-format-17 + linux-ubuntu: + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} + name: "${{matrix.desc}}" + uses: ./.github/workflows/build-steps.yml + with: + nametag: ${{ matrix.nametag || 'unnamed!' }} + runner: ${{ matrix.runner || 'ubuntu-latest' }} + container: ${{ matrix.container }} + cc_compiler: ${{ matrix.cc_compiler }} + cxx_compiler: ${{ matrix.cxx_compiler }} + cxx_std: ${{ matrix.cxx_std || '17' }} + build_type: ${{ matrix.build_type || 'Release' }} + depcmds: ${{ matrix.depcmds }} + extra_artifacts: ${{ matrix.extra_artifacts }} + fmt_ver: ${{ matrix.fmt_ver }} + fmt_commit: ${{ matrix.fmt_commit }} + opencolorio_ver: ${{ matrix.opencolorio_ver }} + openexr_ver: ${{ matrix.openexr_ver }} + pybind11_ver: ${{ matrix.pybind11_ver }} + python_ver: ${{ matrix.python_ver }} + setenvs: ${{ matrix.setenvs }} + simd: ${{ matrix.simd }} + skip_build: ${{ matrix.skip_build }} + skip_tests: ${{ matrix.skip_tests }} + abi_check: ${{ matrix.abi_check }} + benchmark: ${{ matrix.benchmark }} + build_docs: ${{ matrix.build_docs }} + clang_format: ${{ matrix.clang_format }} + generator: ${{ matrix.generator }} + ctest_args: ${{ matrix.ctest_args }} + ctest_test_timeout: ${{ matrix.ctest_test_timeout }} + coverage: ${{ matrix.coverage || 0 }} + sonar: ${{ matrix.sonar || 0 }} + gcc_action_ver: ${{ matrix.gcc_action_ver }} + llvm_action_ver: ${{ matrix.llvm_action_ver }} + python_action_ver: ${{ matrix.python_action_ver }} + # Override required_deps to be 'all' and explicitly list as optional + # only the ones we are intentionally not testing for those jobs. + required_deps: ${{ matrix.required_deps || 'all' }} + optional_deps: ${{ matrix.optional_deps || 'CUDAToolkit;DCMTK;JXL;Nuke;OpenGL;openjph;OpenVDB;Ptex;pystring;Qt5;R3DSDK;Libheif;' }}${{matrix.optional_deps_append}} + build_local_deps: ${{ matrix.build_local_deps }} + oiio_python_bindings_backend: ${{ matrix.oiio_python_bindings_backend || '' }} + strategy: + fail-fast: false + matrix: + include: + # Test formatting. This test entry doesn't build at all, it + # just runs clang-format on everything, and passes if nothing is + # misformatted. Upon failure, the build artifact will be the full + # source code with the formatting fixed (diffs will also appear in + # the console output). + - desc: "clang-format" + nametag: clang-format + runner: ubuntu-24.04 + cxx_std: 17 + extra_artifacts: "src/*.*" + python_ver: "3.10" + pybind11_ver: "0" + clang_format: 1 + skip_build: 1 + skip_tests: 1 + setenvs: export SKIP_SYSTEM_DEPS_INSTALL=1 SKIP_APT_GET_UPDATE=1 + INSTALL_OPENCV=0 QT_VERSION=0 USE_LIBHEIF=0 + EXTRA_DEP_PACKAGES="clang-format-17" + CLANG_FORMAT_EXE=clang-format-17 - # - desc: latest releases gcc15 C++23 py3.12 avx2 exr3.4 ocio2.4 - # nametag: linux-latest-releases - # oiio_python_bindings_backend: both - # runner: ubuntu-24.04 - # # cc_compiler: gcc-13 - # # cxx_compiler: g++-13 - # cxx_std: 23 - # fmt_ver: 12.1.0 - # fmt_commit: 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f - # opencolorio_ver: v2.5.0 - # openexr_ver: v3.4.3 - # pybind11_ver: v3.0.2 - # python_ver: "3.12" - # simd: avx2,f16c - # gcc_action_ver: 15 - # setenvs: export OIIO_CC=gcc-15 OIIO_CXX=g++-15 - # LIBJPEGTURBO_VERSION=3.1.2 - # LIBPNG_VERSION=v1.6.50 - # LIBRAW_VERSION=0.22.0 - # LIBTIFF_VERSION=v4.7.1 - # OPENJPEG_VERSION=v2.5.4 - # PTEX_VERSION=v2.5.0 - # PUGIXML_VERSION=v1.15 - # WEBP_VERSION=v1.6.0 - # FREETYPE_VERSION=VER-2-14-1 - # USE_OPENVDB=0 - # UHDR_CMAKE_C_COMPILER=gcc - # UHDR_CMAKE_CXX_COMPILER=g++ - # OPENCOLORIO_CXX=c++ - # # Ensure we are testing all the deps we think we are. We would - # # like this test to have minimal missing dependencies. - # required_deps: all - # optional_deps: 'CUDAToolkit;DCMTK;JXL;Nuke;OpenCV;OpenGL;OpenVDB;R3DSDK' - # - desc: bleeding edge gcc16 C++26 py3.12 OCIO/libtiff/exr-main avx2 - # nametag: linux-bleeding-edge - # runner: ubuntu-24.04 - # # cc_compiler: gcc-15 - # # cxx_compiler: g++-15 - # cxx_std: 26 - # fmt_ver: main - # opencolorio_ver: main - # openexr_ver: main - # pybind11_ver: master - # python_ver: "3.12" - # simd: avx2,f16c - # benchmark: 1 - # gcc_action_ver: 16 - # setenvs: export OIIO_CC=gcc-16 OIIO_CXX=g++-16 - # LIBJPEGTURBO_VERSION=main - # LIBPNG_VERSION=master - # LIBRAW_VERSION=master - # LIBTIFF_VERSION=master - # OPENJPEG_VERSION=master - # PTEX_VERSION=main - # PUGIXML_VERSION=master - # WEBP_VERSION=main - # OIIO_HARDENING=2 - # EXTRA_DEP_PACKAGES="python3.12-dev python3-numpy" - # FREETYPE_VERSION=master - # OpenImageIO_DEPENDENCY_BUILD_ALLOW_UNVERIFIED_TAGS=ON - # QT_VERSION=0 INSTALL_OPENCV=0 USE_OPENVDB=0 - # UHDR_CMAKE_C_COMPILER=gcc - # UHDR_CMAKE_CXX_COMPILER=g++ - # OPENCOLORIO_CXX=c++ - # # Ensure we are testing all the deps we think we are. We would - # # like this test to have minimal missing dependencies. - # required_deps: all - # optional_deps: 'CUDAToolkit;DCMTK;JXL;libuhdr;Nuke;OpenCV;OpenGL;openjph;R3DSDK;' - # - desc: all local builds gcc12 C++17 avx2 exr3.2 ocio2.3 - # nametag: linux-local-builds - # runner: ubuntu-22.04 - # cc_compiler: gcc-12 - # cxx_compiler: g++-12 - # cxx_std: 17 - # python_ver: "3.10" - # simd: avx2,f16c - # build_local_deps: all - # setenvs: export OpenImageIO_DEPENDENCY_BUILD_VERBOSE=ON - # LIBRAW_VERSION=0.22.0 - # PTEX_VERSION=v2.4.2 - # PUGIXML_VERSION=v1.14 - # WEBP_VERSION=v1.4.0 - # OIIO_HARDENING=3 - # - desc: clang18 C++17 avx2 exr3.1 ocio2.3 - # nametag: linux-clang18 - # runner: ubuntu-24.04 - # cxx_compiler: clang++ - # cc_compiler: clang - # cxx_std: 17 - # fmt_ver: 10.1.1 - # fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 - # opencolorio_ver: v2.3.0 - # openexr_ver: v3.1.13 - # pybind11_ver: v2.12.0 - # python_ver: "3.12" - # simd: avx2,f16c - # setenvs: export USE_OPENVDB=0 - # - desc: Linux ARM latest releases gcc14 C++20 py3.12 exr3.4 ocio2.4 - # nametag: linux-arm-latest-releases - # runner: ubuntu-24.04-arm - # cc_compiler: gcc-14 - # cxx_compiler: g++-14 - # cxx_std: 20 - # fmt_ver: 12.1.0 - # fmt_commit: 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f - # opencolorio_ver: v2.5.0 - # openexr_ver: v3.4.3 - # pybind11_ver: v3.0.2 - # python_ver: "3.12" - # setenvs: export LIBJPEGTURBO_VERSION=3.1.2 - # LIBPNG_VERSION=v1.6.50 - # LIBRAW_VERSION=0.22.0 - # LIBTIFF_VERSION=v4.7.1 - # OPENJPEG_VERSION=v2.5.4 - # PTEX_VERSION=v2.4.3 - # PUGIXML_VERSION=v1.15 - # WEBP_VERSION=v1.6.0 - # FREETYPE_VERSION=VER-2-14-1 - # USE_OPENVDB=0 - # - desc: Linux ARM latest releases clang18 C++20 py3.12 exr3.4 ocio2.4 - # nametag: linux-arm-latest-releases-clang - # runner: ubuntu-24.04-arm - # cc_compiler: clang-18 - # cxx_compiler: clang++-18 - # cxx_std: 20 - # fmt_ver: 12.1.0 - # fmt_commit: 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f - # opencolorio_ver: v2.5.0 - # openexr_ver: v3.4.3 - # pybind11_ver: v3.0.2 - # python_ver: "3.12" - # setenvs: export LIBJPEGTURBO_VERSION=3.1.2 - # LIBPNG_VERSION=v1.6.50 - # LIBRAW_VERSION=0.22.0 - # LIBTIFF_VERSION=v4.7.1 - # OPENJPEG_VERSION=v2.5.4 - # PTEX_VERSION=v2.4.3 - # PUGIXML_VERSION=v1.15 - # WEBP_VERSION=v1.6.0 - # FREETYPE_VERSION=VER-2-14-1 - # USE_OPENVDB=0 + - desc: latest releases gcc15 C++23 py3.12 avx2 exr3.4 ocio2.4 + nametag: linux-latest-releases + oiio_python_bindings_backend: both + runner: ubuntu-24.04 + # cc_compiler: gcc-13 + # cxx_compiler: g++-13 + cxx_std: 23 + fmt_ver: 12.1.0 + fmt_commit: 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f + opencolorio_ver: v2.5.0 + openexr_ver: v3.4.3 + pybind11_ver: v3.0.2 + python_ver: "3.12" + simd: avx2,f16c + gcc_action_ver: 15 + setenvs: export OIIO_CC=gcc-15 OIIO_CXX=g++-15 + LIBJPEGTURBO_VERSION=3.1.2 + LIBPNG_VERSION=v1.6.50 + LIBRAW_VERSION=0.22.0 + LIBTIFF_VERSION=v4.7.1 + OPENJPEG_VERSION=v2.5.4 + PTEX_VERSION=v2.5.0 + PUGIXML_VERSION=v1.15 + WEBP_VERSION=v1.6.0 + FREETYPE_VERSION=VER-2-14-1 + USE_OPENVDB=0 + UHDR_CMAKE_C_COMPILER=gcc + UHDR_CMAKE_CXX_COMPILER=g++ + OPENCOLORIO_CXX=c++ + # Ensure we are testing all the deps we think we are. We would + # like this test to have minimal missing dependencies. + required_deps: all + optional_deps: 'CUDAToolkit;DCMTK;JXL;Nuke;OpenCV;OpenGL;OpenVDB;R3DSDK' + - desc: bleeding edge gcc16 C++26 py3.12 OCIO/libtiff/exr-main avx2 + nametag: linux-bleeding-edge + runner: ubuntu-24.04 + # cc_compiler: gcc-15 + # cxx_compiler: g++-15 + cxx_std: 26 + fmt_ver: main + opencolorio_ver: main + openexr_ver: main + pybind11_ver: master + python_ver: "3.12" + simd: avx2,f16c + benchmark: 1 + gcc_action_ver: 16 + setenvs: export OIIO_CC=gcc-16 OIIO_CXX=g++-16 + LIBJPEGTURBO_VERSION=main + LIBPNG_VERSION=master + LIBRAW_VERSION=master + LIBTIFF_VERSION=master + OPENJPEG_VERSION=master + PTEX_VERSION=main + PUGIXML_VERSION=master + WEBP_VERSION=main + OIIO_HARDENING=2 + EXTRA_DEP_PACKAGES="python3.12-dev python3-numpy" + FREETYPE_VERSION=master + OpenImageIO_DEPENDENCY_BUILD_ALLOW_UNVERIFIED_TAGS=ON + QT_VERSION=0 INSTALL_OPENCV=0 USE_OPENVDB=0 + UHDR_CMAKE_C_COMPILER=gcc + UHDR_CMAKE_CXX_COMPILER=g++ + OPENCOLORIO_CXX=c++ + # Ensure we are testing all the deps we think we are. We would + # like this test to have minimal missing dependencies. + required_deps: all + optional_deps: 'CUDAToolkit;DCMTK;JXL;libuhdr;Nuke;OpenCV;OpenGL;openjph;R3DSDK;' + - desc: all local builds gcc12 C++17 avx2 exr3.2 ocio2.3 + nametag: linux-local-builds + runner: ubuntu-22.04 + cc_compiler: gcc-12 + cxx_compiler: g++-12 + cxx_std: 17 + python_ver: "3.10" + simd: avx2,f16c + build_local_deps: all + setenvs: export OpenImageIO_DEPENDENCY_BUILD_VERBOSE=ON + LIBRAW_VERSION=0.22.0 + PTEX_VERSION=v2.4.2 + PUGIXML_VERSION=v1.14 + WEBP_VERSION=v1.4.0 + OIIO_HARDENING=3 + - desc: clang18 C++17 avx2 exr3.1 ocio2.3 + nametag: linux-clang18 + runner: ubuntu-24.04 + cxx_compiler: clang++ + cc_compiler: clang + cxx_std: 17 + fmt_ver: 10.1.1 + fmt_commit: f5e54359df4c26b6230fc61d38aa294581393084 + opencolorio_ver: v2.3.0 + openexr_ver: v3.1.13 + pybind11_ver: v2.12.0 + python_ver: "3.12" + simd: avx2,f16c + setenvs: export USE_OPENVDB=0 + - desc: Linux ARM latest releases gcc14 C++20 py3.12 exr3.4 ocio2.4 + nametag: linux-arm-latest-releases + runner: ubuntu-24.04-arm + cc_compiler: gcc-14 + cxx_compiler: g++-14 + cxx_std: 20 + fmt_ver: 12.1.0 + fmt_commit: 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f + opencolorio_ver: v2.5.0 + openexr_ver: v3.4.3 + pybind11_ver: v3.0.2 + python_ver: "3.12" + setenvs: export LIBJPEGTURBO_VERSION=3.1.2 + LIBPNG_VERSION=v1.6.50 + LIBRAW_VERSION=0.22.0 + LIBTIFF_VERSION=v4.7.1 + OPENJPEG_VERSION=v2.5.4 + PTEX_VERSION=v2.4.3 + PUGIXML_VERSION=v1.15 + WEBP_VERSION=v1.6.0 + FREETYPE_VERSION=VER-2-14-1 + USE_OPENVDB=0 + - desc: Linux ARM latest releases clang18 C++20 py3.12 exr3.4 ocio2.4 + nametag: linux-arm-latest-releases-clang + runner: ubuntu-24.04-arm + cc_compiler: clang-18 + cxx_compiler: clang++-18 + cxx_std: 20 + fmt_ver: 12.1.0 + fmt_commit: 407c905e45ad75fc29bf0f9bb7c5c2fd3475976f + opencolorio_ver: v2.5.0 + openexr_ver: v3.4.3 + pybind11_ver: v3.0.2 + python_ver: "3.12" + setenvs: export LIBJPEGTURBO_VERSION=3.1.2 + LIBPNG_VERSION=v1.6.50 + LIBRAW_VERSION=0.22.0 + LIBTIFF_VERSION=v4.7.1 + OPENJPEG_VERSION=v2.5.4 + PTEX_VERSION=v2.4.3 + PUGIXML_VERSION=v1.15 + WEBP_VERSION=v1.6.0 + FREETYPE_VERSION=VER-2-14-1 + USE_OPENVDB=0 - # - desc: oldest gcc9/C++17 py3.9 exr3.1 ocio2.3 - # # Oldest gcc and versions of the dependencies that we support. - # nametag: linux-oldest-ubuntu - # runner: ubuntu-22.04 - # cc_compiler: gcc-9 - # cxx_compiler: g++-9 - # cxx_std: 17 - # fmt_ver: 9.0.0 - # fmt_commit: c4ee726532178e556d923372f29163bd206d7732 - # opencolorio_ver: v2.3.0 - # openexr_ver: v3.1.0 - # pybind11_ver: v2.7.0 - # python_ver: "3.9" - # python_action_ver: "3.9" - # gcc_action_ver: 9 - # setenvs: export CMAKE_VERSION=3.18.2 - # PTEX_VERSION=v2.3.2 - # WEBP_VERSION=v1.1.0 - # PUGIXML_VERSION=v1.8 - # BUILD_PNG_VERSION=1.6.0 - # PIP_SUFFIX=.9 - # PIP_INSTALLS=numpy - # Robinmap_BUILD_VERSION=1.2.0 - # Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d - # TIFF_BUILD_VERSION=4.0.0 - # TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 - # Ktx_BUILD_VERSION=v4.3.2 - # Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd - # # OpenJPEG_BUILD_VERSION=2.2.0 - # # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e - # # libktx > 4.3.2 requires CMake >= 3.22 - # optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' - # build_local_deps: 'TIFF' - # - desc: oldest clang11/C++17 py3.9 exr3.1 ocio2.3 - # # Oldest clang and versions of the dependencies that we support. - # nametag: linux-oldest-clang-ubuntu - # runner: ubuntu-22.04 - # cxx_std: 17 - # fmt_ver: 9.0.0 - # fmt_commit: c4ee726532178e556d923372f29163bd206d7732 - # opencolorio_ver: v2.3.0 - # openexr_ver: v3.1.0 - # pybind11_ver: v2.7.0 - # python_ver: "3.9" - # python_action_ver: "3.9" - # llvm_action_ver: "11" - # setenvs: export CMAKE_VERSION=3.18.2 - # PTEX_VERSION=v2.3.2 - # WEBP_VERSION=v1.1.0 - # PUGIXML_VERSION=v1.8 - # BUILD_PNG_VERSION=1.6.0 - # PIP_SUFFIX=.9 - # PIP_INSTALLS=numpy - # OIIO_CC=clang - # OIIO_CXX=clang++ - # Robinmap_BUILD_VERSION=1.2.0 - # Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d - # TIFF_BUILD_VERSION=4.0.0 - # TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 - # Ktx_BUILD_VERSION=v4.3.2 - # Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd - # # OpenJPEG_BUILD_VERSION=2.2.0 - # # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e - # # libktx > 4.3.2 requires CMake >= 3.22 - # optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' - # build_local_deps: 'TIFF' + - desc: oldest gcc9/C++17 py3.9 exr3.1 ocio2.3 + # Oldest gcc and versions of the dependencies that we support. + nametag: linux-oldest-ubuntu + runner: ubuntu-22.04 + cc_compiler: gcc-9 + cxx_compiler: g++-9 + cxx_std: 17 + fmt_ver: 9.0.0 + fmt_commit: c4ee726532178e556d923372f29163bd206d7732 + opencolorio_ver: v2.3.0 + openexr_ver: v3.1.0 + pybind11_ver: v2.7.0 + python_ver: "3.9" + python_action_ver: "3.9" + gcc_action_ver: 9 + setenvs: export CMAKE_VERSION=3.18.2 + PTEX_VERSION=v2.3.2 + WEBP_VERSION=v1.1.0 + PUGIXML_VERSION=v1.8 + BUILD_PNG_VERSION=1.6.0 + PIP_SUFFIX=.9 + PIP_INSTALLS=numpy + Robinmap_BUILD_VERSION=1.2.0 + Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d + TIFF_BUILD_VERSION=4.0.0 + TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 + Ktx_BUILD_VERSION=v4.3.2 + Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd + # OpenJPEG_BUILD_VERSION=2.2.0 + # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e + # libktx > 4.3.2 requires CMake >= 3.22 + optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' + build_local_deps: 'TIFF' + - desc: oldest clang11/C++17 py3.9 exr3.1 ocio2.3 + # Oldest clang and versions of the dependencies that we support. + nametag: linux-oldest-clang-ubuntu + runner: ubuntu-22.04 + cxx_std: 17 + fmt_ver: 9.0.0 + fmt_commit: c4ee726532178e556d923372f29163bd206d7732 + opencolorio_ver: v2.3.0 + openexr_ver: v3.1.0 + pybind11_ver: v2.7.0 + python_ver: "3.9" + python_action_ver: "3.9" + llvm_action_ver: "11" + setenvs: export CMAKE_VERSION=3.18.2 + PTEX_VERSION=v2.3.2 + WEBP_VERSION=v1.1.0 + PUGIXML_VERSION=v1.8 + BUILD_PNG_VERSION=1.6.0 + PIP_SUFFIX=.9 + PIP_INSTALLS=numpy + OIIO_CC=clang + OIIO_CXX=clang++ + Robinmap_BUILD_VERSION=1.2.0 + Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d + TIFF_BUILD_VERSION=4.0.0 + TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 + Ktx_BUILD_VERSION=v4.3.2 + Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd + # OpenJPEG_BUILD_VERSION=2.2.0 + # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e + # libktx > 4.3.2 requires CMake >= 3.22 + optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' + build_local_deps: 'TIFF' - # - desc: hobbled gcc9/C++17 py3.9 exr-3.1 no-sse - # # Use the oldest supported versions of required dependencies, and - # # disable most optional dependencies and features (no SSE or - # # OpenCV, don't embed plugins). - # nametag: linux-disabled-ubuntu - # runner: ubuntu-22.04 - # cc_compiler: gcc-9 - # cxx_compiler: g++-9 - # cxx_std: 17 - # fmt_ver: 9.0.0 - # fmt_commit: c4ee726532178e556d923372f29163bd206d7732 - # opencolorio_ver: v2.3.0 - # openexr_ver: v3.1.0 - # pybind11_ver: v2.7.0 - # python_ver: "3.9" - # python_action_ver: "3.9" - # gcc_action_ver: 9 - # simd: 0 - # setenvs: export EMBEDPLUGINS=0 - # CMAKE_VERSION=3.18.2 - # PTEX_VERSION=v2.3.2 - # WEBP_VERSION=v1.1.0 - # USE_JPEGTURBO=0 - # USE_OPENCV=0 - # USE_LIBUHDR=0 - # FREETYPE_VERSION=VER-2-10-0 - # PUGIXML_VERSION=v1.8 - # BUILD_PNG_VERSION=1.6.0 - # PIP_SUFFIX=.9 - # PIP_INSTALLS=numpy - # Robinmap_BUILD_VERSION=1.2.0 - # Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d - # TIFF_BUILD_VERSION=4.0.0 - # TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 - # Ktx_BUILD_VERSION=v4.3.2 - # Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd - # # libktx > 4.3.2 requires CMake >= 3.22 - # required_deps: none - # build_local_deps: 'TIFF' + - desc: hobbled gcc9/C++17 py3.9 exr-3.1 no-sse + # Use the oldest supported versions of required dependencies, and + # disable most optional dependencies and features (no SSE or + # OpenCV, don't embed plugins). + nametag: linux-disabled-ubuntu + runner: ubuntu-22.04 + cc_compiler: gcc-9 + cxx_compiler: g++-9 + cxx_std: 17 + fmt_ver: 9.0.0 + fmt_commit: c4ee726532178e556d923372f29163bd206d7732 + opencolorio_ver: v2.3.0 + openexr_ver: v3.1.0 + pybind11_ver: v2.7.0 + python_ver: "3.9" + python_action_ver: "3.9" + gcc_action_ver: 9 + simd: 0 + setenvs: export EMBEDPLUGINS=0 + CMAKE_VERSION=3.18.2 + PTEX_VERSION=v2.3.2 + WEBP_VERSION=v1.1.0 + USE_JPEGTURBO=0 + USE_OPENCV=0 + USE_LIBUHDR=0 + FREETYPE_VERSION=VER-2-10-0 + PUGIXML_VERSION=v1.8 + BUILD_PNG_VERSION=1.6.0 + PIP_SUFFIX=.9 + PIP_INSTALLS=numpy + Robinmap_BUILD_VERSION=1.2.0 + Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d + TIFF_BUILD_VERSION=4.0.0 + TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 + Ktx_BUILD_VERSION=v4.3.2 + Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd + # libktx > 4.3.2 requires CMake >= 3.22 + required_deps: none + build_local_deps: 'TIFF' # @@ -603,100 +603,100 @@ jobs: cxx_compiler: /usr/bin/clang++ cxx_std: 17 python_ver: "3.14" + simd: sse4.2,avx2 ctest_test_timeout: 1200 - setenvs: export MACOSX_DEPLOYMENT_TARGET=13.3 + setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 INSTALL_QT=0 INSTALL_OPENCV=0 INSTALL_OPENVDB=0 - ASTCENC_ISA_SSE41=ON - CMAKE_OSX_ARCHITECTURES=x86_64 - # Temporarily link to the PR that addresses libktx build issues - # TODO: revert this before merging, don't forget!!! + Ktx_ASTCENC_ISA=ASTCENC_ISA_SSE41 + # Intel-based MacOS runners don't support AVX2 SIMD which is the + # default set by libktx. Explicitly set to SSE4.1 (2nd fastest after AVX2) optional_deps_append: 'OpenCV;OpenVDB;Qt5;Qt6' benchmark: 1 - # - desc: MacOS-14-ARM aclang15/C++20/py3.13 - # runner: macos-14 - # nametag: macos14-arm-py313 - # oiio_python_bindings_backend: both - # cc_compiler: /usr/bin/clang - # cxx_compiler: /usr/bin/clang++ - # cxx_std: 20 - # python_ver: "3.13" - # - desc: MacOS-15-ARM aclang16/C++20/py3.13 - # runner: macos-15 - # nametag: macos15-arm-py313 - # cc_compiler: /usr/bin/clang - # cxx_compiler: /usr/bin/clang++ - # cxx_std: 20 - # python_ver: "3.13" - # benchmark: 1 - # - desc: MacOS-26-ARM aclang16/C++20/py3.13 - # runner: macos-26 - # nametag: macos26-arm-py313 - # cc_compiler: /usr/bin/clang - # cxx_compiler: /usr/bin/clang++ - # cxx_std: 20 - # python_ver: "3.14" - # benchmark: 1 + - desc: MacOS-14-ARM aclang15/C++20/py3.13 + runner: macos-14 + nametag: macos14-arm-py313 + oiio_python_bindings_backend: both + cc_compiler: /usr/bin/clang + cxx_compiler: /usr/bin/clang++ + cxx_std: 20 + python_ver: "3.13" + - desc: MacOS-15-ARM aclang16/C++20/py3.13 + runner: macos-15 + nametag: macos15-arm-py313 + cc_compiler: /usr/bin/clang + cxx_compiler: /usr/bin/clang++ + cxx_std: 20 + python_ver: "3.13" + benchmark: 1 + - desc: MacOS-26-ARM aclang16/C++20/py3.13 + runner: macos-26 + nametag: macos26-arm-py313 + cc_compiler: /usr/bin/clang + cxx_compiler: /usr/bin/clang++ + cxx_std: 20 + python_ver: "3.14" + benchmark: 1 # # Windows Tests # - # windows: - # if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'linux-only') && ! contains(github.ref, 'macos-only') }} - # name: "${{matrix.desc}}" - # uses: ./.github/workflows/build-steps.yml - # with: - # nametag: ${{ matrix.nametag || 'unnamed!' }} - # runner: ${{ matrix.runner || 'ubuntu-latest' }} - # container: ${{ matrix.container }} - # cc_compiler: ${{ matrix.cc_compiler }} - # cxx_compiler: ${{ matrix.cxx_compiler }} - # cxx_std: ${{ matrix.cxx_std || '17' }} - # build_type: ${{ matrix.build_type || 'Release' }} - # depcmds: ${{ matrix.depcmds }} - # extra_artifacts: ${{ matrix.extra_artifacts }} - # fmt_ver: ${{ matrix.fmt_ver }} - # fmt_commit: ${{ matrix.fmt_commit }} - # opencolorio_ver: ${{ matrix.opencolorio_ver }} - # openexr_ver: ${{ matrix.openexr_ver }} - # pybind11_ver: ${{ matrix.pybind11_ver }} - # python_ver: ${{ matrix.python_ver }} - # setenvs: ${{ matrix.setenvs }} - # simd: ${{ matrix.simd }} - # skip_build: ${{ matrix.skip_build }} - # skip_tests: ${{ matrix.skip_tests }} - # benchmark: ${{ matrix.benchmark }} - # abi_check: ${{ matrix.abi_check }} - # build_docs: ${{ matrix.build_docs }} - # generator: ${{ matrix.generator }} - # ctest_args: ${{ matrix.ctest_args }} - # ctest_test_timeout: ${{ matrix.ctest_test_timeout }} - # coverage: ${{ matrix.coverage || 0 }} - # sonar: ${{ matrix.sonar || 0 }} - # # Windows is a PITA, so we expect very few dependencies to be present or - # # built. But we would like to add more dependencies and reduce this list - # # of exceptions in the future. - # required_deps: ${{ matrix.required_deps || 'all' }} - # optional_deps: ${{ matrix.optional_deps || 'BZip2;CUDAToolkit;DCMTK;FFmpeg;GIF;JXL;Libheif;LibRaw;Nuke;OpenCV;OpenGL;OpenJPEG;openjph;OpenCV;OpenVDB;Ptex;pystring;Qt5;Qt6;TBB;R3DSDK;${{matrix.optional_deps_append}}' }} - # build_local_deps: ${{ matrix.build_local_deps }} - # oiio_python_bindings_backend: ${{ matrix.oiio_python_bindings_backend || '' }} - # strategy: - # fail-fast: false - # matrix: - # include: - # - desc: Windows-2022 VS2022 - # runner: windows-2022 - # nametag: windows-2022 - # generator: "Visual Studio 17 2022" - # python_ver: "3.12" - # ctest_test_timeout: "240" - # setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 - # - desc: Windows-2025 VS2026 - # runner: windows-2025-vs2026 - # nametag: windows-2025-vs2026 - # generator: "Visual Studio 18 2026" - # python_ver: "3.12" - # ctest_test_timeout: "240" - # setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 - # benchmark: 1 - # oiio_python_bindings_backend: both + windows: + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'linux-only') && ! contains(github.ref, 'macos-only') }} + name: "${{matrix.desc}}" + uses: ./.github/workflows/build-steps.yml + with: + nametag: ${{ matrix.nametag || 'unnamed!' }} + runner: ${{ matrix.runner || 'ubuntu-latest' }} + container: ${{ matrix.container }} + cc_compiler: ${{ matrix.cc_compiler }} + cxx_compiler: ${{ matrix.cxx_compiler }} + cxx_std: ${{ matrix.cxx_std || '17' }} + build_type: ${{ matrix.build_type || 'Release' }} + depcmds: ${{ matrix.depcmds }} + extra_artifacts: ${{ matrix.extra_artifacts }} + fmt_ver: ${{ matrix.fmt_ver }} + fmt_commit: ${{ matrix.fmt_commit }} + opencolorio_ver: ${{ matrix.opencolorio_ver }} + openexr_ver: ${{ matrix.openexr_ver }} + pybind11_ver: ${{ matrix.pybind11_ver }} + python_ver: ${{ matrix.python_ver }} + setenvs: ${{ matrix.setenvs }} + simd: ${{ matrix.simd }} + skip_build: ${{ matrix.skip_build }} + skip_tests: ${{ matrix.skip_tests }} + benchmark: ${{ matrix.benchmark }} + abi_check: ${{ matrix.abi_check }} + build_docs: ${{ matrix.build_docs }} + generator: ${{ matrix.generator }} + ctest_args: ${{ matrix.ctest_args }} + ctest_test_timeout: ${{ matrix.ctest_test_timeout }} + coverage: ${{ matrix.coverage || 0 }} + sonar: ${{ matrix.sonar || 0 }} + # Windows is a PITA, so we expect very few dependencies to be present or + # built. But we would like to add more dependencies and reduce this list + # of exceptions in the future. + required_deps: ${{ matrix.required_deps || 'all' }} + optional_deps: ${{ matrix.optional_deps || 'BZip2;CUDAToolkit;DCMTK;FFmpeg;GIF;JXL;Libheif;LibRaw;Nuke;OpenCV;OpenGL;OpenJPEG;openjph;OpenCV;OpenVDB;Ptex;pystring;Qt5;Qt6;TBB;R3DSDK;${{matrix.optional_deps_append}}' }} + build_local_deps: ${{ matrix.build_local_deps }} + oiio_python_bindings_backend: ${{ matrix.oiio_python_bindings_backend || '' }} + strategy: + fail-fast: false + matrix: + include: + - desc: Windows-2022 VS2022 + runner: windows-2022 + nametag: windows-2022 + generator: "Visual Studio 17 2022" + python_ver: "3.12" + ctest_test_timeout: "240" + setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 + - desc: Windows-2025 VS2026 + runner: windows-2025-vs2026 + nametag: windows-2025-vs2026 + generator: "Visual Studio 18 2026" + python_ver: "3.12" + ctest_test_timeout: "240" + setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 + benchmark: 1 + oiio_python_bindings_backend: both diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index 28533a2aa5..a9019c9e60 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -3,13 +3,16 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO set_cache (Ktx_BUILD_VERSION main "Ktx version for local builds") -set_cache (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git" - "git repository from where to fetch libktx") +set (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") set_cache (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") set_cache (Ktx_GIT_COMMIT "2ca7d54109f4c23298a969f22b68769e94138de5" "commit hash to verify tag/branch against") set_cache (Ktx_BUILD_SHARED_LIBS OFF ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} - DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) + DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) + +# TODO: if libktx is built as a shared library, astcenc have to be linked-against. +# I don't know how to 'cleanly' do this in OIIO CMake (yet). +# Even though all CIs pass, do not merge before addressing this! string (MAKE_C_IDENTIFIER ${Ktx_BUILD_VERSION} Ktx_VERSION_IDENT) @@ -17,6 +20,24 @@ string (MAKE_C_IDENTIFIER ${Ktx_BUILD_VERSION} Ktx_VERSION_IDENT) set_cache (KTX_CMAKE_C_COMPILER ${CMAKE_C_COMPILER} "libktx build C compiler override" ADVANCED) set_cache (KTX_CMAKE_CXX_COMPILER ${CMAKE_CXX_COMPILER} "libktx build C++ compiler override" ADVANCED) +# On x86_64, libktx defaults to using AVX2 for ASTC. For Intel-based MacOS, the +# default AVX2 requires x86_64h which may not be available (e.g., Intel-based +# MacOS Github Actions CIs). For ARM64 libktx defaults to using Neon. For +# 'unknown' CPUs, SIMD is disabled (see KTX-Software/lib/CMakeLists.txt for details). +# +# Possible values: +# - "": default, let libktx decide +# - ASTCENC_ISA_NATIVE: native SIMD +# - ASTCENC_ISA_NONE: disable SIMD +# - ASTCENC_ISA_SVE_256: slowest on Arm +# - ASTCENC_ISA_SVE_128: 2nd fastest on Arm +# - ASTCENC_ISA_NEON: fasted on Arm +# - ASTCENC_ISA_AVX2: fastest on x86_64 (may not be supported on Intel-based MacOS runners) +# - ASTCENC_ISA_SSE41: 2nd fastest on x86_64 (guaranteed to be supported) +# - ASTCENC_ISA_SSE2: slowest on x86_64 (guaranteed to be supported) +# +set_cache (Ktx_ASTCENC_ISA "" "ASTC specific SIMD instruction set. See astc-encoder/CMakeLists.txt for more details" ADVANCED) + # # The only two tested libktx versions are: # - v5.0.0-rc1 (newest supported) @@ -51,6 +72,7 @@ if (Ktx_BUILD_VERSION STREQUAL "v4.3.2") -D KTX_FEATURE_KTX2=ON -D KTX_FEATURE_VK_UPLOAD=OFF -D KTX_FEATURE_GL_UPLOAD=OFF + $<$:-D ${Ktx_ASTCENC_ISA}=ON> -D CMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER} -D CMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER} ) @@ -72,7 +94,7 @@ else() # v5.0.0-rc1 or a branch with similar CMake setup -D LIBKTX_FEATURE_VK_UPLOAD=OFF -D LIBKTX_FEATURE_GL_UPLOAD=OFF -D LIBKTX_FEATURE_ETC_UNPACK=OFF # This has some weird licensing and I don't feel comfortable including it ... - -D ASTCENC_ISA_SSE41=ON + $<$:-D ${Ktx_ASTCENC_ISA}=ON> -D CMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER} -D CMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER} # as per KTX-Software: From 11034c5f6e29458481fda23846c056acad13167a Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sat, 11 Jul 2026 04:13:33 +0200 Subject: [PATCH 31/42] Fix libktx CMAKE_ARGS lists Remove wrong CMake generator expression. Unsurprisingly, whenever I use them I mess something up. Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 74 +++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index a9019c9e60..39bd5b40b5 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -38,11 +38,27 @@ set_cache (KTX_CMAKE_CXX_COMPILER ${CMAKE_CXX_COMPILER} "libktx build C++ compil # set_cache (Ktx_ASTCENC_ISA "" "ASTC specific SIMD instruction set. See astc-encoder/CMakeLists.txt for more details" ADVANCED) +string(JOIN " " CMAKE_ARGS_STR + "-D CMAKE_POSITION_INDEPENDENT_CODE=ON" + "-D CMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER}" + "-D CMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER}" +) + +if(Ktx_ASTCENC_ISA) + string(APPEND CMAKE_ARGS_STR " -D ${Ktx_ASTCENC_ISA}=ON") +endif() + # -# The only two tested libktx versions are: +# The only three tested libktx versions are: +# - v0.0.0 (main:HEAD which is versionless) # - v5.0.0-rc1 (newest supported) # - v4.3.2 (newest version with minimum CMake required <= 3.18) -# libktx v4.3.2 requires a different setup than newer libktx versions +# +# libktx v4.3.2 is needed for older systems (see CI runners with 'oldest' in +# name) on which CMake >= 3.22 cannot be installed (this is the minimum version +# required by libktx > 4.3.2). +# +# libktx v4.3.2 requires a different setup than newer libktx versions. # # for detailed build instructions, see: # https://github.com/KhronosGroup/KTX-Software/blob/main/BUILDING.md @@ -58,52 +74,42 @@ if (Ktx_BUILD_VERSION STREQUAL "v4.3.2") else() set(Ktx_BUILD_STATIC_LIBS ON) endif() + string(APPEND CMAKE_ARGS_STR + " -D KTX_FEATURE_STATIC_LIBRARY=${Ktx_BUILD_STATIC_LIBS}" # no BUILD_SHARED_LIBS in older libktx versions ... + " -D KTX_FEATURE_TOOLS=OFF" + " -D KTX_FEATURE_TESTS=OFF" + " -D KTX_FEATURE_KTX1=ON" + " -D KTX_FEATURE_KTX2=ON" + " -D KTX_FEATURE_KTX2=ON" + " -D KTX_FEATURE_GL_UPLOAD=OFF" + ) build_dependency_with_cmake(Ktx VERSION ${Ktx_BUILD_VERSION} GIT_REPOSITORY ${Ktx_GIT_REPOSITORY} GIT_TAG ${Ktx_GIT_TAG} GIT_COMMIT ${Ktx_GIT_COMMIT} - CMAKE_ARGS - -D KTX_FEATURE_STATIC_LIBRARY=${Ktx_BUILD_STATIC_LIBS} # no BUILD_SHARED_LIBS in older libktx versions ... - -D CMAKE_POSITION_INDEPENDENT_CODE=ON - -D KTX_FEATURE_TOOLS=OFF - -D KTX_FEATURE_TESTS=OFF - -D KTX_FEATURE_KTX1=ON # TODO: test with OFF - -D KTX_FEATURE_KTX2=ON - -D KTX_FEATURE_VK_UPLOAD=OFF - -D KTX_FEATURE_GL_UPLOAD=OFF - $<$:-D ${Ktx_ASTCENC_ISA}=ON> - -D CMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER} - -D CMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER} - ) + CMAKE_ARGS ${CMAKE_ARGS_STR}) else() # v5.0.0-rc1 or a branch with similar CMake setup + string(APPEND CMAKE_ARGS_STR + " -D BUILD_SHARED_LIBS=${Ktx_BUILD_SHARED_LIBS}" + " -D CMAKE_INSTALL_LIBDIR=lib" + " -D LIBKTX_VERSION_READ_ONLY=OFF" + " -D LIBKTX_VERSION_FULL=ON" + " -D LIBKTX_FEATURE_KTX1=ON" # Setting this to OFF causes linker issues + " -D LIBKTX_FEATURE_KTX2=ON" + " -D LIBKTX_FEATURE_VK_UPLOAD=OFF" + " -D LIBKTX_FEATURE_GL_UPLOAD=OFF" + " -D LIBKTX_FEATURE_ETC_UNPACK=OFF" # This has some weird licensing and I don't feel comfortable including it ... + ) build_dependency_with_cmake(Ktx VERSION ${Ktx_BUILD_VERSION} GIT_REPOSITORY ${Ktx_GIT_REPOSITORY} GIT_TAG ${Ktx_GIT_TAG} GIT_COMMIT ${Ktx_GIT_COMMIT} SOURCE_SUBDIR lib # To only build Ktx, cmake has to point to: KTX-Software/lib - CMAKE_ARGS - -D BUILD_SHARED_LIBS=${Ktx_BUILD_SHARED_LIBS} - -D CMAKE_INSTALL_LIBDIR=lib - -D CMAKE_POSITION_INDEPENDENT_CODE=ON - -D LIBKTX_VERSION_READ_ONLY=OFF - -D LIBKTX_VERSION_FULL=ON - -D LIBKTX_FEATURE_KTX1=ON # Setting this to OFF causes linker issues - -D LIBKTX_FEATURE_KTX2=ON - -D LIBKTX_FEATURE_VK_UPLOAD=OFF - -D LIBKTX_FEATURE_GL_UPLOAD=OFF - -D LIBKTX_FEATURE_ETC_UNPACK=OFF # This has some weird licensing and I don't feel comfortable including it ... - $<$:-D ${Ktx_ASTCENC_ISA}=ON> - -D CMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER} - -D CMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER} - # as per KTX-Software: - # > Intel Macs have support for SSE, but if you're building universal - # > binaries, you have to disable SSE or the build will fail. - ) + CMAKE_ARGS ${CMAKE_ARGS_STR}) endif() - # Set some things up that we'll need for a subsequent find_package to work set (Ktx_ROOT ${Ktx_LOCAL_INSTALL_DIR}) set (Ktx_DIR ${Ktx_LOCAL_INSTALL_DIR}/lib/cmake/ktx) From c31baf0e68b1522d9b6d42b704c69a80e5aa1a04 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sat, 11 Jul 2026 04:35:54 +0200 Subject: [PATCH 32/42] Fix CMAKE_ARGS list Prior to this commit, CMAKE_ARGS was provided as a single string of "[<-D var=value>...]". This is incorrect as CMAKE_ARGS have to be provided as a list and not a string. Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 54 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index 39bd5b40b5..da75b1900c 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -38,14 +38,14 @@ set_cache (KTX_CMAKE_CXX_COMPILER ${CMAKE_CXX_COMPILER} "libktx build C++ compil # set_cache (Ktx_ASTCENC_ISA "" "ASTC specific SIMD instruction set. See astc-encoder/CMakeLists.txt for more details" ADVANCED) -string(JOIN " " CMAKE_ARGS_STR - "-D CMAKE_POSITION_INDEPENDENT_CODE=ON" - "-D CMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER}" - "-D CMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER}" +set(CMAKE_ARGS_LIST + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -DCMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER} ) if(Ktx_ASTCENC_ISA) - string(APPEND CMAKE_ARGS_STR " -D ${Ktx_ASTCENC_ISA}=ON") + list(APPEND CMAKE_ARGS_LIST -D${Ktx_ASTCENC_ISA}=ON) endif() # @@ -70,36 +70,36 @@ endif() if (Ktx_BUILD_VERSION STREQUAL "v4.3.2") # Because you can't negate variables in CMake ... if(Ktx_BUILD_SHARED_LIBS) - set(Ktx_BUILD_STATIC_LIBS OFF) + set(Ktx_BUILD_STATIC_LIBS OFF) else() - set(Ktx_BUILD_STATIC_LIBS ON) + set(Ktx_BUILD_STATIC_LIBS ON) endif() - string(APPEND CMAKE_ARGS_STR - " -D KTX_FEATURE_STATIC_LIBRARY=${Ktx_BUILD_STATIC_LIBS}" # no BUILD_SHARED_LIBS in older libktx versions ... - " -D KTX_FEATURE_TOOLS=OFF" - " -D KTX_FEATURE_TESTS=OFF" - " -D KTX_FEATURE_KTX1=ON" - " -D KTX_FEATURE_KTX2=ON" - " -D KTX_FEATURE_KTX2=ON" - " -D KTX_FEATURE_GL_UPLOAD=OFF" + list(APPEND CMAKE_ARGS_LIST + -DKTX_FEATURE_STATIC_LIBRARY=${Ktx_BUILD_STATIC_LIBS} # no BUILD_SHARED_LIBS in older libktx versions ... + -DKTX_FEATURE_TOOLS=OFF + -DKTX_FEATURE_TESTS=OFF + -DKTX_FEATURE_KTX1=ON + -DKTX_FEATURE_KTX2=ON + -DKTX_FEATURE_KTX2=ON + -DKTX_FEATURE_GL_UPLOAD=OFF ) build_dependency_with_cmake(Ktx VERSION ${Ktx_BUILD_VERSION} GIT_REPOSITORY ${Ktx_GIT_REPOSITORY} GIT_TAG ${Ktx_GIT_TAG} GIT_COMMIT ${Ktx_GIT_COMMIT} - CMAKE_ARGS ${CMAKE_ARGS_STR}) + CMAKE_ARGS ${CMAKE_ARGS_LIST}) else() # v5.0.0-rc1 or a branch with similar CMake setup - string(APPEND CMAKE_ARGS_STR - " -D BUILD_SHARED_LIBS=${Ktx_BUILD_SHARED_LIBS}" - " -D CMAKE_INSTALL_LIBDIR=lib" - " -D LIBKTX_VERSION_READ_ONLY=OFF" - " -D LIBKTX_VERSION_FULL=ON" - " -D LIBKTX_FEATURE_KTX1=ON" # Setting this to OFF causes linker issues - " -D LIBKTX_FEATURE_KTX2=ON" - " -D LIBKTX_FEATURE_VK_UPLOAD=OFF" - " -D LIBKTX_FEATURE_GL_UPLOAD=OFF" - " -D LIBKTX_FEATURE_ETC_UNPACK=OFF" # This has some weird licensing and I don't feel comfortable including it ... + list(APPEND CMAKE_ARGS_LIST + -DBUILD_SHARED_LIBS=${Ktx_BUILD_SHARED_LIBS} + -DCMAKE_INSTALL_LIBDIR=lib + -DLIBKTX_VERSION_READ_ONLY=OFF + -DLIBKTX_VERSION_FULL=ON + -DLIBKTX_FEATURE_KTX1=ON # Setting this to OFF causes linker issues + -DLIBKTX_FEATURE_KTX2=ON + -DLIBKTX_FEATURE_VK_UPLOAD=OFF + -DLIBKTX_FEATURE_GL_UPLOAD=OFF + -DLIBKTX_FEATURE_ETC_UNPACK=OFF # This has some weird licensing and I don't feel comfortable including it ... ) build_dependency_with_cmake(Ktx VERSION ${Ktx_BUILD_VERSION} @@ -107,7 +107,7 @@ else() # v5.0.0-rc1 or a branch with similar CMake setup GIT_TAG ${Ktx_GIT_TAG} GIT_COMMIT ${Ktx_GIT_COMMIT} SOURCE_SUBDIR lib # To only build Ktx, cmake has to point to: KTX-Software/lib - CMAKE_ARGS ${CMAKE_ARGS_STR}) + CMAKE_ARGS ${CMAKE_ARGS_LIST}) endif() # Set some things up that we'll need for a subsequent find_package to work From c4134c64102d7c7e2b6d5279bb65bab4eb9fb60a Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Fri, 17 Jul 2026 23:04:16 +0200 Subject: [PATCH 33/42] tmp commit to save status * Following different approaches to adding Volume and Cubemap support to ktx. Expecting major changes so saving a temporary commit in case new approach doesn't work. Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 16 +- src/cmake/testing.cmake | 2 +- src/ktx.imageio/ktx_pvt.h | 59 +- src/ktx.imageio/ktxinput.cpp | 628 ++++++++++-------- src/ktx.imageio/ktxoutput.cpp | 1159 ++++++++++++++++++++------------- 5 files changed, 1076 insertions(+), 788 deletions(-) diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index da75b1900c..27a149367a 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -5,9 +5,9 @@ set_cache (Ktx_BUILD_VERSION main "Ktx version for local builds") set (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") set_cache (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") -set_cache (Ktx_GIT_COMMIT "2ca7d54109f4c23298a969f22b68769e94138de5" +set_cache (Ktx_GIT_COMMIT "0082055d7147f558f241e531d85af5c39d9c09dc" "commit hash to verify tag/branch against") -set_cache (Ktx_BUILD_SHARED_LIBS OFF ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} +set_cache (Ktx_BUILD_SHARED_LIBS OFF #${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) # TODO: if libktx is built as a shared library, astcenc have to be linked-against. @@ -112,19 +112,11 @@ endif() # Set some things up that we'll need for a subsequent find_package to work set (Ktx_ROOT ${Ktx_LOCAL_INSTALL_DIR}) -set (Ktx_DIR ${Ktx_LOCAL_INSTALL_DIR}/lib/cmake/ktx) # Signal to caller that we need to find again at the installed location -# set (Ktx_REFIND TRUE) -# set (Ktx_REFIND_ARGS CONFIG) - -find_package (Ktx CONFIG REQUIRED - HINTS - ${Ktx_LOCAL_INSTALL_DIR}/lib/cmake/ktx/ - ${Ktx_LOCAL_INSTALL_DIR} - ) +set (Ktx_REFIND TRUE) +set (Ktx_REFIND_ARGS CONFIG) if (Ktx_BUILD_SHARED_LIBS) - # install_local_dependency_libs (pkgname libname) install_local_dependency_libs (Ktx ktx) # notice libname is lowercase endif () diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 0900c688a5..2cdbfa0a97 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -545,7 +545,7 @@ function (oiio_setup_test_data) # TODO: revert this after accepting OpenImageIO-images PR and before merging (just so that the CI passes) oiio_get_test_data (oiio-images REPO https://github.com/walcht/OpenImageIO-images.git - BRANCH main) + BRANCH add-ktx2-images) oiio_get_test_data (openexr-images REPO https://github.com/AcademySoftwareFoundation/openexr-images.git BRANCH main) diff --git a/src/ktx.imageio/ktx_pvt.h b/src/ktx.imageio/ktx_pvt.h index daa472e053..05a952686e 100644 --- a/src/ktx.imageio/ktx_pvt.h +++ b/src/ktx.imageio/ktx_pvt.h @@ -16,36 +16,26 @@ OIIO_PLUGIN_NAMESPACE_BEGIN -enum class TextureKind : uint32_t { - SINGLE_TEXTURE_1D, - SINGLE_TEXTURE_2D, - SINGLE_TEXTURE_3D, - CUBEMAP_TEXTURE, - ARRAY_TEXTURE_1D, - ARRAY_TEXTURE_2D, - ARRAY_TEXTURE_3D, - ARRAY_TEXTURE_CUBEMAP, -}; - - +#define DBG if (1) enum class BlockCompression : uint8_t { - NONE = 0, - BC1 = 1, ///< aka DXT1 - BC2 = 2, ///< aka DXT3 - BC3 = 3, ///< aka DXT5 - BC4 = 4, - BC5 = 5, - BC6HU = 6, - BC6HS = 7, - BC7 = 8, - ETC1 = 9, - ETC2_RGB = 10, - ETC2_RGB_A1 = 11, - ETC2_RGBA = 12, - EAC_R11 = 13, - EAC_RG11 = 14, - ASTC = 15, + NONE = 0u, + BC1 = 1u, ///< aka DXT1 + BC1A = 2u, + BC2 = 3u, ///< aka DXT3 + BC3 = 4u, ///< aka DXT5 + BC4 = 5u, + BC5 = 6u, + BC6HU = 7u, + BC6HS = 8u, + BC7 = 9u, + ETC1 = 10u, + ETC2_RGB = 11u, + ETC2_RGB_A1 = 12u, + ETC2_RGBA = 13u, + EAC_R11 = 14u, + EAC_RG11 = 15u, + ASTC = 16u, }; @@ -56,6 +46,7 @@ block_compression_name(BlockCompression cmp) switch (cmp) { case BlockCompression::NONE: return "NONE"; case BlockCompression::BC1: return "BC1"; + case BlockCompression::BC1A: return "BC1A"; case BlockCompression::BC2: return "BC2"; case BlockCompression::BC3: return "BC3"; case BlockCompression::BC4: return "BC4"; @@ -381,21 +372,11 @@ struct FormatInfo { int nbrchannels; TypeDesc typedesc; BlockCompression compression { BlockCompression::NONE }; - VkFormat decompressed_format { VK_FORMAT_UNDEFINED }; + VkFormat uncompressed_format { VK_FORMAT_UNDEFINED }; }; -/// Wrapper around ktxTexture_Destroy (which is a macro) to be passed as a -/// deleter to smart pointers -static void -ktxTexture_Deleter(ktxTexture2* tex) -{ - ktxTexture_Destroy((ktxTexture*)tex); -} - - - /// /// Colorspace detection should be done via the color model and transfer function /// and not via VkFormat. See: diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index 54bfe6253a..4f0f7f78b0 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -2,20 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 // https://github.com/AcademySoftwareFoundation/OpenImageIO -#include -#include - -// Per KTX-Software BUILDING.md: -// > When linking to the static library, make sure to -// > define `KHRONOS_STATIC` before including KTX header files. -// > This is especially important on Windows. -#ifndef BUILD_SHARED_LIBS -# define KHRONOS_STATIC 1 -#endif - #include "ktx_pvt.h" -#include +#include #include +#include OIIO_PLUGIN_NAMESPACE_BEGIN @@ -53,24 +43,18 @@ class KtxInput final : public ImageInput { bool read_native_scanlines(int subimage, int miplevel, int ybegin, int yend, int z, void* data) override; - bool read_native_scanlines(int subimage, int miplevel, int ybegin, int yend, - span data) override; + // TODO: why there is no `read_native_scanlines` that takes a span + // but also a `z` slice index (same as unsafe `read_native_scanlines`)? + // bool read_native_scanlines(int subimage, int miplevel, int ybegin, int yend, + // span data) override; const std::string& filename() const { return m_filename; } bool close() override; - int current_subimage(void) const override - { - lock_guard lock(*this); - return m_subimage; - } + int current_subimage(void) const override { return m_subimage; } - int current_miplevel(void) const override - { - lock_guard lock(*this); - return m_miplevel; - } + int current_miplevel(void) const override { return m_miplevel; } bool seek_subimage(int subimage, int miplevel) override; @@ -78,19 +62,15 @@ class KtxInput final : public ImageInput { std::string m_filename; /// KTX2 texture. - std::unique_ptr m_tex { - nullptr, ktxTexture_Deleter + std::unique_ptr m_tex { + nullptr, ktxTexture2_Destroy }; - /// Non-owning pointer to first byte of the requested (miplevel, slice). - /// This points to first byte of the whole texture data. - uint8_t* m_data_ptr { nullptr }; + int m_subimage { -1 }; ///< What subimage are we looking at. This is not + ///< used anywhere else except in current_subimage() - ktx_size_t m_offset { 0 }; ///< Current offset from subimage call. - int m_subimage { -1 }; ///< What subimage are we looking at? - int m_nbrsubimages { -1 }; ///< Number of slices/faces in texture - int m_miplevel { -1 }; ///< What mip level are we looking at? - int m_nbrmiplevels { -1 }; ///< Number of mip levels + int m_miplevel { -1 }; ///< What mip level are we looking at. This is not + ///< used anywhere else except in current_miplevel() /// GPU block compression kind (only set in case of GPU-block-compressed KTX /// textures). @@ -112,9 +92,11 @@ class KtxInput final : public ImageInput { /// Checks the magic bool ktx_magic_cmp(const uint8_t* sig, size_t start) const; - TextureKind get_texture_kind() const; - std::string get_colorspace() const; + + void parse_ktx_sc_params_metadata(std::string_view ktx_sc_params); + + bool check(int subimage, int miplevel) const; }; @@ -207,16 +189,16 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) return false; } - // - // IMPORTANT: // // KTX can hold layered compressions (i.e., on top of the potential GPU- // compatible compression like ASTC, the whole data can be furthermore - // compressed using a super compression scheme). + // compressed using a super compression scheme). We call such compression + // `supercompression` and does NOT refer to the usual compression (e.g., + // GPU-block compression). // // If KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT is provided for any - // of the ktxTexture_CreateFrom* calls, then libktx will allocate an internal - // buffer large enough to hold all data inflated IF AND ONLY IF + // of the ktxTexture_CreateFrom* calls, then libktx will allocate an + // internal buffer large enough to hold all data inflated IF AND ONLY IF // supercompressionScheme == KTX_SS_ZSTD or KTX_SS_ZLIB. // // Whithin the same call, ALL the texture data is then loaded. This is not @@ -225,18 +207,18 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // // TODO: // Implementing the per-subimage allocation approach requires some effort. - // For the moment, let's make sure this approach is working (i.e., - // all tests are passing) then let's profile and see what more experienced - // users might say about this. + // For the moment, let's make sure this approach is working (i.e., all tests + // are passing). // // For under-the-hood details, see official libktx repo: - // https://github.com/KhronosGroup/KTX-Software/blob/main/lib/src/texture.c + // https://github.com/KhronosGroup/KTX-Software/blob/main/lib/src/texture.c // if (proxytype == "file") { - auto fd = reinterpret_cast(m_io)->handle(); + FILE* fd = reinterpret_cast(m_io)->handle(); ktxTexture2* p_tex = nullptr; - auto res = ktxTexture2_CreateFromStdioStream( - fd, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &p_tex); + auto res + = ktxTexture2_CreateFromStdioStream(fd, KTX_TEXTURE_CREATE_NO_FLAGS, + &p_tex); m_tex.reset(p_tex); if (KTX_SUCCESS != res) { errorfmt("Failed to create ktx texture using " @@ -247,9 +229,9 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) OIIO_ASSERT(proxytype == "memreader"); auto buff = reinterpret_cast(m_io)->buffer(); ktxTexture2* p_tex = nullptr; - auto res = ktxTexture2_CreateFromMemory( - buff.data(), buff.size(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, - &p_tex); + auto res = ktxTexture2_CreateFromMemory(buff.data(), buff.size(), + KTX_TEXTURE_CREATE_NO_FLAGS, + &p_tex); m_tex.reset(p_tex); if (KTX_SUCCESS != res) { errorfmt( @@ -258,9 +240,6 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) } } - m_nbrmiplevels = m_tex->numLevels; - m_nbrsubimages = m_tex->numFaces; - m_spec = ImageSpec(m_tex->baseWidth, m_tex->baseHeight, 4 /* dummy value - will be overwritten */, TypeDesc::UINT8); @@ -268,56 +247,49 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) std::string colorspace = get_colorspace(); m_spec.set_colorspace(colorspace); + // Set textureformat attribute + // TODO: we don't use this in ktxoutput, is this needed? + if (m_tex->numDimensions == 2) { + if (m_tex->numFaces > 1) + m_spec.attribute("textureformat", "CubeFace Environment"); + else + m_spec.attribute("textureformat", "Plain Texture"); + } else if (m_tex->numDimensions == 3) { + m_spec.attribute("textureformat", "Volume Texture"); + } else { + m_spec.attribute("textureformat", "unknown"); + } + // // Make sure to save everything that is needed to recreate this exact same // KTX texture from OIIO API (i.e., fields of `ktxTextureCreateInfo`). // - // Note: // KtxTexture fields may change after some libktx calls that take // KtxTexture* argument because they may potentially modify the texture // (e.g., in ktxTexture2_DecodeAstc supercompressionScheme is overwritten to - // none, ktxTexture2_TranscodeBasis overwrites texture format, etc.). + // none, ktxTexture2_TranscodeBasis overwrites texture format, etc.). So, + // store these now and NOT after libktx calls (e.g., + // ktxTexture2_TranscodeBasis). // - // TODO: save original supercompressionScheme BEFORE infalting the texture m_spec.extra_attribs.attribute("ktx:supercompressionscheme", - TypeDesc::UINT32, 1, - cspan( - m_tex->supercompressionScheme)); - - m_spec.extra_attribs.attribute("ktx:texturekind", TypeDesc::UINT32, 1, - cspan(static_cast( - get_texture_kind()))); - - // save as string - m_spec.extra_attribs.attribute("ktx:version", "2.0"); - - // Contrary to the specs' layerCount, numLayers is always >= 1 (even for - // non-array types) - m_spec.extra_attribs.attribute("ktx:nlayers", TypeDesc::UINT32, 1, - cspan(m_tex->numLayers)); - - m_spec.extra_attribs.attribute("ktx:miplevels", TypeDesc::UINT32, 1, - cspan(m_tex->numLevels)); - - m_spec.extra_attribs.attribute("ktx:generatemipmaps", TypeDesc::UINT8, 1, - cspan(m_tex->generateMipmaps)); + (uint32_t)m_tex->supercompressionScheme); + // save as string (for future use, in case KTX1 is added) + m_spec.extra_attribs.attribute("ktx:version", 2.0f); + // Contrary to the specs' layerCount, numLayers is always >= 1 + m_spec.extra_attribs.attribute("ktx:nlayers", m_tex->numLayers); + m_spec.extra_attribs.attribute("ktx:miplevels", m_tex->numLevels); + m_spec.extra_attribs.attribute("ktx:generatemipmaps", + m_tex->generateMipmaps); // Store colormodel so that if a KTX2 is requested to be generated, we know // if a Basis Universal scheme has to be applied. - m_spec.extra_attribs.attribute("ktx:colormodel", TypeDesc::UINT32, 1, - cspan(ktxTexture2_GetColorModel_e( - m_tex.get()))); + m_spec.extra_attribs.attribute("ktx:colormodel", + (uint32_t)KHR_DFDVAL(m_tex->pDfd + 1, MODEL)); + m_spec.extra_attribs.attribute("ktx:vkformat", (uint32_t)m_tex->vkFormat); // m_spec.extra_attribs.attribute("ktx:transferfunction", TypeDesc::UINT32, 1, // cspan(transfer_function)); - // TODO: do we actually need the dfd data to re-generate the same KTX2 file? - // uint32_t dfdTotalSize = *m_tex->pDfd; - // m_spec.extra_attribs.attribute("ktx:dfd", TypeDesc::UINT8, dfdTotalSize, - // make_cspan(reinterpret_cast( - // m_tex->pDfd), - // dfdTotalSize)); - // // Save arbitrary metadata. KTX allows for the storage of arbitrary // key/value metadata pairs as per the specification here: @@ -375,6 +347,16 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) m_spec.extra_attribs.attribute( ktx_prefixed_attr_name, std::string(char_ptr, char_ptr + (vallen - 1))); + + } else if (attr_name == KTX_WRITER_SCPARAMS_KEY) { + // KTXwriterScParams is used to report all kinds of non-default parameters used by ktx tools to write this KTX2 file. + // This includes: + // non default Basis Universal params (i.e., for UASTC/ETC1S), non-default supercompression params, non-default mipmap generation params, etc. + // Should be NUL terminated. + if (vallen <= 1) + continue; + // auto char_ptr = reinterpret_cast(val); + } else if (attr_name == "KTXcubemapIncomplete") { OIIO_ASSERT(vallen == 1); // TODO: handle KTXcubemapIncomplete @@ -432,16 +414,14 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) return false; } - // - // Store original VkFormat (i.e., after Basic Universal transcoding and - // before potential GPU block format decompression). - // - // Important: - // Call this BEFORE (potential) ktxTexture2_TranscodeBasis call - // - m_spec.extra_attribs.attribute("ktx:vkformat", TypeDesc::UINT32, 1, - cspan( - static_cast(m_tex->vkFormat))); + // Load the actual image data (pBuffer is NULL => m_tex own the buffer in + // which the data will be loaded) + if (auto result = ktxTexture2_LoadImageData(m_tex.get(), NULL, 0); + result != KTX_SUCCESS) { + errorfmt("ktxTexture2_LoadImageData returned Ktx error code: {}", + static_cast(result)); + return false; + } // // Do we need to transcode this texture (i.e., is this a Basis Universal @@ -484,9 +464,21 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // PVRTC: TODO: wait for libktx PR. // if (m_tex->isCompressed /* i.e., is GPU block compressed? */) { + // m_cmp = get_block_compression_from_format(m_tex->vkFormat); + FormatInfo format_info; + if (!get_info_from_vkformat(static_cast(m_tex->vkFormat), + format_info)) { + close(); + errorfmt("Could not extract format info from provided " + "VkFormat: {}. This format is unsupported", + static_cast(m_vkformat)); + return false; + } + m_cmp = format_info.compression; switch (m_cmp) { /* BCn GPU formats */ case BlockCompression::BC1: + case BlockCompression::BC1A: case BlockCompression::BC2: case BlockCompression::BC3: case BlockCompression::BC4: @@ -555,12 +547,14 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) #endif default: - errorfmt("Unknown/unsupported GPU block compression kind: {}", - static_cast(m_cmp)); + errorfmt("GPU block compression format {} is unsupported", + block_compression_name(m_cmp)); return false; } } + OIIO_ASSERT(!m_tex->isCompressed); + // // This could mean one of the following as per the specs at: // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_use_of_vk_format_undefined @@ -616,37 +610,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) return false; } - // - // In case this KTX texture is GPU block compressed, we need to map its - // vkformat to the corresponding decompressed VkFormat. - // - // E.g., VK_FORMAT_BC7_SRGB_BLOCK --> VK_FORMAT_R8G8B8A8_SRGB - // - // We do this so that we can save the correct crucial stats in the spec - // (e.g., nchannels, colorspace, typedesc, etc.) and because the internal - // state of data in OIIO is always decompressed (i.e., we never return - // block-compressed data from read_native_scanline(s) functions). - // auto format = static_cast(m_tex->vkFormat); - if (m_tex->isCompressed) { - FormatInfo format_info; - if (!get_info_from_vkformat(static_cast(format), - format_info)) { - errorfmt( - "Failed to extract info (e.g., nchannels, typedesc, etc.) from VkFormat: {}", - static_cast(format)); - return false; - } - if (format_info.compression == BlockCompression::NONE - || format_info.decompressed_format == VK_FORMAT_UNDEFINED) { - errorfmt( - "KTX texture is GPU-block-compressed using unsuppoted format: {}", - static_cast(format)); - return false; - } - format = format_info.decompressed_format; - m_cmp = format_info.compression; - } // // Important: @@ -654,19 +618,15 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // ktxTexture2_TranscodeBasis) and AFTER detecting which GPU block // compression scheme is used. // - { - FormatInfo format_info; - if (!get_info_from_vkformat(static_cast(format), - format_info)) { - errorfmt( - "Failed to extract info (e.g., nchannels, typedesc, etc.) from VkFormat: {}", - static_cast(format)); - return false; - } - - m_spec.set_format(format_info.typedesc); - m_spec.nchannels = format_info.nbrchannels; + FormatInfo format_info; + if (!get_info_from_vkformat(static_cast(format), format_info)) { + errorfmt( + "Failed to extract info (e.g., nchannels, typedesc, etc.) from VkFormat: {}", + static_cast(format)); + return false; } + m_spec.set_format(format_info.typedesc); + m_spec.nchannels = format_info.nbrchannels; // TODO: verify the x, y, z limits (probably not 65535) if (!check_open(m_spec, { 0, 65535, 0, 65535, 0, 65535, 0, 4 })) @@ -676,8 +636,6 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // errorfmt is set via seek_subimage return false; - OIIO_ASSERT(m_data_ptr != nullptr); - newspec = m_spec; return true; } @@ -696,31 +654,48 @@ KtxInput::close() +bool +KtxInput::check(int subimage, int miplevel) const +{ + // + // Before doing any calls, check if provided subimage and mip lvl are valid. + // This is how OIIO figures out the number of subimages/miplevels. + // + if (subimage < 0 || miplevel < 0 || (uint32_t)subimage >= m_tex->numLayers + || (uint32_t)miplevel >= m_tex->numLevels) + // don't errorfmt here + return false; + return true; +} + + + +// +// Since we load the whole KTX texture data in open(), actual seeking makes no +// sense in this context. You can just read at any pixel by just computing an +// offset. // -// In the context of KTX, `subimage` CAN be interpreted as (1D textures are -// considered 2D textures with height set to 1): -// 1. array layer (if texture is a 2D texture array) -// 2. depth slice (if texture is 3D) -// 3. cube map face (if texture is a cubemap) -// 4. depth slice (of first 3D texture if texture is a 3D texture array) -// 5. cube map face (of first cubemap texture if texture is a cubmap array) +// What this seek_subimage does, is essentially parameter verification (i.e., +// are provided subimage and miplevel sane for the current texture kind). // -// `miplevel` is simply interpreted as a mip level of the above `subimage`. +// In the context of KTX: +// - For 3D textures (i.e., depth > 1): `subimage` does NOT reflect the +// 3D texture depth slice. subimage SHOULD be 0. TODO: how to read a 3D slice then? +// - For Cubemaps (i.e., tile_width/tile_height > 1): `subimage` does NOT reflect +// the cubemap face. subimage SHOULD be 0. TODO: how to read a cubemap face tile? +// - Arrays of 2D textures: `subimage` maps to layer in libktx (i.e., index in +// array). subimage SHOULD be [0, m_tex->numLayers[. +// - Arrays of 3D textures: same as array of 2D textures except each `subimage` +// refer to 3D texture instead of a 2D one. // -// In other cases, if subimage is > 0, it is invalid. +// For all kinds of textures, `miplevel` is ALWAYS interpreted as a mip level of +// the above `subimage` (e.g., `miplevel` 0 of 3D texture refers to base-level +// volume). // bool KtxInput::seek_subimage(int subimage, int miplevel) { - lock_guard lock(*this); - - // - // Before doing any calls, check if provided subimage and mip lvl are valid. - // This is how OIIO figures out the number of subimages/miplevels. - // - if (subimage < 0 || miplevel < 0 || subimage >= m_nbrsubimages - || miplevel >= m_nbrmiplevels) - /* don't errorfmt here */ + if (!check(subimage, miplevel)) return false; // if same subimage and miplevel as current => early out @@ -731,24 +706,6 @@ KtxInput::seek_subimage(int subimage, int miplevel) m_subimage = subimage; m_miplevel = miplevel; - // cast to ktx_uint32_t to stop the compiler/clangd from complaining - auto _subimage = static_cast(subimage); - - ktx_uint32_t arr_layer { 0 }; // array layer - ktx_uint32_t face_slice { 0 }; // 3d texture slice or cubemap face - - // is this a cubemap? (i.e., subimage means cubemap face) - if (m_tex->isCubemap) - face_slice = _subimage; - - // is this an array texture? (i.e., subimage means array layer) - if (m_tex->isArray) - arr_layer = _subimage; - - // is this a 3D texture? (i.e., subimage means face slice) - if (m_tex->numDimensions == 3) - face_slice = _subimage; - // // According to official libktx source code, this is how they compute // dimensions of a miplevel. See: @@ -762,108 +719,76 @@ KtxInput::seek_subimage(int subimage, int miplevel) m_spec.height = height; m_spec.depth = depth; - // Should never be true (because the texture is decompressed in open()) - if (m_tex->isCompressed) { - OIIO_ASSERT( - false - && "KTX2 texture should be decompressed before a call to subimage."); - return false; - } - - // - // GetImageOffset implements internal checks depending on texture kind (e.g., - // 3D, cubemap, etc.) and incase of invalid input, KTX_INVALID_OPERATION is - // returned. - // - ktx_size_t offset; - if (auto status - = ktxTexture_GetImageOffset(reinterpret_cast(m_tex.get()), - miplevel, arr_layer, face_slice, &offset); - status != KTX_SUCCESS) { - errorfmt("ktxTexture2_GetImageOffset failed with exit code: {}", - static_cast(status)); - return false; - } - m_data_ptr = m_tex->pData + offset; - const uint32_t pitch - = ktxTexture_GetRowPitch(reinterpret_cast(m_tex.get()), - miplevel); - OIIO_ASSERT(pitch == m_spec.scanline_bytes()); - return true; } bool -KtxInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, +KtxInput::read_native_scanline(int subimage, int miplevel, int y, int z, void* data) { - lock_guard lock(*this); - - // Since we will access m_spec - if (!seek_subimage(subimage, miplevel)) - return false; - - return read_native_scanlines(subimage, miplevel, y, y + 1, - as_writable_bytes(data, m_spec.scanline_bytes( - true))); + return read_native_scanlines(subimage, miplevel, y, y + 1, z, data); } bool KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, - int yend, int /* z */, void* data) + int yend, int z, void* data) { - lock_guard lock(*this); + const int width = std::max(m_tex->baseWidth >> miplevel, 1u); + const int height = std::max(m_tex->baseHeight >> miplevel, 1u); + const int depth = std::max(m_tex->baseDepth >> miplevel, 1u); + const size_t pitch = m_spec.pixel_bytes() * width; + ktx_size_t offset; - if (ybegin >= yend) { - errorfmt("Invalid scanline range requested: {}-{}", ybegin, yend); + if (!check(subimage, miplevel)) { + errorfmt("KTX read_native_scanlines: invalid subimage or miplevel"); return false; } - // Since we will access m_spec - if (!seek_subimage(subimage, miplevel)) + if (ybegin < 0 || ybegin >= yend || yend > height || z < 0 || z >= depth) { + errorfmt( + "KTX read_native_scanlines: Out of valid range scanline indices"); return false; + } - size_t size = m_spec.scanline_bytes(true) * size_t(yend - ybegin); - return read_native_scanlines(subimage, miplevel, ybegin, yend, - as_writable_bytes(data, size)); -} - - -bool -KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, - int yend, span data) -{ - lock_guard lock(*this); + OIIO_ASSERT(pitch + == ktxTexture_GetRowPitch((ktxTexture*)m_tex.get(), miplevel)); - if (!seek_subimage(subimage, miplevel)) + // Use this in case OIIO API provides read_native_scanlines with `data` as + // `span` and a `z` slice param +#if 0 + // Can the provided span hold the requested scanlines? + // This only accesses nchannels of the m_spec, so this is thread safe. + if (!valid_raw_span_size(data, m_spec, 0, width, ybegin, yend)) + // errorfmt is set within valid_raw_span_size return false; +#endif - // is provided [ybegin, yend[ valid? - if (ybegin < 0 || ybegin >= yend || yend > m_spec.height) { - // out of range scanlines - errorfmt("KTX read_native_scanlines: Out of valid range scanline indices " - "(b={} e={}).", - ybegin, yend); + // + // GetImageOffset implements internal checks depending on texture kind (e.g., + // 3D, cubemap, etc.) and incase of invalid input, KTX_INVALID_OPERATION is + // returned. + // + // TODO: face slice idx + // + if (auto status = ktxTexture2_GetImageOffset(m_tex.get(), miplevel, + subimage, z, &offset); + status != KTX_SUCCESS) { + errorfmt("ktxTexture_GetImageOffset failed with exit code: {}", + static_cast(status)); return false; } - // can the provided span hold the requested scanlines? - if (!valid_raw_span_size(data, m_spec, 0, m_spec.width, ybegin, yend)) - // errorfmt is set within valid_raw_span_size - return false; - - OIIO_ASSERT(m_data_ptr != nullptr); - - const size_t pitch = m_spec.scanline_bytes(); + auto data_ptr = m_tex->pData + offset; // since miplevel is valid => get number of bytes in a row for this mip - memcpy(data.data(), m_data_ptr, pitch * (yend - ybegin)); - // std::cout << "read_native_scanlines(" << subimage << ", " << miplevel - // << ", " << ybegin << ", " << yend << ")" << '\n'; + memcpy(data, data_ptr, pitch * size_t(yend - ybegin)); + DBG std::cout << fmt::format( + "[ktxinput] read_native_scanlines(subimage={},miplevel={},ybegin={},yend={},z={})\n", + subimage, miplevel, ybegin, yend, z); return true; } @@ -899,30 +824,6 @@ KtxInput::ktx_magic_cmp(const uint8_t* sig, size_t start) const -TextureKind -KtxInput::get_texture_kind() const -{ - switch (m_tex->numDimensions) { - case 1: - if (m_tex->isArray) - return TextureKind::ARRAY_TEXTURE_1D; - return TextureKind::SINGLE_TEXTURE_1D; - case 2: - if (m_tex->isArray && m_tex->isCubemap) - return TextureKind::ARRAY_TEXTURE_CUBEMAP; - else if (m_tex->isArray) - return TextureKind::ARRAY_TEXTURE_2D; - return TextureKind::SINGLE_TEXTURE_2D; - case 3: - if (m_tex->isArray) - return TextureKind::ARRAY_TEXTURE_3D; - return TextureKind::SINGLE_TEXTURE_3D; - default: return TextureKind::SINGLE_TEXTURE_2D; - } -} - - - std::string KtxInput::get_colorspace() const { @@ -962,4 +863,177 @@ KtxInput::get_colorspace() const return "unknown"; } + + +template +inline bool +parse_number(const std::string& str, T& num) +{ + auto [_, ec] = std::from_chars(str.data(), str.data() + str.size(), num); + return ec != std::errc {}; +} + + + +/// +/// Parses KTXwriterScParams metadata and sets relevant attributes accordingly. +/// E.g., if Basis Universal non-default params were found, they are set. +/// +/// One cannot know from the KTX2 file itself the kind of parameters that were +/// used to create/encode it (e.g., RDO params). +/// +/// This is only useful when reading a KTX2 via OIIO and re-writing it again +/// (which is a bad idea since each re-write cycle worsens the quality). That +/// being said, if the user intends to use OIIO this way, the written KTX2 file +/// should be as similar as possible to the given input (assuming a read-write +/// of a KTX2 file without any change to its data). +/// +void +KtxInput::parse_ktx_sc_params_metadata(const std::string_view ktx_sc_params) +{ + std::cmatch m; + const auto f = std::regex_constants::icase; + + { // UASTC params (see KTX-Software/tools/ktx/encode_utils_basis.h) + std::regex uastc_quality_re("--uastc-quality\\s+(\\d+)", f); + std::regex uastc_rdo_re("--uastc-rdo", f); + std::regex uastc_rdo_l_re("--uastc-rdo-l\\s+((\\d*[.])?\\d+)", f); + std::regex uastc_rdo_d_re("--uastc-rdo-d\\s+(\\d+)", f); + std::regex uastc_rdo_b_re("--uastc-rdo-b\\s+((\\d*[.])?\\d+)", f); + std::regex uastc_rdo_s_re("--uastc-rdo-s\\s+((\\d*[.])?\\d+)", f); + std::regex uastc_rdo_f_re("--uastc-rdo-f", f); + std::regex uastc_rdo_m_re("--uastc-rdo-m", f); + std::regex uastc_rdo_uber_mode_re("--uastc-hdr-uber-mode", f); + std::regex uastc_rdo_ultra_quant_re("--uastc-hdr-ultra-quant", f); + std::regex uastc_rdo_favor_astc_re("--uastc-hdr-favor-astc", f); + std::regex uastc_hdr_lambda_re("--uastc-hdr-lambda\\s+((\\d*[.])?\\d+)", + f); + std::regex uastc_hdr_6x6i_level_re("--uastc-hdr-6x6i-level\\s+(\\d+)", + f); + + if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, + uastc_quality_re) + && m.size() == 2) { + uint32_t uastc_quality; + if (parse_number(m[1].str(), uastc_quality)) { + const uint32_t uastc_flags + = (unsigned int)~KTX_PACK_UASTC_LEVEL_MASK | uastc_quality; + m_spec.extra_attribs.attribute("ktx:uastcFlags", uastc_flags); + m_spec.extra_attribs.attribute("ktx:uastcHDRLevel", + uastc_quality); + } + } + + if (std::regex_match(ktx_sc_params.cbegin(), ktx_sc_params.cend(), + uastc_rdo_re)) + m_spec.extra_attribs.attribute("ktx:uastcRDO", true); + + if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, + uastc_rdo_l_re) + && m.size() == 2) { + float uastc_rdo_l; + if (parse_number(m[1].str(), uastc_rdo_l)) { + m_spec.extra_attribs.attribute("ktx:uastcRDOQualityScalar", + uastc_rdo_l); + } + } + + if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, + uastc_rdo_d_re) + && m.size() == 2) { + uint32_t uastc_rdo_d; + if (parse_number(m[1].str(), uastc_rdo_d)) { + m_spec.extra_attribs.attribute("ktx:uastcRDODictSize", + uastc_rdo_d); + } + } + + if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, + uastc_rdo_b_re) + && m.size() == 2) { + float uastc_rdo_b; + if (parse_number(m[1].str(), uastc_rdo_b)) { + m_spec.extra_attribs.attribute( + "ktx:uastcRDOMaxSmoothBlockErrorScale", uastc_rdo_b); + } + } + + if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, + uastc_rdo_s_re) + && m.size() == 2) { + float uastc_rdo_s; + if (parse_number(m[1].str(), uastc_rdo_s)) { + m_spec.extra_attribs.attribute( + "ktx:uastcRDOMaxSmoothBlockStdDev", uastc_rdo_s); + } + } + + if (std::regex_match(ktx_sc_params.cbegin(), ktx_sc_params.cend(), + uastc_rdo_f_re)) + m_spec.extra_attribs.attribute("ktx:uastcRDODontFavorSimplerModes", + true); + + if (std::regex_match(ktx_sc_params.cbegin(), ktx_sc_params.cend(), + uastc_rdo_m_re)) + m_spec.extra_attribs.attribute("ktx:uastcRDONoMultithreading", + true); + + if (std::regex_match(ktx_sc_params.cbegin(), ktx_sc_params.cend(), + uastc_rdo_uber_mode_re)) + m_spec.extra_attribs.attribute("ktx:uastcHDRUberMode", true); + + if (std::regex_match(ktx_sc_params.cbegin(), ktx_sc_params.cend(), + uastc_rdo_ultra_quant_re)) + m_spec.extra_attribs.attribute("ktx:uastcHDRUltraQuant", true); + + if (std::regex_match(ktx_sc_params.cbegin(), ktx_sc_params.cend(), + uastc_rdo_favor_astc_re)) + m_spec.extra_attribs.attribute("ktx:uastcHDRFavorAstc", true); + + if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, + uastc_hdr_lambda_re) + && m.size() == 2) { + float uastc_hdr_lambda; + if (parse_number(m[1].str(), uastc_hdr_lambda)) { + m_spec.extra_attribs.attribute("ktx:uastcHDRLambda", + uastc_hdr_lambda); + } + } + + if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, + uastc_hdr_6x6i_level_re) + && m.size() == 2) { + uint32_t uastc_hdr_6x6i_level; + if (parse_number(m[1].str(), uastc_hdr_6x6i_level)) { + m_spec.extra_attribs.attribute("ktx:uastcHDRLevel", + uastc_hdr_6x6i_level); + } + } + } + + { // ETC1S params (see KTX-Software/tools/ktx/encode_utils_basis.h) + std::regex etc1s_clevel("--clevel\\s+(\\d+)", f); + std::regex etc1s_qlevel("--qlevel\\s+(\\d+)", f); + std::regex etc1s_max_endpoints("--max-endpoints\\s+(\\d+)", f); + std::regex etc1s_endpoint_rdo_threshold( + "--endpoint-rdo-threshold\\s+((\\d*[.])?\\d+)", f); + std::regex etc1s_max_selectors("--max-selectors\\s+(\\d+)", f); + + std::regex uastc_rdo_re("", f); + std::regex uastc_rdo_l_re("--uastc-rdo-l\\s+((\\d*[.])?\\d+)", f); + std::regex uastc_rdo_d_re("--uastc-rdo-d\\s+(\\d+)", f); + std::regex uastc_rdo_b_re("--uastc-rdo-b\\s+((\\d*[.])?\\d+)", f); + std::regex uastc_rdo_s_re("--uastc-rdo-s\\s+((\\d*[.])?\\d+)", f); + std::regex uastc_rdo_f_re("--uastc-rdo-f", f); + std::regex uastc_rdo_m_re("--uastc-rdo-m", f); + std::regex uastc_rdo_uber_mode_re("--uastc-hdr-uber-mode", f); + std::regex uastc_rdo_ultra_quant_re("--uastc-hdr-ultra-quant", f); + std::regex uastc_rdo_favor_astc_re("--uastc-hdr-favor-astc", f); + std::regex uastc_hdr_lambda_re("--uastc-hdr-lambda\\s+((\\d*[.])?\\d+)", + f); + std::regex uastc_hdr_6x6i_level_re("--uastc-hdr-6x6i-level\\s+(\\d+)", + f); + } +} + OIIO_PLUGIN_NAMESPACE_END diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp index 482c7f7db3..7e58980e52 100644 --- a/src/ktx.imageio/ktxoutput.cpp +++ b/src/ktx.imageio/ktxoutput.cpp @@ -2,24 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 // https://github.com/AcademySoftwareFoundation/OpenImageIO -// TODO: only set this if libktx is statically built/linked against -// Per KTX-Software BUILDING.md: -// > When linking to the static library, make sure to -// > define `KHRONOS_STATIC` before including KTX header files. -// > This is especially important on Windows. -#ifndef BUILD_SHARED_LIBS -# define KHRONOS_STATIC 1 -#endif - +#include "OpenImageIO/imageio.h" #include "ktx_pvt.h" #include -#include OIIO_PLUGIN_NAMESPACE_BEGIN class KtxOutput final : public ImageOutput { public: - KtxOutput() {} + KtxOutput() + { +#if defined(KHRONOS_STATIC) + // This has to be set if libktx is statically built + DBG std::cout << "KHRONOS_STATIC set to 1" << '\n'; +#endif + } ~KtxOutput() override { close(); } @@ -29,18 +26,25 @@ class KtxOutput final : public ImageOutput { { return ( feature == "alpha" || feature == "ioproxy" || - // as per the KTX1/2 specs: + // as per the KTX2 specs: // registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_keyvalue_data feature == "arbitrary_metadata" || - /* ktx supports 3D textures, cubmap textures, texture arrays, ... */ + // ktx supports 3D textures, cubmap textures, texture arrays, ... feature == "multiimage" || - /* not sure ... this naming is confusing */ - feature == "mipmap"); + // ktx supports mipmaps + feature == "mipmap" || + // Ktx supports 3D textures + feature == "volumes" || feature == "random_access" + // Can write in any order whatsoever + ); } bool open(const std::string& name, const ImageSpec& spec, OpenMode mode = Create) override; + bool open(const std::string& name, int subimages, + const ImageSpec* specs) override; + bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; @@ -53,43 +57,81 @@ class KtxOutput final : public ImageOutput { private: std::string m_filename; - /// KTX2 texture. - std::unique_ptr m_tex { - nullptr, ktxTexture_Deleter - }; + bool m_initialized { false }; ///< Has open() with mode == Create was + + /// Vulkan format fetched from "ktx:vkformat" attribute or set from current + /// image spec. + VkFormat m_vkformat { VK_FORMAT_UNDEFINED }; + + /// Uncompressed Vulkan format to create the texture with in case original + /// vkforma is a GPU-block-compressed format. This might be different that + /// the actual format supplied via the "ktx:vkformat" attribute in the case + /// of GPU-block-compressed formats (e.g., VK_FORMAT_ASTC_4x4_SRGB_BLOCK). + VkFormat m_vkformat_uncompressed { VK_FORMAT_UNDEFINED }; + + uint32_t m_miplevel_idx { 0 }; ///< Current MIP level + + uint32_t m_subimages_to_write { 1 }; ///< Number of slices/faces to write + + uint32_t m_max_nmiplevels { 1 }; ///< Max number allowable MIP levels + + uint32_t m_basewidth { 0 }; ///< MIP level 0 width - uint32_t m_nlayers { 1 }; + uint32_t m_baseheight { 0 }; ///< MIP level 0 height - uint32_t m_miplevels { 1 }; + uint32_t m_basedepth { 0 }; ///< MIP level 0 depth ktxSupercmpScheme m_superCmp { KTX_SS_NONE }; - khr_df_model_e m_colormodel { KHR_DF_MODEL_UNSPECIFIED }; + /// Whether to generate MIP maps when loading texture to graphics API. This + /// will be passed to ktxTextureCreateInfo's generateMipmaps param. + bool m_generate_mipmaps { false }; BlockCompression m_cmp { BlockCompression::NONE }; - std::vector m_scratch; + bool m_use_basis_universal { false }; - /// libktx only supports writing whole images (i.e., (miplevel, layer, face_slice/depth) - /// hence why we keep a large std::vector at all times. This is also needed because we - /// apply compression upon file closure and not each time on write_scanline(s). - std::vector m_img; + ktxBasisParams m_basis_params { 0 }; - // TODO: what about volumetric textures? do we keep an array of these very - // large vectors? For the moment, these are just not supported. + ktxAstcParams m_astc_params { 0 }; ///< Only for ASTC block compression - void init(); + // ktxBCnParams m_bcn_params { 0 }; ///< Only for BCn block compression - bool basisu_basislz_compress(); + uint32_t m_zlib_level { 9 }; ///< Only for Zlib supercompression. Defaults + ///< to highest compression level. - bool basisu_uastc_compress(); + uint32_t m_zstd_level { 22 }; ///< Only for ZSTD supercompression. Defaults + ///< to highest compression level. + + std::vector m_scratch; + + /// + /// Container for raw (i.e., uncompressed) texture data structured as + /// follows: + /// slice/face -> mipmaps -> mipmap data + /// + /// The number of slices/faces is known before hand (i.e., appending + /// slices/faces is not supported). This significantly simplifies the + /// implementation. + /// + /// Q. Why store all subimages/levels/slices/faces here? + /// A. libktx only supports writing whole images (i.e., + /// miplevel+layer+slice/face hence why we keep a large std::vector at + /// all times. This is also needed because we apply compression upon file + /// closure and not each time on write_scanline(s). + /// + /// To access underlying data: [slice_idx + face_idx][miplevel_idx] + /// Note: slice_idx and face_idx are mutually exclusive so summing them is + /// perfectly fine. + /// + std::vector>> m_imgs; + + void init(); bool write_ktx2(); - // void generate_mip_levels(const image_span& base_lvl_image, - // ImageInput& inputFile, uint32_t numMipLevels, - // uint32_t layerIndex, uint32_t faceIndex, - // uint32_t depthSliceIndex); + bool construct_basis_params(ktxBasisParams& params, uint32_t codec, + uint32_t threads = 1) const; }; @@ -108,266 +150,304 @@ OIIO_PLUGIN_EXPORTS_END bool -KtxOutput::open(const std::string& name, const ImageSpec& newspec, - OpenMode mode) +KtxOutput::open(const std::string& name, int subimages, const ImageSpec* specs) { - // TODO: verify the x, y, z limits (probably not 65535) - // This does: m_spec = newspec - if (!check_open(mode, newspec, { 0, 65535, 0, 65535, 0, 65535, 0, 4 })) + if (m_initialized) { + errorfmt("Cannot call open() on an already initialized ImageOutput"); return false; + } - // Save name and spec for later use - m_filename = name; - - // If not uint8, default to uint8 (HDR not yet supported) - if (m_spec.format - != TypeDesc::UINT8 /* && m_spec.format != TypeDesc::UINT16 */) - m_spec.set_format(TypeDesc::UINT8); - - ioproxy_retrieve_from_config(m_spec); - if (!ioproxy_use_or_open(m_filename)) + if (subimages > 10) { + errorfmt("Ktx does not support more than {} subimages", 10); return false; - - std::string colorspace = m_spec.get_string_attribute("oiio:ColorSpace", - "srgb_rec709_scene"); - bool is_srgb = colorspace == "srgb_rec709_scene"; - - // TODO: get_int_attribute causes a segfault and I have no idea why ... - // Weirdly, calling find_attribute directly (and checking the resulting - // pointer) works, but not get_int_attribute ... - - // keep this commented in case we need it - /* - cspan dfd; - ParamValue* dfdQ = m_spec.find_attribute("ktx:dfd", TypeDesc::UINT8); - if (dfdQ) { - dfd = dfdQ->as_cspan(); - std::cout << "[ktxoutput] found dfd with len: " << dfdQ->nvalues() - << '\n'; } - // Copy dfd data from const span because ktxTextureCreateInfo does not take - // a const uint8_t ptr. - ktx_uint32_t* pDfd { nullptr }; - std::vector dfd_copy; - if (!dfd.empty()) { - dfd_copy.assign(dfd.begin(), dfd.end()); - pDfd = reinterpret_cast(dfd_copy.data()); - std::cout << "pDfd is set to != nullptr" << '\n'; + if (subimages < 1) { + errorfmt("subimages has to be at least >= 1"); + return false; } - */ // - // Use sensible default in case the input data did not originate from a KTX2 - // file and the user did not provide a supercompression scheme. KTX2 usually - // uses Basis LZ supercompression scheme to benefit from both: smaller - // disk filesizes and on-the-fly transcoding to a supported native GPU - // format. + // All provided specs have to be the same (same width, height, nchannels, + // format, etc.). This is because KTX does not support subimages of + // different dimensions/types/nchannels etc. We only use the first supplid + // spec and the rest are not needed. // - ParamValue* superCmpSchemQ - = m_spec.find_attribute("ktx:supercompressionscheme", TypeDesc::UINT32); - if (superCmpSchemQ) { - m_superCmp = static_cast( - *reinterpret_cast(superCmpSchemQ->data())); - // std::cout << "[ktxoutput] found supercompression scheme: " << m_superCmp - // << '\n'; + const ImageSpec& ref_spec = specs[0]; + // for (int i = 1; i < subimages; ++i) { + // const ImageSpec& spec = specs[i]; + // if (spec.width != ref_spec.width || spec.height != ref_spec.height + // || spec.nchannels != ref_spec.nchannels + // || spec.format != ref_spec.format) { + // errorfmt( + // "Ktx expects all specs for all provided subimages to be the same. Different subimage specs is not supported"); + // return false; + // } + // } + + OIIO_ASSERT(m_imgs.empty() && "slices/faces std::vector should be empty"); + + bool ok = open(name, ref_spec, Create); + if (ok) { + m_subimages_to_write = subimages; + m_imgs.resize(m_subimages_to_write); + for (auto& mipmaps_vec : m_imgs) + // default is to assume a single mip level + mipmaps_vec.emplace_back(ref_spec.scanline_bytes() + * ref_spec.height); } + return ok; +} - // Get transfer function (for color space conversions) - // ParamValue* tfQ = m_spec.find_attribute("ktx:transferfunction", - // TypeDesc::UINT32); - // if (tfQ) { - // m_tf = static_cast( - // *reinterpret_cast(tfQ->data())); - // std::cout << "[ktxoutput] found tf: " << m_tf << '\n'; - // } - // Get color model (to detect GPU compression, Basis Universal format, etc.) - ParamValue* colorModelQ = m_spec.find_attribute("ktx:colormodel", - TypeDesc::UINT32); - if (colorModelQ) { - m_colormodel = static_cast( - *reinterpret_cast(colorModelQ->data())); - // std::cout << "[ktxoutput] found color model: " << m_colormodel << '\n'; - } +bool +KtxOutput::open(const std::string& name, const ImageSpec& userspec, + OpenMode mode) +{ + if (mode == Create) { + // TODO: verify the x, y, z limits (probably not 65535) + // This does: m_spec = userspec + // Q. Why zend is 0 (i.e., volumes not permitted even though "volumes" are supported? + // A. User is expected to call AppendSubimage to a append a slices/faces. + // As far as I know, there no ImageOutput API that allows + // random-access writes of volume slides, TODO: or is there? + if (!check_open(mode, userspec, { 0, 65535, 0, 65535, 0, 65535, 0, 4 })) + return false; - // Do an early check on supported supercompressionscheme values - if (m_superCmp != KTX_SS_BASIS_LZ && m_superCmp != KTX_SS_NONE) { - // doing an `errorfmt()` then `close()` causes a seg fault... - close(); - errorfmt("unsupported super compression scheme: {}", - static_cast(m_superCmp)); - return false; - } + m_basewidth = m_spec.width; + m_baseheight = m_spec.height; + m_basedepth = m_spec.depth; - // - // If provided, get target VkFormat explicitly set via the "ktx:vkformat" - // attribute. - // - auto vkFormat = VkFormat::VK_FORMAT_UNDEFINED; - ParamValue* vkFormatQ = m_spec.find_attribute("ktx:vkformat", - TypeDesc::UINT32); - if (vkFormatQ) { - vkFormat = static_cast( - *reinterpret_cast(vkFormatQ->data())); - // Get GPU-block-compression from provided VkFormat - if (vkFormat != VK_FORMAT_UNDEFINED) { - FormatInfo format_info; - if (!get_info_from_vkformat(vkFormat, format_info)) { + // Save name and spec for later use + m_filename = name; + + // If not uint8, default to uint8 (HDR not yet supported) + if (m_spec.format + != TypeDesc::UINT8 /* && m_spec.format != TypeDesc::UINT16 */) + m_spec.set_format(TypeDesc::UINT8); + + ioproxy_retrieve_from_config(m_spec); + if (!ioproxy_use_or_open(m_filename)) + return false; + + // Try to figure the texture kind (i.e., plain 2D texture, volume, or cubemap) + // const auto textureformat = m_spec.get_string_attribute("textureformat"); + // if (textureformat == "Volume Texture") { + // if (m_spec.depth <= 1) { + // errorfmt("Volume textures are expected to have a depth > 1"); + // close(); + // return false; + // } + // m_texturekind = TextureKind::SINGLE_TEXTURE_3D; + // } else if (textureformat == "CubeFace Environment") { + // // TODO: check on tiles + // m_texturekind = TextureKind::CUBEMAP_TEXTURE; + // } else /* unknown */ { + // m_texturekind = TextureKind::SINGLE_TEXTURE_2D; + // } + + std::string colorspace + = m_spec.get_string_attribute("oiio:ColorSpace", + "srgb_rec709_scene"); + bool is_srgb = colorspace == "srgb_rec709_scene"; + + // TODO: get_int_attribute causes a segfault and I have no idea why ... + // Weirdly, calling find_attribute directly (and checking the resulting + // pointer) works, but not get_int_attribute ... + + // + // Use sensible default in case the input data did not originate from a KTX2 + // file and the user did not provide a supercompression scheme. KTX2 usually + // uses Basis LZ supercompression scheme to benefit from both: smaller + // disk filesizes and on-the-fly transcoding to a supported native GPU + // format. + // + if (auto Q = m_spec.find_attribute("ktx:supercompressionscheme", + TypeDesc::UINT32)) { + m_superCmp = static_cast( + *(uint32_t*)(Q->data())); + // Do an early check on supported supercompressionscheme values + if (m_superCmp != KTX_SS_BASIS_LZ && m_superCmp != KTX_SS_NONE) { + // doing an `errorfmt()` then `close()` causes a seg fault... close(); - errorfmt("Could not extract format info from provided " - "VkFormat: {}. This format is probably unsupported.", - static_cast(vkFormat)); + errorfmt("unsupported super compression scheme: {}", + static_cast(m_superCmp)); return false; } - m_cmp = format_info.compression; + DBG std::cout << "[ktxoutput] supercompression scheme: " + << m_superCmp << '\n'; } - // std::cout << "[ktxoutput] found vkformat: " << vkFormat << '\n'; - } - // - // User provided nothing about neither the target VkFormat nor the target - // super-compression scheme. Choose a sane default from provided spec (i.e., - // nchannels + bit depth). Two choices: compress to raw format and nullify - // the benefit of writing a KTX2 at the benefit of a losseless write. Or, - // use UASTC format which is what is typically used within KTX. - // - if (vkFormat == VK_FORMAT_UNDEFINED - && m_colormodel == KHR_DF_MODEL_UNSPECIFIED) { - if (m_spec.format != TypeDesc::UINT8) { - close(); - errorfmt("Non-LDR input is not yet supported."); - return false; + if (auto Q = m_spec.find_attribute("ktx:generatemipmaps", + TypeDesc::INT)) { + m_generate_mipmaps = static_cast(*(int*)Q->data()); + DBG std::cout << "[ktxoutput] generate mipmaps: " << std::boolalpha + << m_generate_mipmaps << '\n'; } - m_colormodel = KHR_DF_MODEL_UASTC; - } - // - // Since we are using libktx's ktxTexture2_CompressAstc, the format has to - // be set to an uncompressed VkFormat otherwise we get KTX_INVALID_OPERATION - // error code. - // - if (m_cmp == BlockCompression::ASTC) - vkFormat = VK_FORMAT_R8G8B8A8_SRGB; + // We can check m_basis_params.codec != NONE but this won't work for + // libktx 4.3.2 which is why we just use a bool var + m_use_basis_universal = false; + if (auto Q = m_spec.find_attribute("ktx:codec", TypeDesc::UINT32)) { + auto codec = *(uint32_t*)Q->data(); + if (!construct_basis_params(m_basis_params, codec)) { + close(); + // construct_basis_params calls errorfmt + return false; + } + m_use_basis_universal = true; + DBG std::cout + << "[ktxoutput] basis universal codec (from \"ktx:codec\"): " + << codec << '\n'; + } - // - // If a Basis Universal format compression is not requested and - // "ktx:vkformat" is VK_FORMAT_UNDEFINED, then we error out. The user has to - // set the vkformat so that we know in which format we write the texture to. - // - if ((m_colormodel != KHR_DF_MODEL_ETC1S - && m_colormodel != KHR_DF_MODEL_UASTC) - && vkFormat == VK_FORMAT_UNDEFINED) { - close(); - errorfmt( - "VkFormat is set to VK_FORMAT_UNDEFINED even though the " - "supercompression scheme is not BasisLZ. You have to set the " - "target VkFormat by setting the ImageSpec's attribute 'ktx:vkformat'."); - return false; - } + // + // If provided, get target VkFormat explicitly set via the "ktx:vkformat" + // attribute. + // + if (auto Q = m_spec.find_attribute("ktx:vkformat", TypeDesc::UINT32)) { + m_vkformat = static_cast(*(uint32_t*)(Q->data())); + if (m_vkformat != VK_FORMAT_UNDEFINED) { + FormatInfo format_info; + if (!get_info_from_vkformat(m_vkformat, format_info)) { + close(); + errorfmt( + "Could not extract format info from provided " + "VkFormat: {}. This format is probably unsupported.", + static_cast(m_vkformat)); + return false; + } + if (m_spec.format != format_info.typedesc + || m_spec.nchannels != format_info.nbrchannels) { + errorfmt( + "Provided image spec is not aligned with provided VkFormat {}", + static_cast(m_vkformat)); + return false; + } + m_cmp = format_info.compression; + m_vkformat_uncompressed = format_info.uncompressed_format; + DBG std::cout << "[ktxoutput] found vkformat: " << m_vkformat + << '\n'; + DBG std::cout << "[ktxoutput] set uncompressed vkformat: " + << m_vkformat_uncompressed << '\n'; + } + } - // - // If we intend to compress to BasisLZ/ETC1S or UASTC then we need to figure - // the VkFormat so that ktxTexture_SetImageFromMemory does not segfault. - // (makes sense, since we are creating a KTX texture and telling it to - // allocate storage, how would it know the size of a given subimage if we - // provide it with VK_FORMAT_UNDEFINED?) - // - if ((m_colormodel == KHR_DF_MODEL_ETC1S - || m_colormodel == KHR_DF_MODEL_UASTC) - && vkFormat == VK_FORMAT_UNDEFINED) { - vkFormat = get_vkformat_from_info(m_spec.nchannels, m_spec.format, - is_srgb); - } else if (m_colormodel == KHR_DF_MODEL_ETC1S - || m_colormodel == KHR_DF_MODEL_UASTC) { - // TODO: It could be that the user explicitly provided a vkformat - in which - // case we have to make sure it aligns with the spec. - // if (!is_vkformat_aligned_with_spec()) ... - close(); - errorfmt("Expected vkformat to be VK_FORMAT_UNDEFINED for Basis " - "Universal (UASTC or ETC1S) target KTX textures."); - return false; - } + // + // User provided nothing about neither the target VkFormat nor the + // target super-compression scheme. Default to writing raw VK_FORMAT + // with ZSTD supercompression (write as losseless KTX2 output). + // + if (m_vkformat == VK_FORMAT_UNDEFINED && m_basis_params.codec == 0) { + m_vkformat = get_vkformat_from_info(m_spec.nchannels, m_spec.format, + is_srgb); + } - // get number of mip levels - m_miplevels = 1; - ParamValue* miplevelsQ = m_spec.find_attribute("ktx:miplevels", - TypeDesc::UINT32); - if (miplevelsQ) { - m_miplevels = *reinterpret_cast(miplevelsQ->data()); - } + // + // If a Basis Universal format compression is not requested and + // "ktx:vkformat" is VK_FORMAT_UNDEFINED, then we error out. The user has to + // set the vkformat so that we know in which format we write the texture to. + // + if (!m_use_basis_universal && m_vkformat == VK_FORMAT_UNDEFINED) { + close(); + errorfmt( + "VkFormat is set to VK_FORMAT_UNDEFINED even though the " + "supercompression scheme is not BasisLZ. You have to set the " + "target VkFormat by setting the ImageSpec's attribute 'ktx:vkformat'."); + return false; + } - if (m_miplevels > 1) { - close(); - errorfmt("Cannot re-generate mip levels because there is no way to " - "know the original filter that was used to generate them."); - return false; - } + // + // If we intend to compress to BasisLZ/ETC1S or UASTC then we need to figure + // the VkFormat so that ktxTexture_SetImageFromMemory does not segfault. + // (makes sense, since we are creating a KTX texture and telling it to + // allocate storage, how would it know the size of a given subimage if we + // provide it with VK_FORMAT_UNDEFINED?) + // + if (m_use_basis_universal && m_vkformat == VK_FORMAT_UNDEFINED) { + m_vkformat = get_vkformat_from_info(m_spec.nchannels, m_spec.format, + is_srgb); + } else if (m_use_basis_universal) { + // TODO: It could be that the user explicitly provided a vkformat - in which + // case we have to make sure it aligns with the spec. + // if (!is_vkformat_aligned_with_spec()) ... + close(); + errorfmt("Expected vkformat to be VK_FORMAT_UNDEFINED for Basis " + "Universal textures."); + return false; + } - // - // TODO: sanity checks on provided attributes (e.g., certain - // supercompression schemes cannot be applied to certain basisu formats, - // etc.) - // + m_max_nmiplevels = (uint32_t)floor( + logf(std::min(m_spec.width, m_spec.height)) + / logf(2)) + + 1; + + DBG std::cout << "[ktxoutput] max number mip levels allowed: " + << m_max_nmiplevels << std::endl; + + // Initialize slices/faces container if not already initialized by a + // previous call to open(name, subimages, specs) + if (m_imgs.empty()) { + OIIO_ASSERT( + m_subimages_to_write == 1 + && "open(name, subimages, specs) should initialize slices/faces container"); + m_imgs.emplace_back(1); + // default is to assume a single mip level + m_imgs[0].emplace_back(m_spec.scanline_bytes() * m_spec.height); + } - // get number of layers - m_nlayers = 1; - ParamValue* nlayersQ = m_spec.find_attribute("ktx:nlayers", - TypeDesc::UINT32); - if (nlayersQ) { - m_nlayers = *reinterpret_cast(nlayersQ->data()); - } + m_initialized = true; + return true; + } // mode == Create - // std::cout << "vkformat: " << vkFormat << '\n'; - // std::cout << "mip levels: " << m_miplevels << '\n'; - // std::cout << "nlayers: " << m_nlayers << '\n'; - // std::cout << "[width, height, depth]: [" << m_spec.width << ", " - // << m_spec.height << ", " << m_spec.depth << "] \n"; + if (mode == AppendMIPLevel) { + if (!m_initialized) { + errorfmt("Cannot append a MIP level if no file has been opened"); + return false; + } - // TODO: avoid some of these static casts into ktx_uint*_t types by storing - // uint*_t as unsigned integers and not as integers. - OIIO_ASSERT(vkFormat != VK_FORMAT_UNDEFINED); // otherwise segfault - ktxTextureCreateInfo create_info; - create_info.glInternalformat = 0; // Ignored as we'll create a KTX2 texture - create_info.vkFormat = vkFormat; - create_info.pDfd = nullptr; - create_info.baseWidth = static_cast(m_spec.width); - create_info.baseHeight = static_cast(m_spec.height); - create_info.baseDepth = static_cast(m_spec.depth); - create_info.numDimensions = 2; // TODO: this is currently hardcoded - create_info.numLevels = 1; // static_cast(m_miplevels) - create_info.numLayers = 1; // static_cast(nlayers) - create_info.numFaces = 1; // TODO: this is currently hardcoded - create_info.isArray = KTX_FALSE; - create_info.generateMipmaps = KTX_FALSE; + if ((m_miplevel_idx + 1) >= m_max_nmiplevels) { + errorfmt("Maximum number of mip levels is reached"); + return false; + } - ktxTexture2* p_tex = nullptr; - auto result = ktxTexture2_Create(&create_info, - KTX_TEXTURE_CREATE_ALLOC_STORAGE, &p_tex); - m_tex.reset(p_tex); + uint32_t miplevel_width = std::max(1u, + m_basewidth >> (m_miplevel_idx + 1)); + uint32_t miplevel_height = std::max(1u, m_baseheight + >> (m_miplevel_idx + 1)); + + // Copy the new mip level size. Keep everything else from the + // original level. + if ((uint32_t)userspec.width != miplevel_width + || (uint32_t)userspec.height != miplevel_height) { + errorfmt("Expected (widht,height): ({},{}) but got: ({},{})", + miplevel_width, miplevel_height, userspec.width, + userspec.height); + return false; + } + m_spec.width = userspec.width; + m_spec.height = userspec.height; + // depth not updated because we don't support appending 3D volume mips + // Dow we need to set something else? + ++m_miplevel_idx; + + // + // For each slice/face, add additional mip map std::vector with + // allocated size of this new mipmap level. KTX doesn't support + // different mip level count per slice/face. + // + for (auto& slice_vec : m_imgs) + slice_vec.emplace_back(m_spec.scanline_bytes() * m_spec.height); - if (result != KTX_SUCCESS) { - close(); - errorfmt("ktxTexture_Create return KTX exit error code: {}", - static_cast(result)); - return false; - } + return true; + } // mode == AppendMIPLevel - // Reserve space for base level mipmap - if (!m_tex->isCompressed) { - m_img.resize( - ktxTexture_GetImageSize(reinterpret_cast(m_tex.get()), - 0)); - } else { - // TODO: - // Not compressed => make sure that vector's size matches the expected - // size from the set raw VkFormat: - // (e.g., VK_FORMAT_R8G8_SRGB => width * height * 3 ) - m_img.resize(m_spec.scanline_bytes() * m_spec.height); - } + // (mode == AppendSubimage) is NOT supported. Pre-declaring the number of + // subimages is easier to implement (random access to volume slices is + // already provided). - return true; + return false; } @@ -381,26 +461,28 @@ KtxOutput::write_scanline(int y, int z, TypeDesc format, const void* data, -// TODO: use the span alternative. Apparently, there isn't one that takes a -// depth parameter (i.e., z). bool -KtxOutput::write_scanlines(int ybegin, int yend, - int z /* slice or face or layer */, TypeDesc format, +KtxOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, const void* data, stride_t xstride, stride_t ystride) { + const int width = std::max(m_basewidth >> m_miplevel_idx, 1u); + const int height = std::max(m_baseheight >> m_miplevel_idx, 1u); + if (ybegin < 0 || ybegin >= yend || yend > height || z < 0 + || z >= (int)m_basedepth) { + errorfmt( + "KTX write_scanlines: Out of valid range scanline indices. " + "Provided: ybegin={} yend={} z={}. " + "Constraints: ybegin: [0, min({},yend)[, yend>ybegin, z: [0,{}[", + ybegin, yend, z, height, m_basedepth); + return false; + } // std::cout << "write_scanlines called with: ybegin=" << ybegin // << "; yend=" << yend << "; z=" << z << "; format=" << format // << "; xstride=" << xstride << '\n'; stride_t zstride = AutoStride; - m_spec.auto_stride(xstride, ystride, zstride, format, spec().nchannels, - m_spec.width, m_spec.height); - // const void* origdata = data; - - // to_native_rectangle will do this check and assignment. Keep it here for - // consistency with JPEG writer - if (format == TypeUnknown) - format = m_spec.format; + m_spec.auto_stride(xstride, ystride, zstride, format, m_spec.nchannels, + width, height); // // Convert to the native format the current specs expects. This is needed, @@ -410,8 +492,8 @@ KtxOutput::write_scanlines(int ybegin, int yend, // Returned data pointer may be the same as the provided pointer (i.e., no // conversion is needed because supplied data is already in native format). // - data = to_native_rectangle(m_spec.x, m_spec.x + m_spec.width, ybegin, yend, - z, z + 1, format, data, xstride, ystride, + data = to_native_rectangle(/* m_spec.x */ 0, /* m_spec.x + */ width, ybegin, + yend, z, z + 1, format, data, xstride, ystride, zstride, m_scratch, false, 0, ybegin, z); // data should now be contiguous and of the expected format (UINT8 or @@ -422,8 +504,9 @@ KtxOutput::write_scanlines(int ybegin, int yend, size_t offset = ybegin * pitch; size_t datalen = (yend - ybegin) * pitch; - memcpy(m_img.data() + offset, pSrc, datalen); - // std::cout << "write_scanlines success" << '\n'; + memcpy(m_imgs[z][m_miplevel_idx].data() + offset, pSrc, datalen); + DBG std::cout << "write_scanlines wrote " << datalen << " bytes" + << std::endl; return true; } @@ -432,18 +515,16 @@ KtxOutput::write_scanlines(int ybegin, int yend, bool KtxOutput::close() { + DBG std::cout << "[ktxoutput] close() called" << std::endl; + // TODO: sure about returning true? (closing an un-opened ImageOutput instance is fine, right?) + if (!m_initialized) + return true; // Check if already closed => if so, then the KTX2 file is already saved if (!ioproxy_opened()) { init(); return true; } - - bool result = true; - if (m_tex) { - // Apparently we can't do (or I don't know yet how to) partial writes - // using libktx. We can only write whole ktxTextures all together. - result = write_ktx2(); // TODO: can this throw? (prob not) - } + bool result = write_ktx2(); // TODO: can this throw? (prob not) init(); return result; } @@ -453,81 +534,175 @@ KtxOutput::close() void KtxOutput::init() { - // TODO: calling open() after close() on this hasn't been testes yet ... - m_tex = nullptr; - m_superCmp = KTX_SS_NONE; - m_colormodel = KHR_DF_MODEL_UNSPECIFIED; - // TODO: other stuff... + // TODO: calling open() after close() on this hasn't been tested yet ... + m_initialized = false; + m_filename = std::string(); + m_vkformat = VK_FORMAT_UNDEFINED; + m_vkformat_uncompressed = VK_FORMAT_UNDEFINED; + m_miplevel_idx = 0; + m_subimages_to_write = 1; + m_max_nmiplevels = 1; + m_basewidth = 0; + m_baseheight = 0; + m_basedepth = 0; + m_superCmp = KTX_SS_NONE; + m_generate_mipmaps = false; + m_cmp = BlockCompression::NONE; + m_use_basis_universal = false; + m_basis_params = { 0 }; + m_astc_params = { 0 }; + // m_bcn_params = { 0 }; + m_zlib_level = 9; + m_zstd_level = 22; + m_imgs.clear(); ioproxy_clear(); } -// -// Applies BasisLZ/ETC1S supercompression to this KTX2 texture. The ImageSpec is -// queried (searched) for attribute that determine the BasisLZ/ETC1S compression -// params (see ktxBasisParams struct in libktx). -// +/// +/// Contruct ktxBasisParams struct from given input (from the provided +/// ImageSpec). There are two ways to approach this: +/// 1. Expect the whole ktxBasisParams to be provided by the user +/// 2. Search for each individual field +/// +/// Option 1) has the following benefits: +/// - Expose only one attribute "ktx:basisparams" +/// But: +/// - The user has to be aware of which libktx version is used (very bad) +/// - Less safe (?) +/// +/// Options 2) has the following benefits: +/// - User just has to provide each parameter separately thus => don't have +/// to care about which libktx version is used. +/// - Safer (?) +/// But: +/// - A lot of "ktx:" attributes have to be exposed. We can +/// provide "high-level" parameters but that will make writing KTX2 output +/// using OIIO significantly less customizable. +/// +/// TODO: update comment when we finally agree on which approach. +/// +/// For the moment, option 2) option 2) is opted for. +/// bool -KtxOutput::basisu_basislz_compress() +KtxOutput::construct_basis_params(ktxBasisParams& params, uint32_t codec, + uint32_t threads) const { - // TODO: retrieve BasisLZ/ETC1S compression params. `ktx info` prints some - // Basis Supercompression Global Data that might be useful in figuring out - // what params the original data was compressed with so that we can reproduce - // it. - // TODO: expose as "ktx:" attribute(s) - ktxBasisParams params = { 0 }; - params.structSize = sizeof(ktxBasisParams); + // Set defaults + params = { 0 }; + params.structSize = sizeof(ktxBasisParams); + // params.codec = KTX_BASIS_CODEC_UASTC_LDR_4x4; + params.verbose = false; + params.noSSE = false; + params.threadCount = threads; + #if Ktx_VERSION >= OIIO_MAKE_VERSION(5, 0, 0) || Ktx_VERSION == Ktx_VERSIONLESS - params.codec = ktx_basis_codec_e::KTX_BASIS_CODEC_ETC1S; params.etc1sCompressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL; #else - params.uastc = false; params.compressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL; #endif - params.verbose = false; - params.noSSE = false; - params.threadCount = 1; - // TODO: expose RDO support for ETC1S - if (auto status = ktxTexture2_CompressBasisEx(m_tex.get(), ¶ms); - status != KTX_SUCCESS) { - errorfmt("ktxTexture2_CompressBasisEx returned error code: ", - static_cast(status)); - return false; - } - return true; -} - + params.uastcFlags = KTX_PACK_UASTC_LEVEL_DEFAULT; + params.uastcRDO = false; -// -// Applies UASTC basis universal 'compression' to this KTX2 texture. -// The ImageSpec is queried (searched) for attribute that determine the UASTC -// compression params (see ktxBasisParams struct in libktx). -// -bool -KtxOutput::basisu_uastc_compress() -{ - // TODO: expose parameters - ktxBasisParams params = { 0 }; - params.structSize = sizeof(ktxBasisParams); #if Ktx_VERSION >= OIIO_MAKE_VERSION(5, 0, 0) || Ktx_VERSION == Ktx_VERSIONLESS - params.codec = ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_LDR_4x4; + if (codec == ktx_basis_codec_e::KTX_BASIS_CODEC_NONE + || codec + >= ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_HDR_6x6_INTERMEDIATE) { + errorfmt("Provided Basis codec is invalid"); + return false; + } + params.codec = codec; #else - params.uastc = true; -#endif - params.verbose = false; - params.noSSE = false; - params.threadCount = 1; - params.uastcFlags = KTX_PACK_UASTC_LEVEL_DEFAULT; - params.uastcRDO = false; - // TODO: expose RDO support for UASTC - if (auto status = ktxTexture2_CompressBasisEx(m_tex.get(), ¶ms); - status != KTX_SUCCESS) { - errorfmt("ktxTexture2_CompressBasisEx returned error code: ", - static_cast(status)); + // Is provided codec valid (ETC1S or UASTC 4x4 LDR) + if (codec != 1 && codec != 2) { + errorfmt( + "Provided Basis codec is invalid. Expected 1 for ETC1S or 2 for UASTC but got: {}", + codec); return false; } + // 2 == ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_LDR_4x4 in libktx 5.0.0 + params.uastc = codec == 2; +#endif + + if (params.codec == ktx_basis_codec_e::KTX_BASIS_CODEC_ETC1S) { + // Params that only apply to ETC1S + if (auto Q = m_spec.find_attribute("ktx:etc1sCompressionLevel", + TypeDesc::UINT32)) + params.etc1sCompressionLevel = *(uint32_t*)Q->data(); + if (auto Q = m_spec.find_attribute("ktx:qualityLevel", TypeDesc::UINT32)) + params.qualityLevel = *(uint32_t*)Q->data(); + if (auto Q = m_spec.find_attribute("ktx:maxEndpoints", TypeDesc::UINT32)) + params.maxEndpoints = *(uint32_t*)Q->data(); + if (auto Q = m_spec.find_attribute("ktx:endpointRDOThreshold", + TypeDesc::FLOAT)) + params.endpointRDOThreshold = *(float*)Q->data(); + if (auto Q = m_spec.find_attribute("ktx:maxSelectors", TypeDesc::UINT32)) + params.maxSelectors = *(uint32_t*)Q->data(); + if (auto Q = m_spec.find_attribute("ktx:selectorRDOThreshold", + TypeDesc::FLOAT)) + params.selectorRDOThreshold = *(float*)Q->data(); + if (m_spec.find_attribute("ktx:normalMap")) + params.normalMap = true; + if (m_spec.find_attribute("ktx:separateRGToRGB_A")) + params.separateRGToRGB_A = true; + if (m_spec.find_attribute("ktx:preSwizzle")) + params.preSwizzle = true; + if (m_spec.find_attribute("ktx:noEndpointRDO")) + params.noEndpointRDO = true; + if (m_spec.find_attribute("ktx:noSelectorRDO")) + params.noSelectorRDO = true; + } else if (params.codec == ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_LDR_4x4 + || params.codec + == ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_HDR_4x4 + || params.codec + == ktx_basis_codec_e:: + KTX_BASIS_CODEC_UASTC_HDR_6x6_INTERMEDIATE) { + // Params that only apply to UASTC + if (auto Q = m_spec.find_attribute("ktx:uastcFlags", TypeDesc::UINT32)) + params.uastcFlags = *(uint32_t*)Q->data(); + if (m_spec.find_attribute("ktx:uastcRDO")) + params.uastcRDO = true; + if (auto Q = m_spec.find_attribute("ktx:uastcRDOQualityScalar", + TypeDesc::FLOAT)) + params.uastcRDOQualityScalar = *(float*)Q->data(); + if (auto Q = m_spec.find_attribute("ktx:uastcRDODictSize", + TypeDesc::UINT32)) + params.uastcRDODictSize = *(uint32_t*)Q->data(); + if (auto Q + = m_spec.find_attribute("ktx:uastcRDOMaxSmoothBlockErrorScale", + TypeDesc::FLOAT)) + params.uastcRDOMaxSmoothBlockErrorScale = *(float*)Q->data(); + if (auto Q = m_spec.find_attribute("ktx:uastcRDOMaxSmoothBlockStdDev", + TypeDesc::FLOAT)) + params.uastcRDOMaxSmoothBlockStdDev = *(float*)Q->data(); + if (m_spec.find_attribute("ktx:uastcRDODontFavorSimplerModes")) + params.uastcRDODontFavorSimplerModes = true; + if (m_spec.find_attribute("ktx:uastcRDONoMultithreading")) + params.uastcRDONoMultithreading = true; + if (auto Q = m_spec.find_attribute("ktx:uastcHDRQuality", + TypeDesc::UINT32)) + params.uastcHDRQuality = *(uint32_t*)Q->data(); + if (m_spec.find_attribute("ktx:uastcHDRUberMode")) + params.uastcHDRUberMode = true; + if (m_spec.find_attribute("ktx:uastcHDRUltraQuant")) + params.uastcHDRUltraQuant = true; + if (m_spec.find_attribute("ktx:uastcHDRFavorAstc")) + params.uastcHDRFavorAstc = true; + if (auto Q = m_spec.find_attribute("ktx:uastcHDRLambda", + TypeDesc::FLOAT)) + params.uastcHDRLambda = *(float*)Q->data(); + if (auto Q = m_spec.find_attribute("ktx:uastcHDRLevel", + TypeDesc::UINT32)) + params.uastcHDRLevel = *(uint32_t*)Q->data(); + } + + // Params that apply to both ETC1S and UASTC + if (auto Q = m_spec.find_attribute("ktx:inputSwizzle", + TypeDesc(TypeDesc::CHAR, 4))) + memcpy(params.inputSwizzle, Q->data(), 4); + return true; } @@ -536,115 +711,201 @@ KtxOutput::basisu_uastc_compress() bool KtxOutput::write_ktx2() { - // TODO: this attribute should be ignored in testing. - // Add/overwrite the KTXwriter metadata entry. The specs encourages us to do - // so. - char writer[100]; - snprintf(writer, sizeof(writer), "oiio version %d - plugin version %d", - OPENIMAGEIO_VERSION, OIIO_PLUGIN_VERSION); - ktxHashList_AddKVPair(&m_tex->kvDataHead, KTX_WRITER_KEY, - (ktx_uint32_t)strlen(writer) + 1, writer); - // std::cout << "KTXwrite: " << writer << '\n'; + /// + /// KTX2 texture RAII'fied via a unique_ptr. + /// Q. Why not create this in open()? + /// A. open() can be called with AppendMIPLevel mode which means we don't + /// actually know the number of miplevels to create this texture with + /// until a call to close(). libktx doesn't support changing texture + /// attributes after its creation. + /// + std::unique_ptr tex { + nullptr, ktxTexture2_Destroy + }; + + uint32_t vkformat = m_cmp == BlockCompression::NONE + ? m_vkformat + : m_vkformat_uncompressed; + + OIIO_ASSERT(vkformat != VK_FORMAT_UNDEFINED); // otherwise segfault + ktxTextureCreateInfo create_info; + create_info.glInternalformat = 0; // Ignored as this is not a KTX1 texture + create_info.vkFormat = vkformat; + create_info.pDfd = nullptr; + create_info.baseWidth = m_basewidth; + create_info.baseHeight = m_baseheight; + create_info.baseDepth = m_basedepth; + create_info.numDimensions = m_basedepth > 1 ? 3 : 2; + create_info.numLevels = m_miplevel_idx + 1; + create_info.numLayers = 1; // Can't support this with current OIIO API + create_info.numFaces = 1; + create_info.isArray = KTX_FALSE; // Can't support this with current OIIO API + create_info.generateMipmaps = m_generate_mipmaps; + + DBG std::cout << "calling ktxTexture2_Create with: " + << "vkFormat=" << create_info.vkFormat << "; " + << "baseWidth=" << create_info.baseWidth << "; " + << "baseHeight=" << create_info.baseHeight << "; " + << "baseDepth=" << create_info.baseDepth << "; " + << "numDimensions=" << create_info.numDimensions << "; " + << "numLevels=" << create_info.numLevels << "; " + << "numLayers=" << create_info.numLayers << "; " + << "numFaces=" << create_info.numFaces << "; " + << "isArray=" << create_info.isArray << "; " + << "generateMipmaps=" << create_info.generateMipmaps << "; " + << std::endl; + + ktxTexture2* p_tex = nullptr; + auto result = ktxTexture2_Create(&create_info, + KTX_TEXTURE_CREATE_ALLOC_STORAGE, &p_tex); + tex.reset(p_tex); + + if (result != KTX_SUCCESS) { + errorfmt("ktxTexture2_Create returnned ktx_error_code: {}", + static_cast(result)); + return false; + } + + DBG std::cout << "ktxTexture2_Create created texture successfully" + << std::endl; // - // In case data was read from an input KTX2 file with mipmaps, we have to - // write the base level then generate mipmaps up to the specified level - // (via the "ktx:miplevels" attribute). KTX-Software (not necessarily - // libktx), surely has a function somewhere that generates these mipmaps. - // Ideally, we should follow the exact same implementation used in - // KTX-Software to generate the mipmaps. + // At first, set uncompressed data for all miplevels, layers, slices, etc. + // The loop over slices and the loop over faces are mutually exclusive + // (i.e., if tex->numLayers > 1 then tex->numFaces == 1, and vice versa). // // Note 1: // You may notice the `generateMipmaps` flag in the ktxTexture struct, it // is just used to instruct Vulkan or OpenGL to generate mipmaps for the // texture to be uploaded NOT for mipmap generation on the CPU. // - // Note 2: - // There is apparently no metadata to know which filter (+ params) that - // was used to generate the mipmaps. - // // Important: // If the VkFormat related to the KTX texture creation is wrongly set, this // will cause a segfault! // - if (!m_tex->isCompressed) { - if (auto status = ktxTexture_SetImageFromMemory(ktxTexture(m_tex.get()), - 0, 0, 0, m_img.data(), - m_img.size()); - status != KTX_SUCCESS) { - errorfmt( - "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", - static_cast(status)); - return false; - } - } else if (m_cmp == BlockCompression::BC1 || m_cmp == BlockCompression::BC3 - || m_cmp == BlockCompression::BC4 - || m_cmp == BlockCompression::BC5 - || m_cmp == BlockCompression::BC7) { - // First set uncompressed texture - // if (auto status = ktxTexture_SetImageFromMemory(ktxTexture(m_tex), 0, 0, - // 0, m_img.data(), - // m_img.size()); - // status != KTX_SUCCESS) { - // errorfmt( - // "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", - // static_cast(status)); - // return false; - // } + for (uint32_t level_idx = 0; level_idx < tex->numLevels; ++level_idx) { + const uint32_t depth = std::max(tex->baseDepth >> level_idx, 1u); + for (uint32_t face_idx = 0; face_idx < tex->numFaces; ++face_idx) { + for (uint32_t slice_idx = 0; slice_idx < depth; ++slice_idx) { + // ImageBuf mipmap_data = generate_miplevel(make_cspan(m_img[m_miplevel_idx]), level_idx, m_mipmap_generation_params.filtername); + // + // TODO: can this not be contiguous? If so, add another loop + // (and another loop if scanlines are not contiguous). Might end in a black hole at this rate ... + // OIIO_ASSERT(mipmap_data.contiguous()); + // auto mipmap_data_span = mipmap_data.localpixels_as_byte_image_span(); + // auto data_ptr = (const ktx_uint8_t*)mipmap_data_span.data(); + // auto data_size = mipmap_data_span.size_bytes(); + + // Faces and Slices are mutually exclusive, addition is fine + auto data_ptr = m_imgs[slice_idx + face_idx][level_idx].data(); + const size_t data_size + = m_imgs[slice_idx + face_idx][level_idx].size(); + + // Before anything, be absolutely certain that what we are about + // to write is of the exact same size (in bytes) of what libktx + // expects us to write for this mip level. + const size_t expected_size + = ktxTexture2_GetImageSize(tex.get(), level_idx); + if (data_size != expected_size) { + errorfmt( + "libktx expects {} bytes to be written for this mip level {} but {} bytes are instead attempted to be written", + expected_size, level_idx, data_size); + return false; + } + + auto status = ktxTexture_SetImageFromMemory( + (ktxTexture*)tex.get(), level_idx, 0, face_idx + slice_idx, + data_ptr, data_size); + if (status != KTX_SUCCESS) { + errorfmt( + "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", + static_cast(status)); + return false; + } + DBG std::cout << fmt::format( + "ktxTexture_SetImageFromMemory for slice_idx={} face_idx={} level_idx={} wrote {} bytes", + slice_idx, face_idx, level_idx, data_size) + << std::endl; + } // slices + } // faces + } // mip levels + - // Then compress the whole texture to BCn format + // + // After having written all necessary uncompressed data, check if the + // texture is expected to be compressed (i.e., data should be compressed + // using some specified GPU-block-compression format). If so, compress + // said data using libktx. + // + if (m_cmp != BlockCompression::NONE) { + switch (m_cmp) { + // ASTC + case BlockCompression::ASTC: { + // TODO: expose ASTC compression quality parameter as spec attribute + if (auto status = ktxTexture2_CompressAstcEx(tex.get(), + &m_astc_params); + status != KTX_SUCCESS) { + errorfmt( + "ktxTexture2_CompressAstc returned KTX exit error code: {}", + static_cast(status)); + return false; + } + break; + } +#if 0 + // BCn + case BlockCompression::BC1: + case BlockCompression::BC3: + case BlockCompression::BC4: + case BlockCompression::BC5: + case BlockCompression::BC7: // TODO: expose BCn compression quality parameter as spec attribute - // if (auto status = ktxTexture2_CompressBCn(m_tex, nullptr); - // status != KTX_SUCCESS) { - // errorfmt("ktxTexture2_CompressBCn returned KTX exit error code: {}", - // static_cast(status)); - // return false; - // } - errorfmt("Writing/Encoding BCn compression is not yet supported."); - return false; - } else if (m_cmp == BlockCompression::ASTC) { - // First set uncompressed images - if (auto status = ktxTexture_SetImageFromMemory( - reinterpret_cast(m_tex.get()), 0, 0, 0, - m_img.data(), m_img.size()); + if (auto status = ktxTexture2_CompressBCn(tex, nullptr); status != KTX_SUCCESS) { - errorfmt( - "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", - static_cast(status)); + errorfmt("ktxTexture2_CompressBCn returned KTX exit error code: {}", + static_cast(status)); return false; } - - // Then compress the whole texture to ASTC format - // TODO: expose ASTC compression quality parameter as spec attribute - if (auto status = ktxTexture2_CompressAstc(m_tex.get(), 0); - status != KTX_SUCCESS) { - errorfmt("ktxTexture2_CompressAstc returned KTX exit error code: {}", - static_cast(status)); +#endif + default: + errorfmt("Writing/Encoding {} compression is not supported", + block_compression_name(m_cmp)); return false; } } - // If basis universal compression is requested (i.e., to BasisLZ/ETC1S - // or UASTC), compress the texture before writing. - if (m_colormodel == KHR_DF_MODEL_ETC1S) { - if (!basisu_basislz_compress()) - return false; - } else if (m_colormodel == KHR_DF_MODEL_UASTC) { - if (!basisu_uastc_compress()) + // + // If a Basis Universal compression is requested (i.e., to BasisLZ/ETC1S + // or UASTC), compress the texture before writing. This is mutually + // exclusive with the isCompressed check above (i.e., you can't have a + // texture that is compressed using some GPU-block-format that also uses + // some Basis Universal latent format). + // + if (m_use_basis_universal) { + if (auto status = ktxTexture2_CompressBasisEx(tex.get(), + &m_basis_params); + status != KTX_SUCCESS) { + errorfmt("ktxTexture2_CompressBasisEx returned error code: ", + static_cast(status)); return false; + } } - // Finally, apply the supercompression scheme (if any) + // + // Finally, apply the supercompression scheme (if any). Supercompression + // can be applied (especially if RDO is used with BCn/ASTC) to significantly + // reduce disk file size at the expense of additional CPU data load time + // (i.e., data now has to be inflated before being uploaded to the GPU). + // if (m_superCmp == KTX_SS_ZLIB) { - if (auto status = ktxTexture2_DeflateZLIB(m_tex.get(), 0); + if (auto status = ktxTexture2_DeflateZLIB(tex.get(), m_zlib_level); status != KTX_SUCCESS) { errorfmt("ktxTexture2_DeflateZLIB returned KTX exit error code: {}", static_cast(status)); return false; } - } else if (m_superCmp == KTX_SS_ZSTD) { - if (auto status = ktxTexture2_DeflateZstd(m_tex.get(), 0); + if (auto status = ktxTexture2_DeflateZstd(tex.get(), m_zstd_level); status != KTX_SUCCESS) { errorfmt("ktxTexture2_DeflateZstd returned KTX exit error code: {}", static_cast(status)); @@ -652,11 +913,27 @@ KtxOutput::write_ktx2() } } + // + // Now write key/value data (KVD). Ideally, we should follow the KTX2 spec + // about which KVD we are encouraged to write and also follow same process + // as KTX-Software (e.g., write used mipmap filter in KTXScWriterParams + // entry, write KTXWriter fields, etc.) + // + + // Add/overwrite the KTXwriter metadata entry. The specs encourages us to do + // so. + char writer[100]; + snprintf(writer, sizeof(writer), "oiio version %d - plugin version %d", + OPENIMAGEIO_VERSION, OIIO_PLUGIN_VERSION); + ktxHashList_AddKVPair(&tex->kvDataHead, KTX_WRITER_KEY, + (ktx_uint32_t)strlen(writer) + 1, writer); + // std::cout << "KTXwrite: " << writer << '\n'; + + Filesystem::IOProxy* m_io = ioproxy(); if (!strcmp(m_io->proxytype(), "file")) { auto fd = reinterpret_cast(m_io)->handle(); - if (auto status = ktxTexture_WriteToStdioStream( - reinterpret_cast(m_tex.get()), fd); + if (auto status = ktxTexture2_WriteToStdioStream(tex.get(), fd); status != KTX_SUCCESS) { errorfmt( "ktxTexture2_WriteToStdioStream returned KTX exit error code: {}", @@ -670,8 +947,8 @@ KtxOutput::write_ktx2() auto proxy = reinterpret_cast(m_io); ktx_uint8_t* buff; ktx_size_t buff_size; - if (auto status = ktxTexture_WriteToMemory( - reinterpret_cast(m_tex.get()), &buff, &buff_size); + if (auto status = ktxTexture2_WriteToMemory(tex.get(), &buff, + &buff_size); status != KTX_SUCCESS) { errorfmt( "ktxTexture2_WriteToMemory returned KTX exit error code: {}", @@ -691,40 +968,4 @@ KtxOutput::write_ktx2() return false; } - - -// void -// KtxOutput::generate_mip_levels(const image_span& base_lvl_image, -// ImageInput& inputFile, uint32_t numMipLevels, -// uint32_t layerIndex, uint32_t faceIndex, -// uint32_t depthSliceIndex) -// { -// //if (isFormatINT(static_cast(texture->vkFormat))) -// // fatal(rc::NOT_SUPPORTED, "Mipmap generation for SINT or UINT format {} is not supported.", -// // toString(static_cast(texture->vkFormat))); -// -// for (uint32_t mipLevelIndex = 1; mipLevelIndex < numMipLevels; -// ++mipLevelIndex) { -// const auto mipImageWidth = std::max(1u, m_tex->baseWidth -// >> (mipLevelIndex)); -// const auto mipImageHeight = std::max(1u, m_tex->baseHeight -// >> (mipLevelIndex)); -// -// ROI roi(0, mipImageHeight, 0, mipImageWidth, 0, 1, /*chans:*/ 0, 4); -// ImageBuf dst = ImageBufAlgo::resample(Src, true, roi); -// // if (options.normalize) -// // image->normalize(); -// -// // const auto imageData = convert(levelImage, options.vkFormat, inputFile, -// // true); -// -// const auto ret = ktxTexture_SetImageFromMemory( -// m_tex, mipLevelIndex, layerIndex, -// faceIndex -// + depthSliceIndex, // Faces and Depths are mutually exclusive, Addition is acceptable -// NULL, 0); -// // (ret == KTX_SUCCESS && "Internal error"); -// } -// } - OIIO_PLUGIN_NAMESPACE_END From 0644e14e7b25eff412e95cfdcce698e80783c0f8 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sat, 18 Jul 2026 05:43:59 +0200 Subject: [PATCH 34/42] Add ktx input tests and remove libktx v4.3.2 support Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 10 +- src/cmake/build_Ktx.cmake | 2 +- src/cmake/externalpackages.cmake | 19 +- src/ktx.imageio/ktx_pvt.h | 59 +++- src/ktx.imageio/ktxinput.cpp | 84 +++--- src/ktx.imageio/ktxoutput.cpp | 273 ++++++++----------- testsuite/ktx/ref/out.txt | 455 ++++++++++++++++++++++--------- testsuite/ktx/run.py | 81 +++++- 8 files changed, 622 insertions(+), 361 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec1a2cfcfb..671b5112c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -468,12 +468,10 @@ jobs: Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 - Ktx_BUILD_VERSION=v4.3.2 - Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd # OpenJPEG_BUILD_VERSION=2.2.0 # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e # libktx > 4.3.2 requires CMake >= 3.22 - optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' + optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6;Ktx' build_local_deps: 'TIFF' - desc: oldest clang11/C++17 py3.9 exr3.1 ocio2.3 # Oldest clang and versions of the dependencies that we support. @@ -501,12 +499,10 @@ jobs: Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 - Ktx_BUILD_VERSION=v4.3.2 - Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd # OpenJPEG_BUILD_VERSION=2.2.0 # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e # libktx > 4.3.2 requires CMake >= 3.22 - optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' + optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6;Ktx' build_local_deps: 'TIFF' - desc: hobbled gcc9/C++17 py3.9 exr-3.1 no-sse @@ -543,8 +539,6 @@ jobs: Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 - Ktx_BUILD_VERSION=v4.3.2 - Ktx_GIT_COMMIT=91ace88675ac59a97e55d0378a6602a9ae6b98bd # libktx > 4.3.2 requires CMake >= 3.22 required_deps: none build_local_deps: 'TIFF' diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index 27a149367a..baad194e37 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -5,7 +5,7 @@ set_cache (Ktx_BUILD_VERSION main "Ktx version for local builds") set (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") set_cache (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") -set_cache (Ktx_GIT_COMMIT "0082055d7147f558f241e531d85af5c39d9c09dc" +set_cache (Ktx_GIT_COMMIT "e2f948066c108b56b8d0052b460b2ac7d34886aa" "commit hash to verify tag/branch against") set_cache (Ktx_BUILD_SHARED_LIBS OFF #${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index b476cdc56d..e577614a9d 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -259,17 +259,20 @@ else () get_target_property(FMT_INCLUDE_DIR fmt::fmt-header-only INTERFACE_INCLUDE_DIRECTORIES) endif () -# Ktx for KTX2 textures -# -# Note for KTX plugin developers: +# Note for Ktx plugin developers: # set VERSION_MIN to 0.0.0 if you have a locally-built tracking HEAD:main of # KTX-Software because building non-tagged libktx sets the version to 0.0.0. # -checked_find_package (Ktx - # VERSION_MIN 4.3.2 - VERSION_MIN 0.0.0 # If you target main branch of KTX-Software which provides no version - BUILD_LOCAL missing -) +# libktx 5.0.0 requires CMake >= 3.22. libktx v4.3.2 supports CMake v3.18 which +# is used in "oldest gcc" CIs but supporting two versions of libktx is very +# cumbersome. +if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.22") + checked_find_package (Ktx + # VERSION_MIN 5.0.0 + VERSION_MIN 0.0.0 # If you target main branch of KTX-Software (versionless) + BUILD_LOCAL missing + ) +endif() ########################################################################### diff --git a/src/ktx.imageio/ktx_pvt.h b/src/ktx.imageio/ktx_pvt.h index 05a952686e..c9f49659c6 100644 --- a/src/ktx.imageio/ktx_pvt.h +++ b/src/ktx.imageio/ktx_pvt.h @@ -11,12 +11,9 @@ #define Ktx_VERSION \ OIIO_MAKE_VERSION(Ktx_VERSION_MAJOR, Ktx_VERSION_MINOR, Ktx_VERSION_PATCH) -// main branch of KTX-Software (libktx) does not set the version of libktx -#define Ktx_VERSIONLESS OIIO_MAKE_VERSION(0, 0, 0) - OIIO_PLUGIN_NAMESPACE_BEGIN -#define DBG if (1) +#define DBG if (0) enum class BlockCompression : uint8_t { NONE = 0u, @@ -82,7 +79,7 @@ struct KTXglFormat { /// Autogenerating this proved to be a bigger headache that simply copying and /// pasting the autogenerated enum from libktx. /// -typedef enum VkFormat { +enum VkFormat : uint32_t { VK_FORMAT_UNDEFINED = 0, VK_FORMAT_R4G4_UNORM_PACK8 = 1, VK_FORMAT_R4G4B4A4_UNORM_PACK16 = 2, @@ -364,7 +361,7 @@ typedef enum VkFormat { VK_FORMAT_A1B5G5R5_UNORM_PACK16_KHR = 1000470000, VK_FORMAT_A8_UNORM_KHR = 1000470001, VK_FORMAT_MAX_ENUM = 0x7FFFFFFF -} VkFormat; +}; @@ -391,24 +388,66 @@ get_info_from_vkformat(VkFormat vkformat, FormatInfo& formatinfo) { // clang-format off switch (vkformat) { - // Raw, uncompressed formats + // Raw, uncompressed formats case VK_FORMAT_R8_UNORM: formatinfo = { 1, TypeDesc::UINT8, BlockCompression::NONE }; return true; case VK_FORMAT_R8G8_UNORM: formatinfo = { 2, TypeDesc::UINT8, BlockCompression::NONE }; return true; + case VK_FORMAT_R8G8B8_UNORM: case VK_FORMAT_R8G8B8_SRGB: formatinfo = { 3, TypeDesc::UINT8, BlockCompression::NONE }; return true; + case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_R8G8B8A8_SRGB: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::NONE }; return true; - // ETC2 block-compressed formats - // TODO: decompress ETC2_RGB into RGB format (not RGBA). This requires some changes + + // ETC2 block-compressed formats case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: formatinfo = { 3, TypeDesc::UINT8, BlockCompression::ETC2_RGB, VK_FORMAT_R8G8B8A8_SRGB }; return true; case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ETC2_RGB_A1, VK_FORMAT_R8G8B8A8_SRGB }; return true; case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ETC2_RGBA, VK_FORMAT_R8G8B8A8_SRGB }; return true; + // BCn formats + case VK_FORMAT_BC1_RGB_UNORM_BLOCK: formatinfo = { 3, TypeDesc::UINT8, BlockCompression::BC1, VK_FORMAT_R8G8B8A8_UNORM }; return true; case VK_FORMAT_BC1_RGB_SRGB_BLOCK: formatinfo = { 3, TypeDesc::UINT8, BlockCompression::BC1, VK_FORMAT_R8G8B8A8_SRGB }; return true; + case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC1A, VK_FORMAT_R8G8B8A8_UNORM }; return true; + case VK_FORMAT_BC1_RGBA_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC1A, VK_FORMAT_R8G8B8A8_SRGB }; return true; + case VK_FORMAT_BC2_UNORM_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC2, VK_FORMAT_R8G8B8A8_UNORM }; return true; + case VK_FORMAT_BC2_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC2, VK_FORMAT_R8G8B8A8_SRGB }; return true; + case VK_FORMAT_BC3_UNORM_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC3, VK_FORMAT_R8G8B8A8_UNORM }; return true; case VK_FORMAT_BC3_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC3, VK_FORMAT_R8G8B8A8_SRGB }; return true; case VK_FORMAT_BC4_UNORM_BLOCK: formatinfo = { 1, TypeDesc::UINT8, BlockCompression::BC4, VK_FORMAT_R8_UNORM }; return true; + case VK_FORMAT_BC4_SNORM_BLOCK: formatinfo = { 1, TypeDesc::UINT8, BlockCompression::BC4, VK_FORMAT_R8_SNORM }; return true; case VK_FORMAT_BC5_UNORM_BLOCK: formatinfo = { 2, TypeDesc::UINT8, BlockCompression::BC5, VK_FORMAT_R8G8_UNORM }; return true; + case VK_FORMAT_BC5_SNORM_BLOCK: formatinfo = { 2, TypeDesc::UINT8, BlockCompression::BC5, VK_FORMAT_R8G8_SNORM }; return true; + case VK_FORMAT_BC6H_UFLOAT_BLOCK: formatinfo = { 3, TypeDesc::HALF, BlockCompression::BC6HU, VK_FORMAT_R16G16B16_SFLOAT }; return true; + case VK_FORMAT_BC6H_SFLOAT_BLOCK: formatinfo = { 3, TypeDesc::HALF, BlockCompression::BC6HS, VK_FORMAT_R16G16B16_SFLOAT }; return true; + case VK_FORMAT_BC7_UNORM_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC7, VK_FORMAT_R8G8B8A8_UNORM }; return true; case VK_FORMAT_BC7_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC7, VK_FORMAT_R8G8B8A8_SRGB }; return true; + // ASTC formats (2D blocks) - case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ASTC, VK_FORMAT_R8G8B8A8_SRGB }; return true; + case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: + case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: + case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: + case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: + case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ASTC, VK_FORMAT_R8G8B8A8_UNORM }; return true; + case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: + case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: + case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: + case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: + case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: + case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: + case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: + case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: + case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: + case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: + case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: + case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: + case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: + case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ASTC, VK_FORMAT_R8G8B8A8_SRGB }; return true; default: break; } // clang-format on diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index 4f0f7f78b0..ce3bdea8e3 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -24,9 +24,12 @@ class KtxInput final : public ImageInput { // as per the KTX1/2 specs: // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_keyvalue_data feature == "arbitrary_metadata" || - /* ktx supports 3D textures, cubmap textures, texture arrays, etc. */ + // KTX2 supports 2D texture arrays, 3D texture arrays, and cubemap + // arrays. That being said, 2D texture arrays is the only one + // supported by this OIIO plugin. feature == "multiimage" || - /* ktx supports storage of mipmaps */ + // KTX2 supports mipmaps. 3D texture mipmaps are treated as a + // per-slice mipmap. feature == "mipmap"); } @@ -240,6 +243,16 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) } } + if (m_tex->isArray && m_tex->numDimensions == 3) { + errorfmt("3D texture arrays are not supported"); + return false; + } + + if (m_tex->isArray && m_tex->numFaces > 1) { + errorfmt("Cubemap texture arrays are not supported"); + return false; + } + m_spec = ImageSpec(m_tex->baseWidth, m_tex->baseHeight, 4 /* dummy value - will be overwritten */, TypeDesc::UINT8); @@ -280,16 +293,11 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) m_spec.extra_attribs.attribute("ktx:miplevels", m_tex->numLevels); m_spec.extra_attribs.attribute("ktx:generatemipmaps", m_tex->generateMipmaps); - - // Store colormodel so that if a KTX2 is requested to be generated, we know - // if a Basis Universal scheme has to be applied. + // TODO: do we need this? m_spec.extra_attribs.attribute("ktx:colormodel", (uint32_t)KHR_DFDVAL(m_tex->pDfd + 1, MODEL)); m_spec.extra_attribs.attribute("ktx:vkformat", (uint32_t)m_tex->vkFormat); - // m_spec.extra_attribs.attribute("ktx:transferfunction", TypeDesc::UINT32, 1, - // cspan(transfer_function)); - // // Save arbitrary metadata. KTX allows for the storage of arbitrary // key/value metadata pairs as per the specification here: @@ -339,15 +347,9 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) auto ktx_prefixed_attr_name = fmt::format("ktx:{}", attr_name); if (attr_name == KTX_WRITER_KEY) { - // KTXwriter identifies the program used to write this KTX file - // Should be NUL terminated. - if (vallen <= 1) - continue; - auto char_ptr = reinterpret_cast(val); - m_spec.extra_attribs.attribute( - ktx_prefixed_attr_name, - std::string(char_ptr, char_ptr + (vallen - 1))); - + // KTXwriter identifies the program used to write this KTX file. + // We don't care about such entry + continue; } else if (attr_name == KTX_WRITER_SCPARAMS_KEY) { // KTXwriterScParams is used to report all kinds of non-default parameters used by ktx tools to write this KTX2 file. // This includes: @@ -458,10 +460,12 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // TODO: wait for my RP in libktx to get merged then add BCn // support. // - // ETC2: TODO: some licensing clarification is needed from the part of - // etcunpack usage in libktx. + // ETC2: libktx provides decoders but they fall under non-open-source + // license. To quote KTX-Software: "The file lib/etcdec.cxx is not + // open source. It is made available under the terms of an Ericsson + // license, found in the file itself." // - // PVRTC: TODO: wait for libktx PR. + // PVRTC: not planned (there are pending PRs in libktx). // if (m_tex->isCompressed /* i.e., is GPU block compressed? */) { // m_cmp = get_block_compression_from_format(m_tex->vkFormat); @@ -471,11 +475,12 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) close(); errorfmt("Could not extract format info from provided " "VkFormat: {}. This format is unsupported", - static_cast(m_vkformat)); + m_tex->vkFormat); return false; } m_cmp = format_info.compression; switch (m_cmp) { +#if 0 // TODO: wait for my PR in libktx to be merged /* BCn GPU formats */ case BlockCompression::BC1: case BlockCompression::BC1A: @@ -486,8 +491,6 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) case BlockCompression::BC6HU: case BlockCompression::BC6HS: case BlockCompression::BC7: - // - // TODO: wait for my PR in libktx to be merged // // Note: // ktxTexture2_DecodeBCn internally creates a new ktxTexture2 texture @@ -499,27 +502,18 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // After this, m_tex->isCompressed will be false => this will only // be called once. // - // if (auto status = ktxTexture2_DecodeBCn(m_tex); - // status != KTX_SUCCESS) { - // errorfmt("failed to decode BCn-compressed texture. " - // "ktxTexture2_DecodeBCn returned Ktx error code: {}", - // static_cast(status)); - // return false; - // } - // break; - errorfmt("BCn GPU-compressed formats are not yet supported."); - return false; - - /* ETC formats */ - case BlockCompression::ETC2_RGB: - case BlockCompression::ETC2_RGB_A1: - case BlockCompression::ETC2_RGBA: - errorfmt("ETC GPU-compressed formats are not yet supported."); - return false; + if (auto status = ktxTexture2_DecodeBCn(m_tex); + status != KTX_SUCCESS) { + errorfmt("failed to decode BCn-compressed texture. " + "ktxTexture2_DecodeBCn returned Ktx error code: {}", + static_cast(status)); + return false; + } + break; +#endif /* ASTC formats */ case BlockCompression::ASTC: -#if Ktx_VERSION > OIIO_MAKE_VERSION(4, 3, 2) || Ktx_VERSION == Ktx_VERSIONLESS // // Note: // ktxTexture2_DecodeAstc internally creates a new ktxTexture2 texture @@ -539,15 +533,9 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) return false; } break; -#else - errorfmt( - "ASTC decoding is not available in libktx v{}. Consider using libktx > 4.3.2", - Ktx_VERSION); - return false; -#endif default: - errorfmt("GPU block compression format {} is unsupported", + errorfmt("{} GPU-compressed formats are not supported", block_compression_name(m_cmp)); return false; } diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp index 7e58980e52..8269037fe5 100644 --- a/src/ktx.imageio/ktxoutput.cpp +++ b/src/ktx.imageio/ktxoutput.cpp @@ -8,6 +8,9 @@ OIIO_PLUGIN_NAMESPACE_BEGIN +// TODO: +// - Should we add support to 2D texture arrays (i.e., "multiimage")? + class KtxOutput final : public ImageOutput { public: KtxOutput() @@ -29,8 +32,9 @@ class KtxOutput final : public ImageOutput { // as per the KTX2 specs: // registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_keyvalue_data feature == "arbitrary_metadata" || - // ktx supports 3D textures, cubmap textures, texture arrays, ... - feature == "multiimage" || + // KTX2 supports 2D texture arrays, cubmap arrays, and 2D texture + // arrays. That being said, we only support 2D texture arrays. + // feature == "multiimage" || // ktx supports mipmaps feature == "mipmap" || // Ktx supports 3D textures @@ -42,9 +46,6 @@ class KtxOutput final : public ImageOutput { bool open(const std::string& name, const ImageSpec& spec, OpenMode mode = Create) override; - bool open(const std::string& name, int subimages, - const ImageSpec* specs) override; - bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; @@ -71,8 +72,6 @@ class KtxOutput final : public ImageOutput { uint32_t m_miplevel_idx { 0 }; ///< Current MIP level - uint32_t m_subimages_to_write { 1 }; ///< Number of slices/faces to write - uint32_t m_max_nmiplevels { 1 }; ///< Max number allowable MIP levels uint32_t m_basewidth { 0 }; ///< MIP level 0 width @@ -108,7 +107,7 @@ class KtxOutput final : public ImageOutput { /// /// Container for raw (i.e., uncompressed) texture data structured as /// follows: - /// slice/face -> mipmaps -> mipmap data + /// mip level -> slice/face -> pixels /// /// The number of slices/faces is known before hand (i.e., appending /// slices/faces is not supported). This significantly simplifies the @@ -120,18 +119,24 @@ class KtxOutput final : public ImageOutput { /// all times. This is also needed because we apply compression upon file /// closure and not each time on write_scanline(s). /// - /// To access underlying data: [slice_idx + face_idx][miplevel_idx] + /// To access underlying data: [miplevel_idx][slice_idx + face_idx] /// Note: slice_idx and face_idx are mutually exclusive so summing them is /// perfectly fine. /// std::vector>> m_imgs; + void append_mipmaps_vector(); + void init(); bool write_ktx2(); bool construct_basis_params(ktxBasisParams& params, uint32_t codec, uint32_t threads = 1) const; + + // bool construct_astc_params(ktxAstcParams& param) const; + + // bool construct_bcn_params(ktxBCnParams& params) const; }; @@ -149,71 +154,24 @@ OIIO_PLUGIN_EXPORTS_END -bool -KtxOutput::open(const std::string& name, int subimages, const ImageSpec* specs) -{ - if (m_initialized) { - errorfmt("Cannot call open() on an already initialized ImageOutput"); - return false; - } - - if (subimages > 10) { - errorfmt("Ktx does not support more than {} subimages", 10); - return false; - } - - if (subimages < 1) { - errorfmt("subimages has to be at least >= 1"); - return false; - } - - // - // All provided specs have to be the same (same width, height, nchannels, - // format, etc.). This is because KTX does not support subimages of - // different dimensions/types/nchannels etc. We only use the first supplid - // spec and the rest are not needed. - // - const ImageSpec& ref_spec = specs[0]; - // for (int i = 1; i < subimages; ++i) { - // const ImageSpec& spec = specs[i]; - // if (spec.width != ref_spec.width || spec.height != ref_spec.height - // || spec.nchannels != ref_spec.nchannels - // || spec.format != ref_spec.format) { - // errorfmt( - // "Ktx expects all specs for all provided subimages to be the same. Different subimage specs is not supported"); - // return false; - // } - // } - - OIIO_ASSERT(m_imgs.empty() && "slices/faces std::vector should be empty"); - - bool ok = open(name, ref_spec, Create); - if (ok) { - m_subimages_to_write = subimages; - m_imgs.resize(m_subimages_to_write); - for (auto& mipmaps_vec : m_imgs) - // default is to assume a single mip level - mipmaps_vec.emplace_back(ref_spec.scanline_bytes() - * ref_spec.height); - } - return ok; -} - - - bool KtxOutput::open(const std::string& name, const ImageSpec& userspec, OpenMode mode) { if (mode == Create) { - // TODO: verify the x, y, z limits (probably not 65535) - // This does: m_spec = userspec - // Q. Why zend is 0 (i.e., volumes not permitted even though "volumes" are supported? - // A. User is expected to call AppendSubimage to a append a slices/faces. - // As far as I know, there no ImageOutput API that allows - // random-access writes of volume slides, TODO: or is there? - if (!check_open(mode, userspec, { 0, 65535, 0, 65535, 0, 65535, 0, 4 })) - return false; + if (userspec.depth + > 1) { // Volume texture are limited to 4096x4096x4096 + if (!check_open(mode, userspec, { 0, 4096, 0, 4096, 0, 4096, 0, 4 })) + return false; + } else if (userspec.tile_width + > 1) { // Cubemap texture are limited to 16384x16384 + if (!check_open(mode, userspec, + { 0, 16384, 0, 16384 * 6, 0, 1, 0, 4 })) + return false; + } else { // 2D texture are limited to 32768x32768 + if (!check_open(mode, userspec, { 0, 32768, 0, 32768, 0, 1, 0, 4 })) + return false; + } m_basewidth = m_spec.width; m_baseheight = m_spec.height; @@ -378,24 +336,30 @@ KtxOutput::open(const std::string& name, const ImageSpec& userspec, return false; } - m_max_nmiplevels = (uint32_t)floor( - logf(std::min(m_spec.width, m_spec.height)) - / logf(2)) - + 1; + if (m_spec.depth > 1) + m_max_nmiplevels + = (uint32_t)floor( + logf(std::min(std::min(m_spec.width, m_spec.height), + m_spec.depth)) + / logf(2)) + + 1; + else + m_max_nmiplevels = (uint32_t)floor( + logf(std::min(m_spec.width, m_spec.height)) + / logf(2)) + + 1; DBG std::cout << "[ktxoutput] max number mip levels allowed: " << m_max_nmiplevels << std::endl; // Initialize slices/faces container if not already initialized by a // previous call to open(name, subimages, specs) - if (m_imgs.empty()) { - OIIO_ASSERT( - m_subimages_to_write == 1 - && "open(name, subimages, specs) should initialize slices/faces container"); - m_imgs.emplace_back(1); - // default is to assume a single mip level - m_imgs[0].emplace_back(m_spec.scanline_bytes() * m_spec.height); - } + OIIO_ASSERT(m_imgs.empty() + && "Expected mip levels container to be empty"); + OIIO_ASSERT(m_miplevel_idx == 0); + + // Reserve space for base-level mipmap (level 0) + append_mipmaps_vector(); m_initialized = true; return true; @@ -416,29 +380,27 @@ KtxOutput::open(const std::string& name, const ImageSpec& userspec, m_basewidth >> (m_miplevel_idx + 1)); uint32_t miplevel_height = std::max(1u, m_baseheight >> (m_miplevel_idx + 1)); + uint32_t miplevel_depth = std::max(1u, + m_basedepth >> (m_miplevel_idx + 1)); // Copy the new mip level size. Keep everything else from the // original level. if ((uint32_t)userspec.width != miplevel_width - || (uint32_t)userspec.height != miplevel_height) { - errorfmt("Expected (widht,height): ({},{}) but got: ({},{})", - miplevel_width, miplevel_height, userspec.width, - userspec.height); + || (uint32_t)userspec.height != miplevel_height + || (uint32_t)userspec.depth != miplevel_depth) { + errorfmt( + "Expected (widht,height,depth): ({},{},{}) but got: ({},{},{})", + miplevel_width, miplevel_height, miplevel_depth, userspec.width, + userspec.height, userspec.depth); return false; } m_spec.width = userspec.width; m_spec.height = userspec.height; - // depth not updated because we don't support appending 3D volume mips - // Dow we need to set something else? + m_spec.depth = userspec.depth; ++m_miplevel_idx; - // - // For each slice/face, add additional mip map std::vector with - // allocated size of this new mipmap level. KTX doesn't support - // different mip level count per slice/face. - // - for (auto& slice_vec : m_imgs) - slice_vec.emplace_back(m_spec.scanline_bytes() * m_spec.height); + // Reserve memory for this mip level + append_mipmaps_vector(); return true; } // mode == AppendMIPLevel @@ -504,7 +466,7 @@ KtxOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, size_t offset = ybegin * pitch; size_t datalen = (yend - ybegin) * pitch; - memcpy(m_imgs[z][m_miplevel_idx].data() + offset, pSrc, datalen); + memcpy(m_imgs[m_miplevel_idx][z].data() + offset, pSrc, datalen); DBG std::cout << "write_scanlines wrote " << datalen << " bytes" << std::endl; return true; @@ -531,6 +493,17 @@ KtxOutput::close() +void +KtxOutput::append_mipmaps_vector() +{ + m_imgs.emplace_back(m_spec.depth); + for (auto& slices_vec : m_imgs[m_miplevel_idx]) { + slices_vec.resize(m_spec.scanline_bytes() * m_spec.height); + } +} + + + void KtxOutput::init() { @@ -540,7 +513,6 @@ KtxOutput::init() m_vkformat = VK_FORMAT_UNDEFINED; m_vkformat_uncompressed = VK_FORMAT_UNDEFINED; m_miplevel_idx = 0; - m_subimages_to_write = 1; m_max_nmiplevels = 1; m_basewidth = 0; m_baseheight = 0; @@ -597,16 +569,11 @@ KtxOutput::construct_basis_params(ktxBasisParams& params, uint32_t codec, params.noSSE = false; params.threadCount = threads; -#if Ktx_VERSION >= OIIO_MAKE_VERSION(5, 0, 0) || Ktx_VERSION == Ktx_VERSIONLESS params.etc1sCompressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL; -#else - params.compressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL; -#endif params.uastcFlags = KTX_PACK_UASTC_LEVEL_DEFAULT; params.uastcRDO = false; -#if Ktx_VERSION >= OIIO_MAKE_VERSION(5, 0, 0) || Ktx_VERSION == Ktx_VERSIONLESS if (codec == ktx_basis_codec_e::KTX_BASIS_CODEC_NONE || codec >= ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_HDR_6x6_INTERMEDIATE) { @@ -614,17 +581,6 @@ KtxOutput::construct_basis_params(ktxBasisParams& params, uint32_t codec, return false; } params.codec = codec; -#else - // Is provided codec valid (ETC1S or UASTC 4x4 LDR) - if (codec != 1 && codec != 2) { - errorfmt( - "Provided Basis codec is invalid. Expected 1 for ETC1S or 2 for UASTC but got: {}", - codec); - return false; - } - // 2 == ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_LDR_4x4 in libktx 5.0.0 - params.uastc = codec == 2; -#endif if (params.codec == ktx_basis_codec_e::KTX_BASIS_CODEC_ETC1S) { // Params that only apply to ETC1S @@ -785,50 +741,55 @@ KtxOutput::write_ktx2() // for (uint32_t level_idx = 0; level_idx < tex->numLevels; ++level_idx) { const uint32_t depth = std::max(tex->baseDepth >> level_idx, 1u); - for (uint32_t face_idx = 0; face_idx < tex->numFaces; ++face_idx) { - for (uint32_t slice_idx = 0; slice_idx < depth; ++slice_idx) { - // ImageBuf mipmap_data = generate_miplevel(make_cspan(m_img[m_miplevel_idx]), level_idx, m_mipmap_generation_params.filtername); - // - // TODO: can this not be contiguous? If so, add another loop - // (and another loop if scanlines are not contiguous). Might end in a black hole at this rate ... - // OIIO_ASSERT(mipmap_data.contiguous()); - // auto mipmap_data_span = mipmap_data.localpixels_as_byte_image_span(); - // auto data_ptr = (const ktx_uint8_t*)mipmap_data_span.data(); - // auto data_size = mipmap_data_span.size_bytes(); - - // Faces and Slices are mutually exclusive, addition is fine - auto data_ptr = m_imgs[slice_idx + face_idx][level_idx].data(); - const size_t data_size - = m_imgs[slice_idx + face_idx][level_idx].size(); - - // Before anything, be absolutely certain that what we are about - // to write is of the exact same size (in bytes) of what libktx - // expects us to write for this mip level. - const size_t expected_size - = ktxTexture2_GetImageSize(tex.get(), level_idx); - if (data_size != expected_size) { - errorfmt( - "libktx expects {} bytes to be written for this mip level {} but {} bytes are instead attempted to be written", - expected_size, level_idx, data_size); - return false; - } - - auto status = ktxTexture_SetImageFromMemory( - (ktxTexture*)tex.get(), level_idx, 0, face_idx + slice_idx, - data_ptr, data_size); - if (status != KTX_SUCCESS) { - errorfmt( - "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", - static_cast(status)); - return false; - } - DBG std::cout << fmt::format( - "ktxTexture_SetImageFromMemory for slice_idx={} face_idx={} level_idx={} wrote {} bytes", - slice_idx, face_idx, level_idx, data_size) - << std::endl; - } // slices - } // faces - } // mip levels + // Since array layers are not supported, this loop will only execute once + for (ktx_uint32_t layer_idx = 0; layer_idx < tex->numLayers; + ++layer_idx) { + for (uint32_t face_idx = 0; face_idx < tex->numFaces; ++face_idx) { + for (uint32_t slice_idx = 0; slice_idx < depth; ++slice_idx) { + // ImageBuf mipmap_data = generate_miplevel(make_cspan(m_img[m_miplevel_idx]), level_idx, m_mipmap_generation_params.filtername); + // + // TODO: can this not be contiguous? If so, add another loop + // (and another loop if scanlines are not contiguous). Might end in a black hole at this rate ... + // OIIO_ASSERT(mipmap_data.contiguous()); + // auto mipmap_data_span = mipmap_data.localpixels_as_byte_image_span(); + // auto data_ptr = (const ktx_uint8_t*)mipmap_data_span.data(); + // auto data_size = mipmap_data_span.size_bytes(); + + // Faces and Slices are mutually exclusive, addition is fine + auto data_ptr + = m_imgs[level_idx][slice_idx + face_idx].data(); + const size_t data_size + = m_imgs[level_idx][slice_idx + face_idx].size(); + + // Before anything, be absolutely certain that what we are about + // to write is of the exact same size (in bytes) of what libktx + // expects us to write for this mip level. + const size_t expected_size + = ktxTexture2_GetImageSize(tex.get(), level_idx); + if (data_size != expected_size) { + errorfmt( + "libktx expects {} bytes to be written for this mip level {} but {} bytes are instead attempted to be written", + expected_size, level_idx, data_size); + return false; + } + + auto status = ktxTexture_SetImageFromMemory( + (ktxTexture*)tex.get(), level_idx, 0, + face_idx + slice_idx, data_ptr, data_size); + if (status != KTX_SUCCESS) { + errorfmt( + "ktxTexture_SetImageFromMemory returned KTX exit error code: {}", + static_cast(status)); + return false; + } + DBG std::cout << fmt::format( + "ktxTexture_SetImageFromMemory for slice_idx={} face_idx={} level_idx={} wrote {} bytes", + slice_idx, face_idx, level_idx, data_size) + << std::endl; + } // slices + } // faces + } // layers + } // mip levels // diff --git a/testsuite/ktx/ref/out.txt b/testsuite/ktx/ref/out.txt index 201a5c8a95..b2eaa624a9 100644 --- a/testsuite/ktx/ref/out.txt +++ b/testsuite/ktx/ref/out.txt @@ -1,180 +1,391 @@ -Reading ../oiio-images/ktx2/2d_rgb8.ktx2 -../oiio-images/ktx2/2d_rgb8.ktx2 : 40 x 40, 3 channel, uint8 ktx - MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 - SHA-1: 5BFA8296353869F0D950DC016A7E31C08D513EE8 +Reading ../oiio-images/ktx2/r8g8b8a8_srgb.ktx2 +../oiio-images/ktx2/r8g8b8a8_srgb.ktx2 : 128 x 128, 4 channel, uint8 ktx + SHA-1: 827AAFAEA2C443CE1B6DD30196163B0E01B4B816 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 1 + ktx:generatemipmaps: 0 + ktx:miplevels: 1 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:version: 2 + ktx:vkformat: 43 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/r8g8b8a8_srgb_mip.ktx2 +../oiio-images/ktx2/r8g8b8a8_srgb_mip.ktx2 : 64 x 64, 4 channel, uint8 ktx + MIP-map levels: 64x64 32x32 16x16 8x8 4x4 2x2 1x1 + SHA-1: 03052CEA1E271CEB85C2A084E44DC23F5BD7345C + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 1 + ktx:generatemipmaps: 0 + ktx:miplevels: 7 + ktx:nlayers: 1 + ktx:supercompressionscheme: 0 + ktx:version: 2 + ktx:vkformat: 43 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/r8g8b8_srgb_mip.ktx2 +../oiio-images/ktx2/r8g8b8_srgb_mip.ktx2 : 64 x 64, 3 channel, uint8 ktx + MIP-map levels: 64x64 32x32 16x16 8x8 4x4 2x2 1x1 + SHA-1: BE1D5EA24E907A4C4B3FB3C28EAC872A20F5B414 channel list: R, G, B + textureformat: "Plain Texture" ktx:colormodel: 1 ktx:generatemipmaps: 0 - ktx:KTXwriter: "ktx create v4.3.1~1 / libktx v4.3.0~1" - ktx:miplevels: 6 + ktx:miplevels: 7 ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:texturekind: 1 - ktx:version: "2.0" + ktx:version: 2 ktx:vkformat: 29 oiio:ColorSpace: "srgb_rec709_scene" -Reading ../oiio-images/ktx2/2d_rgba8.ktx2 -../oiio-images/ktx2/2d_rgba8.ktx2 : 40 x 40, 4 channel, uint8 ktx - MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 - SHA-1: 9B40384A9B89EE338F24212AB876C14F9BABDBC0 +Reading ../oiio-images/ktx2/r8g8b8a8_srgb_3d_7.ktx2 +../oiio-images/ktx2/r8g8b8a8_srgb_3d_7.ktx2 : 16 x 16 x 7, 4 channel, volume uint8 ktx + SHA-1: B25CE648E0293F6B8C5D0433295EFDC7182AA0C6 channel list: R, G, B, A + textureformat: "Volume Texture" ktx:colormodel: 1 ktx:generatemipmaps: 0 - ktx:KTXwriter: "ktx create v4.3.1~1 / libktx v4.3.0~1" - ktx:miplevels: 6 + ktx:miplevels: 1 ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:texturekind: 1 - ktx:version: "2.0" + ktx:version: 2 ktx:vkformat: 43 oiio:ColorSpace: "srgb_rec709_scene" -Reading ../oiio-images/ktx2/2d_uastc.ktx2 -../oiio-images/ktx2/2d_uastc.ktx2 : 40 x 40, 4 channel, uint8 ktx - MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 - SHA-1: A41EE02136B25739FB99520244FF5D0C69A0353C +Reading ../oiio-images/ktx2/r8g8b8a8_srgb_array_7_mip.ktx2 +../oiio-images/ktx2/r8g8b8a8_srgb_array_7_mip.ktx2 : 16 x 16, 4 channel, uint8 ktx + 7 subimages: 16x16 [u8,u8,u8,u8], 16x16 [u8,u8,u8,u8], 16x16 [u8,u8,u8,u8], 16x16 [u8,u8,u8,u8], 16x16 [u8,u8,u8,u8], 16x16 [u8,u8,u8,u8], 16x16 [u8,u8,u8,u8] + subimage 0: 16 x 16, 4 channel, uint8 ktx + MIP-map levels: 16x16 8x8 4x4 2x2 1x1 + SHA-1: A324F1AFE01140C4D58618875715EE545B9F1ECC channel list: R, G, B, A - ktx:colormodel: 166 + textureformat: "Plain Texture" + ktx:colormodel: 1 ktx:generatemipmaps: 0 - ktx:KTXwriter: "ktx create v4.3.1~1 / libktx v4.3.0~1" - ktx:miplevels: 6 - ktx:nlayers: 1 + ktx:miplevels: 5 + ktx:nlayers: 7 ktx:supercompressionscheme: 0 - ktx:texturekind: 1 - ktx:version: "2.0" - ktx:vkformat: 0 + ktx:version: 2 + ktx:vkformat: 43 oiio:ColorSpace: "srgb_rec709_scene" -Reading ../oiio-images/ktx2/2d_etc1s.ktx2 -../oiio-images/ktx2/2d_etc1s.ktx2 : 40 x 40, 4 channel, uint8 ktx - MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 - SHA-1: CD540B1FE2CE1F85BE11F7EC21446A8405844029 + subimage 1: 16 x 16, 4 channel, uint8 ktx + MIP-map levels: 16x16 8x8 4x4 2x2 1x1 + SHA-1: 710B4D8675FE67BFE9E6897E0514EBCB2255385F channel list: R, G, B, A - ktx:colormodel: 163 + textureformat: "Plain Texture" + ktx:colormodel: 1 + ktx:generatemipmaps: 0 + ktx:miplevels: 5 + ktx:nlayers: 7 + ktx:supercompressionscheme: 0 + ktx:version: 2 + ktx:vkformat: 43 + oiio:ColorSpace: "srgb_rec709_scene" + subimage 2: 16 x 16, 4 channel, uint8 ktx + MIP-map levels: 16x16 8x8 4x4 2x2 1x1 + SHA-1: CCB0462997F53FCDAFD94FA2E6C862E0D2CA022A + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 1 ktx:generatemipmaps: 0 - ktx:KTXwriter: "ktx create v4.3.1~1 / libktx v4.3.0~1" - ktx:miplevels: 6 + ktx:miplevels: 5 + ktx:nlayers: 7 + ktx:supercompressionscheme: 0 + ktx:version: 2 + ktx:vkformat: 43 + oiio:ColorSpace: "srgb_rec709_scene" + subimage 3: 16 x 16, 4 channel, uint8 ktx + MIP-map levels: 16x16 8x8 4x4 2x2 1x1 + SHA-1: CF06A8313BD1A85F6677F1CA7915C3CFEF954C6E + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 1 + ktx:generatemipmaps: 0 + ktx:miplevels: 5 + ktx:nlayers: 7 + ktx:supercompressionscheme: 0 + ktx:version: 2 + ktx:vkformat: 43 + oiio:ColorSpace: "srgb_rec709_scene" + subimage 4: 16 x 16, 4 channel, uint8 ktx + MIP-map levels: 16x16 8x8 4x4 2x2 1x1 + SHA-1: 54034063DBC1C1BB61CB60EC57E4498678DC2B13 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 1 + ktx:generatemipmaps: 0 + ktx:miplevels: 5 + ktx:nlayers: 7 + ktx:supercompressionscheme: 0 + ktx:version: 2 + ktx:vkformat: 43 + oiio:ColorSpace: "srgb_rec709_scene" + subimage 5: 16 x 16, 4 channel, uint8 ktx + MIP-map levels: 16x16 8x8 4x4 2x2 1x1 + SHA-1: 408D3A129857F4942CA5A8D10CBA2D51C5F01901 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 1 + ktx:generatemipmaps: 0 + ktx:miplevels: 5 + ktx:nlayers: 7 + ktx:supercompressionscheme: 0 + ktx:version: 2 + ktx:vkformat: 43 + oiio:ColorSpace: "srgb_rec709_scene" + subimage 6: 16 x 16, 4 channel, uint8 ktx + MIP-map levels: 16x16 8x8 4x4 2x2 1x1 + SHA-1: 2966B466A5D0AFB2531AEBFEDB7304D560DC3D74 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 1 + ktx:generatemipmaps: 0 + ktx:miplevels: 5 + ktx:nlayers: 7 + ktx:supercompressionscheme: 0 + ktx:version: 2 + ktx:vkformat: 43 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/color_grid_zstd_5.ktx2 +../oiio-images/ktx2/color_grid_zstd_5.ktx2 : 1024 x 1024, 4 channel, uint8 ktx + SHA-1: 4964D0A088333B6C31CD3BA19A2B8CC1CE879DA8 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 1 + ktx:generatemipmaps: 0 + ktx:miplevels: 1 ktx:nlayers: 1 - ktx:supercompressionscheme: 1 - ktx:texturekind: 1 - ktx:version: "2.0" - ktx:vkformat: 0 + ktx:supercompressionscheme: 2 + ktx:version: 2 + ktx:vkformat: 43 oiio:ColorSpace: "srgb_rec709_scene" -Reading ../oiio-images/ktx2/2d_astc4x4.ktx2 -../oiio-images/ktx2/2d_astc4x4.ktx2 : 40 x 40, 4 channel, uint8 ktx - MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 - SHA-1: 0F24F23031B85B09E74A4E35D7EC519A9A9259CC +Reading ../oiio-images/ktx2/r8g8b8a8_srgb_mip_astc.ktx2 +../oiio-images/ktx2/r8g8b8a8_srgb_mip_astc.ktx2 : 64 x 64, 4 channel, uint8 ktx + MIP-map levels: 64x64 32x32 16x16 8x8 4x4 2x2 1x1 + SHA-1: 03052CEA1E271CEB85C2A084E44DC23F5BD7345C channel list: R, G, B, A + textureformat: "Plain Texture" ktx:colormodel: 162 ktx:generatemipmaps: 0 - ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" - ktx:miplevels: 6 + ktx:miplevels: 7 ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:texturekind: 1 - ktx:version: "2.0" + ktx:version: 2 ktx:vkformat: 158 oiio:ColorSpace: "srgb_rec709_scene" -Reading ../oiio-images/ktx2/2d_bc1.ktx2 -../oiio-images/ktx2/2d_bc1.ktx2 : 40 x 40, 4 channel, uint8 ktx - MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 - SHA-1: 0E7F8EBBCA431855C12D2BF5A638AC46922582CC +Reading ../oiio-images/ktx2/ktx_app_astc_8x8.ktx2 +../oiio-images/ktx2/ktx_app_astc_8x8.ktx2 : 1024 x 1024, 4 channel, uint8 ktx + MIP-map levels: 1024x1024 512x512 256x256 128x128 64x64 32x32 16x16 8x8 4x4 2x2 1x1 + SHA-1: 1EF0757CD38CA8D9AF19EDF0B961FF7D5C635274 channel list: R, G, B, A - ktx:colormodel: 128 + textureformat: "Plain Texture" + ktx:colormodel: 162 ktx:generatemipmaps: 0 - ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" - ktx:miplevels: 6 + ktx:miplevels: 11 ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:texturekind: 1 - ktx:version: "2.0" - ktx:vkformat: 132 + ktx:version: 2 + ktx:vkformat: 172 oiio:ColorSpace: "srgb_rec709_scene" -Reading ../oiio-images/ktx2/2d_bc3.ktx2 -../oiio-images/ktx2/2d_bc3.ktx2 : 40 x 40, 4 channel, uint8 ktx - MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 - SHA-1: B88FED8C339C75154B4D79FEBFADA05CAA06B4E4 +Reading ../oiio-images/ktx2/color_grid_uastc_zstd_5.ktx2 +../oiio-images/ktx2/color_grid_uastc_zstd_5.ktx2 : 1024 x 1024, 4 channel, uint8 ktx + SHA-1: B631629A3A81C50B2CE264D8CCA4EC4E169DFD6E channel list: R, G, B, A - ktx:colormodel: 130 + textureformat: "Plain Texture" + ktx:colormodel: 166 ktx:generatemipmaps: 0 - ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" - ktx:miplevels: 6 + ktx:miplevels: 1 ktx:nlayers: 1 - ktx:supercompressionscheme: 0 - ktx:texturekind: 1 - ktx:version: "2.0" - ktx:vkformat: 138 + ktx:supercompressionscheme: 2 + ktx:version: 2 + ktx:vkformat: 0 oiio:ColorSpace: "srgb_rec709_scene" -Reading ../oiio-images/ktx2/2d_bc4.ktx2 -../oiio-images/ktx2/2d_bc4.ktx2 : 40 x 40, 1 channel, uint8 ktx - MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 - SHA-1: E0352908B50C8CEC4593866CE8704532EE42A600 - channel list: R - ktx:colormodel: 131 +Reading ../oiio-images/ktx2/Iron_Bars_001_normal_uastc_zstd_10.ktx2 +../oiio-images/ktx2/Iron_Bars_001_normal_uastc_zstd_10.ktx2 : 1024 x 1024, 4 channel, uint8 ktx + MIP-map levels: 1024x1024 512x512 256x256 128x128 64x64 32x32 16x16 8x8 4x4 2x2 1x1 + SHA-1: D428118F2CD6DE70F04C9BD6D092A5A5DCAB195D + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 166 ktx:generatemipmaps: 0 - ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" - ktx:miplevels: 6 + ktx:miplevels: 11 ktx:nlayers: 1 - ktx:supercompressionscheme: 0 - ktx:texturekind: 1 - ktx:version: "2.0" - ktx:vkformat: 139 + ktx:supercompressionscheme: 2 + ktx:version: 2 + ktx:vkformat: 0 oiio:ColorSpace: "lin_rec709_scene" -Reading ../oiio-images/ktx2/2d_bc5.ktx2 -../oiio-images/ktx2/2d_bc5.ktx2 : 40 x 40, 2 channel, uint8 ktx - MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 - SHA-1: F136624C2A6714A137A0155DAF47C86A31D69ECD - channel list: R, G - ktx:colormodel: 132 +Reading ../oiio-images/ktx2/ktx_document_uastc_rdo_4_zstd_5.ktx2 +../oiio-images/ktx2/ktx_document_uastc_rdo_4_zstd_5.ktx2 : 1024 x 1024, 4 channel, uint8 ktx + MIP-map levels: 1024x1024 512x512 256x256 128x128 64x64 32x32 16x16 8x8 4x4 2x2 1x1 + SHA-1: 8644BD94DE7A340007640D0063DF80FC2B744849 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 166 ktx:generatemipmaps: 0 - ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" - ktx:miplevels: 6 + ktx:miplevels: 11 ktx:nlayers: 1 - ktx:supercompressionscheme: 0 - ktx:texturekind: 1 - ktx:version: "2.0" - ktx:vkformat: 141 + ktx:supercompressionscheme: 2 + ktx:version: 2 + ktx:vkformat: 0 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/cubemap_goldengate_uastc_rdo_4_zstd_5.ktx2 +../oiio-images/ktx2/cubemap_goldengate_uastc_rdo_4_zstd_5.ktx2 : 1024 x 1024, 4 channel, uint8 ktx + MIP-map levels: 1024x1024 512x512 256x256 128x128 64x64 32x32 16x16 8x8 4x4 2x2 1x1 + SHA-1: 730E2C2DABA5D591458C91C4D952638BC4EAADD6 + channel list: R, G, B, A + textureformat: "CubeFace Environment" + ktx:colormodel: 166 + ktx:generatemipmaps: 0 + ktx:miplevels: 11 + ktx:nlayers: 1 + ktx:supercompressionscheme: 2 + ktx:version: 2 + ktx:vkformat: 0 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/kodim17_blze.ktx2 +../oiio-images/ktx2/kodim17_blze.ktx2 : 512 x 768, 4 channel, uint8 ktx + SHA-1: 4313438D8827BD8CB040EBD28A27A64EC7B62F68 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 163 + ktx:generatemipmaps: 0 + ktx:miplevels: 1 + ktx:nlayers: 1 + ktx:supercompressionscheme: 1 + ktx:version: 2 + ktx:vkformat: 0 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/r8g8b8a8_srgb_mip_blze.ktx2 +../oiio-images/ktx2/r8g8b8a8_srgb_mip_blze.ktx2 : 64 x 64, 4 channel, uint8 ktx + MIP-map levels: 64x64 32x32 16x16 8x8 4x4 2x2 1x1 + SHA-1: A6350253C75FF2B9E24C35E89381C23EC1A77D29 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 163 + ktx:generatemipmaps: 0 + ktx:miplevels: 7 + ktx:nlayers: 1 + ktx:supercompressionscheme: 1 + ktx:version: 2 + ktx:vkformat: 0 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/color_grid_blze.ktx2 +../oiio-images/ktx2/color_grid_blze.ktx2 : 1024 x 1024, 4 channel, uint8 ktx + SHA-1: 8C159DD2A2DC4FF1B2D6B84A0DF4E925A0FCBF46 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 163 + ktx:generatemipmaps: 0 + ktx:miplevels: 1 + ktx:nlayers: 1 + ktx:supercompressionscheme: 1 + ktx:version: 2 + ktx:vkformat: 0 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/alpha_simple_blze.ktx2 +../oiio-images/ktx2/alpha_simple_blze.ktx2 : 8 x 8, 4 channel, uint8 ktx + SHA-1: 23BD7228F104BBA18152E7FDC50825286379F581 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 163 + ktx:generatemipmaps: 0 + ktx:miplevels: 1 + ktx:nlayers: 1 + ktx:supercompressionscheme: 1 + ktx:version: 2 + ktx:vkformat: 0 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/cubemap_yokohama_blze.ktx2 +../oiio-images/ktx2/cubemap_yokohama_blze.ktx2 : 2048 x 2048, 4 channel, uint8 ktx + MIP-map levels: 2048x2048 1024x1024 512x512 256x256 128x128 64x64 32x32 16x16 8x8 4x4 2x2 1x1 + SHA-1: E1D8635C6E17878F937840E2DA2E8F5A1F61F501 + channel list: R, G, B, A + textureformat: "CubeFace Environment" + ktx:colormodel: 163 + ktx:generatemipmaps: 0 + ktx:miplevels: 12 + ktx:nlayers: 1 + ktx:supercompressionscheme: 1 + ktx:version: 2 + ktx:vkformat: 0 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/FlightHelmet_baseColor_blze.ktx2 +../oiio-images/ktx2/FlightHelmet_baseColor_blze.ktx2 : 2048 x 2048, 4 channel, uint8 ktx + SHA-1: FD4B9268979E2925598D7D3FF663293FF3807926 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 163 + ktx:generatemipmaps: 0 + ktx:miplevels: 1 + ktx:nlayers: 1 + ktx:supercompressionscheme: 1 + ktx:version: 2 + ktx:vkformat: 0 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/Iron_Bars_001_normal_blze.ktx2 +../oiio-images/ktx2/Iron_Bars_001_normal_blze.ktx2 : 1024 x 1024, 4 channel, uint8 ktx + MIP-map levels: 1024x1024 512x512 256x256 128x128 64x64 32x32 16x16 8x8 4x4 2x2 1x1 + SHA-1: 4B447C9281806EBDC73619B0622F7F3CCC317F05 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 163 + ktx:generatemipmaps: 0 + ktx:miplevels: 11 + ktx:nlayers: 1 + ktx:supercompressionscheme: 1 + ktx:version: 2 + ktx:vkformat: 0 oiio:ColorSpace: "lin_rec709_scene" -Reading ../oiio-images/ktx2/2d_bc7.ktx2 -../oiio-images/ktx2/2d_bc7.ktx2 : 40 x 40, 4 channel, uint8 ktx - MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 - SHA-1: 1A8C2DBD9F198A97E15348E26D39B81B565D447C +Reading ../oiio-images/ktx2/ktx_document_blze.ktx2 +../oiio-images/ktx2/ktx_document_blze.ktx2 : 1024 x 1024, 4 channel, uint8 ktx + MIP-map levels: 1024x1024 512x512 256x256 128x128 64x64 32x32 16x16 8x8 4x4 2x2 1x1 + SHA-1: AE251370FDCA642A65531BAC14C939A2EC369DC5 channel list: R, G, B, A - ktx:colormodel: 134 + textureformat: "Plain Texture" + ktx:colormodel: 163 ktx:generatemipmaps: 0 - ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" - ktx:miplevels: 6 + ktx:miplevels: 11 ktx:nlayers: 1 - ktx:supercompressionscheme: 0 - ktx:texturekind: 1 - ktx:version: "2.0" - ktx:vkformat: 146 + ktx:supercompressionscheme: 1 + ktx:version: 2 + ktx:vkformat: 0 oiio:ColorSpace: "srgb_rec709_scene" -Reading ../oiio-images/ktx2/2d_etc1.ktx2 -../oiio-images/ktx2/2d_etc1.ktx2 : 40 x 40, 4 channel, uint8 ktx - MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 - SHA-1: 365A5301637FA43F7D7787874A440AA40C9C4E3E +Reading ../oiio-images/ktx2/alpha_complex_straight.ktx2 +../oiio-images/ktx2/alpha_complex_straight.ktx2 : 256 x 256, 4 channel, uint8 ktx + SHA-1: 4DC3A14E38526B307226E2DDE083608891EBD27F channel list: R, G, B, A - ktx:colormodel: 161 + textureformat: "Plain Texture" + ktx:colormodel: 1 ktx:generatemipmaps: 0 - ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" - ktx:miplevels: 6 + ktx:miplevels: 1 + ktx:nlayers: 1 + ktx:supercompressionscheme: 2 + ktx:version: 2 + ktx:vkformat: 43 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/orient_down_metadata.ktx2 +../oiio-images/ktx2/orient_down_metadata.ktx2 : 128 x 128, 4 channel, uint8 ktx + SHA-1: BAA0E4B9FC815F0CCB235CA0E05E4EFE918FB3EC + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:colormodel: 1 + ktx:generatemipmaps: 0 + ktx:miplevels: 1 ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:texturekind: 1 - ktx:version: "2.0" - ktx:vkformat: 148 + ktx:version: 2 + ktx:vkformat: 43 oiio:ColorSpace: "srgb_rec709_scene" -Reading ../oiio-images/ktx2/2d_etc2.ktx2 -../oiio-images/ktx2/2d_etc2.ktx2 : 40 x 40, 4 channel, uint8 ktx - MIP-map levels: 40x40 20x20 10x10 5x5 2x2 1x1 - SHA-1: 6E71EEF6D68C31DE010657E64F9A2030048BCC0F +Reading ../oiio-images/ktx2/orient_up_metadata.ktx2 +../oiio-images/ktx2/orient_up_metadata.ktx2 : 128 x 128, 4 channel, uint8 ktx + SHA-1: A446B2FB2A0D391584A2FE45E9F24BE35D896CAA channel list: R, G, B, A - ktx:colormodel: 161 + textureformat: "Plain Texture" + ktx:colormodel: 1 ktx:generatemipmaps: 0 - ktx:KTXwriter: "ktx transcode v4.3.1~1 / libktx v4.3.0~1" - ktx:miplevels: 6 + ktx:miplevels: 1 ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:texturekind: 1 - ktx:version: "2.0" - ktx:vkformat: 152 + ktx:version: 2 + ktx:vkformat: 43 oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/ktx/run.py b/testsuite/ktx/run.py index 2b982b30e5..1a0bf035a3 100644 --- a/testsuite/ktx/run.py +++ b/testsuite/ktx/run.py @@ -4,18 +4,83 @@ # SPDX-License-Identifier: BSD-3-Clause and Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +# All of the test files here are copied, as is, from KTX-Software repo and fall +# under the license of KTX-Software: +# +# Copyright 2013-2020 Mark Callow SPDX-License-Identifier: Apache-2.0 + +# KTX-Software has two sets of ktx2 test files: +# - a relatively small set for libktx: https://github.com/KhronosGroup/KTX-Software/tree/e2f948066c108b56b8d0052b460b2ac7d34886aa/tests/resources/ktx2 +# - a very larget tests set for ktx tools: https://github.com/KhronosGroup/KTX-Software-CTS/tree/6d23ae9e52cce2ebc6495c4692ec89f632ff70d4 +# +# commit hashse: +# - libktx test files: 6c474d8627999de8acf07d819c196f83d025cd44 +# - ktx tools test files (CTS): 6d23ae9e52cce2ebc6495c4692ec89f632ff70d4 + # save the error output redirect = ' >> out.txt 2>&1 ' files = [ - # Raw, uncompressed formats (sRGB) - "2d_rgb8.ktx2", - "2d_rgba8.ktx2", - # Basis Universal formats - "2d_uastc.ktx2", - "2d_etc1s.ktx2", - # GPU-block-compressed formats - "2d_astc4x4.ktx2", + + # raw (uncompressed + non-supercompressed) formats + "r8g8b8a8_srgb.ktx2", + "r8g8b8a8_srgb_mip.ktx2", + "r8g8b8_srgb_mip.ktx2", + "r8g8b8a8_srgb_3d_7.ktx2", + "r8g8b8a8_srgb_array_7_mip.ktx2", + + # ETC-compressed formats (not supported) + # "r8g8b8a8_srgb_mip_etc2.ktx2", + # "etc2_unorm_array_7.ktx2", + + # raw (uncompressed) and supercompressed formats + "color_grid_zstd_5.ktx2", + # "skybox_zstd_22.ktx2", # VK_FORMAT_B10G11R11_UFLOAT_PACK32 not yet supported + + # ASTC-compressed formats + "r8g8b8a8_srgb_mip_astc.ktx2", + "ktx_app_astc_8x8.ktx2", + # "astc_8x8_unorm_array_7.ktx2", # VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK not yet supported + + # BCn-compressed formats (to be supported) + # "bc3_unorm_array_7.ktx2", + # "pattern_02_bc2.ktx2", + + # UASTC formats (widely used within KTX2 container format) + "color_grid_uastc_zstd_5.ktx2", + "Iron_Bars_001_normal_uastc_zstd_10.ktx2", + "ktx_document_uastc_rdo_4_zstd_5.ktx2", + "cubemap_goldengate_uastc_rdo_4_zstd_5.ktx2", + + # HDR formats (not yet supported) + # "Desk_uastc_hdr4x4_zstd_15.ktx2", + # "Desk_uastc_hdr6x6i.ktx2", + # "Desk_astc_hdr6x6.ktx2", + # "Desk_small_zstd_15.ktx2", # VK_FORMAT_R16G16B16_SFLOAT is not yet supported + + # Basis LZ/ETC1S formats (widely used within KTX2 container format) + "kodim17_blze.ktx2", + "r8g8b8a8_srgb_mip_blze.ktx2", + "color_grid_blze.ktx2", + "alpha_simple_blze.ktx2", + "cubemap_yokohama_blze.ktx2", + "FlightHelmet_baseColor_blze.ktx2", + "Iron_Bars_001_normal_blze.ktx2", + "ktx_document_blze.ktx2", + + # Misc (orientation flags, alpha configurations, etc.) + "alpha_complex_straight.ktx2", + "orient_down_metadata.ktx2", + "orient_up_metadata.ktx2", ] for f in files: command += info_command (OIIO_TESTSUITE_IMAGEDIR + "/" + f) + +# We do not test read-write of compressed-ktx2 files because any read-write +# cycle worsens quality and is absolutely not the intended purpose of ktx usage +# within OIIO + +# Test write of PNG inputs + +# Default write (with nothing specified) should default to a loseless format + supercompression scheme + From a883e534a9532cbc12f1f21c4c9003ba036e8eb5 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sat, 18 Jul 2026 06:57:18 +0200 Subject: [PATCH 35/42] Replace std::from_char with std::stol/std::stol * Removed because std::from_char on MacOS still doesn't support parsing of floating point values. Signed-off-by: Walid Chtioui --- src/ktx.imageio/ktxinput.cpp | 136 ++++++++++++++--------------------- 1 file changed, 52 insertions(+), 84 deletions(-) diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index ce3bdea8e3..f0b77fa3ee 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -3,7 +3,7 @@ // https://github.com/AcademySoftwareFoundation/OpenImageIO #include "ktx_pvt.h" -#include +#include #include #include @@ -97,7 +97,7 @@ class KtxInput final : public ImageInput { std::string get_colorspace() const; - void parse_ktx_sc_params_metadata(std::string_view ktx_sc_params); + void parse_ktx_sc_params_metadata(const std::string& ktx_sc_params); bool check(int subimage, int miplevel) const; }; @@ -853,16 +853,6 @@ KtxInput::get_colorspace() const -template -inline bool -parse_number(const std::string& str, T& num) -{ - auto [_, ec] = std::from_chars(str.data(), str.data() + str.size(), num); - return ec != std::errc {}; -} - - - /// /// Parses KTXwriterScParams metadata and sets relevant attributes accordingly. /// E.g., if Basis Universal non-default params were found, they are set. @@ -876,10 +866,13 @@ parse_number(const std::string& str, T& num) /// should be as similar as possible to the given input (assuming a read-write /// of a KTX2 file without any change to its data). /// +/// Note: +/// std::from_chars fails on MacOS because it still apparently doesn't support +/// floating point values ... +/// void -KtxInput::parse_ktx_sc_params_metadata(const std::string_view ktx_sc_params) +KtxInput::parse_ktx_sc_params_metadata(const std::string& ktx_sc_params) { - std::cmatch m; const auto f = std::regex_constants::icase; { // UASTC params (see KTX-Software/tools/ktx/encode_utils_basis.h) @@ -899,106 +892,80 @@ KtxInput::parse_ktx_sc_params_metadata(const std::string_view ktx_sc_params) std::regex uastc_hdr_6x6i_level_re("--uastc-hdr-6x6i-level\\s+(\\d+)", f); - if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, - uastc_quality_re) - && m.size() == 2) { - uint32_t uastc_quality; - if (parse_number(m[1].str(), uastc_quality)) { - const uint32_t uastc_flags - = (unsigned int)~KTX_PACK_UASTC_LEVEL_MASK | uastc_quality; - m_spec.extra_attribs.attribute("ktx:uastcFlags", uastc_flags); - m_spec.extra_attribs.attribute("ktx:uastcHDRLevel", - uastc_quality); - } + if (std::smatch m; std::regex_search(ktx_sc_params, m, uastc_quality_re) + && m.size() == 2) { + auto uastc_quality = static_cast(std::stol(m[1].str())); + const uint32_t uastc_flags + = (unsigned int)~KTX_PACK_UASTC_LEVEL_MASK | uastc_quality; + m_spec.extra_attribs.attribute("ktx:uastcFlags", uastc_flags); + m_spec.extra_attribs.attribute("ktx:uastcHDRLevel", uastc_quality); } - if (std::regex_match(ktx_sc_params.cbegin(), ktx_sc_params.cend(), - uastc_rdo_re)) + if (std::regex_match(ktx_sc_params, uastc_rdo_re)) m_spec.extra_attribs.attribute("ktx:uastcRDO", true); - if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, - uastc_rdo_l_re) - && m.size() == 2) { - float uastc_rdo_l; - if (parse_number(m[1].str(), uastc_rdo_l)) { - m_spec.extra_attribs.attribute("ktx:uastcRDOQualityScalar", - uastc_rdo_l); - } + if (std::smatch m; std::regex_search(ktx_sc_params, m, uastc_rdo_l_re) + && m.size() == 2) { + auto uastc_rdo_l = std::stof(m[1].str()); + m_spec.extra_attribs.attribute("ktx:uastcRDOQualityScalar", + uastc_rdo_l); } - if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, - uastc_rdo_d_re) - && m.size() == 2) { - uint32_t uastc_rdo_d; - if (parse_number(m[1].str(), uastc_rdo_d)) { - m_spec.extra_attribs.attribute("ktx:uastcRDODictSize", - uastc_rdo_d); - } + if (std::smatch m; std::regex_search(ktx_sc_params, m, uastc_rdo_d_re) + && m.size() == 2) { + auto uastc_rdo_d = static_cast(std::stol(m[1].str())); + m_spec.extra_attribs.attribute("ktx:uastcRDODictSize", uastc_rdo_d); } - if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, - uastc_rdo_b_re) - && m.size() == 2) { - float uastc_rdo_b; - if (parse_number(m[1].str(), uastc_rdo_b)) { - m_spec.extra_attribs.attribute( - "ktx:uastcRDOMaxSmoothBlockErrorScale", uastc_rdo_b); - } + if (std::smatch m; std::regex_search(ktx_sc_params, m, uastc_rdo_b_re) + && m.size() == 2) { + auto uastc_rdo_b = std::stof(m[1].str()); + m_spec.extra_attribs.attribute( + "ktx:uastcRDOMaxSmoothBlockErrorScale", uastc_rdo_b); } - if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, - uastc_rdo_s_re) - && m.size() == 2) { - float uastc_rdo_s; - if (parse_number(m[1].str(), uastc_rdo_s)) { - m_spec.extra_attribs.attribute( - "ktx:uastcRDOMaxSmoothBlockStdDev", uastc_rdo_s); - } + if (std::smatch m; std::regex_search(ktx_sc_params, m, uastc_rdo_s_re) + && m.size() == 2) { + auto uastc_rdo_s = std::stof(m[1].str()); + m_spec.extra_attribs.attribute("ktx:uastcRDOMaxSmoothBlockStdDev", + uastc_rdo_s); } - if (std::regex_match(ktx_sc_params.cbegin(), ktx_sc_params.cend(), - uastc_rdo_f_re)) + if (std::regex_match(ktx_sc_params, uastc_rdo_f_re)) m_spec.extra_attribs.attribute("ktx:uastcRDODontFavorSimplerModes", true); - if (std::regex_match(ktx_sc_params.cbegin(), ktx_sc_params.cend(), - uastc_rdo_m_re)) + if (std::regex_match(ktx_sc_params, uastc_rdo_m_re)) m_spec.extra_attribs.attribute("ktx:uastcRDONoMultithreading", true); - if (std::regex_match(ktx_sc_params.cbegin(), ktx_sc_params.cend(), - uastc_rdo_uber_mode_re)) + if (std::regex_match(ktx_sc_params, uastc_rdo_uber_mode_re)) m_spec.extra_attribs.attribute("ktx:uastcHDRUberMode", true); - if (std::regex_match(ktx_sc_params.cbegin(), ktx_sc_params.cend(), - uastc_rdo_ultra_quant_re)) + if (std::regex_match(ktx_sc_params, uastc_rdo_ultra_quant_re)) m_spec.extra_attribs.attribute("ktx:uastcHDRUltraQuant", true); - if (std::regex_match(ktx_sc_params.cbegin(), ktx_sc_params.cend(), - uastc_rdo_favor_astc_re)) + if (std::regex_match(ktx_sc_params, uastc_rdo_favor_astc_re)) m_spec.extra_attribs.attribute("ktx:uastcHDRFavorAstc", true); - if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, - uastc_hdr_lambda_re) + if (std::smatch m; + std::regex_search(ktx_sc_params, m, uastc_hdr_lambda_re) && m.size() == 2) { - float uastc_hdr_lambda; - if (parse_number(m[1].str(), uastc_hdr_lambda)) { - m_spec.extra_attribs.attribute("ktx:uastcHDRLambda", - uastc_hdr_lambda); - } + auto uastc_hdr_lambda = std::stof(m[1].str()); + m_spec.extra_attribs.attribute("ktx:uastcHDRLambda", + uastc_hdr_lambda); } - if (std::regex_search(ktx_sc_params.cbegin(), ktx_sc_params.cend(), m, - uastc_hdr_6x6i_level_re) + if (std::smatch m; + std::regex_search(ktx_sc_params, m, uastc_hdr_6x6i_level_re) && m.size() == 2) { - uint32_t uastc_hdr_6x6i_level; - if (parse_number(m[1].str(), uastc_hdr_6x6i_level)) { - m_spec.extra_attribs.attribute("ktx:uastcHDRLevel", - uastc_hdr_6x6i_level); - } + auto uastc_hdr_6x6i_level = static_cast( + std::stol(m[1].str())); + m_spec.extra_attribs.attribute("ktx:uastcHDRLevel", + uastc_hdr_6x6i_level); } } - +#if 0 { // ETC1S params (see KTX-Software/tools/ktx/encode_utils_basis.h) std::regex etc1s_clevel("--clevel\\s+(\\d+)", f); std::regex etc1s_qlevel("--qlevel\\s+(\\d+)", f); @@ -1022,6 +989,7 @@ KtxInput::parse_ktx_sc_params_metadata(const std::string_view ktx_sc_params) std::regex uastc_hdr_6x6i_level_re("--uastc-hdr-6x6i-level\\s+(\\d+)", f); } +#endif } OIIO_PLUGIN_NAMESPACE_END From b72dc17fba49849c1a4fb3fac0e0d99845c3f2d6 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sat, 18 Jul 2026 07:32:18 +0200 Subject: [PATCH 36/42] fix: add ENABLE_KTX=0 to disable Ktx building and testing on old CIs * CIs with old gcc versions ("oldest" CIs) use an older version of CMake than that is required by libktx 5.0.0. This commit attempts to disable ktx plugin testing on such old configurations by setting ENABLE_KTX to 0. Signed-off-by: Walid Chtioui --- .github/workflows/ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 671b5112c3..76b5dd0669 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -468,10 +468,11 @@ jobs: Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 + ENABLE_KTX=0 # OpenJPEG_BUILD_VERSION=2.2.0 # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e # libktx > 4.3.2 requires CMake >= 3.22 - optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6;Ktx' + optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' build_local_deps: 'TIFF' - desc: oldest clang11/C++17 py3.9 exr3.1 ocio2.3 # Oldest clang and versions of the dependencies that we support. @@ -499,10 +500,11 @@ jobs: Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 + ENABLE_KTX=0 # OpenJPEG_BUILD_VERSION=2.2.0 # OpenJPEG_GIT_COMMIT=3d7cde5fc9fbc5618d02160900d32e02ed12a00e # libktx > 4.3.2 requires CMake >= 3.22 - optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6;Ktx' + optional_deps_append: 'FFmpeg;LibRaw;Ptex;Qt6' build_local_deps: 'TIFF' - desc: hobbled gcc9/C++17 py3.9 exr-3.1 no-sse @@ -539,6 +541,7 @@ jobs: Robinmap_GIT_COMMIT=68ff7325b3898fca267a103bad5c509e8861144d TIFF_BUILD_VERSION=4.0.0 TIFF_GIT_COMMIT=f7b79dc7dc86ccbaabe9882e2b9ffa5ee8dac917 + ENABLE_KTX=0 # libktx > 4.3.2 requires CMake >= 3.22 required_deps: none build_local_deps: 'TIFF' From 68e223fe3d81122a7f2841de56d2b30ad1179307 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Sat, 18 Jul 2026 07:44:55 +0200 Subject: [PATCH 37/42] Update libktx commit hash and remove libktx 4.3.2 CMake support * libktx v4.3.2 is removed from build_Ktx.cmake and, from now on, only libktx v5.0.0 or newer is supported. Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 91 +++++++++++++-------------------------- 1 file changed, 29 insertions(+), 62 deletions(-) diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index baad194e37..fb4dd442f9 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -5,7 +5,7 @@ set_cache (Ktx_BUILD_VERSION main "Ktx version for local builds") set (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") set_cache (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") -set_cache (Ktx_GIT_COMMIT "e2f948066c108b56b8d0052b460b2ac7d34886aa" +set_cache (Ktx_GIT_COMMIT "78ad9c1d0e014d8958450fccbf750cd6793c3903" "commit hash to verify tag/branch against") set_cache (Ktx_BUILD_SHARED_LIBS OFF #${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) @@ -20,6 +20,17 @@ string (MAKE_C_IDENTIFIER ${Ktx_BUILD_VERSION} Ktx_VERSION_IDENT) set_cache (KTX_CMAKE_C_COMPILER ${CMAKE_C_COMPILER} "libktx build C compiler override" ADVANCED) set_cache (KTX_CMAKE_CXX_COMPILER ${CMAKE_CXX_COMPILER} "libktx build C++ compiler override" ADVANCED) +# The only tested libktx versions are: +# - v0.0.0 (main:HEAD which is versionless) +# - v5.0.0-rc1 (newest supported) +# +# for detailed build instructions, see: +# https://github.com/KhronosGroup/KTX-Software/blob/main/BUILDING.md +# +# KTX-Software not only provides Ktx but also a set of CLI tools and load +# test applications that we do not need. We only need the libktx component +# without any GPU texture loading functionalities. +# # On x86_64, libktx defaults to using AVX2 for ASTC. For Intel-based MacOS, the # default AVX2 requires x86_64h which may not be available (e.g., Intel-based # MacOS Github Actions CIs). For ARM64 libktx defaults to using Neon. For @@ -42,73 +53,29 @@ set(CMAKE_ARGS_LIST -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_COMPILER=${KTX_CMAKE_C_COMPILER} -DCMAKE_CXX_COMPILER=${KTX_CMAKE_CXX_COMPILER} + -DBUILD_SHARED_LIBS=${Ktx_BUILD_SHARED_LIBS} + -DCMAKE_INSTALL_LIBDIR=lib + -DLIBKTX_VERSION_READ_ONLY=OFF + -DLIBKTX_VERSION_FULL=ON + -DLIBKTX_FEATURE_KTX1=ON # Setting this to OFF causes linker issues + -DLIBKTX_FEATURE_KTX2=ON + -DLIBKTX_FEATURE_VK_UPLOAD=OFF + -DLIBKTX_FEATURE_GL_UPLOAD=OFF + -DLIBKTX_FEATURE_ETC_UNPACK=OFF # This has some weird licensing and I don't feel comfortable including it ... ) if(Ktx_ASTCENC_ISA) list(APPEND CMAKE_ARGS_LIST -D${Ktx_ASTCENC_ISA}=ON) endif() -# -# The only three tested libktx versions are: -# - v0.0.0 (main:HEAD which is versionless) -# - v5.0.0-rc1 (newest supported) -# - v4.3.2 (newest version with minimum CMake required <= 3.18) -# -# libktx v4.3.2 is needed for older systems (see CI runners with 'oldest' in -# name) on which CMake >= 3.22 cannot be installed (this is the minimum version -# required by libktx > 4.3.2). -# -# libktx v4.3.2 requires a different setup than newer libktx versions. -# -# for detailed build instructions, see: -# https://github.com/KhronosGroup/KTX-Software/blob/main/BUILDING.md -# -# KTX-Software not only provides Ktx but also a set of CLI tools and load -# test applications that we do not need. We only need the libktx component -# without any GPU texture loading functionalities. -# -if (Ktx_BUILD_VERSION STREQUAL "v4.3.2") - # Because you can't negate variables in CMake ... - if(Ktx_BUILD_SHARED_LIBS) - set(Ktx_BUILD_STATIC_LIBS OFF) - else() - set(Ktx_BUILD_STATIC_LIBS ON) - endif() - list(APPEND CMAKE_ARGS_LIST - -DKTX_FEATURE_STATIC_LIBRARY=${Ktx_BUILD_STATIC_LIBS} # no BUILD_SHARED_LIBS in older libktx versions ... - -DKTX_FEATURE_TOOLS=OFF - -DKTX_FEATURE_TESTS=OFF - -DKTX_FEATURE_KTX1=ON - -DKTX_FEATURE_KTX2=ON - -DKTX_FEATURE_KTX2=ON - -DKTX_FEATURE_GL_UPLOAD=OFF - ) - build_dependency_with_cmake(Ktx - VERSION ${Ktx_BUILD_VERSION} - GIT_REPOSITORY ${Ktx_GIT_REPOSITORY} - GIT_TAG ${Ktx_GIT_TAG} - GIT_COMMIT ${Ktx_GIT_COMMIT} - CMAKE_ARGS ${CMAKE_ARGS_LIST}) -else() # v5.0.0-rc1 or a branch with similar CMake setup - list(APPEND CMAKE_ARGS_LIST - -DBUILD_SHARED_LIBS=${Ktx_BUILD_SHARED_LIBS} - -DCMAKE_INSTALL_LIBDIR=lib - -DLIBKTX_VERSION_READ_ONLY=OFF - -DLIBKTX_VERSION_FULL=ON - -DLIBKTX_FEATURE_KTX1=ON # Setting this to OFF causes linker issues - -DLIBKTX_FEATURE_KTX2=ON - -DLIBKTX_FEATURE_VK_UPLOAD=OFF - -DLIBKTX_FEATURE_GL_UPLOAD=OFF - -DLIBKTX_FEATURE_ETC_UNPACK=OFF # This has some weird licensing and I don't feel comfortable including it ... - ) - build_dependency_with_cmake(Ktx - VERSION ${Ktx_BUILD_VERSION} - GIT_REPOSITORY ${Ktx_GIT_REPOSITORY} - GIT_TAG ${Ktx_GIT_TAG} - GIT_COMMIT ${Ktx_GIT_COMMIT} - SOURCE_SUBDIR lib # To only build Ktx, cmake has to point to: KTX-Software/lib - CMAKE_ARGS ${CMAKE_ARGS_LIST}) -endif() +build_dependency_with_cmake(Ktx + VERSION ${Ktx_BUILD_VERSION} + GIT_REPOSITORY ${Ktx_GIT_REPOSITORY} + GIT_TAG ${Ktx_GIT_TAG} + GIT_COMMIT ${Ktx_GIT_COMMIT} + SOURCE_SUBDIR lib # To only build Ktx, cmake has to point to: KTX-Software/lib + CMAKE_ARGS ${CMAKE_ARGS_LIST} +) # Set some things up that we'll need for a subsequent find_package to work set (Ktx_ROOT ${Ktx_LOCAL_INSTALL_DIR}) From 437ababc90833ea81f7fc32316426810d212462a Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Wed, 22 Jul 2026 03:00:30 +0200 Subject: [PATCH 38/42] Fix UASTC/ETC1S Basis Universal attributes * Add initial documentation about ktx plugin and its exposed attributes * Add more extensive testing * Misc cleanups Signed-off-by: Walid Chtioui --- .gitignore | 1 + src/cmake/build_Ktx.cmake | 2 +- src/doc/builtinplugins.rst | 290 ++++++++++++++++++++++++++++ src/iv/imageviewer.cpp | 1 + src/ktx.imageio/README.md | 90 +++++++-- src/ktx.imageio/ktx_pvt.h | 135 +++++++++---- src/ktx.imageio/ktxinput.cpp | 221 +++++++++++++--------- src/ktx.imageio/ktxoutput.cpp | 343 +++++++++++++++------------------- testsuite/ktx/ref/out.txt | 186 ++++-------------- testsuite/ktx/run.py | 31 ++- 10 files changed, 811 insertions(+), 489 deletions(-) diff --git a/.gitignore b/.gitignore index 2058a138ce..83f2475a9b 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ gastest.o /*.jxl /*.tx /*.log +/*.ktx2 # AI coding tool directories — generated locally by .agents/setup-agent .claude/ diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index fb4dd442f9..5f32080b90 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -5,7 +5,7 @@ set_cache (Ktx_BUILD_VERSION main "Ktx version for local builds") set (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") set_cache (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") -set_cache (Ktx_GIT_COMMIT "78ad9c1d0e014d8958450fccbf750cd6793c3903" +set_cache (Ktx_GIT_COMMIT "89d2b6e916dbd3e7a09ca62e8e7807dc61d32c0f" "commit hash to verify tag/branch against") set_cache (Ktx_BUILD_SHARED_LIBS OFF #${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index 7b5c882ad4..1bcc869a39 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -1400,6 +1400,296 @@ control aspects of the writing itself: (Does not work as expected at this moment. Box is written but content unreadable in exif readers.) +| + +.. _sec-bundledplugins-ktx: + +KTX +=============================================== + +KTX (Khronos Texture) is an efficient, lightweight container format for reliably +distributing GPU textures to diverse platforms and applications. + +A KTX2 file can contain a 2D texture, a 3D texture (volume texture), a cubemap +texture, any of said textures with mipmaps, or an array of any of said textures +with or without mipmaps. KTX files use the file extension :file:`.ktx2`. + +OpenImageIO supports both reading and writing KTX2 files. Deprecated KTX1 files +are currently not supported by OpenImageIO (hence why the file extension is +:file:`.ktx2`). + +Although OpenImageIO supports writing KTX2 files, users are encouraged to use +the official CLI tools provided by KTX-Software to generate KTX2 files. This is +due to the high number of parameters that can be configured to generate a KTX2 +file which can be cumbersome to set when using OpenImageIO. OpenImageIO supports +said parameters via setting KTX-specific attributes that share similar parameter +names to those used by libktx. + +KTX is comparable, to some degree, with DDS formats when storing ready-to-upload +BC1-BC7 GPU block-compressed texture data or plain uncompressed pixels. GPU +block-compressed formats stay compressed in video memory and are decoded by +the GPU on the fly while sampling. This has the drawback that each GPU format +only works on hardware that supports it. KTX has additional support for latent +codecs that can be transcoded on the fly to the most compatible and supported +GPU format. KTX also has the additional benefit of supporting ASTC +block-compressed formats which offer a great deal of flexibility (i.e., multiple +configurations that affect quality metric vs. bitrate). + +KTX plugin support is mainly provided through the official libktx library. +OpenImageIO does not do any encoding/deconding by its own and simply forwards +all operations to libktx. libktx requires, however, a significant amount of +parameters to configure the different formats/codecs KTX container supports. +Consequently, the libktx attributes exposed by OpenImageIO follow the parameters +of libktx v5.0.0. In case of any mismatch, users are encouraged to refer to the +original parameters in libktx. + +Although this KTX plugin provides support for both input and output, users are +discouraged from reading KTX inputs and re-writing them as that might +significantly worsen the quality even if no changes were introduced. This does +not hold true in case the input KTX container uses a lossless compression +format but that is rarely the case with KTX files. + +It is important to note that KTX specification does not force the mention of +which parameters were used to, say, generate a UASTC format. This plugin relies +on the heuristic that KTX files are most likely created via the official KTX +tools CLI which saves the non-default parameters in the metadata entry +`KTXScWriterParams`. That being said, as stated above, knowledge of the +parameters is only needed to regenerate a given KTX input which is not the +intended use-case of this plugin within OpenImageIO. + +**Configuration settings for KTX input** + +When opening a KTX ImageInput with a *configuration* (see +Section :ref:`sec-input-with-config`), the following special configuration +attributes are supported: + +.. list-table:: + :widths: 30 10 65 + :header-rows: 1 + + * - Input Configuration Attribute + - Type + - Meaning + * - ``oiio:ioproxy`` + - ptr + - Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for + example by reading from memory rather than the file system. + +**Configuration settings for KTX output** + +OpenImageIO's KTX2 plugin exposes a high number of parameters that are forwarded +to libktx to configure the different compression formats/codecs. When opening a +KTX2 ImageOutput, the following special configuration attributes are exposed: + +.. list-table:: + :widths: 30 10 65 + :header-rows: 1 + + * - ImageSpec Attribute + - Type + - Description + * - ``compression`` + - string + - Optional GPU-block compression type. Can be one of the following values: + NONE ASTC + Defaults to NONE. + * - ``textureformat`` + - string + - Set correctly to one of ``"Plain Texture"``, ``"Volume Texture"``, or + ``"CubeFace Environment"``. This attribute by itself does not convey + whether the texture is an array texture. + * - ``ktx:supercompressionscheme`` + - string + - Supercompression scheme to apply. Can be one of the following values: + NONE ZSTD ZLIB + Defaults to NONE. BasisLZ does not have to be explicitly set because it + is set by default when ETC1S codec is used. + * - ``ktx:codec`` + - string + - Basis Universal codec to apply. Can be one of the following values: + NONE UASTC UASTC-LDR UASTC-LDR-4x4 ETC1S UASTC-HDR UASTC-HDR-4x4 + UASTC-HDR-6x6 + Defaults to NONE. + * - ``ktx:version`` + - string + - KTX specification version. Currently always set to ``"2.0"``. + * - ``ktx:nlayers`` + - int + - Number of array entries for texture arrays. Has to be >= ``1`` for all + kinds of textures. + Defaults to ``1``. + * - ``ktx:nfaces`` + - int + - Number of cubemap faces. Has to be >= ``1`` for all kinds of textures. + Defaults to ``1``. + * - ``ktx:miplevels`` + - int + - Number of MIP levels. Has to be >= ``1`` for all kinds of textures. + Defaults to ``1``. + * - ``ktx:generatemipmaps`` + - int (bool) + - Whether to generate mipmaps when texture is uploaded to 3D graphics GPU + API. This is not to be confused with runtime mipmap generation which is + expected to be done by the user via OpenImageIO. + Defaults to false. + * - ``ktx:normalMap`` + - int (bool) + - Only valid for linear textures with two or more components. If the input + texture has three or four linear components it is assumed to be a three + component linear normal map storing unit length normals as + (R=X, G=Y, B=Z). A fourth component will be ignored. The map will be + converted to a two component X+Y normal map stored as (RGB=X, A=Y) prior + to encoding. + Defaults to false. + * - ``ktx:noSSE`` + - int (bool) + - Forbid use of the SSE instruction set. Ignored if CPU does not support + SSE. SSE can only be disabled for the basis-lz and uastc encoders. + Ignored for other encoders. + Defaults to false. + * - ``ktx:preSwizzle`` + - int (bool) + - If the texture has `KTXswizzle` metadata, apply it before compressing. + Swizzling, like `rabb` may yield drastically different error metrics if + done after supercompression. Usable for both ETC1S and UASTC. + Defaults to false. + * - ``ktx:inputSwizzle`` + - char[4] + - A swizzle to apply before encoding. It must match the regular expression + `/^[rgba01]{4}$/`. Should not be specified if ``ktx:preSwizzle` is set. + Defaults to no swizzle (i.e., zero'ed array). + * - ``ktx:uastcFlags`` + - uint + - A set of ``ktx_pack_uastc_flag_bits`` controlling UASTC encoding. + Defaults to 0 (maps to KTX_PACK_UASTC_LEVEL_FASTEST). + * - ``ktx:uastcHDRLevel`` + - uint + - UASTC HDR 4x4: Sets the UASTC HDR 4x4 compressor's level. Valid range is + [0,4] - higher=slower but higher quality. + Defaults to 1. + * - ``ktx:uastcRDO`` + - int (bool) + - Whether to enable Rate Distortion Optimization (RDO) post-processing. + Defaults to false. + * - ``ktx:uastcRDOQualityScalar`` + - float + - UASTC RDO quality scalar (lambda). Lower values yield higher + quality/larger LZ compressed files, higher values yield lower + quality/smaller LZ compressed files. A good range to try is [.2,4]. + Full range is [.001,50.0]. + Defaults to 1.0. + * - ``ktx:uastcRDODictSize`` + - uint + - UASTC RDO dictionary size in bytes. Lower values=faster, but give less + compression. Range is [64,65536]. + Defaults to 4096. + * - ``ktx:uastcRDOMaxSmoothBlockErrorScale`` + - float + - UASTC RDO max smooth block error scale. Range is [1,300]. 1.0 is + disabled. Larger values suppress more artifacts (and allocate more bits) + on smooth blocks. + Defaults to 10.0 + * - ``ktx:uastcRDOMaxSmoothBlockStdDev`` + - float + - UASTC RDO max smooth block standard deviation. Range is [.01,65536.0]. + Larger values expand the range of blocks considered smooth. + Defaults to 18.0. + * - ``ktx:uastcRDODontFavorSimplerModes`` + - int (bool) + - Do not favor simpler UASTC modes in RDO mode. + Defaults to false. + * - ``ktx:uastcRDONoMultithreading`` + - int (bool) + - Disable RDO multithreading (slightly higher compression, deterministic). + Defaults to false. + * - ``ktx:uastcHDRQuality`` + - uint + - UASTC HDR 4x4: Sets the UASTC HDR 4x4 compressor's level. Valid range is + [0,4] - higher=slower but higher quality. Level 0=fastest/lowest quality, + 3=highest practical setting, 4=exhaustive. + Defaults to 1. + * - ``ktx:uastcHDRUberMode`` + - int (bool) + - UASTC HDR 4x4: Allow the UASTC HDR 4x4 encoder to try varying the CEM 11 + selectors more for slightly higher quality (slower). This may negatively + impact BC6H quality, however. + Defaults to false. + * - ``ktx:uastcHDRUltraQuant`` + - int (bool) + - UASTC HDR 4x4: Try to find better quantized CEM 7/11 endpoint values + (slower). + Defaults to false. + * - ``ktx:uastcHDRFavorAstc`` + - int (bool) + - UASTC HDR 4x4: By default the UASTC HDR 4x4 encoder tries to strike a + balance or even slightly favor BC6H quality. If this option is specified, + ASTC HDR 4x4 quality is favored instead. + Defaults to false. + * - ``ktx:uastcHDRLambda`` + - float + - UASTC HDR 6x6i specific option: Enables rate distortion optimization + (RDO). The higher this value, the lower the quality, but the smaller the + file size. Try 100-20000, or higher values on some images. + Defaults to 0. + * - ``ktx:uastcHDRLevel`` + - uint + - UASTC HDR 6x6i specific option: Controls the 6x6 HDR intermediate mode + encoder performance vs. max quality tradeoff. X may range from [0,12]. + Defaults to 2. + * - ``ktx:etc1sCompressionLevel`` + - uint + - ETC1S compression effort level. Range is [0,6]. Higher values are much + slower, but give slightly higher quality. Higher levels are intended + for video. Note this is NOT the same as the ETC1S quality level, and most + users shouldn't change this. + Defaults to 2. + * - ``ktx:etc1sQualityLevel`` + - uint + - Compression quality. Range is [1,255]. Lower gives better + compression/lower quality/faster. Higher gives less compression + /higher quality/slower. This automatically determines values for + ``"ktx:etc1sMaxEndpoints"``, ``"ktx:etc1sMaxSelectors"``, + ``"ktx:etc1sEndpointRDOThreshold"`` and + ``"ktx:etc1sSelectorRDOThreshold"`` for the target quality level. Setting + these parameters overrides the values determined by + ``"ktx:etc1sQualityLevel"``. + Defaults to 128 if neither ``"ktx:etc1sMaxEndpoints"`` nor + ``"ktx:etc1sMaxSelectors"`` have been set. + * - ``ktx:etc1sMaxEndpoints`` + - uint + - Manually set the max number of color endpoint clusters. Range is + [1,16128]. If this is set, ``ktx:etc1sMaxSelectors`` must also be set, + otherwise the value will be ignored. + Defaults to 0. + * - ``ktx:etc1sEndpointRDOThreshold`` + - float + - Set endpoint RDO quality threshold. Lower is higher + quality but less quality per output bit (try [1.0,3.0]. This will + override the value chosen by @c qualityLevel. + Defaults to 1.25. + * - ``ktx:etc1sMaxSelectors`` + - uint + - Manually set the max number of color selector clusters. Range is + [1,16128]. If this is set, ``ktx:etc1sMaxEndpoints`` must also be set, + otherwise the value will be ignored. + Defaults to 0. + * - ``ktx:etc1sSelectorRDOThreshold`` + - float + - Set selector RDO quality threshold. Lower is higher quality but less + quality per output bit (try [1.0,3.0]). This will override the value + chosen by ``ktx:etc1sQualityLevel``. + Defaults to 1.5. + * - ``ktx:etc1sNoEndpointRDO`` + - int (bool) + - Disable endpoint rate distortion optimizations. Slightly faster, less + noisy output, but lower quality per output bit. + Defaults to false. + * - ``ktx:etc1sNoSelectorRDO`` + - int (bool) + - Disable selector rate distortion optimizations. Slightly faster, less + noisy output, but lower quality per output bit. + Defaults to false. + .. _sec-bundledplugins-ffmpeg: Movie formats (using ffmpeg) diff --git a/src/iv/imageviewer.cpp b/src/iv/imageviewer.cpp index 0462db8220..8cb2977f6f 100644 --- a/src/iv/imageviewer.cpp +++ b/src/iv/imageviewer.cpp @@ -79,6 +79,7 @@ static const char *s_file_filters = "" "JPEG (*.jpg *.jpe *.jpeg *.jif *.jfif *.jfi);;" "JPEG-2000 (*.jp2 *.j2k);;" "JPEG XL (*.jxl);;" + "Khronos Texture (*.ktx2);;" "OpenEXR (*.exr);;" "OpenVDB (*.vdb);;" "PhotoShop (*.psd);;" diff --git a/src/ktx.imageio/README.md b/src/ktx.imageio/README.md index 0319c2458d..b3f5266f5c 100644 --- a/src/ktx.imageio/README.md +++ b/src/ktx.imageio/README.md @@ -1,9 +1,9 @@ # About This KTX plugin support obviously nullifies the benefits of using KTX in the -first place (i.e., to reduce upload time to GPUs or totally eliminate the need for -transcoding to GPU-conformant format before uploading). That being said, this -plugin is still useful so that end users don't have to convert back and forth +first place (i.e., to reduce upload time to GPUs or totally eliminate the need +for transcoding to GPU-conformant formats before uploading). This plugin is, +however, still useful so that end users don't have to convert back and forth between KTX <-> supported format (e.g., PNG). It is also useful to convert to and from KTX2 format. @@ -13,30 +13,76 @@ An important note about DDS -> KTX conversion: without having to decode then encode to KTX format. A PR is currently being worked on. - If you use OIIO for this conversion, then the quality will almost certainly - degrade. + degrade (well, if the input KTX2 file is compressed using a lossy method, + which is almost always the case with KTX files). An example use-case would be Blender and its glTf import/export plugin. Ideally, at some point in the future, OIIO may introduce a new API to -accommodate texture formats that are mainly used for fast texture uploads tow +accommodate texture formats that are mainly used for fast texture uploads to GPUs. This is outside the scope of this basic format support addition. -Below you will find a set of notes on why this plugin is implemented the way -it is. It took me some time to understand how libktx works and what it provides -(and why). Some terminology is also defined here. +Below, you will find a set of notes on why this plugin is implemented the way +it is. It took some time to understand how libktx works and what it provides +(and why). Some terminology is also defined here (to avoid confusion, since KTX +may use multiple layers of compression). ## KTX2 - Brief Introduction -KTX2 (the 2 here is to distinguish it from deprecated KTX/KTX1) is a binary -container format that is intended for usage for fast loading of textures to the -GPU. KTX2 contains GPU-native formats (e.g., block-compressed format BC7) with -an optional additional layer of compression (hereafter referred to as -*supercompression*). - -As per the specs, KTX2 formats may store downsampled texture data for each mip +KTX2 (`2` to distinguish it from deprecated KTX/KTX1) is a lightweight container +format for very fast loading of textures to the GPU. + +KTX2 is mainly centered around the *intermediate* GPU codecs provided by Basis +Universal. Contrary to the *usual* GPU-block-compressed textures such as +BC1-BC7, ASTC, or ETC, the codecs introduced by Basis Universal provide +additional flexibility, universal support (with fallback), and +highly-configurable parameters for storage-size/compression/c. These codecs come +at the additional cost, relative to native GPU formats, of very fast transcoding +to the most compatible supported GPU format for the current GPU. To understand +these better, refer to Basis Universal's [Wiki][basisu-wiki]. + +KTX2 may also simply contain GPU-native formats (e.g., block-compressed format +BC7) with an optional additional layer of compression (hereafter referred to as +*supercompression*), usually ZStandard or ZLIB. These offer less flexibility +relative to UASTC or ETC1S (Basis Universal's main codecs) but can be directly +loaded to the GPU (no transcoding step if we don't count decompressing the +supercompression). + +As per the specs, KTX2 formats may store downsampled texture data for each MIP level (not necessarily the whole pyramid). This introduces problems for the KTX2 writer (at `ktxoutput.cpp`) because the spec doesn't force the mention of -which filter/downsampler was used to create the mip levels. +which filter/downsampler was used to create the mipmaps. That being said, +this limitation only occurs when trying to regenerate a KTX2 file which is not +the intended use-case for this plugin in OIIO (nor is the intended use of KTX +format, each read-write cycle of a KTX2 file significantly worsens its texture +quality). + +## KTX vs. DDS + +KTX is comparable, to some degree, with DDS formats when storing ready-to-upload +BC1-BC7 GPU block-compressed texture data or plain uncompressed pixels. GPU +block-compressed formats stay compressed in video memory and are decoded by +the GPU on the fly while sampling. This has the drawback that each GPU format +only works on hardware that supports it. KTX has additional support for latent +codecs that can be transcoded on the fly to the most compatible and supported +GPU format. + +## Basis Universal Codecs + +### UASTC + + + +### ETC1S + +From [basis-ETC1S][Basis Universal's ETC1S wiki]: + +> ETC1S is a simplified subset of the Khronos ETC1 GPU texture format, which is +very popular on Android. ... ETC1S is still 100% standard ETC1, so +transcoding to ETC1 or the color block of ETC2 is a no-op. We chose ETC1S +because it has the very valuable property that it can be quickly transcoded to +almost any other GPU texture format at very high quality using only simple +per-block operations with small 1D lookup tables. ... ## GPU Block Compression Formats @@ -46,7 +92,7 @@ requirements: - Random access (to some degree, you still pay the price for decoding a very small number of neighboring pixels to access a given pixel). - - Fixed-rate encoding (requirement for random access) + - Fixed-rate encoding (consequent requirement for random access) - Support for hardware-decoding on the GPU (i.e., extremely fast to decode and results in better performance due to lower cache usage). @@ -64,7 +110,8 @@ formats is offloaded to libktx. ### ASTC libktx provides ASTC encoders/decoders and we don't have to deal with ASTC's -extreme complexity (e.g., there are many different block sizes). +extreme complexity (e.g., there are many different block sizes with many +different bitrates). ### ETC2 @@ -244,8 +291,8 @@ To test (of course, make sure image assets are downloaded - see the CMake flag a ## Misc Notes for Developers -Use `clang-format-17` for formatting and not whatever newest version you have -on your machine (in my case, clang-format v18 caused clang-format CI to fail): +Use `clang-format-17` for formatting and not whatever version you have on your +machine (in my case, clang-format v18 caused clang-format CI to fail): ```bash find . -regex '.*\.\(cpp\|hpp\|cc\|cxx\)' -exec clang-format-17 -style=file -i {} \; @@ -257,5 +304,8 @@ find . -regex '.*\.\(cpp\|hpp\|cc\|cxx\)' -exec clang-format-17 -style=file -i { - [Official Implementation (KTX-Software)](https://github.com/KhronosGroup/KTX-Software) - [Basis Universal Supercompression Implementation (used by libktx)](https://github.com/BinomialLLC/basis_universal) - [Comparing-BCn-texture-decoders](https://aras-p.info/blog/2022/06/23/Comparing-BCn-texture-decoders/) +- [basisu-wiki](https://github.com/BinomialLLC/basis_universal/wiki) +- [gpu-textures-are-infrastructure](https://github.com/BinomialLLC/basis_universal/wiki#conceptual-architecture-gpu-textures-are-infrastructure) +- [basis-ETC1S](https://github.com/BinomialLLC/basis_universal/wiki/.basis-File-Format-and-ETC1S-Texture-Video-Specification) [libktx]: https://github.com/KhronosGroup/KTX-Software.git diff --git a/src/ktx.imageio/ktx_pvt.h b/src/ktx.imageio/ktx_pvt.h index c9f49659c6..633c9b52fc 100644 --- a/src/ktx.imageio/ktx_pvt.h +++ b/src/ktx.imageio/ktx_pvt.h @@ -420,33 +420,33 @@ get_info_from_vkformat(VkFormat vkformat, FormatInfo& formatinfo) case VK_FORMAT_BC7_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC7, VK_FORMAT_R8G8B8A8_SRGB }; return true; // ASTC formats (2D blocks) - case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: - case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: - case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: - case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: - case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: - case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: - case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: - case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: - case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: - case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: - case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: - case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: - case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: + case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: + case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: + case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: + case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ASTC, VK_FORMAT_R8G8B8A8_UNORM }; return true; - case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: - case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: - case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: - case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: - case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: - case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: - case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: - case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: - case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: - case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: - case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: - case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: - case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: + case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: + case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: + case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: + case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: + case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: + case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: + case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: + case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: + case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: + case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: + case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: + case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: + case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ASTC, VK_FORMAT_R8G8B8A8_SRGB }; return true; default: break; } @@ -459,27 +459,86 @@ get_info_from_vkformat(VkFormat vkformat, FormatInfo& formatinfo) inline VkFormat get_vkformat_from_info(int nchannels, TypeDesc format, bool srgb_colorspace) { - if (format != TypeDesc::UINT8) + if (format != TypeDesc::UINT8 && format != TypeDesc::UINT16 + && format != TypeDesc::HALF) return VK_FORMAT_UNDEFINED; + const bool is_ldr = format == TypeDesc::UINT8; + switch (nchannels) { case 1: - return srgb_colorspace ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM; - break; + return is_ldr + ? (srgb_colorspace ? VK_FORMAT_R8_SRGB : VK_FORMAT_R8_UNORM) + : VK_FORMAT_R16_SFLOAT; case 2: - return srgb_colorspace ? VK_FORMAT_R8G8_SRGB : VK_FORMAT_R8G8_UNORM; - break; + return is_ldr ? (srgb_colorspace ? VK_FORMAT_R8G8_SRGB + : VK_FORMAT_R8G8_UNORM) + : VK_FORMAT_R16G16_SFLOAT; case 3: - return srgb_colorspace ? VK_FORMAT_R8G8B8_SRGB : VK_FORMAT_R8G8B8_UNORM; - break; + return is_ldr ? (srgb_colorspace ? VK_FORMAT_R8G8B8_SRGB + : VK_FORMAT_R8G8B8_UNORM) + : VK_FORMAT_R16G16B16_SFLOAT; case 4: - return srgb_colorspace ? VK_FORMAT_R8G8B8A8_SRGB - : VK_FORMAT_R8G8B8A8_UNORM; - break; - default: break; + return is_ldr ? (srgb_colorspace ? VK_FORMAT_R8G8B8A8_SRGB + : VK_FORMAT_R8G8B8A8_UNORM) + : VK_FORMAT_R16G16B16A16_SFLOAT; + default: return VK_FORMAT_UNDEFINED; } +} + + - return VK_FORMAT_UNDEFINED; +inline uint32_t +get_astc_block_size(VkFormat format) +{ + switch (format) { + case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: + case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: return 4 * 4; + case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: + case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: return 5 * 4; + case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: return 5 * 5; + case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: return 6 * 5; + case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: return 6 * 6; + case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: return 8 * 5; + case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: return 8 * 6; + case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: return 8 * 8; + case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: return 10 * 5; + case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: return 10 * 6; + case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: return 10 * 8; + case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: return 10 * 10; + case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: + case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: return 12 * 10; + case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: + case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: return 12 * 12; + default: return 0; + } +} + + + +inline bool +ichar_equals(unsigned char a, unsigned char b) +{ + return std::tolower(a) == std::tolower(b); +} + + + +inline bool +iequals(std::string_view lhs, std::string_view rhs) +{ + return std::equal(lhs.cbegin(), lhs.cend(), rhs.cbegin(), rhs.cend(), + ichar_equals); } diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index f0b77fa3ee..eaa62ec985 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -130,21 +130,7 @@ bool KtxInput::open(const std::string& name, ImageSpec& newspec, const ImageSpec& config) { - // - // OIIO API is limited for certain KTX texture types (e.g., 3D array - // textures, cubemap array textures). Therefore we add the option to specify - // which layer to use in case these textures are used. This is ignored for - // other types of textures (e.g., 2D array textures, cubemaps, etc.) - // - // m_array_layer_idx = config.get_int_attribute("ktx:ArrayLayerIndex", - // m_array_layer_idx); - // Check 'config' for any special requests - // if (config.get_int_attribute("oiio:UnassociatedAlpha", 0) == 1) - // m_keep_unassociated_alpha = true; - // m_linear_premult = config.get_int_attribute("png:linear_premult", - // OIIO::get_int_attribute( - // "png:linear_premult")); ioproxy_retrieve_from_config(config); m_config.reset(new ImageSpec(config)); // save config spec return open(name, newspec); @@ -229,7 +215,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) return false; } } else /* (proxytype == "memreader") */ { - OIIO_ASSERT(proxytype == "memreader"); + OIIO_DASSERT(proxytype == "memreader"); auto buff = reinterpret_cast(m_io)->buffer(); ktxTexture2* p_tex = nullptr; auto res = ktxTexture2_CreateFromMemory(buff.data(), buff.size(), @@ -258,10 +244,11 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) TypeDesc::UINT8); m_spec.depth = m_spec.full_depth = m_tex->baseDepth; std::string colorspace = get_colorspace(); + if (iequals(colorspace, "unknown")) + return false; m_spec.set_colorspace(colorspace); // Set textureformat attribute - // TODO: we don't use this in ktxoutput, is this needed? if (m_tex->numDimensions == 2) { if (m_tex->numFaces > 1) m_spec.attribute("textureformat", "CubeFace Environment"); @@ -287,16 +274,16 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) m_spec.extra_attribs.attribute("ktx:supercompressionscheme", (uint32_t)m_tex->supercompressionScheme); // save as string (for future use, in case KTX1 is added) - m_spec.extra_attribs.attribute("ktx:version", 2.0f); + m_spec.extra_attribs.attribute("ktx:version", "2.0"); // Contrary to the specs' layerCount, numLayers is always >= 1 - m_spec.extra_attribs.attribute("ktx:nlayers", m_tex->numLayers); - m_spec.extra_attribs.attribute("ktx:miplevels", m_tex->numLevels); - m_spec.extra_attribs.attribute("ktx:generatemipmaps", - m_tex->generateMipmaps); - // TODO: do we need this? - m_spec.extra_attribs.attribute("ktx:colormodel", - (uint32_t)KHR_DFDVAL(m_tex->pDfd + 1, MODEL)); - m_spec.extra_attribs.attribute("ktx:vkformat", (uint32_t)m_tex->vkFormat); + if (m_tex->numLayers > 1) + m_spec.extra_attribs.attribute("ktx:nlayers", m_tex->numLayers); + if (m_tex->numFaces > 1) + m_spec.extra_attribs.attribute("ktx:nfaces", m_tex->numFaces); + if (m_tex->numLevels > 1) + m_spec.extra_attribs.attribute("ktx:miplevels", m_tex->numLevels); + if (m_tex->generateMipmaps) + m_spec.extra_attribs.attribute("ktx:generatemipmaps", 1); // // Save arbitrary metadata. KTX allows for the storage of arbitrary @@ -360,7 +347,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // auto char_ptr = reinterpret_cast(val); } else if (attr_name == "KTXcubemapIncomplete") { - OIIO_ASSERT(vallen == 1); + OIIO_DASSERT(vallen == 1); // TODO: handle KTXcubemapIncomplete } else if (attr_name == KTX_ORIENTATION_KEY) { // @@ -372,7 +359,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // TODO: set orientation functions } else if (attr_name == "KTXglFormat") { - OIIO_ASSERT(vallen == sizeof(KTXglFormat) /* 12 bytes */); + OIIO_DASSERT(vallen == sizeof(KTXglFormat) /* 12 bytes */); KTXglFormat glFormat; glFormat.glInternalformat = *reinterpret_cast(val); glFormat.glFormat = *(reinterpret_cast(val) + 1); @@ -382,12 +369,12 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) ktx_prefixed_attr_name, TypeDesc::UINT32, 3, make_cspan(reinterpret_cast(val), 3)); } else if (attr_name == "KTXdxgiFormat__") { - OIIO_ASSERT(vallen == sizeof(uint32_t)); + OIIO_DASSERT(vallen == sizeof(uint32_t)); m_dxgiFormat = *reinterpret_cast(val); m_spec.extra_attribs.attribute(ktx_prefixed_attr_name, m_dxgiFormat.value()); } else if (attr_name == "KTXmetalPixelFormat") { - OIIO_ASSERT(vallen == sizeof(uint32_t)); + OIIO_DASSERT(vallen == sizeof(uint32_t)); m_metalFormat = *reinterpret_cast(val); m_spec.extra_attribs.attribute(ktx_prefixed_attr_name, m_metalFormat.value()); @@ -400,24 +387,14 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) } while ((kventry = ktxHashList_Next(kventry))); - // - // We only support KTX_SS_NONE, KTX_SS_ZLIB, KTX_SS_ZSTD, and - // KTX_SS_BASIS_LZ supercompression schemes. New schemes may be added to the - // spec hence why we do a strict if check. - // - if (m_tex->supercompressionScheme != KTX_SS_NONE - && m_tex->supercompressionScheme != KTX_SS_ZSTD - && m_tex->supercompressionScheme != KTX_SS_ZLIB - && m_tex->supercompressionScheme != KTX_SS_BASIS_LZ) { - // vendor-specific or newly introduced supercompression schemes (not - // supported) - errorfmt("unsuppoted supercompression scheme: {}", + // We don't support vendor-specific supercompression schemes + if (m_tex->supercompressionScheme > KTX_SS_END_RANGE) { + errorfmt("Unsuppoted supercompression scheme: {}", static_cast(m_tex->supercompressionScheme)); return false; } - // Load the actual image data (pBuffer is NULL => m_tex own the buffer in - // which the data will be loaded) + // Load the actual image data (libktx owns the data buffer) if (auto result = ktxTexture2_LoadImageData(m_tex.get(), NULL, 0); result != KTX_SUCCESS) { errorfmt("ktxTexture2_LoadImageData returned Ktx error code: {}", @@ -437,6 +414,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // essential properties for proper KTX2 regeneration. // if (ktxTexture2_NeedsTranscoding(m_tex.get())) { + // TODO: in case of HDR, use KTX_TTF_RGBA_HALF if (auto status = ktxTexture2_TranscodeBasis( m_tex.get(), ktx_transcode_fmt_e::KTX_TTF_RGBA32, 0); status != KTX_SUCCESS) { @@ -457,8 +435,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // // BCn: libktx will provide decoders/encoders for BCn block compression // via ktxTexture2_DecodeBCn. - // TODO: wait for my RP in libktx to get merged then add BCn - // support. + // TODO: wait libktx BCn PR to get merged then add BCn support. // // ETC2: libktx provides decoders but they fall under non-open-source // license. To quote KTX-Software: "The file lib/etcdec.cxx is not @@ -479,6 +456,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) return false; } m_cmp = format_info.compression; + m_spec.attribute("compression", block_compression_name(m_cmp)); switch (m_cmp) { #if 0 // TODO: wait for my PR in libktx to be merged /* BCn GPU formats */ @@ -513,7 +491,15 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) #endif /* ASTC formats */ - case BlockCompression::ASTC: + case BlockCompression::ASTC: { + const uint32_t blocksize = get_astc_block_size( + static_cast(m_tex->vkFormat)); + if (blocksize == 0) { + errorfmt( + "Failed to determine block size for ASTC-compressed texture from its VkFormat {}", + m_tex->vkFormat); + return false; + } // // Note: // ktxTexture2_DecodeAstc internally creates a new ktxTexture2 texture @@ -527,12 +513,13 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // if (auto status = ktxTexture2_DecodeAstc(m_tex.get()); status != KTX_SUCCESS) { - errorfmt("failed to decode ASTC-compressed texture. " + errorfmt("Failed to decode ASTC-compressed texture. " "ktxTexture2_DecodeAstc returned Ktx error code: {}", static_cast(status)); return false; } break; + } default: errorfmt("{} GPU-compressed formats are not supported", @@ -541,7 +528,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) } } - OIIO_ASSERT(!m_tex->isCompressed); + OIIO_DASSERT(!m_tex->isCompressed); // // This could mean one of the following as per the specs at: @@ -616,7 +603,18 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) m_spec.set_format(format_info.typedesc); m_spec.nchannels = format_info.nbrchannels; - // TODO: verify the x, y, z limits (probably not 65535) + if (m_tex->baseDepth > 1) { // Volume texture are limited to 4096x4096x4096 + if (!check_open(m_spec, { 0, 4096, 0, 4096, 0, 4096, 0, 4 })) + return false; + } else if (m_tex->numFaces + > 1) { // Cubemap texture are limited to 16384x16384 + if (!check_open(m_spec, { 0, 16384, 0, 16384 * 6, 0, 1, 0, 4 })) + return false; + } else { // 2D texture are limited to 32768x32768 + if (!check_open(m_spec, { 0, 32768, 0, 32768, 0, 1, 0, 4 })) + return false; + } // Array textures are not supported + if (!check_open(m_spec, { 0, 65535, 0, 65535, 0, 65535, 0, 4 })) return false; @@ -742,8 +740,8 @@ KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, return false; } - OIIO_ASSERT(pitch - == ktxTexture_GetRowPitch((ktxTexture*)m_tex.get(), miplevel)); + OIIO_DASSERT(pitch + == ktxTexture_GetRowPitch((ktxTexture*)m_tex.get(), miplevel)); // Use this in case OIIO API provides read_native_scanlines with `data` as // `span` and a `z` slice param @@ -824,27 +822,26 @@ KtxInput::get_colorspace() const // Don't use ktxTexture2_GetPrimaries_e/ktxTexture2_GetTransferFunction_e as these are only // available in newer versions of libktx (>= 5.0.0, I think) // - const auto transfer_function = static_cast( - KHR_DFDVAL(m_tex->pDfd + 1, TRANSFER)); - const auto primaries = static_cast( - KHR_DFDVAL(m_tex->pDfd + 1, PRIMARIES)); - // std::cout << "tf: " << transfer_function << "; primaries: " << primaries - // << '\n'; + const auto transfer_function = KHR_DFDVAL(m_tex->pDfd + 1, TRANSFER); + const auto primaries = KHR_DFDVAL(m_tex->pDfd + 1, PRIMARIES); + DBG std::cout << "tf: " << transfer_function << "; primaries: " << primaries + << '\n'; switch (transfer_function) { case KHR_DF_TRANSFER_SRGB: switch (primaries) { case KHR_DF_PRIMARIES_BT709: return "srgb_rec709_scene"; - default: break; + default: errorfmt("Unsupported color primaries {}", primaries); break; } break; case KHR_DF_TRANSFER_LINEAR: switch (primaries) { case KHR_DF_PRIMARIES_BT709: return "lin_rec709_scene"; - default: break; + default: errorfmt("Unsupported color primaries {}", primaries); break; } break; - // case KHR_DF_TRANSFER_DCIP3: colorspace = "lin_rec709_scene"; return true; - default: break; + default: + errorfmt("Unsupported color transfer function {}", transfer_function); + break; } // TODO: need to generate test files before adding support for any other // colorspaces @@ -875,6 +872,67 @@ KtxInput::parse_ktx_sc_params_metadata(const std::string& ktx_sc_params) { const auto f = std::regex_constants::icase; + { // BasisLZ/ETC1S params (see KTX-Software/tools/ktx/encode_utils_basis.h) + std::regex etc1s_clevel("--clevel\\s+(\\d+)", f); + std::regex etc1s_qlevel("--qlevel\\s+(\\d+)", f); + std::regex etc1s_max_endpoints("--max-endpoints\\s+(\\d+)", f); + std::regex etc1s_endpoint_rdo_threshold( + "--endpoint-rdo-threshold\\s+((\\d*[.])?\\d+)", f); + std::regex etc1s_max_selectors("--max-selectors\\s+(\\d+)", f); + std::regex etc1s_selector_rdo_threshold( + "--selector-rdo-threshold\\s+((\\d*[.])?\\d+)", f); + std::regex etc1s_no_endpoint_rdo_re("--no-endpoint-rdo", f); + std::regex etc1s_no_selector_rdo_re("--no-selector-rdo", f); + + if (std::smatch m; std::regex_search(ktx_sc_params, m, etc1s_clevel) + && m.size() == 2) { + auto level = static_cast(std::stol(m[1].str())); + m_spec.extra_attribs.attribute("ktx:etc1sCompressionLevel", level); + } + + if (std::smatch m; std::regex_search(ktx_sc_params, m, etc1s_qlevel) + && m.size() == 2) { + auto level = static_cast(std::stol(m[1].str())); + m_spec.extra_attribs.attribute("ktx:etc1sQualityLevel", level); + } + + if (std::smatch m; + std::regex_search(ktx_sc_params, m, etc1s_max_endpoints) + && m.size() == 2) { + auto level = static_cast(std::stol(m[1].str())); + m_spec.extra_attribs.attribute("ktx:etc1sMaxEndpoints", level); + } + + if (std::smatch m; + std::regex_search(ktx_sc_params, m, etc1s_endpoint_rdo_threshold) + && m.size() == 2) { + auto val = std::stof(m[1].str()); + m_spec.extra_attribs.attribute("ktx:etc1sEndpointRDOThreshold", + val); + } + + if (std::smatch m; + std::regex_search(ktx_sc_params, m, etc1s_max_selectors) + && m.size() == 2) { + auto level = static_cast(std::stol(m[1].str())); + m_spec.extra_attribs.attribute("ktx:etc1sMaxSelectors", level); + } + + if (std::smatch m; + std::regex_search(ktx_sc_params, m, etc1s_selector_rdo_threshold) + && m.size() == 2) { + auto val = std::stof(m[1].str()); + m_spec.extra_attribs.attribute("ktx:etc1sSelectorRDOThreshold", + val); + } + + if (std::regex_match(ktx_sc_params, etc1s_no_endpoint_rdo_re)) + m_spec.extra_attribs.attribute("ktx:etc1sNoEndpointRDO", true); + + if (std::regex_match(ktx_sc_params, etc1s_no_selector_rdo_re)) + m_spec.extra_attribs.attribute("ktx:etc1sNoSelectorRDO", true); + } + { // UASTC params (see KTX-Software/tools/ktx/encode_utils_basis.h) std::regex uastc_quality_re("--uastc-quality\\s+(\\d+)", f); std::regex uastc_rdo_re("--uastc-rdo", f); @@ -898,7 +956,8 @@ KtxInput::parse_ktx_sc_params_metadata(const std::string& ktx_sc_params) const uint32_t uastc_flags = (unsigned int)~KTX_PACK_UASTC_LEVEL_MASK | uastc_quality; m_spec.extra_attribs.attribute("ktx:uastcFlags", uastc_flags); - m_spec.extra_attribs.attribute("ktx:uastcHDRLevel", uastc_quality); + m_spec.extra_attribs.attribute("ktx:uastcHDRQuality", + uastc_quality); } if (std::regex_match(ktx_sc_params, uastc_rdo_re)) @@ -934,17 +993,16 @@ KtxInput::parse_ktx_sc_params_metadata(const std::string& ktx_sc_params) if (std::regex_match(ktx_sc_params, uastc_rdo_f_re)) m_spec.extra_attribs.attribute("ktx:uastcRDODontFavorSimplerModes", true); - if (std::regex_match(ktx_sc_params, uastc_rdo_m_re)) m_spec.extra_attribs.attribute("ktx:uastcRDONoMultithreading", true); + if (std::regex_match(ktx_sc_params, uastc_rdo_m_re)) + m_spec.extra_attribs.attribute("ktx:uastcHDRQuality", true); if (std::regex_match(ktx_sc_params, uastc_rdo_uber_mode_re)) m_spec.extra_attribs.attribute("ktx:uastcHDRUberMode", true); - if (std::regex_match(ktx_sc_params, uastc_rdo_ultra_quant_re)) m_spec.extra_attribs.attribute("ktx:uastcHDRUltraQuant", true); - if (std::regex_match(ktx_sc_params, uastc_rdo_favor_astc_re)) m_spec.extra_attribs.attribute("ktx:uastcHDRFavorAstc", true); @@ -965,31 +1023,18 @@ KtxInput::parse_ktx_sc_params_metadata(const std::string& ktx_sc_params) uastc_hdr_6x6i_level); } } -#if 0 - { // ETC1S params (see KTX-Software/tools/ktx/encode_utils_basis.h) - std::regex etc1s_clevel("--clevel\\s+(\\d+)", f); - std::regex etc1s_qlevel("--qlevel\\s+(\\d+)", f); - std::regex etc1s_max_endpoints("--max-endpoints\\s+(\\d+)", f); - std::regex etc1s_endpoint_rdo_threshold( - "--endpoint-rdo-threshold\\s+((\\d*[.])?\\d+)", f); - std::regex etc1s_max_selectors("--max-selectors\\s+(\\d+)", f); - std::regex uastc_rdo_re("", f); - std::regex uastc_rdo_l_re("--uastc-rdo-l\\s+((\\d*[.])?\\d+)", f); - std::regex uastc_rdo_d_re("--uastc-rdo-d\\s+(\\d+)", f); - std::regex uastc_rdo_b_re("--uastc-rdo-b\\s+((\\d*[.])?\\d+)", f); - std::regex uastc_rdo_s_re("--uastc-rdo-s\\s+((\\d*[.])?\\d+)", f); - std::regex uastc_rdo_f_re("--uastc-rdo-f", f); - std::regex uastc_rdo_m_re("--uastc-rdo-m", f); - std::regex uastc_rdo_uber_mode_re("--uastc-hdr-uber-mode", f); - std::regex uastc_rdo_ultra_quant_re("--uastc-hdr-ultra-quant", f); - std::regex uastc_rdo_favor_astc_re("--uastc-hdr-favor-astc", f); - std::regex uastc_hdr_lambda_re("--uastc-hdr-lambda\\s+((\\d*[.])?\\d+)", - f); - std::regex uastc_hdr_6x6i_level_re("--uastc-hdr-6x6i-level\\s+(\\d+)", - f); + { // Common params (see KTX-Software/tools/ktx/encode_utils_common.h) + std::regex common_normal_map("--normal-mode", f); + std::regex common_no_sse("--no-sse", f); + + if (std::regex_match(ktx_sc_params, common_normal_map)) + m_spec.extra_attribs.attribute("ktx:normalMap", true); + if (std::regex_match(ktx_sc_params, common_no_sse)) + m_spec.extra_attribs.attribute("ktx:noSSE", true); } -#endif + + // preSwizzle and inputSwizzle are not exposed in ktx tools } OIIO_PLUGIN_NAMESPACE_END diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp index 8269037fe5..6c1535ae0d 100644 --- a/src/ktx.imageio/ktxoutput.cpp +++ b/src/ktx.imageio/ktxoutput.cpp @@ -60,16 +60,9 @@ class KtxOutput final : public ImageOutput { bool m_initialized { false }; ///< Has open() with mode == Create was - /// Vulkan format fetched from "ktx:vkformat" attribute or set from current - /// image spec. + /// Uncompressed Vulkan format to create the initial texture with. VkFormat m_vkformat { VK_FORMAT_UNDEFINED }; - /// Uncompressed Vulkan format to create the texture with in case original - /// vkforma is a GPU-block-compressed format. This might be different that - /// the actual format supplied via the "ktx:vkformat" attribute in the case - /// of GPU-block-compressed formats (e.g., VK_FORMAT_ASTC_4x4_SRGB_BLOCK). - VkFormat m_vkformat_uncompressed { VK_FORMAT_UNDEFINED }; - uint32_t m_miplevel_idx { 0 }; ///< Current MIP level uint32_t m_max_nmiplevels { 1 }; ///< Max number allowable MIP levels @@ -131,7 +124,7 @@ class KtxOutput final : public ImageOutput { bool write_ktx2(); - bool construct_basis_params(ktxBasisParams& params, uint32_t codec, + bool construct_basis_params(ktxBasisParams& params, std::string_view codec, uint32_t threads = 1) const; // bool construct_astc_params(ktxAstcParams& param) const; @@ -205,10 +198,24 @@ KtxOutput::open(const std::string& name, const ImageSpec& userspec, // m_texturekind = TextureKind::SINGLE_TEXTURE_2D; // } - std::string colorspace - = m_spec.get_string_attribute("oiio:ColorSpace", - "srgb_rec709_scene"); - bool is_srgb = colorspace == "srgb_rec709_scene"; + const auto compression = m_spec.get_string_attribute("compression", + "NONE"); + if (iequals(compression, "NONE")) { + m_cmp = BlockCompression::NONE; + } else if (iequals(compression, "ASTC")) { + m_cmp = BlockCompression::ASTC; + } else { + errorfmt( + "Unsupported/Unknown compression from string attribute \"compression\": ", + compression); + return false; + } + + // Currently only two colorspaces are tested, linear or REC709 sRGB + const auto colorspace = m_spec.get_string_attribute("oiio:ColorSpace", + "srgb_rec709_scene"); + bool is_srgb = colorspace == "srgb_rec709_scene"; + m_spec.set_colorspace(colorspace); // TODO: get_int_attribute causes a segfault and I have no idea why ... // Weirdly, calling find_attribute directly (and checking the resulting @@ -221,17 +228,28 @@ KtxOutput::open(const std::string& name, const ImageSpec& userspec, // disk filesizes and on-the-fly transcoding to a supported native GPU // format. // - if (auto Q = m_spec.find_attribute("ktx:supercompressionscheme", - TypeDesc::UINT32)) { + const auto& supercompression_str + = m_spec.get_string_attribute("ktx:supercompressionscheme", "NONE"); + if (iequals(supercompression_str, "NONE")) { + m_superCmp = ktxSupercmpScheme::KTX_SS_NONE; + } else if (iequals(supercompression_str, "ZSTD")) { + m_superCmp = ktxSupercmpScheme::KTX_SS_ZSTD; + } else if (iequals(supercompression_str, "ZLIB")) { + m_superCmp = ktxSupercmpScheme::KTX_SS_ZLIB; + } else { + close(); + errorfmt("unsupported super compression scheme: {}", + static_cast(m_superCmp)); + return false; + } + if (auto Q = m_spec.find_attribute("", TypeDesc::STRING)) { + const auto& supercompression_str = Q->get_string(); + m_superCmp = static_cast( *(uint32_t*)(Q->data())); // Do an early check on supported supercompressionscheme values if (m_superCmp != KTX_SS_BASIS_LZ && m_superCmp != KTX_SS_NONE) { // doing an `errorfmt()` then `close()` causes a seg fault... - close(); - errorfmt("unsupported super compression scheme: {}", - static_cast(m_superCmp)); - return false; } DBG std::cout << "[ktxoutput] supercompression scheme: " << m_superCmp << '\n'; @@ -239,7 +257,7 @@ KtxOutput::open(const std::string& name, const ImageSpec& userspec, if (auto Q = m_spec.find_attribute("ktx:generatemipmaps", TypeDesc::INT)) { - m_generate_mipmaps = static_cast(*(int*)Q->data()); + m_generate_mipmaps = static_cast(Q->get_int()); DBG std::cout << "[ktxoutput] generate mipmaps: " << std::boolalpha << m_generate_mipmaps << '\n'; } @@ -247,94 +265,42 @@ KtxOutput::open(const std::string& name, const ImageSpec& userspec, // We can check m_basis_params.codec != NONE but this won't work for // libktx 4.3.2 which is why we just use a bool var m_use_basis_universal = false; - if (auto Q = m_spec.find_attribute("ktx:codec", TypeDesc::UINT32)) { - auto codec = *(uint32_t*)Q->data(); + if (auto Q = m_spec.find_attribute("ktx:codec", TypeDesc::STRING)) { + const auto& codec = Q->get_string(); if (!construct_basis_params(m_basis_params, codec)) { close(); // construct_basis_params calls errorfmt return false; } - m_use_basis_universal = true; + m_use_basis_universal = m_basis_params.codec + != KTX_BASIS_CODEC_NONE; DBG std::cout << "[ktxoutput] basis universal codec (from \"ktx:codec\"): " << codec << '\n'; } // - // If provided, get target VkFormat explicitly set via the "ktx:vkformat" - // attribute. - // - if (auto Q = m_spec.find_attribute("ktx:vkformat", TypeDesc::UINT32)) { - m_vkformat = static_cast(*(uint32_t*)(Q->data())); - if (m_vkformat != VK_FORMAT_UNDEFINED) { - FormatInfo format_info; - if (!get_info_from_vkformat(m_vkformat, format_info)) { - close(); - errorfmt( - "Could not extract format info from provided " - "VkFormat: {}. This format is probably unsupported.", - static_cast(m_vkformat)); - return false; - } - if (m_spec.format != format_info.typedesc - || m_spec.nchannels != format_info.nbrchannels) { - errorfmt( - "Provided image spec is not aligned with provided VkFormat {}", - static_cast(m_vkformat)); - return false; - } - m_cmp = format_info.compression; - m_vkformat_uncompressed = format_info.uncompressed_format; - DBG std::cout << "[ktxoutput] found vkformat: " << m_vkformat - << '\n'; - DBG std::cout << "[ktxoutput] set uncompressed vkformat: " - << m_vkformat_uncompressed << '\n'; - } - } - - // - // User provided nothing about neither the target VkFormat nor the - // target super-compression scheme. Default to writing raw VK_FORMAT - // with ZSTD supercompression (write as losseless KTX2 output). - // - if (m_vkformat == VK_FORMAT_UNDEFINED && m_basis_params.codec == 0) { - m_vkformat = get_vkformat_from_info(m_spec.nchannels, m_spec.format, - is_srgb); - } - + // User provided nothing about neither the target GPU block compression + // nor the target Basis Universal codec. Default to writing uncompressed + // VK_FORMAT with ZSTD supercompression (write as losseless KTX2 + // output). // - // If a Basis Universal format compression is not requested and - // "ktx:vkformat" is VK_FORMAT_UNDEFINED, then we error out. The user has to - // set the vkformat so that we know in which format we write the texture to. + // If we intend to compress to BasisLZ/ETC1S or UASTC then we need to + // figure the VkFormat so that ktxTexture_SetImageFromMemory does not + // segfault. (makes sense, since we are creating a KTX texture and + // telling it to allocate storage, how would it know the size of a given + // subimage if we provide it with VK_FORMAT_UNDEFINED?) // - if (!m_use_basis_universal && m_vkformat == VK_FORMAT_UNDEFINED) { + m_vkformat = get_vkformat_from_info(m_spec.nchannels, m_spec.format, + is_srgb); + if (m_vkformat == VK_FORMAT_UNDEFINED) { close(); errorfmt( - "VkFormat is set to VK_FORMAT_UNDEFINED even though the " - "supercompression scheme is not BasisLZ. You have to set the " - "target VkFormat by setting the ImageSpec's attribute 'ktx:vkformat'."); + "Failed to determine VkFormat from nchannels={} format={} colorspace={}", + m_spec.nchannels, m_spec.format, colorspace); return false; } - // - // If we intend to compress to BasisLZ/ETC1S or UASTC then we need to figure - // the VkFormat so that ktxTexture_SetImageFromMemory does not segfault. - // (makes sense, since we are creating a KTX texture and telling it to - // allocate storage, how would it know the size of a given subimage if we - // provide it with VK_FORMAT_UNDEFINED?) - // - if (m_use_basis_universal && m_vkformat == VK_FORMAT_UNDEFINED) { - m_vkformat = get_vkformat_from_info(m_spec.nchannels, m_spec.format, - is_srgb); - } else if (m_use_basis_universal) { - // TODO: It could be that the user explicitly provided a vkformat - in which - // case we have to make sure it aligns with the spec. - // if (!is_vkformat_aligned_with_spec()) ... - close(); - errorfmt("Expected vkformat to be VK_FORMAT_UNDEFINED for Basis " - "Universal textures."); - return false; - } if (m_spec.depth > 1) m_max_nmiplevels @@ -354,9 +320,9 @@ KtxOutput::open(const std::string& name, const ImageSpec& userspec, // Initialize slices/faces container if not already initialized by a // previous call to open(name, subimages, specs) - OIIO_ASSERT(m_imgs.empty() - && "Expected mip levels container to be empty"); - OIIO_ASSERT(m_miplevel_idx == 0); + OIIO_DASSERT_MSG(m_imgs.empty(), + "Expected mip levels container to be empty"); + OIIO_DASSERT(m_miplevel_idx == 0); // Reserve space for base-level mipmap (level 0) append_mipmaps_vector(); @@ -438,14 +404,9 @@ KtxOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, ybegin, yend, z, height, m_basedepth); return false; } - // std::cout << "write_scanlines called with: ybegin=" << ybegin - // << "; yend=" << yend << "; z=" << z << "; format=" << format - // << "; xstride=" << xstride << '\n'; - stride_t zstride = AutoStride; m_spec.auto_stride(xstride, ystride, zstride, format, m_spec.nchannels, width, height); - // // Convert to the native format the current specs expects. This is needed, // for instance, to convert a given TypeDesc::FLOAT into native format that @@ -465,7 +426,6 @@ KtxOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, auto pSrc = reinterpret_cast(data); size_t offset = ybegin * pitch; size_t datalen = (yend - ybegin) * pitch; - memcpy(m_imgs[m_miplevel_idx][z].data() + offset, pSrc, datalen); DBG std::cout << "write_scanlines wrote " << datalen << " bytes" << std::endl; @@ -478,7 +438,7 @@ bool KtxOutput::close() { DBG std::cout << "[ktxoutput] close() called" << std::endl; - // TODO: sure about returning true? (closing an un-opened ImageOutput instance is fine, right?) + // closing an un-opened ImageOutput instance is fine if (!m_initialized) return true; // Check if already closed => if so, then the KTX2 file is already saved @@ -508,21 +468,20 @@ void KtxOutput::init() { // TODO: calling open() after close() on this hasn't been tested yet ... - m_initialized = false; - m_filename = std::string(); - m_vkformat = VK_FORMAT_UNDEFINED; - m_vkformat_uncompressed = VK_FORMAT_UNDEFINED; - m_miplevel_idx = 0; - m_max_nmiplevels = 1; - m_basewidth = 0; - m_baseheight = 0; - m_basedepth = 0; - m_superCmp = KTX_SS_NONE; - m_generate_mipmaps = false; - m_cmp = BlockCompression::NONE; - m_use_basis_universal = false; - m_basis_params = { 0 }; - m_astc_params = { 0 }; + m_initialized = false; + m_filename = std::string(); + m_vkformat = VK_FORMAT_UNDEFINED; + m_miplevel_idx = 0; + m_max_nmiplevels = 1; + m_basewidth = 0; + m_baseheight = 0; + m_basedepth = 0; + m_superCmp = KTX_SS_NONE; + m_generate_mipmaps = false; + m_cmp = BlockCompression::NONE; + m_use_basis_universal = false; + m_basis_params = { 0 }; + m_astc_params = { 0 }; // m_bcn_params = { 0 }; m_zlib_level = 9; m_zstd_level = 22; @@ -558,68 +517,72 @@ KtxOutput::init() /// For the moment, option 2) option 2) is opted for. /// bool -KtxOutput::construct_basis_params(ktxBasisParams& params, uint32_t codec, +KtxOutput::construct_basis_params(ktxBasisParams& params, + std::string_view codec, uint32_t threads) const { // Set defaults - params = { 0 }; - params.structSize = sizeof(ktxBasisParams); - // params.codec = KTX_BASIS_CODEC_UASTC_LDR_4x4; - params.verbose = false; - params.noSSE = false; + params = { 0 }; + params.structSize = sizeof(ktxBasisParams); params.threadCount = threads; params.etc1sCompressionLevel = KTX_ETC1S_DEFAULT_COMPRESSION_LEVEL; params.uastcFlags = KTX_PACK_UASTC_LEVEL_DEFAULT; - params.uastcRDO = false; - if (codec == ktx_basis_codec_e::KTX_BASIS_CODEC_NONE - || codec - >= ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_HDR_6x6_INTERMEDIATE) { - errorfmt("Provided Basis codec is invalid"); + if (iequals(codec, "NONE")) { + // no need to fill remaining struct members + params.codec = KTX_BASIS_CODEC_NONE; + return true; + } + + if (iequals(codec, "uastc") || iequals(codec, "uastc-ldr") + || iequals(codec, "uastc-ldr-4x4")) { + params.codec = KTX_BASIS_CODEC_UASTC_LDR_4x4; + } else if (iequals(codec, "uastc-hdr") || iequals(codec, "uastc-hdr-4x4")) { + params.codec = KTX_BASIS_CODEC_UASTC_HDR_4x4; + } else if (iequals(codec, "etc1s")) { + params.codec = KTX_BASIS_CODEC_ETC1S; + } else if (iequals(codec, "uastc-hdr-6x6")) { + params.codec = KTX_BASIS_CODEC_UASTC_HDR_6x6_INTERMEDIATE; + } else { + errorfmt( + "Provided Basis Universal codec \"{}\" is invalid. Supported values: \"uastc\" \"etc1s\" \"uastc-hdr-4x4\" \"uastc-hdr-6x6\"", + codec); return false; } - params.codec = codec; - if (params.codec == ktx_basis_codec_e::KTX_BASIS_CODEC_ETC1S) { + if (params.codec == KTX_BASIS_CODEC_ETC1S) { // Params that only apply to ETC1S if (auto Q = m_spec.find_attribute("ktx:etc1sCompressionLevel", TypeDesc::UINT32)) params.etc1sCompressionLevel = *(uint32_t*)Q->data(); - if (auto Q = m_spec.find_attribute("ktx:qualityLevel", TypeDesc::UINT32)) + if (auto Q = m_spec.find_attribute("ktx:etc1sQualityLevel", + TypeDesc::UINT32)) params.qualityLevel = *(uint32_t*)Q->data(); - if (auto Q = m_spec.find_attribute("ktx:maxEndpoints", TypeDesc::UINT32)) + if (auto Q = m_spec.find_attribute("ktx:etc1sMaxEndpoints", + TypeDesc::UINT32)) params.maxEndpoints = *(uint32_t*)Q->data(); - if (auto Q = m_spec.find_attribute("ktx:endpointRDOThreshold", + if (auto Q = m_spec.find_attribute("ktx:etc1sEndpointRDOThreshold", TypeDesc::FLOAT)) params.endpointRDOThreshold = *(float*)Q->data(); - if (auto Q = m_spec.find_attribute("ktx:maxSelectors", TypeDesc::UINT32)) + if (auto Q = m_spec.find_attribute("ktx:etc1sMaxSelectors", + TypeDesc::UINT32)) params.maxSelectors = *(uint32_t*)Q->data(); - if (auto Q = m_spec.find_attribute("ktx:selectorRDOThreshold", + if (auto Q = m_spec.find_attribute("ktx:etc1sSelectorRDOThreshold", TypeDesc::FLOAT)) params.selectorRDOThreshold = *(float*)Q->data(); - if (m_spec.find_attribute("ktx:normalMap")) - params.normalMap = true; - if (m_spec.find_attribute("ktx:separateRGToRGB_A")) - params.separateRGToRGB_A = true; - if (m_spec.find_attribute("ktx:preSwizzle")) - params.preSwizzle = true; - if (m_spec.find_attribute("ktx:noEndpointRDO")) - params.noEndpointRDO = true; - if (m_spec.find_attribute("ktx:noSelectorRDO")) - params.noSelectorRDO = true; - } else if (params.codec == ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_LDR_4x4 - || params.codec - == ktx_basis_codec_e::KTX_BASIS_CODEC_UASTC_HDR_4x4 - || params.codec - == ktx_basis_codec_e:: - KTX_BASIS_CODEC_UASTC_HDR_6x6_INTERMEDIATE) { + if (auto Q = m_spec.find_attribute("ktx:etc1sNoEndpointRDO")) + params.noEndpointRDO = static_cast(Q->get_int()); + if (auto Q = m_spec.find_attribute("ktx:etc1sNoSelectorRDO", + TypeDesc::INT)) + params.noSelectorRDO = static_cast(Q->get_int()); + } else if (params.codec == KTX_BASIS_CODEC_UASTC_LDR_4x4) { // Params that only apply to UASTC if (auto Q = m_spec.find_attribute("ktx:uastcFlags", TypeDesc::UINT32)) params.uastcFlags = *(uint32_t*)Q->data(); - if (m_spec.find_attribute("ktx:uastcRDO")) - params.uastcRDO = true; + if (auto Q = m_spec.find_attribute("ktx:uastcRDO", TypeDesc::INT)) + params.uastcRDO = static_cast(Q->get_int()); if (auto Q = m_spec.find_attribute("ktx:uastcRDOQualityScalar", TypeDesc::FLOAT)) params.uastcRDOQualityScalar = *(float*)Q->data(); @@ -633,19 +596,27 @@ KtxOutput::construct_basis_params(ktxBasisParams& params, uint32_t codec, if (auto Q = m_spec.find_attribute("ktx:uastcRDOMaxSmoothBlockStdDev", TypeDesc::FLOAT)) params.uastcRDOMaxSmoothBlockStdDev = *(float*)Q->data(); - if (m_spec.find_attribute("ktx:uastcRDODontFavorSimplerModes")) - params.uastcRDODontFavorSimplerModes = true; - if (m_spec.find_attribute("ktx:uastcRDONoMultithreading")) - params.uastcRDONoMultithreading = true; + if (auto Q = m_spec.find_attribute("ktx:uastcRDODontFavorSimplerModes", + TypeDesc::INT)) + params.uastcRDODontFavorSimplerModes = static_cast( + Q->get_int()); + if (auto Q = m_spec.find_attribute("ktx:uastcRDONoMultithreading", + TypeDesc::INT)) + params.uastcRDONoMultithreading = static_cast(Q->get_int()); + } else if (params.codec == KTX_BASIS_CODEC_UASTC_HDR_4x4 + || params.codec == KTX_BASIS_CODEC_UASTC_HDR_6x6_INTERMEDIATE) { if (auto Q = m_spec.find_attribute("ktx:uastcHDRQuality", TypeDesc::UINT32)) params.uastcHDRQuality = *(uint32_t*)Q->data(); - if (m_spec.find_attribute("ktx:uastcHDRUberMode")) - params.uastcHDRUberMode = true; - if (m_spec.find_attribute("ktx:uastcHDRUltraQuant")) - params.uastcHDRUltraQuant = true; - if (m_spec.find_attribute("ktx:uastcHDRFavorAstc")) - params.uastcHDRFavorAstc = true; + if (auto Q = m_spec.find_attribute("ktx:uastcHDRUberMode", + TypeDesc::INT)) + params.uastcHDRUberMode = static_cast(Q->get_int()); + if (auto Q = m_spec.find_attribute("ktx:uastcHDRUltraQuant", + TypeDesc::INT)) + params.uastcHDRUltraQuant = static_cast(Q->get_int()); + if (auto Q = m_spec.find_attribute("ktx:uastcHDRFavorAstc", + TypeDesc::INT)) + params.uastcHDRFavorAstc = static_cast(Q->get_int()); if (auto Q = m_spec.find_attribute("ktx:uastcHDRLambda", TypeDesc::FLOAT)) params.uastcHDRLambda = *(float*)Q->data(); @@ -655,9 +626,15 @@ KtxOutput::construct_basis_params(ktxBasisParams& params, uint32_t codec, } // Params that apply to both ETC1S and UASTC + if (auto Q = m_spec.find_attribute("ktx:noSSE", TypeDesc::INT)) + params.noSSE = static_cast(Q->get_int()); + if (auto Q = m_spec.find_attribute("ktx:normalMap", TypeDesc::INT)) + params.normalMap = static_cast(Q->get_int()); if (auto Q = m_spec.find_attribute("ktx:inputSwizzle", TypeDesc(TypeDesc::CHAR, 4))) memcpy(params.inputSwizzle, Q->data(), 4); + if (auto Q = m_spec.find_attribute("ktx:preSwizzle", TypeDesc::INT)) + params.preSwizzle = static_cast(Q->get_int()); return true; } @@ -679,24 +656,23 @@ KtxOutput::write_ktx2() nullptr, ktxTexture2_Destroy }; - uint32_t vkformat = m_cmp == BlockCompression::NONE - ? m_vkformat - : m_vkformat_uncompressed; - - OIIO_ASSERT(vkformat != VK_FORMAT_UNDEFINED); // otherwise segfault - ktxTextureCreateInfo create_info; - create_info.glInternalformat = 0; // Ignored as this is not a KTX1 texture - create_info.vkFormat = vkformat; - create_info.pDfd = nullptr; - create_info.baseWidth = m_basewidth; - create_info.baseHeight = m_baseheight; - create_info.baseDepth = m_basedepth; - create_info.numDimensions = m_basedepth > 1 ? 3 : 2; - create_info.numLevels = m_miplevel_idx + 1; - create_info.numLayers = 1; // Can't support this with current OIIO API - create_info.numFaces = 1; - create_info.isArray = KTX_FALSE; // Can't support this with current OIIO API - create_info.generateMipmaps = m_generate_mipmaps; + OIIO_DASSERT_MSG( + m_vkformat != VK_FORMAT_UNDEFINED, + "VkFormat should never be VK_FORMAT_UNDEFINED when creating a KTX2 texture"); + ktxTextureCreateInfo create_info { + .glInternalformat = 0, // Ignored as this is not a KTX1 texture + .vkFormat = m_vkformat, + .pDfd = nullptr, + .baseWidth = m_basewidth, + .baseHeight = m_baseheight, + .baseDepth = m_basedepth, + .numDimensions = static_cast(m_basedepth > 1 ? 3 : 2), + .numLevels = m_miplevel_idx + 1, + .numLayers = 1, // Can't support this with current OIIO API + .numFaces = 1, // TODO + .isArray = KTX_FALSE, // Can't support this with current OIIO API + .generateMipmaps = m_generate_mipmaps, + }; DBG std::cout << "calling ktxTexture2_Create with: " << "vkFormat=" << create_info.vkFormat << "; " @@ -746,15 +722,6 @@ KtxOutput::write_ktx2() ++layer_idx) { for (uint32_t face_idx = 0; face_idx < tex->numFaces; ++face_idx) { for (uint32_t slice_idx = 0; slice_idx < depth; ++slice_idx) { - // ImageBuf mipmap_data = generate_miplevel(make_cspan(m_img[m_miplevel_idx]), level_idx, m_mipmap_generation_params.filtername); - // - // TODO: can this not be contiguous? If so, add another loop - // (and another loop if scanlines are not contiguous). Might end in a black hole at this rate ... - // OIIO_ASSERT(mipmap_data.contiguous()); - // auto mipmap_data_span = mipmap_data.localpixels_as_byte_image_span(); - // auto data_ptr = (const ktx_uint8_t*)mipmap_data_span.data(); - // auto data_size = mipmap_data_span.size_bytes(); - // Faces and Slices are mutually exclusive, addition is fine auto data_ptr = m_imgs[level_idx][slice_idx + face_idx].data(); diff --git a/testsuite/ktx/ref/out.txt b/testsuite/ktx/ref/out.txt index b2eaa624a9..9b18b62bdc 100644 --- a/testsuite/ktx/ref/out.txt +++ b/testsuite/ktx/ref/out.txt @@ -3,13 +3,8 @@ Reading ../oiio-images/ktx2/r8g8b8a8_srgb.ktx2 SHA-1: 827AAFAEA2C443CE1B6DD30196163B0E01B4B816 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 - ktx:miplevels: 1 - ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/r8g8b8a8_srgb_mip.ktx2 ../oiio-images/ktx2/r8g8b8a8_srgb_mip.ktx2 : 64 x 64, 4 channel, uint8 ktx @@ -17,13 +12,9 @@ Reading ../oiio-images/ktx2/r8g8b8a8_srgb_mip.ktx2 SHA-1: 03052CEA1E271CEB85C2A084E44DC23F5BD7345C channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 ktx:miplevels: 7 - ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/r8g8b8_srgb_mip.ktx2 ../oiio-images/ktx2/r8g8b8_srgb_mip.ktx2 : 64 x 64, 3 channel, uint8 ktx @@ -31,26 +22,17 @@ Reading ../oiio-images/ktx2/r8g8b8_srgb_mip.ktx2 SHA-1: BE1D5EA24E907A4C4B3FB3C28EAC872A20F5B414 channel list: R, G, B textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 ktx:miplevels: 7 - ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 29 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/r8g8b8a8_srgb_3d_7.ktx2 ../oiio-images/ktx2/r8g8b8a8_srgb_3d_7.ktx2 : 16 x 16 x 7, 4 channel, volume uint8 ktx SHA-1: B25CE648E0293F6B8C5D0433295EFDC7182AA0C6 channel list: R, G, B, A textureformat: "Volume Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 - ktx:miplevels: 1 - ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/r8g8b8a8_srgb_array_7_mip.ktx2 ../oiio-images/ktx2/r8g8b8a8_srgb_array_7_mip.ktx2 : 16 x 16, 4 channel, uint8 ktx @@ -60,145 +42,108 @@ Reading ../oiio-images/ktx2/r8g8b8a8_srgb_array_7_mip.ktx2 SHA-1: A324F1AFE01140C4D58618875715EE545B9F1ECC channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 ktx:miplevels: 5 ktx:nlayers: 7 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" subimage 1: 16 x 16, 4 channel, uint8 ktx MIP-map levels: 16x16 8x8 4x4 2x2 1x1 SHA-1: 710B4D8675FE67BFE9E6897E0514EBCB2255385F channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 ktx:miplevels: 5 ktx:nlayers: 7 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" subimage 2: 16 x 16, 4 channel, uint8 ktx MIP-map levels: 16x16 8x8 4x4 2x2 1x1 SHA-1: CCB0462997F53FCDAFD94FA2E6C862E0D2CA022A channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 ktx:miplevels: 5 ktx:nlayers: 7 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" subimage 3: 16 x 16, 4 channel, uint8 ktx MIP-map levels: 16x16 8x8 4x4 2x2 1x1 SHA-1: CF06A8313BD1A85F6677F1CA7915C3CFEF954C6E channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 ktx:miplevels: 5 ktx:nlayers: 7 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" subimage 4: 16 x 16, 4 channel, uint8 ktx MIP-map levels: 16x16 8x8 4x4 2x2 1x1 SHA-1: 54034063DBC1C1BB61CB60EC57E4498678DC2B13 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 ktx:miplevels: 5 ktx:nlayers: 7 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" subimage 5: 16 x 16, 4 channel, uint8 ktx MIP-map levels: 16x16 8x8 4x4 2x2 1x1 SHA-1: 408D3A129857F4942CA5A8D10CBA2D51C5F01901 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 ktx:miplevels: 5 ktx:nlayers: 7 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" subimage 6: 16 x 16, 4 channel, uint8 ktx MIP-map levels: 16x16 8x8 4x4 2x2 1x1 SHA-1: 2966B466A5D0AFB2531AEBFEDB7304D560DC3D74 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 ktx:miplevels: 5 ktx:nlayers: 7 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/color_grid_zstd_5.ktx2 ../oiio-images/ktx2/color_grid_zstd_5.ktx2 : 1024 x 1024, 4 channel, uint8 ktx SHA-1: 4964D0A088333B6C31CD3BA19A2B8CC1CE879DA8 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 - ktx:miplevels: 1 - ktx:nlayers: 1 ktx:supercompressionscheme: 2 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/r8g8b8a8_srgb_mip_astc.ktx2 ../oiio-images/ktx2/r8g8b8a8_srgb_mip_astc.ktx2 : 64 x 64, 4 channel, uint8 ktx MIP-map levels: 64x64 32x32 16x16 8x8 4x4 2x2 1x1 SHA-1: 03052CEA1E271CEB85C2A084E44DC23F5BD7345C channel list: R, G, B, A + compression: "ASTC" textureformat: "Plain Texture" - ktx:colormodel: 162 - ktx:generatemipmaps: 0 ktx:miplevels: 7 - ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 158 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/ktx_app_astc_8x8.ktx2 ../oiio-images/ktx2/ktx_app_astc_8x8.ktx2 : 1024 x 1024, 4 channel, uint8 ktx MIP-map levels: 1024x1024 512x512 256x256 128x128 64x64 32x32 16x16 8x8 4x4 2x2 1x1 SHA-1: 1EF0757CD38CA8D9AF19EDF0B961FF7D5C635274 channel list: R, G, B, A + compression: "ASTC" textureformat: "Plain Texture" - ktx:colormodel: 162 - ktx:generatemipmaps: 0 ktx:miplevels: 11 - ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 172 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/color_grid_uastc_zstd_5.ktx2 ../oiio-images/ktx2/color_grid_uastc_zstd_5.ktx2 : 1024 x 1024, 4 channel, uint8 ktx SHA-1: B631629A3A81C50B2CE264D8CCA4EC4E169DFD6E channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 166 - ktx:generatemipmaps: 0 - ktx:miplevels: 1 - ktx:nlayers: 1 ktx:supercompressionscheme: 2 - ktx:version: 2 - ktx:vkformat: 0 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/Iron_Bars_001_normal_uastc_zstd_10.ktx2 ../oiio-images/ktx2/Iron_Bars_001_normal_uastc_zstd_10.ktx2 : 1024 x 1024, 4 channel, uint8 ktx @@ -206,13 +151,9 @@ Reading ../oiio-images/ktx2/Iron_Bars_001_normal_uastc_zstd_10.ktx2 SHA-1: D428118F2CD6DE70F04C9BD6D092A5A5DCAB195D channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 166 - ktx:generatemipmaps: 0 ktx:miplevels: 11 - ktx:nlayers: 1 ktx:supercompressionscheme: 2 - ktx:version: 2 - ktx:vkformat: 0 + ktx:version: "2.0" oiio:ColorSpace: "lin_rec709_scene" Reading ../oiio-images/ktx2/ktx_document_uastc_rdo_4_zstd_5.ktx2 ../oiio-images/ktx2/ktx_document_uastc_rdo_4_zstd_5.ktx2 : 1024 x 1024, 4 channel, uint8 ktx @@ -220,13 +161,9 @@ Reading ../oiio-images/ktx2/ktx_document_uastc_rdo_4_zstd_5.ktx2 SHA-1: 8644BD94DE7A340007640D0063DF80FC2B744849 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 166 - ktx:generatemipmaps: 0 ktx:miplevels: 11 - ktx:nlayers: 1 ktx:supercompressionscheme: 2 - ktx:version: 2 - ktx:vkformat: 0 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/cubemap_goldengate_uastc_rdo_4_zstd_5.ktx2 ../oiio-images/ktx2/cubemap_goldengate_uastc_rdo_4_zstd_5.ktx2 : 1024 x 1024, 4 channel, uint8 ktx @@ -234,26 +171,18 @@ Reading ../oiio-images/ktx2/cubemap_goldengate_uastc_rdo_4_zstd_5.ktx2 SHA-1: 730E2C2DABA5D591458C91C4D952638BC4EAADD6 channel list: R, G, B, A textureformat: "CubeFace Environment" - ktx:colormodel: 166 - ktx:generatemipmaps: 0 ktx:miplevels: 11 - ktx:nlayers: 1 + ktx:nfaces: 6 ktx:supercompressionscheme: 2 - ktx:version: 2 - ktx:vkformat: 0 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/kodim17_blze.ktx2 ../oiio-images/ktx2/kodim17_blze.ktx2 : 512 x 768, 4 channel, uint8 ktx SHA-1: 4313438D8827BD8CB040EBD28A27A64EC7B62F68 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 163 - ktx:generatemipmaps: 0 - ktx:miplevels: 1 - ktx:nlayers: 1 ktx:supercompressionscheme: 1 - ktx:version: 2 - ktx:vkformat: 0 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/r8g8b8a8_srgb_mip_blze.ktx2 ../oiio-images/ktx2/r8g8b8a8_srgb_mip_blze.ktx2 : 64 x 64, 4 channel, uint8 ktx @@ -261,39 +190,25 @@ Reading ../oiio-images/ktx2/r8g8b8a8_srgb_mip_blze.ktx2 SHA-1: A6350253C75FF2B9E24C35E89381C23EC1A77D29 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 163 - ktx:generatemipmaps: 0 ktx:miplevels: 7 - ktx:nlayers: 1 ktx:supercompressionscheme: 1 - ktx:version: 2 - ktx:vkformat: 0 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/color_grid_blze.ktx2 ../oiio-images/ktx2/color_grid_blze.ktx2 : 1024 x 1024, 4 channel, uint8 ktx SHA-1: 8C159DD2A2DC4FF1B2D6B84A0DF4E925A0FCBF46 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 163 - ktx:generatemipmaps: 0 - ktx:miplevels: 1 - ktx:nlayers: 1 ktx:supercompressionscheme: 1 - ktx:version: 2 - ktx:vkformat: 0 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/alpha_simple_blze.ktx2 ../oiio-images/ktx2/alpha_simple_blze.ktx2 : 8 x 8, 4 channel, uint8 ktx SHA-1: 23BD7228F104BBA18152E7FDC50825286379F581 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 163 - ktx:generatemipmaps: 0 - ktx:miplevels: 1 - ktx:nlayers: 1 ktx:supercompressionscheme: 1 - ktx:version: 2 - ktx:vkformat: 0 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/cubemap_yokohama_blze.ktx2 ../oiio-images/ktx2/cubemap_yokohama_blze.ktx2 : 2048 x 2048, 4 channel, uint8 ktx @@ -301,26 +216,18 @@ Reading ../oiio-images/ktx2/cubemap_yokohama_blze.ktx2 SHA-1: E1D8635C6E17878F937840E2DA2E8F5A1F61F501 channel list: R, G, B, A textureformat: "CubeFace Environment" - ktx:colormodel: 163 - ktx:generatemipmaps: 0 ktx:miplevels: 12 - ktx:nlayers: 1 + ktx:nfaces: 6 ktx:supercompressionscheme: 1 - ktx:version: 2 - ktx:vkformat: 0 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/FlightHelmet_baseColor_blze.ktx2 ../oiio-images/ktx2/FlightHelmet_baseColor_blze.ktx2 : 2048 x 2048, 4 channel, uint8 ktx SHA-1: FD4B9268979E2925598D7D3FF663293FF3807926 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 163 - ktx:generatemipmaps: 0 - ktx:miplevels: 1 - ktx:nlayers: 1 ktx:supercompressionscheme: 1 - ktx:version: 2 - ktx:vkformat: 0 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/Iron_Bars_001_normal_blze.ktx2 ../oiio-images/ktx2/Iron_Bars_001_normal_blze.ktx2 : 1024 x 1024, 4 channel, uint8 ktx @@ -328,13 +235,9 @@ Reading ../oiio-images/ktx2/Iron_Bars_001_normal_blze.ktx2 SHA-1: 4B447C9281806EBDC73619B0622F7F3CCC317F05 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 163 - ktx:generatemipmaps: 0 ktx:miplevels: 11 - ktx:nlayers: 1 ktx:supercompressionscheme: 1 - ktx:version: 2 - ktx:vkformat: 0 + ktx:version: "2.0" oiio:ColorSpace: "lin_rec709_scene" Reading ../oiio-images/ktx2/ktx_document_blze.ktx2 ../oiio-images/ktx2/ktx_document_blze.ktx2 : 1024 x 1024, 4 channel, uint8 ktx @@ -342,50 +245,35 @@ Reading ../oiio-images/ktx2/ktx_document_blze.ktx2 SHA-1: AE251370FDCA642A65531BAC14C939A2EC369DC5 channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 163 - ktx:generatemipmaps: 0 ktx:miplevels: 11 - ktx:nlayers: 1 ktx:supercompressionscheme: 1 - ktx:version: 2 - ktx:vkformat: 0 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/alpha_complex_straight.ktx2 ../oiio-images/ktx2/alpha_complex_straight.ktx2 : 256 x 256, 4 channel, uint8 ktx SHA-1: 4DC3A14E38526B307226E2DDE083608891EBD27F channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 - ktx:miplevels: 1 - ktx:nlayers: 1 ktx:supercompressionscheme: 2 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/orient_down_metadata.ktx2 ../oiio-images/ktx2/orient_down_metadata.ktx2 : 128 x 128, 4 channel, uint8 ktx SHA-1: BAA0E4B9FC815F0CCB235CA0E05E4EFE918FB3EC channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 - ktx:miplevels: 1 - ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" Reading ../oiio-images/ktx2/orient_up_metadata.ktx2 ../oiio-images/ktx2/orient_up_metadata.ktx2 : 128 x 128, 4 channel, uint8 ktx SHA-1: A446B2FB2A0D391584A2FE45E9F24BE35D896CAA channel list: R, G, B, A textureformat: "Plain Texture" - ktx:colormodel: 1 - ktx:generatemipmaps: 0 - ktx:miplevels: 1 - ktx:nlayers: 1 ktx:supercompressionscheme: 0 - ktx:version: 2 - ktx:vkformat: 43 + ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" +Comparing "checker_original.png" and "checker_uastc.ktx2" +PASS +Comparing "checker_original.png" and "checker_etc1s.ktx2" +PASS diff --git a/testsuite/ktx/run.py b/testsuite/ktx/run.py index 1a0bf035a3..c6a797ade6 100644 --- a/testsuite/ktx/run.py +++ b/testsuite/ktx/run.py @@ -5,10 +5,11 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO # All of the test files here are copied, as is, from KTX-Software repo and fall -# under the license of KTX-Software: +# under the Apache-2.0 license of KTX-Software: # # Copyright 2013-2020 Mark Callow SPDX-License-Identifier: Apache-2.0 +# # KTX-Software has two sets of ktx2 test files: # - a relatively small set for libktx: https://github.com/KhronosGroup/KTX-Software/tree/e2f948066c108b56b8d0052b460b2ac7d34886aa/tests/resources/ktx2 # - a very larget tests set for ktx tools: https://github.com/KhronosGroup/KTX-Software-CTS/tree/6d23ae9e52cce2ebc6495c4692ec89f632ff70d4 @@ -16,6 +17,13 @@ # commit hashse: # - libktx test files: 6c474d8627999de8acf07d819c196f83d025cd44 # - ktx tools test files (CTS): 6d23ae9e52cce2ebc6495c4692ec89f632ff70d4 +# +# Since OIIO KTX2 plugin simply forwards all operations to libktx, there is no +# need to do extensive testing on encoding/decoding functionalities. libktx +# already does very extensive testing on thousands of ktx2 inputs. What we do +# instead is that we test that we call libktx correctly and that parameters +# (which are numerous) are passed correctly. +# # save the error output redirect = ' >> out.txt 2>&1 ' @@ -74,13 +82,26 @@ ] for f in files: + # Just test `oiiotool --info` on libktx main test files command += info_command (OIIO_TESTSUITE_IMAGEDIR + "/" + f) +# Create a simple checker pattern RGBA PNG (has to be RGBA because Basis +# Universal codecs cannot transcode to opaque uncompressed formats) +command += (oiio_app("oiiotool") + + " --pattern checker 64x64 4 -d uint8 -o checker_original.png >> out.txt ;\n") + +# UASTC write test: check generation of an UASTC-based KTX2 file +command += oiiotool ("checker_original.png --attrib ktx:codec uastc -o checker_uastc.ktx2") +command += diff_command ("checker_original.png", "checker_uastc.ktx2", "--fail 0.0005 --warn 0.0005") + +# ETC1S write test: check generation of an ETC1S-based KTX2 file +command += oiiotool ("checker_original.png --attrib ktx:codec etc1s -o checker_etc1s.ktx2") +command += diff_command ("checker_original.png", "checker_etc1s.ktx2", "--fail 0.0005 --warn 0.0005") + # We do not test read-write of compressed-ktx2 files because any read-write # cycle worsens quality and is absolutely not the intended purpose of ktx usage -# within OIIO - -# Test write of PNG inputs +# within OIIO (or ktx usage in general). -# Default write (with nothing specified) should default to a loseless format + supercompression scheme +# Default write (with nothing specified) should default to a loseless format +# + supercompression scheme and should match exactly with original input From 6ae4462584a3cda4fa07dcdc9be2980ae2e6cec0 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Wed, 22 Jul 2026 09:55:10 +0200 Subject: [PATCH 39/42] Replace OIIO_DASSERT* with OIIO_ASSERT Signed-off-by: Walid Chtioui --- src/ktx.imageio/ktxinput.cpp | 16 ++++++++-------- src/ktx.imageio/ktxoutput.cpp | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index eaa62ec985..0272b979a3 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -215,7 +215,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) return false; } } else /* (proxytype == "memreader") */ { - OIIO_DASSERT(proxytype == "memreader"); + OIIO_ASSERT(proxytype == "memreader"); auto buff = reinterpret_cast(m_io)->buffer(); ktxTexture2* p_tex = nullptr; auto res = ktxTexture2_CreateFromMemory(buff.data(), buff.size(), @@ -347,7 +347,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // auto char_ptr = reinterpret_cast(val); } else if (attr_name == "KTXcubemapIncomplete") { - OIIO_DASSERT(vallen == 1); + OIIO_ASSERT(vallen == 1); // TODO: handle KTXcubemapIncomplete } else if (attr_name == KTX_ORIENTATION_KEY) { // @@ -359,7 +359,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // TODO: set orientation functions } else if (attr_name == "KTXglFormat") { - OIIO_DASSERT(vallen == sizeof(KTXglFormat) /* 12 bytes */); + OIIO_ASSERT(vallen == sizeof(KTXglFormat) /* 12 bytes */); KTXglFormat glFormat; glFormat.glInternalformat = *reinterpret_cast(val); glFormat.glFormat = *(reinterpret_cast(val) + 1); @@ -369,12 +369,12 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) ktx_prefixed_attr_name, TypeDesc::UINT32, 3, make_cspan(reinterpret_cast(val), 3)); } else if (attr_name == "KTXdxgiFormat__") { - OIIO_DASSERT(vallen == sizeof(uint32_t)); + OIIO_ASSERT(vallen == sizeof(uint32_t)); m_dxgiFormat = *reinterpret_cast(val); m_spec.extra_attribs.attribute(ktx_prefixed_attr_name, m_dxgiFormat.value()); } else if (attr_name == "KTXmetalPixelFormat") { - OIIO_DASSERT(vallen == sizeof(uint32_t)); + OIIO_ASSERT(vallen == sizeof(uint32_t)); m_metalFormat = *reinterpret_cast(val); m_spec.extra_attribs.attribute(ktx_prefixed_attr_name, m_metalFormat.value()); @@ -528,7 +528,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) } } - OIIO_DASSERT(!m_tex->isCompressed); + OIIO_ASSERT(!m_tex->isCompressed); // // This could mean one of the following as per the specs at: @@ -740,8 +740,8 @@ KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, return false; } - OIIO_DASSERT(pitch - == ktxTexture_GetRowPitch((ktxTexture*)m_tex.get(), miplevel)); + OIIO_ASSERT(pitch + == ktxTexture_GetRowPitch((ktxTexture*)m_tex.get(), miplevel)); // Use this in case OIIO API provides read_native_scanlines with `data` as // `span` and a `z` slice param diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp index 6c1535ae0d..624cecad95 100644 --- a/src/ktx.imageio/ktxoutput.cpp +++ b/src/ktx.imageio/ktxoutput.cpp @@ -320,9 +320,9 @@ KtxOutput::open(const std::string& name, const ImageSpec& userspec, // Initialize slices/faces container if not already initialized by a // previous call to open(name, subimages, specs) - OIIO_DASSERT_MSG(m_imgs.empty(), - "Expected mip levels container to be empty"); - OIIO_DASSERT(m_miplevel_idx == 0); + OIIO_ASSERT(m_imgs.empty() + && "Expected mip levels container to be empty"); + OIIO_ASSERT(m_miplevel_idx == 0); // Reserve space for base-level mipmap (level 0) append_mipmaps_vector(); @@ -656,9 +656,9 @@ KtxOutput::write_ktx2() nullptr, ktxTexture2_Destroy }; - OIIO_DASSERT_MSG( - m_vkformat != VK_FORMAT_UNDEFINED, - "VkFormat should never be VK_FORMAT_UNDEFINED when creating a KTX2 texture"); + OIIO_ASSERT( + m_vkformat != VK_FORMAT_UNDEFINED + && "VkFormat should never be VK_FORMAT_UNDEFINED when creating a KTX2 texture"); ktxTextureCreateInfo create_info { .glInternalformat = 0, // Ignored as this is not a KTX1 texture .vkFormat = m_vkformat, From 35dbd422f29afdd86d1ed3a425dbaa17f38087ea Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Wed, 22 Jul 2026 10:31:10 +0200 Subject: [PATCH 40/42] Remove usage of designated initializers Signed-off-by: Walid Chtioui --- src/ktx.imageio/ktxoutput.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/ktx.imageio/ktxoutput.cpp b/src/ktx.imageio/ktxoutput.cpp index 624cecad95..4cc1079ef1 100644 --- a/src/ktx.imageio/ktxoutput.cpp +++ b/src/ktx.imageio/ktxoutput.cpp @@ -659,20 +659,19 @@ KtxOutput::write_ktx2() OIIO_ASSERT( m_vkformat != VK_FORMAT_UNDEFINED && "VkFormat should never be VK_FORMAT_UNDEFINED when creating a KTX2 texture"); - ktxTextureCreateInfo create_info { - .glInternalformat = 0, // Ignored as this is not a KTX1 texture - .vkFormat = m_vkformat, - .pDfd = nullptr, - .baseWidth = m_basewidth, - .baseHeight = m_baseheight, - .baseDepth = m_basedepth, - .numDimensions = static_cast(m_basedepth > 1 ? 3 : 2), - .numLevels = m_miplevel_idx + 1, - .numLayers = 1, // Can't support this with current OIIO API - .numFaces = 1, // TODO - .isArray = KTX_FALSE, // Can't support this with current OIIO API - .generateMipmaps = m_generate_mipmaps, - }; + ktxTextureCreateInfo create_info; + create_info.glInternalformat = 0; // Ignored as this is not a KTX1 texture + create_info.vkFormat = m_vkformat; + create_info.pDfd = nullptr; + create_info.baseWidth = m_basewidth; + create_info.baseHeight = m_baseheight; + create_info.baseDepth = m_basedepth; + create_info.numDimensions = m_basedepth > 1 ? 3u : 2u; + create_info.numLevels = m_miplevel_idx + 1; + create_info.numLayers = 1; // Can't support this with current OIIO API + create_info.numFaces = 1; // TODO + create_info.isArray = KTX_FALSE; // Can't support this with current OIIO API + create_info.generateMipmaps = m_generate_mipmaps; DBG std::cout << "calling ktxTexture2_Create with: " << "vkFormat=" << create_info.vkFormat << "; " From 0482ab5824868cf4882ea760899b0544e1fe2960 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Wed, 22 Jul 2026 19:22:47 +0200 Subject: [PATCH 41/42] Update libktx commit hash Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index 5f32080b90..8fce3ff9c4 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -5,7 +5,7 @@ set_cache (Ktx_BUILD_VERSION main "Ktx version for local builds") set (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") set_cache (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") -set_cache (Ktx_GIT_COMMIT "89d2b6e916dbd3e7a09ca62e8e7807dc61d32c0f" +set_cache (Ktx_GIT_COMMIT "7b088823dfc71178bc8929776c3a7b547b62c045" "commit hash to verify tag/branch against") set_cache (Ktx_BUILD_SHARED_LIBS OFF #${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) From c037288de004eb957b8ebd5ec616515187adfc75 Mon Sep 17 00:00:00 2001 From: Walid Chtioui Date: Mon, 27 Jul 2026 07:52:53 +0200 Subject: [PATCH 42/42] fixes: hdr support; uastc-hdr-4x4 transcoding; erroneous libktx assert * libktx's ktxTexture_GetRowPitch is wrongly padding the actual correct computation to be a multiple of 4. This is only valid for KTXv1 but we only use/support KTXv2 and ktxTexture_GetRowPitch simply does not distinguish between the two. See issue: https://github.com/KhronosGroup/KTX-Software/issues/1228 * Add HDR support for ktxinput alongside UASTC-HDR/uncompressed HDR testcases. * Add support for HDR ASTC vkFormats (ASTC_WxH_SFLOAT_BLOCK). * Clean-up metadata parsing (KTXScWriterParams was wrongly serialized as uint8 array rather than a string). Remove unnecessary/unused metadata. Signed-off-by: Walid Chtioui --- src/cmake/build_Ktx.cmake | 8 +- src/ktx.imageio/ktx_pvt.h | 114 ++++++++++++++------- src/ktx.imageio/ktxinput.cpp | 188 ++++++++++++++--------------------- testsuite/ktx/ref/out.txt | 36 +++++++ testsuite/ktx/run.py | 25 ++--- 5 files changed, 204 insertions(+), 167 deletions(-) diff --git a/src/cmake/build_Ktx.cmake b/src/cmake/build_Ktx.cmake index 8fce3ff9c4..1b534b37ee 100644 --- a/src/cmake/build_Ktx.cmake +++ b/src/cmake/build_Ktx.cmake @@ -2,10 +2,12 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO -set_cache (Ktx_BUILD_VERSION main "Ktx version for local builds") -set (Ktx_GIT_REPOSITORY "https://github.com/KhronosGroup/KTX-Software.git") +# TODO: revert this before merging! I set this to my own PR branch that includes +# BCn and that will be merged (as per the reviewer) +set_cache (Ktx_BUILD_VERSION add-BCn-decoder "Ktx version for local builds") +set (Ktx_GIT_REPOSITORY "https://github.com/walcht/KTX-Software.git") set_cache (Ktx_GIT_TAG "${Ktx_BUILD_VERSION}" "Git branch or tag") -set_cache (Ktx_GIT_COMMIT "7b088823dfc71178bc8929776c3a7b547b62c045" +set_cache (Ktx_GIT_COMMIT "77075ec1d5125df4b938e2f5d96565b10189b1f3" "commit hash to verify tag/branch against") set_cache (Ktx_BUILD_SHARED_LIBS OFF #${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local Ktx build, if necessary, build shared libraries" ADVANCED) diff --git a/src/ktx.imageio/ktx_pvt.h b/src/ktx.imageio/ktx_pvt.h index 633c9b52fc..359064b1b8 100644 --- a/src/ktx.imageio/ktx_pvt.h +++ b/src/ktx.imageio/ktx_pvt.h @@ -388,7 +388,7 @@ get_info_from_vkformat(VkFormat vkformat, FormatInfo& formatinfo) { // clang-format off switch (vkformat) { - // Raw, uncompressed formats + // LDR uncompressed formats case VK_FORMAT_R8_UNORM: formatinfo = { 1, TypeDesc::UINT8, BlockCompression::NONE }; return true; case VK_FORMAT_R8G8_UNORM: formatinfo = { 2, TypeDesc::UINT8, BlockCompression::NONE }; return true; case VK_FORMAT_R8G8B8_UNORM: @@ -396,6 +396,10 @@ get_info_from_vkformat(VkFormat vkformat, FormatInfo& formatinfo) case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_R8G8B8A8_SRGB: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::NONE }; return true; + // HDR uncompressed formats + case VK_FORMAT_R16G16B16_SFLOAT: formatinfo = { 3, TypeDesc::HALF, BlockCompression::NONE }; return true; + case VK_FORMAT_R16G16B16A16_SFLOAT: formatinfo = { 4, TypeDesc::HALF, BlockCompression::NONE }; return true; + // ETC2 block-compressed formats case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: formatinfo = { 3, TypeDesc::UINT8, BlockCompression::ETC2_RGB, VK_FORMAT_R8G8B8A8_SRGB }; return true; case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ETC2_RGB_A1, VK_FORMAT_R8G8B8A8_SRGB }; return true; @@ -419,35 +423,51 @@ get_info_from_vkformat(VkFormat vkformat, FormatInfo& formatinfo) case VK_FORMAT_BC7_UNORM_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC7, VK_FORMAT_R8G8B8A8_UNORM }; return true; case VK_FORMAT_BC7_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::BC7, VK_FORMAT_R8G8B8A8_SRGB }; return true; - // ASTC formats (2D blocks) - case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: - case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: - case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: - case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: - case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: - case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: - case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: - case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: - case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: - case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: - case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: - case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: - case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: + // LDR ASTC formats (2D blocks) + case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: + case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: + case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: + case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ASTC, VK_FORMAT_R8G8B8A8_UNORM }; return true; - case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: - case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: - case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: - case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: - case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: - case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: - case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: - case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: - case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: - case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: - case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: - case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: - case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: + case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: + case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: + case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: + case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: + case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: + case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: + case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: + case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: + case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: + case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: + case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: + case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: + case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: formatinfo = { 4, TypeDesc::UINT8, BlockCompression::ASTC, VK_FORMAT_R8G8B8A8_SRGB }; return true; + + // HDR ASTC formats (2D blocks) + case VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK: + case VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK: formatinfo = { 4, TypeDesc::HALF, BlockCompression::ASTC, VK_FORMAT_R16G16B16A16_SFLOAT }; return true; default: break; } // clang-format on @@ -488,37 +508,66 @@ get_vkformat_from_info(int nchannels, TypeDesc format, bool srgb_colorspace) +/// ASTC supports multiple blocks sizes (unlike BCn that is fixed to 4x4). This +/// gets the block size (width * height) from the given VkFormat. inline uint32_t get_astc_block_size(VkFormat format) { switch (format) { case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: + case VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK: case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: return 4 * 4; + case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: + case VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK: case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: return 5 * 4; + case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK: case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: return 5 * 5; + case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK: case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: return 6 * 5; + case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK: case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: return 6 * 6; + case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK: case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: return 8 * 5; + case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK: case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: return 8 * 6; + case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: + case VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK: case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: return 8 * 8; + case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK: case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: return 10 * 5; + case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK: case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: return 10 * 6; + case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK: case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: return 10 * 8; + case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: + case VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK: case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: return 10 * 10; + case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: + case VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK: case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: return 12 * 10; + case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: + case VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK: case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: return 12 * 12; default: return 0; } @@ -534,6 +583,7 @@ ichar_equals(unsigned char a, unsigned char b) +/// Case-insensitive string equality inline bool iequals(std::string_view lhs, std::string_view rhs) { @@ -541,12 +591,4 @@ iequals(std::string_view lhs, std::string_view rhs) ichar_equals); } - - -// TODO -inline void -gl_to_vkformat() -{ -} - OIIO_PLUGIN_NAMESPACE_END diff --git a/src/ktx.imageio/ktxinput.cpp b/src/ktx.imageio/ktxinput.cpp index 0272b979a3..943ee3b933 100644 --- a/src/ktx.imageio/ktxinput.cpp +++ b/src/ktx.imageio/ktxinput.cpp @@ -4,7 +4,6 @@ #include "ktx_pvt.h" #include -#include #include OIIO_PLUGIN_NAMESPACE_BEGIN @@ -84,11 +83,6 @@ class KtxInput final : public ImageInput { std::unique_ptr m_config; ///< Saved copy of configuration spec - /// TODO: add gl, direct3d, and metal format support - std::optional m_glFormat { std::nullopt }; - std::optional m_dxgiFormat { std::nullopt }; - std::optional m_metalFormat { std::nullopt }; - /// Helper function: performs the actual pixel decoding. bool internal_readimg(unsigned char* dst, int w, int h, int d); @@ -138,23 +132,6 @@ KtxInput::open(const std::string& name, ImageSpec& newspec, -/// Opens the file with given name and seek to the first subimage in the -/// file. Various file attributes are put in `newspec` and a copy -/// is also saved internally to the `ImageInput` (retrievable via -/// `spec()`. From examining `newspec` or `spec()`, you can -/// discern the resolution, if it's tiled, number of channels, native -/// data format, and other metadata about the image. -/// -/// @param name -/// Filename to open, UTF-8 encoded. -/// -/// @param newspec -/// Reference to an ImageSpec in which to deposit a full -/// description of the contents of the first subimage of the -/// file. -/// -/// @returns -/// `true` if the file was found and opened successfully. bool KtxInput::open(const std::string& name, ImageSpec& newspec) { @@ -194,7 +171,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // ideal especially when dealing with, for instance, 3D textures, or even // worse, 3D array textures. // - // TODO: + // TODO: per-subimage and per-miplevel ktx loading // Implementing the per-subimage allocation approach requires some effort. // For the moment, let's make sure this approach is working (i.e., all tests // are passing). @@ -239,6 +216,9 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) return false; } + const bool is_hdr = ktxTexture2_IsHDR(m_tex.get()); + DBG std::cout << "is_hdr: " << is_hdr << '\n'; + m_spec = ImageSpec(m_tex->baseWidth, m_tex->baseHeight, 4 /* dummy value - will be overwritten */, TypeDesc::UINT8); @@ -249,7 +229,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) m_spec.set_colorspace(colorspace); // Set textureformat attribute - if (m_tex->numDimensions == 2) { + if (m_tex->numDimensions == 2 || m_tex->numDimensions == 1) { if (m_tex->numFaces > 1) m_spec.attribute("textureformat", "CubeFace Environment"); else @@ -295,16 +275,11 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // // Predifined keys we care about: // + // - KTXwriterScParams: null-terminated string // - KTXcubemapIncomplete: 1 byte bitfield // - KTXorientation: null-terminated string // - // - KTXglFormat: - // + UInt32 glInternalformat - // + UInt32 glFormat - // + UInt32 glType - // - // - KTXdxgiFormat__: UInt32 - // - KTXmetalPixelFormat: UInt32 + // We don't care about the rest and we don't save them. // auto kventry = m_tex->kvDataHead; if (kventry) @@ -319,70 +294,50 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) != KTX_SUCCESS) continue; - // "The key must be terminated by a NUL character" - // This will probably never occur, but it doesn't hurt to be safe - if (keylen <= 1) - continue; + // Per KTX2 spec: "The key must be terminated by a NUL character" + // We exit on any invalid key that is encountered + if (keylen <= 1 || key[keylen - 1] != '\0') { + errorfmt( + "An empty or non-null terminated metadata key string encountered. This is an invalid KTX file"); + return false; + } if ((status = ktxHashListEntry_GetValue(kventry, &vallen, &val)) - != KTX_SUCCESS) - continue; + != KTX_SUCCESS) { + errorfmt("ktxHashListEntry_GetValue returned error code {}", + static_cast(status)); + return false; + } // vallen checks are done below depending on the attribute name auto attr_name = std::string(key, key + (keylen - 1)); auto ktx_prefixed_attr_name = fmt::format("ktx:{}", attr_name); - if (attr_name == KTX_WRITER_KEY) { - // KTXwriter identifies the program used to write this KTX file. - // We don't care about such entry - continue; - } else if (attr_name == KTX_WRITER_SCPARAMS_KEY) { - // KTXwriterScParams is used to report all kinds of non-default parameters used by ktx tools to write this KTX2 file. - // This includes: - // non default Basis Universal params (i.e., for UASTC/ETC1S), non-default supercompression params, non-default mipmap generation params, etc. + if (attr_name == KTX_WRITER_SCPARAMS_KEY) { + // + // KTXwriterScParams is used to report all kinds of non-default + // parameters used by ktx tools to write this KTX2 file. + // This includes non default Basis Universal params (i.e., for + // UASTC/ETC1S), non-default supercompression params, + // non-default mipmap generation params, etc. // Should be NUL terminated. - if (vallen <= 1) - continue; - // auto char_ptr = reinterpret_cast(val); - + // + auto cval = reinterpret_cast(val); + if (vallen <= 1 || cval[vallen - 1] != '\0') { + errorfmt( + "An empty or non-null terminated metadata value string for KTX_WRITER_SCPARAMS_KEY. This is an invalid KTX file"); + return false; + } + std::string_view val_str(cval, vallen); + m_spec.extra_attribs.attribute(ktx_prefixed_attr_name, val_str); } else if (attr_name == "KTXcubemapIncomplete") { OIIO_ASSERT(vallen == 1); // TODO: handle KTXcubemapIncomplete } else if (attr_name == KTX_ORIENTATION_KEY) { - // // KTX may define a different orientation than the one used by OIIO. See: // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_ktxorientation - // E.g., for KTX1 (OpenGL) without any re-orientation logic images are - // flipped over X axis (top becomes down). - // - // TODO: set orientation functions - } else if (attr_name == "KTXglFormat") { - OIIO_ASSERT(vallen == sizeof(KTXglFormat) /* 12 bytes */); - KTXglFormat glFormat; - glFormat.glInternalformat = *reinterpret_cast(val); - glFormat.glFormat = *(reinterpret_cast(val) + 1); - glFormat.glType = *(reinterpret_cast(val) + 2); - m_glFormat = glFormat; - m_spec.extra_attribs.attribute( - ktx_prefixed_attr_name, TypeDesc::UINT32, 3, - make_cspan(reinterpret_cast(val), 3)); - } else if (attr_name == "KTXdxgiFormat__") { - OIIO_ASSERT(vallen == sizeof(uint32_t)); - m_dxgiFormat = *reinterpret_cast(val); - m_spec.extra_attribs.attribute(ktx_prefixed_attr_name, - m_dxgiFormat.value()); - } else if (attr_name == "KTXmetalPixelFormat") { - OIIO_ASSERT(vallen == sizeof(uint32_t)); - m_metalFormat = *reinterpret_cast(val); - m_spec.extra_attribs.attribute(ktx_prefixed_attr_name, - m_metalFormat.value()); - } else { - // otherwise store the arbitrary value as a byte string - m_spec.extra_attribs.attribute( - ktx_prefixed_attr_name, TypeDesc::UCHAR, vallen, - make_cspan(reinterpret_cast(val), vallen)); } } while ((kventry = ktxHashList_Next(kventry))); @@ -413,10 +368,15 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // This modifies the KtxTexture2 (m_tex) therefore make sure to save // essential properties for proper KTX2 regeneration. // - if (ktxTexture2_NeedsTranscoding(m_tex.get())) { + // Note: + // Do not use ktxTexture2_NeedsTranscoding as it returns false for + // UASTC_HDR_4x4. For all transcodable formats, we want to transcode them + // here to uncompressed format (RGBA32 for LDR and RGBA_HALF for HDR). + // + if (ktxTexture2_IsTranscodable(m_tex.get())) { // TODO: in case of HDR, use KTX_TTF_RGBA_HALF if (auto status = ktxTexture2_TranscodeBasis( - m_tex.get(), ktx_transcode_fmt_e::KTX_TTF_RGBA32, 0); + m_tex.get(), is_hdr ? KTX_TTF_RGBA_HALF : KTX_TTF_RGBA32, 0); status != KTX_SUCCESS) { errorfmt("failed to transcode KTX2 texture to raw pixels. " "ktxTexture2_TranscodeBasis returned Ktx error code: {}", @@ -535,7 +495,7 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // https://registry.khronos.org/KTX/specs/2.0/ktxspec.v2.html#_use_of_vk_format_undefined // // 1. For custom formats that do not have any equivalent in GPU APIs. - // This is currently not supported. + // => Not supported. // // 2. ETC1S/UASTC supercompression scheme: makes no sense since we // transcoded it above to uncompressed format. @@ -547,37 +507,15 @@ KtxInput::open(const std::string& name, ImageSpec& newspec) // - "KTXglFormat" for OpenGL // - "KTXdxgiFormat__" for Direct3D // - "KTXmetalPixelFormat" for Metal - // TODO + // => Not supported. // // 4. Compressed color models in Section 5.6 of [KDF14] or successors that // do not have corresponding Vulkan formats. - // TODO + // => Not supported. + // + // We only keep this for better error messaging. // if (m_tex->vkFormat == VK_FORMAT_UNDEFINED) { - // TODO: check case (4) - color model - - // check case (3) - non-Vulkan GPU formats (here we simply map these - // formats to VkFormat and call it a day) - if (m_glFormat.has_value()) { - // TODO: add glformat support - errorfmt("Loading KTX textures with OpenGL formats but no vkFormat " - "(i.e., VK_FORMAT_UNDEFINED) is currently not supported"); - return false; - } else if (m_dxgiFormat.has_value()) { - // TODO: add direct3d format support - errorfmt( - "Loading KTX textures with Direct3D formats but no vkFormat " - "(i.e., VK_FORMAT_UNDEFINED) is currently not supported"); - return false; - } else if (m_metalFormat.has_value()) { - // TODO: add metal format support - errorfmt("Loading KTX textures with Metal formats but no vkFormat " - "(i.e., VK_FORMAT_UNDEFINED) is currently not supported"); - return false; - } - - // error for other cases (case (2) should not occur and case (1) is - // not supported) errorfmt( "VkFormat of provided KTX texture is VK_FORMAT_UNDEFINED " "which potentially means that a custom format with no equivalent " @@ -723,10 +661,10 @@ bool KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, int yend, int z, void* data) { - const int width = std::max(m_tex->baseWidth >> miplevel, 1u); - const int height = std::max(m_tex->baseHeight >> miplevel, 1u); - const int depth = std::max(m_tex->baseDepth >> miplevel, 1u); - const size_t pitch = m_spec.pixel_bytes() * width; + const int width = std::max(m_tex->baseWidth >> miplevel, 1u); + const int height = std::max(m_tex->baseHeight >> miplevel, 1u); + const int depth = std::max(m_tex->baseDepth >> miplevel, 1u); + const size_t pitch_in_bytes = m_spec.pixel_bytes() * width; ktx_size_t offset; if (!check(subimage, miplevel)) { @@ -740,8 +678,26 @@ KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, return false; } - OIIO_ASSERT(pitch - == ktxTexture_GetRowPitch((ktxTexture*)m_tex.get(), miplevel)); + if (const size_t libktx_image_size = ktxTexture2_GetImageSize(m_tex.get(), + miplevel), + expected_image_size = pitch_in_bytes * height; + libktx_image_size != expected_image_size) { + errorfmt("Expected image size of {} but libktx reported {}", + expected_image_size, libktx_image_size); + return false; + } + +#if 0 + // TODO: ktxTexture_GetRowPitch reports 4-bytes aligned size in bytes (i.e., + // it adds padding when it shouldn't). + if (auto libktx_pitch_in_bytes + = ktxTexture_GetRowPitch((ktxTexture*)m_tex.get(), miplevel); + pitch_in_bytes == libktx_pitch_in_bytes) { + errorfmt("Expected a pitch of size {} but libktx expects {}", + pitch_in_bytes, libktx_pitch_in_bytes); + return false; + } +#endif // Use this in case OIIO API provides read_native_scanlines with `data` as // `span` and a `z` slice param @@ -771,7 +727,7 @@ KtxInput::read_native_scanlines(int subimage, int miplevel, int ybegin, auto data_ptr = m_tex->pData + offset; // since miplevel is valid => get number of bytes in a row for this mip - memcpy(data, data_ptr, pitch * size_t(yend - ybegin)); + memcpy(data, data_ptr, pitch_in_bytes * size_t(yend - ybegin)); DBG std::cout << fmt::format( "[ktxinput] read_native_scanlines(subimage={},miplevel={},ybegin={},yend={},z={})\n", subimage, miplevel, ybegin, yend, z); diff --git a/testsuite/ktx/ref/out.txt b/testsuite/ktx/ref/out.txt index 9b18b62bdc..88e011da0d 100644 --- a/testsuite/ktx/ref/out.txt +++ b/testsuite/ktx/ref/out.txt @@ -112,6 +112,7 @@ Reading ../oiio-images/ktx2/color_grid_zstd_5.ktx2 SHA-1: 4964D0A088333B6C31CD3BA19A2B8CC1CE879DA8 channel list: R, G, B, A textureformat: "Plain Texture" + ktx:KTXwriterScParams: "--zstd 5" ktx:supercompressionscheme: 2 ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" @@ -142,6 +143,7 @@ Reading ../oiio-images/ktx2/color_grid_uastc_zstd_5.ktx2 SHA-1: B631629A3A81C50B2CE264D8CCA4EC4E169DFD6E channel list: R, G, B, A textureformat: "Plain Texture" + ktx:KTXwriterScParams: "--zstd 5" ktx:supercompressionscheme: 2 ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" @@ -151,6 +153,7 @@ Reading ../oiio-images/ktx2/Iron_Bars_001_normal_uastc_zstd_10.ktx2 SHA-1: D428118F2CD6DE70F04C9BD6D092A5A5DCAB195D channel list: R, G, B, A textureformat: "Plain Texture" + ktx:KTXwriterScParams: "--normal-mode --zstd 10" ktx:miplevels: 11 ktx:supercompressionscheme: 2 ktx:version: "2.0" @@ -161,6 +164,7 @@ Reading ../oiio-images/ktx2/ktx_document_uastc_rdo_4_zstd_5.ktx2 SHA-1: 8644BD94DE7A340007640D0063DF80FC2B744849 channel list: R, G, B, A textureformat: "Plain Texture" + ktx:KTXwriterScParams: "--uastc-rdo --uastc-rdo-l 4 --threads 1 --zstd 5" ktx:miplevels: 11 ktx:supercompressionscheme: 2 ktx:version: "2.0" @@ -171,11 +175,39 @@ Reading ../oiio-images/ktx2/cubemap_goldengate_uastc_rdo_4_zstd_5.ktx2 SHA-1: 730E2C2DABA5D591458C91C4D952638BC4EAADD6 channel list: R, G, B, A textureformat: "CubeFace Environment" + ktx:KTXwriterScParams: "--uastc-rdo --uastc-rdo-l 4 --zstd 5" ktx:miplevels: 11 ktx:nfaces: 6 ktx:supercompressionscheme: 2 ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/ktx2/Desk_small_zstd_15.ktx2 +../oiio-images/ktx2/Desk_small_zstd_15.ktx2 : 161 x 218, 3 channel, half ktx + SHA-1: 547517CC24207ECC0A73486D5224697E3FC38AB6 + channel list: R, G, B + textureformat: "Plain Texture" + ktx:KTXwriterScParams: "--zstd 15" + ktx:supercompressionscheme: 2 + ktx:version: "2.0" + oiio:ColorSpace: "lin_rec709_scene" +Reading ../oiio-images/ktx2/Desk_uastc_hdr4x4_zstd_15.ktx2 +../oiio-images/ktx2/Desk_uastc_hdr4x4_zstd_15.ktx2 : 644 x 874, 4 channel, half ktx + SHA-1: BD3B955A6D2F724D306F9AF402BBCA15DCFECC20 + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:KTXwriterScParams: "--uastc-hdr-ultra-quant true --zstd 15" + ktx:supercompressionscheme: 2 + ktx:version: "2.0" + oiio:ColorSpace: "lin_rec709_scene" +Reading ../oiio-images/ktx2/Desk_uastc_hdr6x6i.ktx2 +../oiio-images/ktx2/Desk_uastc_hdr6x6i.ktx2 : 644 x 874, 4 channel, half ktx + SHA-1: 8E0DE70C334CFA0A84AC7FE815F7066DFAF1413B + channel list: R, G, B, A + textureformat: "Plain Texture" + ktx:KTXwriterScParams: "--uastc-hdr-lambda 300" + ktx:supercompressionscheme: 4 + ktx:version: "2.0" + oiio:ColorSpace: "lin_rec709_scene" Reading ../oiio-images/ktx2/kodim17_blze.ktx2 ../oiio-images/ktx2/kodim17_blze.ktx2 : 512 x 768, 4 channel, uint8 ktx SHA-1: 4313438D8827BD8CB040EBD28A27A64EC7B62F68 @@ -235,6 +267,7 @@ Reading ../oiio-images/ktx2/Iron_Bars_001_normal_blze.ktx2 SHA-1: 4B447C9281806EBDC73619B0622F7F3CCC317F05 channel list: R, G, B, A textureformat: "Plain Texture" + ktx:KTXwriterScParams: "--normal-mode" ktx:miplevels: 11 ktx:supercompressionscheme: 1 ktx:version: "2.0" @@ -254,6 +287,7 @@ Reading ../oiio-images/ktx2/alpha_complex_straight.ktx2 SHA-1: 4DC3A14E38526B307226E2DDE083608891EBD27F channel list: R, G, B, A textureformat: "Plain Texture" + ktx:KTXwriterScParams: "--zstd 10" ktx:supercompressionscheme: 2 ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" @@ -273,6 +307,8 @@ Reading ../oiio-images/ktx2/orient_up_metadata.ktx2 ktx:supercompressionscheme: 0 ktx:version: "2.0" oiio:ColorSpace: "srgb_rec709_scene" +Comparing "checker_original.png" and "checker_default.ktx2" +PASS Comparing "checker_original.png" and "checker_uastc.ktx2" PASS Comparing "checker_original.png" and "checker_etc1s.ktx2" diff --git a/testsuite/ktx/run.py b/testsuite/ktx/run.py index c6a797ade6..f649e2e775 100644 --- a/testsuite/ktx/run.py +++ b/testsuite/ktx/run.py @@ -47,7 +47,7 @@ # ASTC-compressed formats "r8g8b8a8_srgb_mip_astc.ktx2", "ktx_app_astc_8x8.ktx2", - # "astc_8x8_unorm_array_7.ktx2", # VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK not yet supported + # "astc_8x8_unorm_array_7.ktx2", # VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK # BCn-compressed formats (to be supported) # "bc3_unorm_array_7.ktx2", @@ -59,11 +59,11 @@ "ktx_document_uastc_rdo_4_zstd_5.ktx2", "cubemap_goldengate_uastc_rdo_4_zstd_5.ktx2", - # HDR formats (not yet supported) - # "Desk_uastc_hdr4x4_zstd_15.ktx2", - # "Desk_uastc_hdr6x6i.ktx2", - # "Desk_astc_hdr6x6.ktx2", - # "Desk_small_zstd_15.ktx2", # VK_FORMAT_R16G16B16_SFLOAT is not yet supported + # HDR formats + "Desk_small_zstd_15.ktx2", # VK_FORMAT_R16G16B16_SFLOAT + "Desk_uastc_hdr4x4_zstd_15.ktx2", + "Desk_uastc_hdr6x6i.ktx2", + # "Desk_astc_hdr6x6.ktx2", # VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK # Basis LZ/ETC1S formats (widely used within KTX2 container format) "kodim17_blze.ktx2", @@ -90,18 +90,19 @@ command += (oiio_app("oiiotool") + " --pattern checker 64x64 4 -d uint8 -o checker_original.png >> out.txt ;\n") +# Default write (with nothing specified) should default to a lossless format +# + supercompression scheme and should match exactly with original input +command += oiiotool ("checker_original.png -o checker_default.ktx2") +command += diff_command ("checker_original.png", "checker_default.ktx2", "--fail 0 --warn 0") + # UASTC write test: check generation of an UASTC-based KTX2 file command += oiiotool ("checker_original.png --attrib ktx:codec uastc -o checker_uastc.ktx2") -command += diff_command ("checker_original.png", "checker_uastc.ktx2", "--fail 0.0005 --warn 0.0005") +command += diff_command ("checker_original.png", "checker_uastc.ktx2") # ETC1S write test: check generation of an ETC1S-based KTX2 file command += oiiotool ("checker_original.png --attrib ktx:codec etc1s -o checker_etc1s.ktx2") -command += diff_command ("checker_original.png", "checker_etc1s.ktx2", "--fail 0.0005 --warn 0.0005") +command += diff_command ("checker_original.png", "checker_etc1s.ktx2") # We do not test read-write of compressed-ktx2 files because any read-write # cycle worsens quality and is absolutely not the intended purpose of ktx usage # within OIIO (or ktx usage in general). - -# Default write (with nothing specified) should default to a loseless format -# + supercompression scheme and should match exactly with original input -