summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorK. S. Ernest (iFire) Lee <ernest.lee@chibifire.com>2022-12-20 10:54:01 -0800
committerRémi Verschelde <rverschelde@gmail.com>2023-01-19 16:27:59 +0100
commit696346f4cc63b9395cc156da0e8d8fb65260c055 (patch)
tree0c52b7b6af5a397ef8a2556356d0b441d236d875
parent14fdd28de9f1f9979cb6cffa4b715a16d406439d (diff)
Add ASTC compression and decompression with Arm astcenc.
Co-authored-by: Gordon A Macpherson <gordon.a.macpherson@gmail.com> Co-authored-by: Rémi Verschelde <rverschelde@gmail.com>
-rw-r--r--COPYRIGHT.txt5
-rw-r--r--modules/astcenc/SCsub55
-rw-r--r--modules/astcenc/config.py6
-rw-r--r--modules/astcenc/image_compress_astcenc.cpp251
-rw-r--r--modules/astcenc/image_compress_astcenc.h39
-rw-r--r--modules/astcenc/register_types.cpp48
-rw-r--r--modules/astcenc/register_types.h39
-rw-r--r--modules/etcpak/image_compress_etcpak.cpp12
-rw-r--r--thirdparty/README.md12
-rw-r--r--thirdparty/astcenc/LICENSE.txt175
-rw-r--r--thirdparty/astcenc/astcenc.h815
-rw-r--r--thirdparty/astcenc/astcenc_averages_and_directions.cpp995
-rw-r--r--thirdparty/astcenc/astcenc_block_sizes.cpp1184
-rw-r--r--thirdparty/astcenc/astcenc_color_quantize.cpp2071
-rw-r--r--thirdparty/astcenc/astcenc_color_unquantize.cpp941
-rw-r--r--thirdparty/astcenc/astcenc_compress_symbolic.cpp1455
-rw-r--r--thirdparty/astcenc/astcenc_compute_variance.cpp472
-rw-r--r--thirdparty/astcenc/astcenc_decompress_symbolic.cpp623
-rw-r--r--thirdparty/astcenc/astcenc_diagnostic_trace.cpp230
-rw-r--r--thirdparty/astcenc/astcenc_diagnostic_trace.h219
-rw-r--r--thirdparty/astcenc/astcenc_entry.cpp1427
-rw-r--r--thirdparty/astcenc/astcenc_find_best_partitioning.cpp780
-rw-r--r--thirdparty/astcenc/astcenc_ideal_endpoints_and_weights.cpp1663
-rw-r--r--thirdparty/astcenc/astcenc_image.cpp558
-rw-r--r--thirdparty/astcenc/astcenc_integer_sequence.cpp739
-rw-r--r--thirdparty/astcenc/astcenc_internal.h2196
-rw-r--r--thirdparty/astcenc/astcenc_internal_entry.h273
-rw-r--r--thirdparty/astcenc/astcenc_mathlib.cpp48
-rw-r--r--thirdparty/astcenc/astcenc_mathlib.h478
-rw-r--r--thirdparty/astcenc/astcenc_mathlib_softfloat.cpp411
-rw-r--r--thirdparty/astcenc/astcenc_partition_tables.cpp481
-rw-r--r--thirdparty/astcenc/astcenc_percentile_tables.cpp1251
-rw-r--r--thirdparty/astcenc/astcenc_pick_best_endpoint_format.cpp1350
-rw-r--r--thirdparty/astcenc/astcenc_platform_isa_detection.cpp166
-rw-r--r--thirdparty/astcenc/astcenc_quantization.cpp904
-rw-r--r--thirdparty/astcenc/astcenc_symbolic_physical.cpp534
-rw-r--r--thirdparty/astcenc/astcenc_vecmathlib.h570
-rw-r--r--thirdparty/astcenc/astcenc_vecmathlib_avx2_8.h1204
-rw-r--r--thirdparty/astcenc/astcenc_vecmathlib_common_4.h423
-rw-r--r--thirdparty/astcenc/astcenc_vecmathlib_neon_4.h1072
-rw-r--r--thirdparty/astcenc/astcenc_vecmathlib_none_4.h1169
-rw-r--r--thirdparty/astcenc/astcenc_vecmathlib_sse_4.h1283
-rw-r--r--thirdparty/astcenc/astcenc_weight_align.cpp479
-rw-r--r--thirdparty/astcenc/astcenc_weight_quant_xfer_tables.cpp147
44 files changed, 29247 insertions, 6 deletions
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index 7fe45657bc..8a966dcb05 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -141,6 +141,11 @@ Comment: AMD FidelityFX Super Resolution
Copyright: 2021, Advanced Micro Devices, Inc.
License: Expat
+Files: ./thirdparty/astcenc/
+Comment: Arm ASTC Encoder
+Copyright: 2011-2023, Arm Limited
+License: Apache-2.0
+
Files: ./thirdparty/basis_universal/
Comment: Basis Universal
Copyright: 2022, Binomial LLC.
diff --git a/modules/astcenc/SCsub b/modules/astcenc/SCsub
new file mode 100644
index 0000000000..0f04f2bc28
--- /dev/null
+++ b/modules/astcenc/SCsub
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_modules")
+
+env_astcenc = env_modules.Clone()
+
+# Thirdparty source files
+
+thirdparty_obj = []
+
+thirdparty_dir = "#thirdparty/astcenc/"
+thirdparty_sources = [
+ "astcenc_averages_and_directions.cpp",
+ "astcenc_block_sizes.cpp",
+ "astcenc_color_quantize.cpp",
+ "astcenc_color_unquantize.cpp",
+ "astcenc_compress_symbolic.cpp",
+ "astcenc_compute_variance.cpp",
+ "astcenc_decompress_symbolic.cpp",
+ "astcenc_diagnostic_trace.cpp",
+ "astcenc_entry.cpp",
+ "astcenc_find_best_partitioning.cpp",
+ "astcenc_ideal_endpoints_and_weights.cpp",
+ "astcenc_image.cpp",
+ "astcenc_integer_sequence.cpp",
+ "astcenc_mathlib.cpp",
+ "astcenc_mathlib_softfloat.cpp",
+ "astcenc_partition_tables.cpp",
+ "astcenc_percentile_tables.cpp",
+ "astcenc_pick_best_endpoint_format.cpp",
+ "astcenc_platform_isa_detection.cpp",
+ "astcenc_quantization.cpp",
+ "astcenc_symbolic_physical.cpp",
+ "astcenc_weight_align.cpp",
+ "astcenc_weight_quant_xfer_tables.cpp",
+]
+thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources]
+
+env_astcenc.Prepend(CPPPATH=[thirdparty_dir])
+
+env_thirdparty = env_astcenc.Clone()
+env_thirdparty.disable_warnings()
+env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources)
+env.modules_sources += thirdparty_obj
+
+# Godot source files
+
+module_obj = []
+
+env_astcenc.add_source_files(module_obj, "*.cpp")
+env.modules_sources += module_obj
+
+# Needed to force rebuilding the module files when the thirdparty library is updated.
+env.Depends(module_obj, thirdparty_obj)
diff --git a/modules/astcenc/config.py b/modules/astcenc/config.py
new file mode 100644
index 0000000000..eb565b85b9
--- /dev/null
+++ b/modules/astcenc/config.py
@@ -0,0 +1,6 @@
+def can_build(env, platform):
+ return env.editor_build
+
+
+def configure(env):
+ pass
diff --git a/modules/astcenc/image_compress_astcenc.cpp b/modules/astcenc/image_compress_astcenc.cpp
new file mode 100644
index 0000000000..ce10201343
--- /dev/null
+++ b/modules/astcenc/image_compress_astcenc.cpp
@@ -0,0 +1,251 @@
+/**************************************************************************/
+/* image_compress_astcenc.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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. */
+/**************************************************************************/
+
+#include "image_compress_astcenc.h"
+
+#include "core/os/os.h"
+#include "core/string/print_string.h"
+
+#include <astcenc.h>
+
+void _compress_astc(Image *r_img, float p_lossy_quality, Image::ASTCFormat p_format) {
+ uint64_t start_time = OS::get_singleton()->get_ticks_msec();
+
+ // TODO: See how to handle lossy quality.
+
+ Image::Format img_format = r_img->get_format();
+ if (img_format >= Image::FORMAT_DXT1) {
+ return; // Do not compress, already compressed.
+ }
+
+ bool is_hdr = false;
+ if ((img_format >= Image::FORMAT_RH) && (img_format <= Image::FORMAT_RGBE9995)) {
+ is_hdr = true;
+ r_img->convert(Image::FORMAT_RGBAF);
+ } else {
+ r_img->convert(Image::FORMAT_RGBA8);
+ }
+
+ // Determine encoder output format from our enum.
+
+ Image::Format target_format = Image::FORMAT_RGBA8;
+ astcenc_profile profile = ASTCENC_PRF_LDR;
+ unsigned int block_x = 4;
+ unsigned int block_y = 4;
+
+ if (p_format == Image::ASTCFormat::ASTC_FORMAT_4x4) {
+ if (is_hdr) {
+ target_format = Image::FORMAT_ASTC_4x4_HDR;
+ profile = ASTCENC_PRF_HDR;
+ } else {
+ target_format = Image::FORMAT_ASTC_4x4;
+ }
+ } else if (p_format == Image::ASTCFormat::ASTC_FORMAT_8x8) {
+ if (is_hdr) {
+ target_format = Image::FORMAT_ASTC_8x8_HDR;
+ profile = ASTCENC_PRF_HDR;
+ } else {
+ target_format = Image::FORMAT_ASTC_8x8;
+ }
+ block_x = 8;
+ block_y = 8;
+ }
+
+ // Compress image data and (if required) mipmaps.
+
+ const bool mipmaps = r_img->has_mipmaps();
+ int width = r_img->get_width();
+ int height = r_img->get_height();
+
+ print_verbose(vformat("astcenc: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : ""));
+
+ // Initialize astcenc.
+
+ astcenc_config config;
+ config.block_x = block_x;
+ config.block_y = block_y;
+ config.profile = profile;
+ const float quality = ASTCENC_PRE_MEDIUM;
+
+ astcenc_error status = astcenc_config_init(profile, block_x, block_y, block_x, quality, 0, &config);
+ ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
+ vformat("astcenc: Configuration initialization failed: %s.", astcenc_get_error_string(status)));
+
+ // Context allocation.
+
+ astcenc_context *context;
+ const unsigned int thread_count = OS::get_singleton()->get_processor_count();
+
+ status = astcenc_context_alloc(&config, thread_count, &context);
+ ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
+ vformat("astcenc: Context allocation failed: %s.", astcenc_get_error_string(status)));
+
+ // Compress image.
+
+ Vector<uint8_t> image_data = r_img->get_data();
+ uint8_t *slices = image_data.ptrw();
+
+ astcenc_image image;
+ image.dim_x = width;
+ image.dim_y = height;
+ image.dim_z = 1;
+ image.data_type = ASTCENC_TYPE_U8;
+ if (is_hdr) {
+ image.data_type = ASTCENC_TYPE_F32;
+ }
+ image.data = reinterpret_cast<void **>(&slices);
+
+ // Compute the number of ASTC blocks in each dimension.
+ unsigned int block_count_x = (width + block_x - 1) / block_x;
+ unsigned int block_count_y = (height + block_y - 1) / block_y;
+ size_t comp_len = block_count_x * block_count_y * 16;
+
+ Vector<uint8_t> compressed_data;
+ compressed_data.resize(comp_len);
+ compressed_data.fill(0);
+
+ const astcenc_swizzle swizzle = {
+ ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A
+ };
+
+ status = astcenc_compress_image(context, &image, &swizzle, compressed_data.ptrw(), comp_len, 0);
+ ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
+ vformat("astcenc: ASTC image compression failed: %s.", astcenc_get_error_string(status)));
+
+ // Replace original image with compressed one.
+
+ r_img->set_data(width, height, mipmaps, target_format, compressed_data);
+
+ print_verbose(vformat("astcenc: Encoding took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time)));
+}
+
+void _decompress_astc(Image *r_img) {
+ uint64_t start_time = OS::get_singleton()->get_ticks_msec();
+
+ // Determine decompression parameters from image format.
+
+ Image::Format img_format = r_img->get_format();
+ bool is_hdr = false;
+ unsigned int block_x = 0;
+ unsigned int block_y = 0;
+ if (img_format == Image::FORMAT_ASTC_4x4) {
+ block_x = 4;
+ block_y = 4;
+ is_hdr = false;
+ } else if (img_format == Image::FORMAT_ASTC_4x4_HDR) {
+ block_x = 4;
+ block_y = 4;
+ is_hdr = true;
+ } else if (img_format == Image::FORMAT_ASTC_8x8) {
+ block_x = 8;
+ block_y = 8;
+ is_hdr = false;
+ } else if (img_format == Image::FORMAT_ASTC_8x8_HDR) {
+ block_x = 8;
+ block_y = 8;
+ is_hdr = true;
+ } else {
+ ERR_FAIL_MSG("astcenc: Cannot decompress Image with a non-ASTC format.");
+ }
+
+ // Initialize astcenc.
+
+ astcenc_profile profile = ASTCENC_PRF_LDR;
+ if (is_hdr) {
+ profile = ASTCENC_PRF_HDR;
+ }
+ astcenc_config config;
+ const float quality = ASTCENC_PRE_MEDIUM;
+
+ astcenc_error status = astcenc_config_init(profile, block_x, block_y, block_x, quality, 0, &config);
+ ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
+ vformat("astcenc: Configuration initialization failed: %s.", astcenc_get_error_string(status)));
+
+ // Context allocation.
+
+ astcenc_context *context = nullptr;
+ const unsigned int thread_count = OS::get_singleton()->get_processor_count();
+
+ status = astcenc_context_alloc(&config, thread_count, &context);
+ ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
+ vformat("astcenc: Context allocation failed: %s.", astcenc_get_error_string(status)));
+
+ // Decompress image.
+
+ const bool mipmaps = r_img->has_mipmaps();
+ int width = r_img->get_width();
+ int height = r_img->get_height();
+
+ astcenc_image image;
+ image.dim_x = width;
+ image.dim_y = height;
+ image.dim_z = 1;
+ image.data_type = ASTCENC_TYPE_U8;
+ Image::Format target_format = Image::FORMAT_RGBA8;
+ if (is_hdr) {
+ target_format = Image::FORMAT_RGBAF;
+ image.data_type = ASTCENC_TYPE_F32;
+ }
+
+ Vector<uint8_t> image_data = r_img->get_data();
+
+ Vector<uint8_t> new_image_data;
+ new_image_data.resize(Image::get_image_data_size(width, height, target_format, false));
+ new_image_data.fill(0);
+ uint8_t *slices = new_image_data.ptrw();
+ image.data = reinterpret_cast<void **>(&slices);
+
+ const astcenc_swizzle swizzle = {
+ ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, ASTCENC_SWZ_A
+ };
+
+ status = astcenc_decompress_image(context, image_data.ptr(), image_data.size(), &image, &swizzle, 0);
+ ERR_FAIL_COND_MSG(status != ASTCENC_SUCCESS,
+ vformat("astcenc: ASTC decompression failed: %s.", astcenc_get_error_string(status)));
+ ERR_FAIL_COND_MSG(image.dim_z > 1,
+ "astcenc: ASTC decompression failed because this is a 3D texture, which is not supported.");
+
+ // Replace original image with compressed one.
+
+ Image::Format image_format = Image::FORMAT_RGBA8;
+ if (image.data_type == ASTCENC_TYPE_F32) {
+ image_format = Image::FORMAT_RGBAF;
+ } else if (image.data_type == ASTCENC_TYPE_U8) {
+ image_format = Image::FORMAT_RGBA8;
+ } else if (image.data_type == ASTCENC_TYPE_F16) {
+ image_format = Image::FORMAT_RGBAH;
+ } else {
+ ERR_FAIL_MSG("astcenc: ASTC decompression failed with an unknown format.");
+ }
+
+ r_img->set_data(image.dim_x, image.dim_y, mipmaps, image_format, new_image_data);
+
+ print_verbose(vformat("astcenc: Decompression took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time)));
+}
diff --git a/modules/astcenc/image_compress_astcenc.h b/modules/astcenc/image_compress_astcenc.h
new file mode 100644
index 0000000000..a197a91e0d
--- /dev/null
+++ b/modules/astcenc/image_compress_astcenc.h
@@ -0,0 +1,39 @@
+/**************************************************************************/
+/* image_compress_astcenc.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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. */
+/**************************************************************************/
+
+#ifndef IMAGE_COMPRESS_ASTCENC_H
+#define IMAGE_COMPRESS_ASTCENC_H
+
+#include "core/io/image.h"
+
+void _compress_astc(Image *r_img, float p_lossy_quality, Image::ASTCFormat p_format);
+void _decompress_astc(Image *r_img);
+
+#endif // IMAGE_COMPRESS_ASTCENC_H
diff --git a/modules/astcenc/register_types.cpp b/modules/astcenc/register_types.cpp
new file mode 100644
index 0000000000..0bb1c3432f
--- /dev/null
+++ b/modules/astcenc/register_types.cpp
@@ -0,0 +1,48 @@
+/**************************************************************************/
+/* register_types.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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. */
+/**************************************************************************/
+
+#include "register_types.h"
+
+#include "image_compress_astcenc.h"
+
+void initialize_astcenc_module(ModuleInitializationLevel p_level) {
+ if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
+ return;
+ }
+
+ Image::_image_compress_astc_func = _compress_astc;
+ Image::_image_decompress_astc = _decompress_astc;
+}
+
+void uninitialize_astcenc_module(ModuleInitializationLevel p_level) {
+ if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
+ return;
+ }
+}
diff --git a/modules/astcenc/register_types.h b/modules/astcenc/register_types.h
new file mode 100644
index 0000000000..636da9ff8b
--- /dev/null
+++ b/modules/astcenc/register_types.h
@@ -0,0 +1,39 @@
+/**************************************************************************/
+/* register_types.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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. */
+/**************************************************************************/
+
+#ifndef ASTCENC_REGISTER_TYPES_H
+#define ASTCENC_REGISTER_TYPES_H
+
+#include "modules/register_module_types.h"
+
+void initialize_astcenc_module(ModuleInitializationLevel p_level);
+void uninitialize_astcenc_module(ModuleInitializationLevel p_level);
+
+#endif // ASTCENC_REGISTER_TYPES_H
diff --git a/modules/etcpak/image_compress_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp
index b5192bd664..a6aeec54cc 100644
--- a/modules/etcpak/image_compress_etcpak.cpp
+++ b/modules/etcpak/image_compress_etcpak.cpp
@@ -33,8 +33,8 @@
#include "core/os/os.h"
#include "core/string/print_string.h"
-#include "thirdparty/etcpak/ProcessDxtc.hpp"
-#include "thirdparty/etcpak/ProcessRGB.hpp"
+#include <ProcessDxtc.hpp>
+#include <ProcessRGB.hpp>
EtcpakType _determine_etc_type(Image::UsedChannels p_channels) {
switch (p_channels) {
@@ -130,7 +130,7 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5) {
target_format = Image::FORMAT_DXT5;
} else {
- ERR_FAIL_MSG("Invalid or unsupported Etcpak compression format.");
+ ERR_FAIL_MSG("Invalid or unsupported etcpak compression format, not ETC or DXT.");
}
// Compress image data and (if required) mipmaps.
@@ -171,7 +171,7 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua
const uint8_t *src_read = r_img->get_data().ptr();
- print_verbose(vformat("ETCPAK: Encoding image size %dx%d to format %s.", width, height, Image::get_format_name(target_format)));
+ print_verbose(vformat("etcpak: Encoding image size %dx%d to format %s%s.", width, height, Image::get_format_name(target_format), mipmaps ? ", with mipmaps" : ""));
int dest_size = Image::get_image_data_size(width, height, target_format, mipmaps);
Vector<uint8_t> dest_data;
@@ -232,12 +232,12 @@ void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_qua
} else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5 || p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) {
CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w);
} else {
- ERR_FAIL_MSG("Invalid or unsupported Etcpak compression format.");
+ ERR_FAIL_MSG("etcpak: Invalid or unsupported compression format.");
}
}
// Replace original image with compressed one.
r_img->set_data(width, height, mipmaps, target_format, dest_data);
- print_verbose(vformat("ETCPAK encode took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time)));
+ print_verbose(vformat("etcpak: Encoding took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time)));
}
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 33f835cbcd..38001b8782 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -17,6 +17,18 @@ Files extracted from upstream source:
- `license.txt`
+## astcenc
+
+- Upstream: https://github.com/ARM-software/astc-encoder
+- Version: 4.3.0 (ec83dda79fcefe07f69cdae7ed980d169bf2c4d4, 2023)
+- License: Apache 2.0
+
+Files extracted from upstream source:
+
+- `astcenc_*` and `astcenc.h` files from `Source`
+- `LICENSE.txt`
+
+
## basis_universal
- Upstream: https://github.com/BinomialLLC/basis_universal
diff --git a/thirdparty/astcenc/LICENSE.txt b/thirdparty/astcenc/LICENSE.txt
new file mode 100644
index 0000000000..b82735a310
--- /dev/null
+++ b/thirdparty/astcenc/LICENSE.txt
@@ -0,0 +1,175 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
diff --git a/thirdparty/astcenc/astcenc.h b/thirdparty/astcenc/astcenc.h
new file mode 100644
index 0000000000..70ae783373
--- /dev/null
+++ b/thirdparty/astcenc/astcenc.h
@@ -0,0 +1,815 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2020-2023 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief The core astcenc codec library interface.
+ *
+ * This interface is the entry point to the core astcenc codec. It aims to be easy to use for
+ * non-experts, but also to allow experts to have fine control over the compressor heuristics if
+ * needed. The core codec only handles compression and decompression, transferring all inputs and
+ * outputs via memory buffers. To catch obvious input/output buffer sizing issues, which can cause
+ * security and stability problems, all transfer buffers are explicitly sized.
+ *
+ * While the aim is that we keep this interface mostly stable, it should be viewed as a mutable
+ * interface tied to a specific source version. We are not trying to maintain backwards
+ * compatibility across codec versions.
+ *
+ * The API state management is based around an explicit context object, which is the context for all
+ * allocated memory resources needed to compress and decompress a single image. A context can be
+ * used to sequentially compress multiple images using the same configuration, allowing setup
+ * overheads to be amortized over multiple images, which is particularly important when images are
+ * small.
+ *
+ * Multi-threading can be used two ways.
+ *
+ * * An application wishing to process multiple images in parallel can allocate multiple
+ * contexts and assign each context to a thread.
+ * * An application wishing to process a single image in using multiple threads can configure
+ * contexts for multi-threaded use, and invoke astcenc_compress/decompress() once per thread
+ * for faster processing. The caller is responsible for creating the worker threads, and
+ * synchronizing between images.
+ *
+ * Threading
+ * =========
+ *
+ * In pseudo-code, the usage for manual user threading looks like this:
+ *
+ * // Configure the compressor run
+ * astcenc_config my_config;
+ * astcenc_config_init(..., &my_config);
+ *
+ * // Power users can tweak <my_config> settings here ...
+ *
+ * // Allocate working state given config and thread_count
+ * astcenc_context* my_context;
+ * astcenc_context_alloc(&my_config, thread_count, &my_context);
+ *
+ * // Compress each image using these config settings
+ * foreach image:
+ * // For each thread in the thread pool
+ * for i in range(0, thread_count):
+ * astcenc_compress_image(my_context, &my_input, my_output, i);
+ *
+ * astcenc_compress_reset(my_context);
+ *
+ * // Clean up
+ * astcenc_context_free(my_context);
+ *
+ * Images
+ * ======
+ *
+ * The codec supports compressing single images, which can be either 2D images or volumetric 3D
+ * images. Calling code is responsible for any handling of aggregate types, such as mipmap chains,
+ * texture arrays, or sliced 3D textures.
+ *
+ * Images are passed in as an astcenc_image structure. Inputs can be either 8-bit unorm, 16-bit
+ * half-float, or 32-bit float, as indicated by the data_type field.
+ *
+ * Images can be any dimension; there is no requirement to be a multiple of the ASTC block size.
+ *
+ * Data is always passed in as 4 color components, and accessed as an array of 2D image slices. Data
+ * within an image slice is always tightly packed without padding. Addressing looks like this:
+ *
+ * data[z_coord][y_coord * x_dim * 4 + x_coord * 4 ] // Red
+ * data[z_coord][y_coord * x_dim * 4 + x_coord * 4 + 1] // Green
+ * data[z_coord][y_coord * x_dim * 4 + x_coord * 4 + 2] // Blue
+ * data[z_coord][y_coord * x_dim * 4 + x_coord * 4 + 3] // Alpha
+ *
+ * Common compressor usage
+ * =======================
+ *
+ * One of the most important things for coding image quality is to align the input data component
+ * count with the ASTC color endpoint mode. This avoids wasting bits encoding components you don't
+ * actually need in the endpoint colors.
+ *
+ * | Input data | Encoding swizzle | Sampling swizzle |
+ * | ------------ | ---------------- | ---------------- |
+ * | 1 component | RRR1 | .[rgb] |
+ * | 2 components | RRRG | .[rgb]a |
+ * | 3 components | RGB1 | .rgb |
+ * | 4 components | RGBA | .rgba |
+ *
+ * The 1 and 2 component modes recommend sampling from "g" to recover the luminance value as this
+ * provide best compatibility with other texture formats where the green component may be stored at
+ * higher precision than the others, such as RGB565. For ASTC any of the RGB components can be used;
+ * the luminance endpoint component will be returned for all three.
+ *
+ * When using the normal map compression mode ASTC will store normals as a two component X+Y map.
+ * Input images must contain unit-length normalized and should be passed in using a two component
+ * swizzle. The astcenc command line tool defaults to an RRRG swizzle, but some developers prefer
+ * to use GGGR for compatability with BC5n which will work just as well. The Z component can be
+ * recovered programmatically in shader code, using knowledge that the vector is unit length and
+ * that Z must be positive for a tangent-space normal map.
+ *
+ * Decompress-only usage
+ * =====================
+ *
+ * For some use cases it is useful to have a cut-down context and/or library which supports
+ * decompression but not compression.
+ *
+ * A context can be made decompress-only using the ASTCENC_FLG_DECOMPRESS_ONLY flag when the context
+ * is allocated. These contexts have lower dynamic memory footprint than a full context.
+ *
+ * The entire library can be made decompress-only by building the files with the define
+ * ASTCENC_DECOMPRESS_ONLY set. In this build the context will be smaller, and the library will
+ * exclude the functionality which is only needed for compression. This reduces the binary size by
+ * ~180KB. For these builds contexts must be created with the ASTCENC_FLG_DECOMPRESS_ONLY flag.
+ *
+ * Note that context structures returned by a library built as decompress-only are incompatible with
+ * a library built with compression included, and visa versa, as they have different sizes and
+ * memory layout.
+ *
+ * Self-decompress-only usage
+ * ==========================
+ *
+ * ASTC is a complex format with a large search space. The parts of this search space that are
+ * searched is determined by heuristics that are, in part, tied to the quality level used when
+ * creating the context.
+ *
+ * A normal context is capable of decompressing any ASTC texture, including those generated by other
+ * compressors with unknown heuristics. This is the most flexible implementation, but forces the
+ * data tables used by the codec to include entries that are not needed during compression. This
+ * can slow down context creation by a significant amount, especially for the faster compression
+ * modes where few data table entries are actually used. To optimize this use case the context can
+ * be created with the ASTCENC_FLG_SELF_DECOMPRESS_ONLY flag. This tells the compressor that it will
+ * only be asked to decompress images that it compressed itself, allowing the data tables to
+ * exclude entries that are not needed by the current compression configuration. This reduces the
+ * size of the context data tables in memory and improves context creation performance. Note that,
+ * as of the 3.6 release, this flag no longer affects compression performance.
+ *
+ * Using this flag while attempting to decompress an valid image which was created by another
+ * compressor, or even another astcenc compressor version or configuration, may result in blocks
+ * returning as solid magenta or NaN value error blocks.
+ */
+
+#ifndef ASTCENC_INCLUDED
+#define ASTCENC_INCLUDED
+
+#include <cstddef>
+#include <cstdint>
+
+#if defined(ASTCENC_DYNAMIC_LIBRARY)
+ #if defined(_MSC_VER)
+ #define ASTCENC_PUBLIC extern "C" __declspec(dllexport)
+ #else
+ #define ASTCENC_PUBLIC extern "C" __attribute__ ((visibility ("default")))
+ #endif
+#else
+ #define ASTCENC_PUBLIC
+#endif
+
+/* ============================================================================
+ Data declarations
+============================================================================ */
+
+/**
+ * @brief An opaque structure; see astcenc_internal.h for definition.
+ */
+struct astcenc_context;
+
+/**
+ * @brief A codec API error code.
+ */
+enum astcenc_error {
+ /** @brief The call was successful. */
+ ASTCENC_SUCCESS = 0,
+ /** @brief The call failed due to low memory, or undersized I/O buffers. */
+ ASTCENC_ERR_OUT_OF_MEM,
+ /** @brief The call failed due to the build using fast math. */
+ ASTCENC_ERR_BAD_CPU_FLOAT,
+ /** @brief The call failed due to the build using an unsupported ISA. */
+ ASTCENC_ERR_BAD_CPU_ISA,
+ /** @brief The call failed due to an out-of-spec parameter. */
+ ASTCENC_ERR_BAD_PARAM,
+ /** @brief The call failed due to an out-of-spec block size. */
+ ASTCENC_ERR_BAD_BLOCK_SIZE,
+ /** @brief The call failed due to an out-of-spec color profile. */
+ ASTCENC_ERR_BAD_PROFILE,
+ /** @brief The call failed due to an out-of-spec quality value. */
+ ASTCENC_ERR_BAD_QUALITY,
+ /** @brief The call failed due to an out-of-spec component swizzle. */
+ ASTCENC_ERR_BAD_SWIZZLE,
+ /** @brief The call failed due to an out-of-spec flag set. */
+ ASTCENC_ERR_BAD_FLAGS,
+ /** @brief The call failed due to the context not supporting the operation. */
+ ASTCENC_ERR_BAD_CONTEXT,
+ /** @brief The call failed due to unimplemented functionality. */
+ ASTCENC_ERR_NOT_IMPLEMENTED,
+#if defined(ASTCENC_DIAGNOSTICS)
+ /** @brief The call failed due to an issue with diagnostic tracing. */
+ ASTCENC_ERR_DTRACE_FAILURE,
+#endif
+};
+
+/**
+ * @brief A codec color profile.
+ */
+enum astcenc_profile {
+ /** @brief The LDR sRGB color profile. */
+ ASTCENC_PRF_LDR_SRGB = 0,
+ /** @brief The LDR linear color profile. */
+ ASTCENC_PRF_LDR,
+ /** @brief The HDR RGB with LDR alpha color profile. */
+ ASTCENC_PRF_HDR_RGB_LDR_A,
+ /** @brief The HDR RGBA color profile. */
+ ASTCENC_PRF_HDR
+};
+
+/** @brief The fastest, lowest quality, search preset. */
+static const float ASTCENC_PRE_FASTEST = 0.0f;
+
+/** @brief The fast search preset. */
+static const float ASTCENC_PRE_FAST = 10.0f;
+
+/** @brief The medium quality search preset. */
+static const float ASTCENC_PRE_MEDIUM = 60.0f;
+
+/** @brief The thorough quality search preset. */
+static const float ASTCENC_PRE_THOROUGH = 98.0f;
+
+/** @brief The thorough quality search preset. */
+static const float ASTCENC_PRE_VERYTHOROUGH = 99.0f;
+
+/** @brief The exhaustive, highest quality, search preset. */
+static const float ASTCENC_PRE_EXHAUSTIVE = 100.0f;
+
+/**
+ * @brief A codec component swizzle selector.
+ */
+enum astcenc_swz
+{
+ /** @brief Select the red component. */
+ ASTCENC_SWZ_R = 0,
+ /** @brief Select the green component. */
+ ASTCENC_SWZ_G = 1,
+ /** @brief Select the blue component. */
+ ASTCENC_SWZ_B = 2,
+ /** @brief Select the alpha component. */
+ ASTCENC_SWZ_A = 3,
+ /** @brief Use a constant zero component. */
+ ASTCENC_SWZ_0 = 4,
+ /** @brief Use a constant one component. */
+ ASTCENC_SWZ_1 = 5,
+ /** @brief Use a reconstructed normal vector Z component. */
+ ASTCENC_SWZ_Z = 6
+};
+
+/**
+ * @brief A texel component swizzle.
+ */
+struct astcenc_swizzle
+{
+ /** @brief The red component selector. */
+ astcenc_swz r;
+ /** @brief The green component selector. */
+ astcenc_swz g;
+ /** @brief The blue component selector. */
+ astcenc_swz b;
+ /** @brief The alpha component selector. */
+ astcenc_swz a;
+};
+
+/**
+ * @brief A texel component data format.
+ */
+enum astcenc_type
+{
+ /** @brief Unorm 8-bit data per component. */
+ ASTCENC_TYPE_U8 = 0,
+ /** @brief 16-bit float per component. */
+ ASTCENC_TYPE_F16 = 1,
+ /** @brief 32-bit float per component. */
+ ASTCENC_TYPE_F32 = 2
+};
+
+/**
+ * @brief Enable normal map compression.
+ *
+ * Input data will be treated a two component normal map, storing X and Y, and the codec will
+ * optimize for angular error rather than simple linear PSNR. In this mode the input swizzle should
+ * be e.g. rrrg (the default ordering for ASTC normals on the command line) or gggr (the ordering
+ * used by BC5n).
+ */
+static const unsigned int ASTCENC_FLG_MAP_NORMAL = 1 << 0;
+
+/**
+ * @brief Enable alpha weighting.
+ *
+ * The input alpha value is used for transparency, so errors in the RGB components are weighted by
+ * the transparency level. This allows the codec to more accurately encode the alpha value in areas
+ * where the color value is less significant.
+ */
+static const unsigned int ASTCENC_FLG_USE_ALPHA_WEIGHT = 1 << 2;
+
+/**
+ * @brief Enable perceptual error metrics.
+ *
+ * This mode enables perceptual compression mode, which will optimize for perceptual error rather
+ * than best PSNR. Only some input modes support perceptual error metrics.
+ */
+static const unsigned int ASTCENC_FLG_USE_PERCEPTUAL = 1 << 3;
+
+/**
+ * @brief Create a decompression-only context.
+ *
+ * This mode disables support for compression. This enables context allocation to skip some
+ * transient buffer allocation, resulting in lower memory usage.
+ */
+static const unsigned int ASTCENC_FLG_DECOMPRESS_ONLY = 1 << 4;
+
+/**
+ * @brief Create a self-decompression context.
+ *
+ * This mode configures the compressor so that it is only guaranteed to be able to decompress images
+ * that were actually created using the current context. This is the common case for compression use
+ * cases, and setting this flag enables additional optimizations, but does mean that the context
+ * cannot reliably decompress arbitrary ASTC images.
+ */
+static const unsigned int ASTCENC_FLG_SELF_DECOMPRESS_ONLY = 1 << 5;
+
+/**
+ * @brief Enable RGBM map compression.
+ *
+ * Input data will be treated as HDR data that has been stored in an LDR RGBM-encoded wrapper
+ * format. Data must be preprocessed by the user to be in LDR RGBM format before calling the
+ * compression function, this flag is only used to control the use of RGBM-specific heuristics and
+ * error metrics.
+ *
+ * IMPORTANT: The ASTC format is prone to bad failure modes with unconstrained RGBM data; very small
+ * M values can round to zero due to quantization and result in black or white pixels. It is highly
+ * recommended that the minimum value of M used in the encoding is kept above a lower threshold (try
+ * 16 or 32). Applying this threshold reduces the number of very dark colors that can be
+ * represented, but is still higher precision than 8-bit LDR.
+ *
+ * When this flag is set the value of @c rgbm_m_scale in the context must be set to the RGBM scale
+ * factor used during reconstruction. This defaults to 5 when in RGBM mode.
+ *
+ * It is recommended that the value of @c cw_a_weight is set to twice the value of the multiplier
+ * scale, ensuring that the M value is accurately encoded. This defaults to 10 when in RGBM mode,
+ * matching the default scale factor.
+ */
+static const unsigned int ASTCENC_FLG_MAP_RGBM = 1 << 6;
+
+/**
+ * @brief The bit mask of all valid flags.
+ */
+static const unsigned int ASTCENC_ALL_FLAGS =
+ ASTCENC_FLG_MAP_NORMAL |
+ ASTCENC_FLG_MAP_RGBM |
+ ASTCENC_FLG_USE_ALPHA_WEIGHT |
+ ASTCENC_FLG_USE_PERCEPTUAL |
+ ASTCENC_FLG_DECOMPRESS_ONLY |
+ ASTCENC_FLG_SELF_DECOMPRESS_ONLY;
+
+/**
+ * @brief The config structure.
+ *
+ * This structure will initially be populated by a call to astcenc_config_init, but power users may
+ * modify it before calling astcenc_context_alloc. See astcenccli_toplevel_help.cpp for full user
+ * documentation of the power-user settings.
+ *
+ * Note for any settings which are associated with a specific color component, the value in the
+ * config applies to the component that exists after any compression data swizzle is applied.
+ */
+struct astcenc_config
+{
+ /** @brief The color profile. */
+ astcenc_profile profile;
+
+ /** @brief The set of set flags. */
+ unsigned int flags;
+
+ /** @brief The ASTC block size X dimension. */
+ unsigned int block_x;
+
+ /** @brief The ASTC block size Y dimension. */
+ unsigned int block_y;
+
+ /** @brief The ASTC block size Z dimension. */
+ unsigned int block_z;
+
+ /** @brief The red component weight scale for error weighting (-cw). */
+ float cw_r_weight;
+
+ /** @brief The green component weight scale for error weighting (-cw). */
+ float cw_g_weight;
+
+ /** @brief The blue component weight scale for error weighting (-cw). */
+ float cw_b_weight;
+
+ /** @brief The alpha component weight scale for error weighting (-cw). */
+ float cw_a_weight;
+
+ /**
+ * @brief The radius for any alpha-weight scaling (-a).
+ *
+ * It is recommended that this is set to 1 when using FLG_USE_ALPHA_WEIGHT on a texture that
+ * will be sampled using linear texture filtering to minimize color bleed out of transparent
+ * texels that are adjacent to non-transparent texels.
+ */
+ unsigned int a_scale_radius;
+
+ /** @brief The RGBM scale factor for the shared multiplier (-rgbm). */
+ float rgbm_m_scale;
+
+ /**
+ * @brief The maximum number of partitions searched (-partitioncountlimit).
+ *
+ * Valid values are between 1 and 4.
+ */
+ unsigned int tune_partition_count_limit;
+
+ /**
+ * @brief The maximum number of partitions searched (-2partitionindexlimit).
+ *
+ * Valid values are between 1 and 1024.
+ */
+ unsigned int tune_2partition_index_limit;
+
+ /**
+ * @brief The maximum number of partitions searched (-3partitionindexlimit).
+ *
+ * Valid values are between 1 and 1024.
+ */
+ unsigned int tune_3partition_index_limit;
+
+ /**
+ * @brief The maximum number of partitions searched (-4partitionindexlimit).
+ *
+ * Valid values are between 1 and 1024.
+ */
+ unsigned int tune_4partition_index_limit;
+
+ /**
+ * @brief The maximum centile for block modes searched (-blockmodelimit).
+ *
+ * Valid values are between 1 and 100.
+ */
+ unsigned int tune_block_mode_limit;
+
+ /**
+ * @brief The maximum iterative refinements applied (-refinementlimit).
+ *
+ * Valid values are between 1 and N; there is no technical upper limit
+ * but little benefit is expected after N=4.
+ */
+ unsigned int tune_refinement_limit;
+
+ /**
+ * @brief The number of trial candidates per mode search (-candidatelimit).
+ *
+ * Valid values are between 1 and TUNE_MAX_TRIAL_CANDIDATES (default 4).
+ */
+ unsigned int tune_candidate_limit;
+
+ /**
+ * @brief The number of trial partitionings per search (-2partitioncandidatelimit).
+ *
+ * Valid values are between 1 and TUNE_MAX_PARTITIONING_CANDIDATES.
+ */
+ unsigned int tune_2partitioning_candidate_limit;
+
+ /**
+ * @brief The number of trial partitionings per search (-3partitioncandidatelimit).
+ *
+ * Valid values are between 1 and TUNE_MAX_PARTITIONING_CANDIDATES.
+ */
+ unsigned int tune_3partitioning_candidate_limit;
+
+ /**
+ * @brief The number of trial partitionings per search (-4partitioncandidatelimit).
+ *
+ * Valid values are between 1 and TUNE_MAX_PARTITIONING_CANDIDATES.
+ */
+ unsigned int tune_4partitioning_candidate_limit;
+
+ /**
+ * @brief The dB threshold for stopping block search (-dblimit).
+ *
+ * This option is ineffective for HDR textures.
+ */
+ float tune_db_limit;
+
+ /**
+ * @brief The amount of MSE overshoot needed to early-out trials.
+ *
+ * The first early-out is for 1 partition, 1 plane trials, where we try a minimal encode using
+ * the high probability block modes. This can short-cut compression for simple blocks.
+ *
+ * The second early-out is for refinement trials, where we can exit refinement once quality is
+ * reached.
+ */
+ float tune_mse_overshoot;
+
+ /**
+ * @brief The threshold for skipping 3.1/4.1 trials (-2partitionlimitfactor).
+ *
+ * This option is further scaled for normal maps, so it skips less often.
+ */
+ float tune_2_partition_early_out_limit_factor;
+
+ /**
+ * @brief The threshold for skipping 4.1 trials (-3partitionlimitfactor).
+ *
+ * This option is further scaled for normal maps, so it skips less often.
+ */
+ float tune_3_partition_early_out_limit_factor;
+
+ /**
+ * @brief The threshold for skipping two weight planes (-2planelimitcorrelation).
+ *
+ * This option is ineffective for normal maps.
+ */
+ float tune_2_plane_early_out_limit_correlation;
+
+#if defined(ASTCENC_DIAGNOSTICS)
+ /**
+ * @brief The path to save the diagnostic trace data to.
+ *
+ * This option is not part of the public API, and requires special builds
+ * of the library.
+ */
+ const char* trace_file_path;
+#endif
+};
+
+/**
+ * @brief An uncompressed 2D or 3D image.
+ *
+ * 3D image are passed in as an array of 2D slices. Each slice has identical
+ * size and color format.
+ */
+struct astcenc_image
+{
+ /** @brief The X dimension of the image, in texels. */
+ unsigned int dim_x;
+
+ /** @brief The Y dimension of the image, in texels. */
+ unsigned int dim_y;
+
+ /** @brief The Z dimension of the image, in texels. */
+ unsigned int dim_z;
+
+ /** @brief The data type per component. */
+ astcenc_type data_type;
+
+ /** @brief The array of 2D slices, of length @c dim_z. */
+ void** data;
+};
+
+/**
+ * @brief A block encoding metadata query result.
+ *
+ * If the block is an error block or a constant color block or an error block all fields other than
+ * the profile, block dimensions, and error/constant indicator will be zero.
+ */
+struct astcenc_block_info
+{
+ /** @brief The block encoding color profile. */
+ astcenc_profile profile;
+
+ /** @brief The number of texels in the X dimension. */
+ unsigned int block_x;
+
+ /** @brief The number of texels in the Y dimension. */
+ unsigned int block_y;
+
+ /** @brief The number of texel in the Z dimension. */
+ unsigned int block_z;
+
+ /** @brief The number of texels in the block. */
+ unsigned int texel_count;
+
+ /** @brief True if this block is an error block. */
+ bool is_error_block;
+
+ /** @brief True if this block is a constant color block. */
+ bool is_constant_block;
+
+ /** @brief True if this block is an HDR block. */
+ bool is_hdr_block;
+
+ /** @brief True if this block uses two weight planes. */
+ bool is_dual_plane_block;
+
+ /** @brief The number of partitions if not constant color. */
+ unsigned int partition_count;
+
+ /** @brief The partition index if 2 - 4 partitions used. */
+ unsigned int partition_index;
+
+ /** @brief The component index of the second plane if dual plane. */
+ unsigned int dual_plane_component;
+
+ /** @brief The color endpoint encoding mode for each partition. */
+ unsigned int color_endpoint_modes[4];
+
+ /** @brief The number of color endpoint quantization levels. */
+ unsigned int color_level_count;
+
+ /** @brief The number of weight quantization levels. */
+ unsigned int weight_level_count;
+
+ /** @brief The number of weights in the X dimension. */
+ unsigned int weight_x;
+
+ /** @brief The number of weights in the Y dimension. */
+ unsigned int weight_y;
+
+ /** @brief The number of weights in the Z dimension. */
+ unsigned int weight_z;
+
+ /** @brief The unpacked color endpoints for each partition. */
+ float color_endpoints[4][2][4];
+
+ /** @brief The per-texel interpolation weights for the block. */
+ float weight_values_plane1[216];
+
+ /** @brief The per-texel interpolation weights for the block. */
+ float weight_values_plane2[216];
+
+ /** @brief The per-texel partition assignments for the block. */
+ uint8_t partition_assignment[216];
+};
+
+/**
+ * Populate a codec config based on default settings.
+ *
+ * Power users can edit the returned config struct to fine tune before allocating the context.
+ *
+ * @param profile Color profile.
+ * @param block_x ASTC block size X dimension.
+ * @param block_y ASTC block size Y dimension.
+ * @param block_z ASTC block size Z dimension.
+ * @param quality Search quality preset / effort level. Either an
+ * @c ASTCENC_PRE_* value, or a effort level between 0
+ * and 100. Performance is not linear between 0 and 100.
+
+ * @param flags A valid set of @c ASTCENC_FLG_* flag bits.
+ * @param[out] config Output config struct to populate.
+ *
+ * @return @c ASTCENC_SUCCESS on success, or an error if the inputs are invalid
+ * either individually, or in combination.
+ */
+ASTCENC_PUBLIC astcenc_error astcenc_config_init(
+ astcenc_profile profile,
+ unsigned int block_x,
+ unsigned int block_y,
+ unsigned int block_z,
+ float quality,
+ unsigned int flags,
+ astcenc_config* config);
+
+/**
+ * @brief Allocate a new codec context based on a config.
+ *
+ * This function allocates all of the memory resources and threads needed by the codec. This can be
+ * slow, so it is recommended that contexts are reused to serially compress or decompress multiple
+ * images to amortize setup cost.
+ *
+ * Contexts can be allocated to support only decompression using the @c ASTCENC_FLG_DECOMPRESS_ONLY
+ * flag when creating the configuration. The compression functions will fail if invoked. For a
+ * decompress-only library build the @c ASTCENC_FLG_DECOMPRESS_ONLY flag must be set when creating
+ * any context.
+ *
+ * @param[in] config Codec config.
+ * @param thread_count Thread count to configure for.
+ * @param[out] context Location to store an opaque context pointer.
+ *
+ * @return @c ASTCENC_SUCCESS on success, or an error if context creation failed.
+ */
+ASTCENC_PUBLIC astcenc_error astcenc_context_alloc(
+ const astcenc_config* config,
+ unsigned int thread_count,
+ astcenc_context** context);
+
+/**
+ * @brief Compress an image.
+ *
+ * A single context can only compress or decompress a single image at a time.
+ *
+ * For a context configured for multi-threading, any set of the N threads can call this function.
+ * Work will be dynamically scheduled across the threads available. Each thread must have a unique
+ * @c thread_index.
+ *
+ * @param context Codec context.
+ * @param[in,out] image An input image, in 2D slices.
+ * @param swizzle Compression data swizzle, applied before compression.
+ * @param[out] data_out Pointer to output data array.
+ * @param data_len Length of the output data array.
+ * @param thread_index Thread index [0..N-1] of calling thread.
+ *
+ * @return @c ASTCENC_SUCCESS on success, or an error if compression failed.
+ */
+ASTCENC_PUBLIC astcenc_error astcenc_compress_image(
+ astcenc_context* context,
+ astcenc_image* image,
+ const astcenc_swizzle* swizzle,
+ uint8_t* data_out,
+ size_t data_len,
+ unsigned int thread_index);
+
+/**
+ * @brief Reset the codec state for a new compression.
+ *
+ * The caller is responsible for synchronizing threads in the worker thread pool. This function must
+ * only be called when all threads have exited the @c astcenc_compress_image() function for image N,
+ * but before any thread enters it for image N + 1.
+ *
+ * Calling this is not required (but won't hurt), if the context is created for single threaded use.
+ *
+ * @param context Codec context.
+ *
+ * @return @c ASTCENC_SUCCESS on success, or an error if reset failed.
+ */
+ASTCENC_PUBLIC astcenc_error astcenc_compress_reset(
+ astcenc_context* context);
+
+/**
+ * @brief Decompress an image.
+ *
+ * @param context Codec context.
+ * @param[in] data Pointer to compressed data.
+ * @param data_len Length of the compressed data, in bytes.
+ * @param[in,out] image_out Output image.
+ * @param swizzle Decompression data swizzle, applied after decompression.
+ * @param thread_index Thread index [0..N-1] of calling thread.
+ *
+ * @return @c ASTCENC_SUCCESS on success, or an error if decompression failed.
+ */
+ASTCENC_PUBLIC astcenc_error astcenc_decompress_image(
+ astcenc_context* context,
+ const uint8_t* data,
+ size_t data_len,
+ astcenc_image* image_out,
+ const astcenc_swizzle* swizzle,
+ unsigned int thread_index);
+
+/**
+ * @brief Reset the codec state for a new decompression.
+ *
+ * The caller is responsible for synchronizing threads in the worker thread pool. This function must
+ * only be called when all threads have exited the @c astcenc_decompress_image() function for image
+ * N, but before any thread enters it for image N + 1.
+ *
+ * Calling this is not required (but won't hurt), if the context is created for single threaded use.
+ *
+ * @param context Codec context.
+ *
+ * @return @c ASTCENC_SUCCESS on success, or an error if reset failed.
+ */
+ASTCENC_PUBLIC astcenc_error astcenc_decompress_reset(
+ astcenc_context* context);
+
+/**
+ * Free the compressor context.
+ *
+ * @param context The codec context.
+ */
+ASTCENC_PUBLIC void astcenc_context_free(
+ astcenc_context* context);
+
+/**
+ * @brief Provide a high level summary of a block's encoding.
+ *
+ * This feature is primarily useful for codec developers but may be useful for developers building
+ * advanced content packaging pipelines.
+ *
+ * @param context Codec context.
+ * @param data One block of compressed ASTC data.
+ * @param info The output info structure to populate.
+ *
+ * @return @c ASTCENC_SUCCESS if the block was decoded, or an error otherwise. Note that this
+ * function will return success even if the block itself was an error block encoding, as the
+ * decode was correctly handled.
+ */
+ASTCENC_PUBLIC astcenc_error astcenc_get_block_info(
+ astcenc_context* context,
+ const uint8_t data[16],
+ astcenc_block_info* info);
+
+/**
+ * @brief Get a printable string for specific status code.
+ *
+ * @param status The status value.
+ *
+ * @return A human readable nul-terminated string.
+ */
+ASTCENC_PUBLIC const char* astcenc_get_error_string(
+ astcenc_error status);
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_averages_and_directions.cpp b/thirdparty/astcenc/astcenc_averages_and_directions.cpp
new file mode 100644
index 0000000000..d1f003844a
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_averages_and_directions.cpp
@@ -0,0 +1,995 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Functions for finding dominant direction of a set of colors.
+ */
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+
+#include "astcenc_internal.h"
+
+#include <cassert>
+
+/**
+ * @brief Compute the average RGB color of each partition.
+ *
+ * The algorithm here uses a vectorized sequential scan and per-partition
+ * color accumulators, using select() to mask texel lanes in other partitions.
+ *
+ * We only accumulate sums for N-1 partitions during the scan; the value for
+ * the last partition can be computed given that we know the block-wide average
+ * already.
+ *
+ * Because of this we could reduce the loop iteration count so it "just" spans
+ * the max texel index needed for the N-1 partitions, which could need fewer
+ * iterations than the full block texel count. However, this makes the loop
+ * count erratic and causes more branch mispredictions so is a net loss.
+ *
+ * @param pi The partitioning to use.
+ * @param blk The block data to process.
+ * @param[out] averages The output averages. Unused partition indices will
+ * not be initialized, and lane<3> will be zero.
+ */
+static void compute_partition_averages_rgb(
+ const partition_info& pi,
+ const image_block& blk,
+ vfloat4 averages[BLOCK_MAX_PARTITIONS]
+) {
+ unsigned int partition_count = pi.partition_count;
+ unsigned int texel_count = blk.texel_count;
+ promise(texel_count > 0);
+
+ // For 1 partition just use the precomputed mean
+ if (partition_count == 1)
+ {
+ averages[0] = blk.data_mean.swz<0, 1, 2>();
+ }
+ // For 2 partitions scan results for partition 0, compute partition 1
+ else if (partition_count == 2)
+ {
+ vfloatacc pp_avg_rgb[3] {};
+
+ vint lane_id = vint::lane_id();
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vint texel_partition(pi.partition_of_texel + i);
+
+ vmask lane_mask = lane_id < vint(texel_count);
+ lane_id += vint(ASTCENC_SIMD_WIDTH);
+
+ vmask p0_mask = lane_mask & (texel_partition == vint(0));
+
+ vfloat data_r = loada(blk.data_r + i);
+ haccumulate(pp_avg_rgb[0], data_r, p0_mask);
+
+ vfloat data_g = loada(blk.data_g + i);
+ haccumulate(pp_avg_rgb[1], data_g, p0_mask);
+
+ vfloat data_b = loada(blk.data_b + i);
+ haccumulate(pp_avg_rgb[2], data_b, p0_mask);
+ }
+
+ vfloat4 block_total = blk.data_mean.swz<0, 1, 2>() * static_cast<float>(blk.texel_count);
+
+ vfloat4 p0_total = vfloat3(hadd_s(pp_avg_rgb[0]),
+ hadd_s(pp_avg_rgb[1]),
+ hadd_s(pp_avg_rgb[2]));
+
+ vfloat4 p1_total = block_total - p0_total;
+
+ averages[0] = p0_total / static_cast<float>(pi.partition_texel_count[0]);
+ averages[1] = p1_total / static_cast<float>(pi.partition_texel_count[1]);
+ }
+ // For 3 partitions scan results for partition 0/1, compute partition 2
+ else if (partition_count == 3)
+ {
+ vfloatacc pp_avg_rgb[2][3] {};
+
+ vint lane_id = vint::lane_id();
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vint texel_partition(pi.partition_of_texel + i);
+
+ vmask lane_mask = lane_id < vint(texel_count);
+ lane_id += vint(ASTCENC_SIMD_WIDTH);
+
+ vmask p0_mask = lane_mask & (texel_partition == vint(0));
+ vmask p1_mask = lane_mask & (texel_partition == vint(1));
+
+ vfloat data_r = loada(blk.data_r + i);
+ haccumulate(pp_avg_rgb[0][0], data_r, p0_mask);
+ haccumulate(pp_avg_rgb[1][0], data_r, p1_mask);
+
+ vfloat data_g = loada(blk.data_g + i);
+ haccumulate(pp_avg_rgb[0][1], data_g, p0_mask);
+ haccumulate(pp_avg_rgb[1][1], data_g, p1_mask);
+
+ vfloat data_b = loada(blk.data_b + i);
+ haccumulate(pp_avg_rgb[0][2], data_b, p0_mask);
+ haccumulate(pp_avg_rgb[1][2], data_b, p1_mask);
+ }
+
+ vfloat4 block_total = blk.data_mean.swz<0, 1, 2>() * static_cast<float>(blk.texel_count);
+
+ vfloat4 p0_total = vfloat3(hadd_s(pp_avg_rgb[0][0]),
+ hadd_s(pp_avg_rgb[0][1]),
+ hadd_s(pp_avg_rgb[0][2]));
+
+ vfloat4 p1_total = vfloat3(hadd_s(pp_avg_rgb[1][0]),
+ hadd_s(pp_avg_rgb[1][1]),
+ hadd_s(pp_avg_rgb[1][2]));
+
+ vfloat4 p2_total = block_total - p0_total - p1_total;
+
+ averages[0] = p0_total / static_cast<float>(pi.partition_texel_count[0]);
+ averages[1] = p1_total / static_cast<float>(pi.partition_texel_count[1]);
+ averages[2] = p2_total / static_cast<float>(pi.partition_texel_count[2]);
+ }
+ else
+ {
+ // For 4 partitions scan results for partition 0/1/2, compute partition 3
+ vfloatacc pp_avg_rgb[3][3] {};
+
+ vint lane_id = vint::lane_id();
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vint texel_partition(pi.partition_of_texel + i);
+
+ vmask lane_mask = lane_id < vint(texel_count);
+ lane_id += vint(ASTCENC_SIMD_WIDTH);
+
+ vmask p0_mask = lane_mask & (texel_partition == vint(0));
+ vmask p1_mask = lane_mask & (texel_partition == vint(1));
+ vmask p2_mask = lane_mask & (texel_partition == vint(2));
+
+ vfloat data_r = loada(blk.data_r + i);
+ haccumulate(pp_avg_rgb[0][0], data_r, p0_mask);
+ haccumulate(pp_avg_rgb[1][0], data_r, p1_mask);
+ haccumulate(pp_avg_rgb[2][0], data_r, p2_mask);
+
+ vfloat data_g = loada(blk.data_g + i);
+ haccumulate(pp_avg_rgb[0][1], data_g, p0_mask);
+ haccumulate(pp_avg_rgb[1][1], data_g, p1_mask);
+ haccumulate(pp_avg_rgb[2][1], data_g, p2_mask);
+
+ vfloat data_b = loada(blk.data_b + i);
+ haccumulate(pp_avg_rgb[0][2], data_b, p0_mask);
+ haccumulate(pp_avg_rgb[1][2], data_b, p1_mask);
+ haccumulate(pp_avg_rgb[2][2], data_b, p2_mask);
+ }
+
+ vfloat4 block_total = blk.data_mean.swz<0, 1, 2>() * static_cast<float>(blk.texel_count);
+
+ vfloat4 p0_total = vfloat3(hadd_s(pp_avg_rgb[0][0]),
+ hadd_s(pp_avg_rgb[0][1]),
+ hadd_s(pp_avg_rgb[0][2]));
+
+ vfloat4 p1_total = vfloat3(hadd_s(pp_avg_rgb[1][0]),
+ hadd_s(pp_avg_rgb[1][1]),
+ hadd_s(pp_avg_rgb[1][2]));
+
+ vfloat4 p2_total = vfloat3(hadd_s(pp_avg_rgb[2][0]),
+ hadd_s(pp_avg_rgb[2][1]),
+ hadd_s(pp_avg_rgb[2][2]));
+
+ vfloat4 p3_total = block_total - p0_total - p1_total- p2_total;
+
+ averages[0] = p0_total / static_cast<float>(pi.partition_texel_count[0]);
+ averages[1] = p1_total / static_cast<float>(pi.partition_texel_count[1]);
+ averages[2] = p2_total / static_cast<float>(pi.partition_texel_count[2]);
+ averages[3] = p3_total / static_cast<float>(pi.partition_texel_count[3]);
+ }
+}
+
+/**
+ * @brief Compute the average RGBA color of each partition.
+ *
+ * The algorithm here uses a vectorized sequential scan and per-partition
+ * color accumulators, using select() to mask texel lanes in other partitions.
+ *
+ * We only accumulate sums for N-1 partitions during the scan; the value for
+ * the last partition can be computed given that we know the block-wide average
+ * already.
+ *
+ * Because of this we could reduce the loop iteration count so it "just" spans
+ * the max texel index needed for the N-1 partitions, which could need fewer
+ * iterations than the full block texel count. However, this makes the loop
+ * count erratic and causes more branch mispredictions so is a net loss.
+ *
+ * @param pi The partitioning to use.
+ * @param blk The block data to process.
+ * @param[out] averages The output averages. Unused partition indices will
+ * not be initialized.
+ */
+static void compute_partition_averages_rgba(
+ const partition_info& pi,
+ const image_block& blk,
+ vfloat4 averages[BLOCK_MAX_PARTITIONS]
+) {
+ unsigned int partition_count = pi.partition_count;
+ unsigned int texel_count = blk.texel_count;
+ promise(texel_count > 0);
+
+ // For 1 partition just use the precomputed mean
+ if (partition_count == 1)
+ {
+ averages[0] = blk.data_mean;
+ }
+ // For 2 partitions scan results for partition 0, compute partition 1
+ else if (partition_count == 2)
+ {
+ vfloat4 pp_avg_rgba[4] {};
+
+ vint lane_id = vint::lane_id();
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vint texel_partition(pi.partition_of_texel + i);
+
+ vmask lane_mask = lane_id < vint(texel_count);
+ lane_id += vint(ASTCENC_SIMD_WIDTH);
+
+ vmask p0_mask = lane_mask & (texel_partition == vint(0));
+
+ vfloat data_r = loada(blk.data_r + i);
+ haccumulate(pp_avg_rgba[0], data_r, p0_mask);
+
+ vfloat data_g = loada(blk.data_g + i);
+ haccumulate(pp_avg_rgba[1], data_g, p0_mask);
+
+ vfloat data_b = loada(blk.data_b + i);
+ haccumulate(pp_avg_rgba[2], data_b, p0_mask);
+
+ vfloat data_a = loada(blk.data_a + i);
+ haccumulate(pp_avg_rgba[3], data_a, p0_mask);
+ }
+
+ vfloat4 block_total = blk.data_mean * static_cast<float>(blk.texel_count);
+
+ vfloat4 p0_total = vfloat4(hadd_s(pp_avg_rgba[0]),
+ hadd_s(pp_avg_rgba[1]),
+ hadd_s(pp_avg_rgba[2]),
+ hadd_s(pp_avg_rgba[3]));
+
+ vfloat4 p1_total = block_total - p0_total;
+
+ averages[0] = p0_total / static_cast<float>(pi.partition_texel_count[0]);
+ averages[1] = p1_total / static_cast<float>(pi.partition_texel_count[1]);
+ }
+ // For 3 partitions scan results for partition 0/1, compute partition 2
+ else if (partition_count == 3)
+ {
+ vfloat4 pp_avg_rgba[2][4] {};
+
+ vint lane_id = vint::lane_id();
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vint texel_partition(pi.partition_of_texel + i);
+
+ vmask lane_mask = lane_id < vint(texel_count);
+ lane_id += vint(ASTCENC_SIMD_WIDTH);
+
+ vmask p0_mask = lane_mask & (texel_partition == vint(0));
+ vmask p1_mask = lane_mask & (texel_partition == vint(1));
+
+ vfloat data_r = loada(blk.data_r + i);
+ haccumulate(pp_avg_rgba[0][0], data_r, p0_mask);
+ haccumulate(pp_avg_rgba[1][0], data_r, p1_mask);
+
+ vfloat data_g = loada(blk.data_g + i);
+ haccumulate(pp_avg_rgba[0][1], data_g, p0_mask);
+ haccumulate(pp_avg_rgba[1][1], data_g, p1_mask);
+
+ vfloat data_b = loada(blk.data_b + i);
+ haccumulate(pp_avg_rgba[0][2], data_b, p0_mask);
+ haccumulate(pp_avg_rgba[1][2], data_b, p1_mask);
+
+ vfloat data_a = loada(blk.data_a + i);
+ haccumulate(pp_avg_rgba[0][3], data_a, p0_mask);
+ haccumulate(pp_avg_rgba[1][3], data_a, p1_mask);
+ }
+
+ vfloat4 block_total = blk.data_mean * static_cast<float>(blk.texel_count);
+
+ vfloat4 p0_total = vfloat4(hadd_s(pp_avg_rgba[0][0]),
+ hadd_s(pp_avg_rgba[0][1]),
+ hadd_s(pp_avg_rgba[0][2]),
+ hadd_s(pp_avg_rgba[0][3]));
+
+ vfloat4 p1_total = vfloat4(hadd_s(pp_avg_rgba[1][0]),
+ hadd_s(pp_avg_rgba[1][1]),
+ hadd_s(pp_avg_rgba[1][2]),
+ hadd_s(pp_avg_rgba[1][3]));
+
+ vfloat4 p2_total = block_total - p0_total - p1_total;
+
+ averages[0] = p0_total / static_cast<float>(pi.partition_texel_count[0]);
+ averages[1] = p1_total / static_cast<float>(pi.partition_texel_count[1]);
+ averages[2] = p2_total / static_cast<float>(pi.partition_texel_count[2]);
+ }
+ else
+ {
+ // For 4 partitions scan results for partition 0/1/2, compute partition 3
+ vfloat4 pp_avg_rgba[3][4] {};
+
+ vint lane_id = vint::lane_id();
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vint texel_partition(pi.partition_of_texel + i);
+
+ vmask lane_mask = lane_id < vint(texel_count);
+ lane_id += vint(ASTCENC_SIMD_WIDTH);
+
+ vmask p0_mask = lane_mask & (texel_partition == vint(0));
+ vmask p1_mask = lane_mask & (texel_partition == vint(1));
+ vmask p2_mask = lane_mask & (texel_partition == vint(2));
+
+ vfloat data_r = loada(blk.data_r + i);
+ haccumulate(pp_avg_rgba[0][0], data_r, p0_mask);
+ haccumulate(pp_avg_rgba[1][0], data_r, p1_mask);
+ haccumulate(pp_avg_rgba[2][0], data_r, p2_mask);
+
+ vfloat data_g = loada(blk.data_g + i);
+ haccumulate(pp_avg_rgba[0][1], data_g, p0_mask);
+ haccumulate(pp_avg_rgba[1][1], data_g, p1_mask);
+ haccumulate(pp_avg_rgba[2][1], data_g, p2_mask);
+
+ vfloat data_b = loada(blk.data_b + i);
+ haccumulate(pp_avg_rgba[0][2], data_b, p0_mask);
+ haccumulate(pp_avg_rgba[1][2], data_b, p1_mask);
+ haccumulate(pp_avg_rgba[2][2], data_b, p2_mask);
+
+ vfloat data_a = loada(blk.data_a + i);
+ haccumulate(pp_avg_rgba[0][3], data_a, p0_mask);
+ haccumulate(pp_avg_rgba[1][3], data_a, p1_mask);
+ haccumulate(pp_avg_rgba[2][3], data_a, p2_mask);
+ }
+
+ vfloat4 block_total = blk.data_mean * static_cast<float>(blk.texel_count);
+
+ vfloat4 p0_total = vfloat4(hadd_s(pp_avg_rgba[0][0]),
+ hadd_s(pp_avg_rgba[0][1]),
+ hadd_s(pp_avg_rgba[0][2]),
+ hadd_s(pp_avg_rgba[0][3]));
+
+ vfloat4 p1_total = vfloat4(hadd_s(pp_avg_rgba[1][0]),
+ hadd_s(pp_avg_rgba[1][1]),
+ hadd_s(pp_avg_rgba[1][2]),
+ hadd_s(pp_avg_rgba[1][3]));
+
+ vfloat4 p2_total = vfloat4(hadd_s(pp_avg_rgba[2][0]),
+ hadd_s(pp_avg_rgba[2][1]),
+ hadd_s(pp_avg_rgba[2][2]),
+ hadd_s(pp_avg_rgba[2][3]));
+
+ vfloat4 p3_total = block_total - p0_total - p1_total- p2_total;
+
+ averages[0] = p0_total / static_cast<float>(pi.partition_texel_count[0]);
+ averages[1] = p1_total / static_cast<float>(pi.partition_texel_count[1]);
+ averages[2] = p2_total / static_cast<float>(pi.partition_texel_count[2]);
+ averages[3] = p3_total / static_cast<float>(pi.partition_texel_count[3]);
+ }
+}
+
+/* See header for documentation. */
+void compute_avgs_and_dirs_4_comp(
+ const partition_info& pi,
+ const image_block& blk,
+ partition_metrics pm[BLOCK_MAX_PARTITIONS]
+) {
+ int partition_count = pi.partition_count;
+ promise(partition_count > 0);
+
+ // Pre-compute partition_averages
+ vfloat4 partition_averages[BLOCK_MAX_PARTITIONS];
+ compute_partition_averages_rgba(pi, blk, partition_averages);
+
+ for (int partition = 0; partition < partition_count; partition++)
+ {
+ const uint8_t *texel_indexes = pi.texels_of_partition[partition];
+ unsigned int texel_count = pi.partition_texel_count[partition];
+ promise(texel_count > 0);
+
+ vfloat4 average = partition_averages[partition];
+ pm[partition].avg = average;
+
+ vfloat4 sum_xp = vfloat4::zero();
+ vfloat4 sum_yp = vfloat4::zero();
+ vfloat4 sum_zp = vfloat4::zero();
+ vfloat4 sum_wp = vfloat4::zero();
+
+ for (unsigned int i = 0; i < texel_count; i++)
+ {
+ unsigned int iwt = texel_indexes[i];
+ vfloat4 texel_datum = blk.texel(iwt);
+ texel_datum = texel_datum - average;
+
+ vfloat4 zero = vfloat4::zero();
+
+ vmask4 tdm0 = texel_datum.swz<0,0,0,0>() > zero;
+ sum_xp += select(zero, texel_datum, tdm0);
+
+ vmask4 tdm1 = texel_datum.swz<1,1,1,1>() > zero;
+ sum_yp += select(zero, texel_datum, tdm1);
+
+ vmask4 tdm2 = texel_datum.swz<2,2,2,2>() > zero;
+ sum_zp += select(zero, texel_datum, tdm2);
+
+ vmask4 tdm3 = texel_datum.swz<3,3,3,3>() > zero;
+ sum_wp += select(zero, texel_datum, tdm3);
+ }
+
+ vfloat4 prod_xp = dot(sum_xp, sum_xp);
+ vfloat4 prod_yp = dot(sum_yp, sum_yp);
+ vfloat4 prod_zp = dot(sum_zp, sum_zp);
+ vfloat4 prod_wp = dot(sum_wp, sum_wp);
+
+ vfloat4 best_vector = sum_xp;
+ vfloat4 best_sum = prod_xp;
+
+ vmask4 mask = prod_yp > best_sum;
+ best_vector = select(best_vector, sum_yp, mask);
+ best_sum = select(best_sum, prod_yp, mask);
+
+ mask = prod_zp > best_sum;
+ best_vector = select(best_vector, sum_zp, mask);
+ best_sum = select(best_sum, prod_zp, mask);
+
+ mask = prod_wp > best_sum;
+ best_vector = select(best_vector, sum_wp, mask);
+
+ pm[partition].dir = best_vector;
+ }
+}
+
+/* See header for documentation. */
+void compute_avgs_and_dirs_3_comp(
+ const partition_info& pi,
+ const image_block& blk,
+ unsigned int omitted_component,
+ partition_metrics pm[BLOCK_MAX_PARTITIONS]
+) {
+ // Pre-compute partition_averages
+ vfloat4 partition_averages[BLOCK_MAX_PARTITIONS];
+ compute_partition_averages_rgba(pi, blk, partition_averages);
+
+ const float* data_vr = blk.data_r;
+ const float* data_vg = blk.data_g;
+ const float* data_vb = blk.data_b;
+
+ // TODO: Data-driven permute would be useful to avoid this ...
+ if (omitted_component == 0)
+ {
+ partition_averages[0] = partition_averages[0].swz<1, 2, 3>();
+ partition_averages[1] = partition_averages[1].swz<1, 2, 3>();
+ partition_averages[2] = partition_averages[2].swz<1, 2, 3>();
+ partition_averages[3] = partition_averages[3].swz<1, 2, 3>();
+
+ data_vr = blk.data_g;
+ data_vg = blk.data_b;
+ data_vb = blk.data_a;
+ }
+ else if (omitted_component == 1)
+ {
+ partition_averages[0] = partition_averages[0].swz<0, 2, 3>();
+ partition_averages[1] = partition_averages[1].swz<0, 2, 3>();
+ partition_averages[2] = partition_averages[2].swz<0, 2, 3>();
+ partition_averages[3] = partition_averages[3].swz<0, 2, 3>();
+
+ data_vg = blk.data_b;
+ data_vb = blk.data_a;
+ }
+ else if (omitted_component == 2)
+ {
+ partition_averages[0] = partition_averages[0].swz<0, 1, 3>();
+ partition_averages[1] = partition_averages[1].swz<0, 1, 3>();
+ partition_averages[2] = partition_averages[2].swz<0, 1, 3>();
+ partition_averages[3] = partition_averages[3].swz<0, 1, 3>();
+
+ data_vb = blk.data_a;
+ }
+ else
+ {
+ partition_averages[0] = partition_averages[0].swz<0, 1, 2>();
+ partition_averages[1] = partition_averages[1].swz<0, 1, 2>();
+ partition_averages[2] = partition_averages[2].swz<0, 1, 2>();
+ partition_averages[3] = partition_averages[3].swz<0, 1, 2>();
+ }
+
+ unsigned int partition_count = pi.partition_count;
+ promise(partition_count > 0);
+
+ for (unsigned int partition = 0; partition < partition_count; partition++)
+ {
+ const uint8_t *texel_indexes = pi.texels_of_partition[partition];
+ unsigned int texel_count = pi.partition_texel_count[partition];
+ promise(texel_count > 0);
+
+ vfloat4 average = partition_averages[partition];
+ pm[partition].avg = average;
+
+ vfloat4 sum_xp = vfloat4::zero();
+ vfloat4 sum_yp = vfloat4::zero();
+ vfloat4 sum_zp = vfloat4::zero();
+
+ for (unsigned int i = 0; i < texel_count; i++)
+ {
+ unsigned int iwt = texel_indexes[i];
+
+ vfloat4 texel_datum = vfloat3(data_vr[iwt],
+ data_vg[iwt],
+ data_vb[iwt]);
+ texel_datum = texel_datum - average;
+
+ vfloat4 zero = vfloat4::zero();
+
+ vmask4 tdm0 = texel_datum.swz<0,0,0,0>() > zero;
+ sum_xp += select(zero, texel_datum, tdm0);
+
+ vmask4 tdm1 = texel_datum.swz<1,1,1,1>() > zero;
+ sum_yp += select(zero, texel_datum, tdm1);
+
+ vmask4 tdm2 = texel_datum.swz<2,2,2,2>() > zero;
+ sum_zp += select(zero, texel_datum, tdm2);
+ }
+
+ vfloat4 prod_xp = dot(sum_xp, sum_xp);
+ vfloat4 prod_yp = dot(sum_yp, sum_yp);
+ vfloat4 prod_zp = dot(sum_zp, sum_zp);
+
+ vfloat4 best_vector = sum_xp;
+ vfloat4 best_sum = prod_xp;
+
+ vmask4 mask = prod_yp > best_sum;
+ best_vector = select(best_vector, sum_yp, mask);
+ best_sum = select(best_sum, prod_yp, mask);
+
+ mask = prod_zp > best_sum;
+ best_vector = select(best_vector, sum_zp, mask);
+
+ pm[partition].dir = best_vector;
+ }
+}
+
+/* See header for documentation. */
+void compute_avgs_and_dirs_3_comp_rgb(
+ const partition_info& pi,
+ const image_block& blk,
+ partition_metrics pm[BLOCK_MAX_PARTITIONS]
+) {
+ unsigned int partition_count = pi.partition_count;
+ promise(partition_count > 0);
+
+ // Pre-compute partition_averages
+ vfloat4 partition_averages[BLOCK_MAX_PARTITIONS];
+ compute_partition_averages_rgb(pi, blk, partition_averages);
+
+ for (unsigned int partition = 0; partition < partition_count; partition++)
+ {
+ const uint8_t *texel_indexes = pi.texels_of_partition[partition];
+ unsigned int texel_count = pi.partition_texel_count[partition];
+ promise(texel_count > 0);
+
+ vfloat4 average = partition_averages[partition];
+ pm[partition].avg = average;
+
+ vfloat4 sum_xp = vfloat4::zero();
+ vfloat4 sum_yp = vfloat4::zero();
+ vfloat4 sum_zp = vfloat4::zero();
+
+ for (unsigned int i = 0; i < texel_count; i++)
+ {
+ unsigned int iwt = texel_indexes[i];
+
+ vfloat4 texel_datum = blk.texel3(iwt);
+ texel_datum = texel_datum - average;
+
+ vfloat4 zero = vfloat4::zero();
+
+ vmask4 tdm0 = texel_datum.swz<0,0,0,0>() > zero;
+ sum_xp += select(zero, texel_datum, tdm0);
+
+ vmask4 tdm1 = texel_datum.swz<1,1,1,1>() > zero;
+ sum_yp += select(zero, texel_datum, tdm1);
+
+ vmask4 tdm2 = texel_datum.swz<2,2,2,2>() > zero;
+ sum_zp += select(zero, texel_datum, tdm2);
+ }
+
+ vfloat4 prod_xp = dot(sum_xp, sum_xp);
+ vfloat4 prod_yp = dot(sum_yp, sum_yp);
+ vfloat4 prod_zp = dot(sum_zp, sum_zp);
+
+ vfloat4 best_vector = sum_xp;
+ vfloat4 best_sum = prod_xp;
+
+ vmask4 mask = prod_yp > best_sum;
+ best_vector = select(best_vector, sum_yp, mask);
+ best_sum = select(best_sum, prod_yp, mask);
+
+ mask = prod_zp > best_sum;
+ best_vector = select(best_vector, sum_zp, mask);
+
+ pm[partition].dir = best_vector;
+ }
+}
+
+/* See header for documentation. */
+void compute_avgs_and_dirs_2_comp(
+ const partition_info& pt,
+ const image_block& blk,
+ unsigned int component1,
+ unsigned int component2,
+ partition_metrics pm[BLOCK_MAX_PARTITIONS]
+) {
+ vfloat4 average;
+
+ const float* data_vr = nullptr;
+ const float* data_vg = nullptr;
+
+ if (component1 == 0 && component2 == 1)
+ {
+ average = blk.data_mean.swz<0, 1>();
+
+ data_vr = blk.data_r;
+ data_vg = blk.data_g;
+ }
+ else if (component1 == 0 && component2 == 2)
+ {
+ average = blk.data_mean.swz<0, 2>();
+
+ data_vr = blk.data_r;
+ data_vg = blk.data_b;
+ }
+ else // (component1 == 1 && component2 == 2)
+ {
+ assert(component1 == 1 && component2 == 2);
+
+ average = blk.data_mean.swz<1, 2>();
+
+ data_vr = blk.data_g;
+ data_vg = blk.data_b;
+ }
+
+ unsigned int partition_count = pt.partition_count;
+ promise(partition_count > 0);
+
+ for (unsigned int partition = 0; partition < partition_count; partition++)
+ {
+ const uint8_t *texel_indexes = pt.texels_of_partition[partition];
+ unsigned int texel_count = pt.partition_texel_count[partition];
+ promise(texel_count > 0);
+
+ // Only compute a partition mean if more than one partition
+ if (partition_count > 1)
+ {
+ average = vfloat4::zero();
+ for (unsigned int i = 0; i < texel_count; i++)
+ {
+ unsigned int iwt = texel_indexes[i];
+ average += vfloat2(data_vr[iwt], data_vg[iwt]);
+ }
+
+ average = average / static_cast<float>(texel_count);
+ }
+
+ pm[partition].avg = average;
+
+ vfloat4 sum_xp = vfloat4::zero();
+ vfloat4 sum_yp = vfloat4::zero();
+
+ for (unsigned int i = 0; i < texel_count; i++)
+ {
+ unsigned int iwt = texel_indexes[i];
+ vfloat4 texel_datum = vfloat2(data_vr[iwt], data_vg[iwt]);
+ texel_datum = texel_datum - average;
+
+ vfloat4 zero = vfloat4::zero();
+
+ vmask4 tdm0 = texel_datum.swz<0,0,0,0>() > zero;
+ sum_xp += select(zero, texel_datum, tdm0);
+
+ vmask4 tdm1 = texel_datum.swz<1,1,1,1>() > zero;
+ sum_yp += select(zero, texel_datum, tdm1);
+ }
+
+ vfloat4 prod_xp = dot(sum_xp, sum_xp);
+ vfloat4 prod_yp = dot(sum_yp, sum_yp);
+
+ vfloat4 best_vector = sum_xp;
+ vfloat4 best_sum = prod_xp;
+
+ vmask4 mask = prod_yp > best_sum;
+ best_vector = select(best_vector, sum_yp, mask);
+
+ pm[partition].dir = best_vector;
+ }
+}
+
+/* See header for documentation. */
+void compute_error_squared_rgba(
+ const partition_info& pi,
+ const image_block& blk,
+ const processed_line4 uncor_plines[BLOCK_MAX_PARTITIONS],
+ const processed_line4 samec_plines[BLOCK_MAX_PARTITIONS],
+ float uncor_lengths[BLOCK_MAX_PARTITIONS],
+ float samec_lengths[BLOCK_MAX_PARTITIONS],
+ float& uncor_error,
+ float& samec_error
+) {
+ unsigned int partition_count = pi.partition_count;
+ promise(partition_count > 0);
+
+ vfloatacc uncor_errorsumv = vfloatacc::zero();
+ vfloatacc samec_errorsumv = vfloatacc::zero();
+
+ for (unsigned int partition = 0; partition < partition_count; partition++)
+ {
+ const uint8_t *texel_indexes = pi.texels_of_partition[partition];
+
+ float uncor_loparam = 1e10f;
+ float uncor_hiparam = -1e10f;
+
+ float samec_loparam = 1e10f;
+ float samec_hiparam = -1e10f;
+
+ processed_line4 l_uncor = uncor_plines[partition];
+ processed_line4 l_samec = samec_plines[partition];
+
+ unsigned int texel_count = pi.partition_texel_count[partition];
+ promise(texel_count > 0);
+
+ // Vectorize some useful scalar inputs
+ vfloat l_uncor_bs0(l_uncor.bs.lane<0>());
+ vfloat l_uncor_bs1(l_uncor.bs.lane<1>());
+ vfloat l_uncor_bs2(l_uncor.bs.lane<2>());
+ vfloat l_uncor_bs3(l_uncor.bs.lane<3>());
+
+ vfloat l_uncor_amod0(l_uncor.amod.lane<0>());
+ vfloat l_uncor_amod1(l_uncor.amod.lane<1>());
+ vfloat l_uncor_amod2(l_uncor.amod.lane<2>());
+ vfloat l_uncor_amod3(l_uncor.amod.lane<3>());
+
+ vfloat l_samec_bs0(l_samec.bs.lane<0>());
+ vfloat l_samec_bs1(l_samec.bs.lane<1>());
+ vfloat l_samec_bs2(l_samec.bs.lane<2>());
+ vfloat l_samec_bs3(l_samec.bs.lane<3>());
+
+ assert(all(l_samec.amod == vfloat4(0.0f)));
+
+ vfloat uncor_loparamv(1e10f);
+ vfloat uncor_hiparamv(-1e10f);
+
+ vfloat samec_loparamv(1e10f);
+ vfloat samec_hiparamv(-1e10f);
+
+ vfloat ew_r(blk.channel_weight.lane<0>());
+ vfloat ew_g(blk.channel_weight.lane<1>());
+ vfloat ew_b(blk.channel_weight.lane<2>());
+ vfloat ew_a(blk.channel_weight.lane<3>());
+
+ // This implementation over-shoots, but this is safe as we initialize the texel_indexes
+ // array to extend the last value. This means min/max are not impacted, but we need to mask
+ // out the dummy values when we compute the line weighting.
+ vint lane_ids = vint::lane_id();
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vmask mask = lane_ids < vint(texel_count);
+ vint texel_idxs(texel_indexes + i);
+
+ vfloat data_r = gatherf(blk.data_r, texel_idxs);
+ vfloat data_g = gatherf(blk.data_g, texel_idxs);
+ vfloat data_b = gatherf(blk.data_b, texel_idxs);
+ vfloat data_a = gatherf(blk.data_a, texel_idxs);
+
+ vfloat uncor_param = (data_r * l_uncor_bs0)
+ + (data_g * l_uncor_bs1)
+ + (data_b * l_uncor_bs2)
+ + (data_a * l_uncor_bs3);
+
+ uncor_loparamv = min(uncor_param, uncor_loparamv);
+ uncor_hiparamv = max(uncor_param, uncor_hiparamv);
+
+ vfloat uncor_dist0 = (l_uncor_amod0 - data_r)
+ + (uncor_param * l_uncor_bs0);
+ vfloat uncor_dist1 = (l_uncor_amod1 - data_g)
+ + (uncor_param * l_uncor_bs1);
+ vfloat uncor_dist2 = (l_uncor_amod2 - data_b)
+ + (uncor_param * l_uncor_bs2);
+ vfloat uncor_dist3 = (l_uncor_amod3 - data_a)
+ + (uncor_param * l_uncor_bs3);
+
+ vfloat uncor_err = (ew_r * uncor_dist0 * uncor_dist0)
+ + (ew_g * uncor_dist1 * uncor_dist1)
+ + (ew_b * uncor_dist2 * uncor_dist2)
+ + (ew_a * uncor_dist3 * uncor_dist3);
+
+ haccumulate(uncor_errorsumv, uncor_err, mask);
+
+ // Process samechroma data
+ vfloat samec_param = (data_r * l_samec_bs0)
+ + (data_g * l_samec_bs1)
+ + (data_b * l_samec_bs2)
+ + (data_a * l_samec_bs3);
+
+ samec_loparamv = min(samec_param, samec_loparamv);
+ samec_hiparamv = max(samec_param, samec_hiparamv);
+
+ vfloat samec_dist0 = samec_param * l_samec_bs0 - data_r;
+ vfloat samec_dist1 = samec_param * l_samec_bs1 - data_g;
+ vfloat samec_dist2 = samec_param * l_samec_bs2 - data_b;
+ vfloat samec_dist3 = samec_param * l_samec_bs3 - data_a;
+
+ vfloat samec_err = (ew_r * samec_dist0 * samec_dist0)
+ + (ew_g * samec_dist1 * samec_dist1)
+ + (ew_b * samec_dist2 * samec_dist2)
+ + (ew_a * samec_dist3 * samec_dist3);
+
+ haccumulate(samec_errorsumv, samec_err, mask);
+
+ lane_ids += vint(ASTCENC_SIMD_WIDTH);
+ }
+
+ uncor_loparam = hmin_s(uncor_loparamv);
+ uncor_hiparam = hmax_s(uncor_hiparamv);
+
+ samec_loparam = hmin_s(samec_loparamv);
+ samec_hiparam = hmax_s(samec_hiparamv);
+
+ float uncor_linelen = uncor_hiparam - uncor_loparam;
+ float samec_linelen = samec_hiparam - samec_loparam;
+
+ // Turn very small numbers and NaNs into a small number
+ uncor_lengths[partition] = astc::max(uncor_linelen, 1e-7f);
+ samec_lengths[partition] = astc::max(samec_linelen, 1e-7f);
+ }
+
+ uncor_error = hadd_s(uncor_errorsumv);
+ samec_error = hadd_s(samec_errorsumv);
+}
+
+/* See header for documentation. */
+void compute_error_squared_rgb(
+ const partition_info& pi,
+ const image_block& blk,
+ partition_lines3 plines[BLOCK_MAX_PARTITIONS],
+ float& uncor_error,
+ float& samec_error
+) {
+ unsigned int partition_count = pi.partition_count;
+ promise(partition_count > 0);
+
+ vfloatacc uncor_errorsumv = vfloatacc::zero();
+ vfloatacc samec_errorsumv = vfloatacc::zero();
+
+ for (unsigned int partition = 0; partition < partition_count; partition++)
+ {
+ partition_lines3& pl = plines[partition];
+ const uint8_t *texel_indexes = pi.texels_of_partition[partition];
+ unsigned int texel_count = pi.partition_texel_count[partition];
+ promise(texel_count > 0);
+
+ float uncor_loparam = 1e10f;
+ float uncor_hiparam = -1e10f;
+
+ float samec_loparam = 1e10f;
+ float samec_hiparam = -1e10f;
+
+ processed_line3 l_uncor = pl.uncor_pline;
+ processed_line3 l_samec = pl.samec_pline;
+
+ // This implementation is an example vectorization of this function.
+ // It works for - the codec is a 2-4% faster than not vectorizing - but
+ // the benefit is limited by the use of gathers and register pressure
+
+ // Vectorize some useful scalar inputs
+ vfloat l_uncor_bs0(l_uncor.bs.lane<0>());
+ vfloat l_uncor_bs1(l_uncor.bs.lane<1>());
+ vfloat l_uncor_bs2(l_uncor.bs.lane<2>());
+
+ vfloat l_uncor_amod0(l_uncor.amod.lane<0>());
+ vfloat l_uncor_amod1(l_uncor.amod.lane<1>());
+ vfloat l_uncor_amod2(l_uncor.amod.lane<2>());
+
+ vfloat l_samec_bs0(l_samec.bs.lane<0>());
+ vfloat l_samec_bs1(l_samec.bs.lane<1>());
+ vfloat l_samec_bs2(l_samec.bs.lane<2>());
+
+ assert(all(l_samec.amod == vfloat4(0.0f)));
+
+ vfloat uncor_loparamv(1e10f);
+ vfloat uncor_hiparamv(-1e10f);
+
+ vfloat samec_loparamv(1e10f);
+ vfloat samec_hiparamv(-1e10f);
+
+ vfloat ew_r(blk.channel_weight.lane<0>());
+ vfloat ew_g(blk.channel_weight.lane<1>());
+ vfloat ew_b(blk.channel_weight.lane<2>());
+
+ // This implementation over-shoots, but this is safe as we initialize the weights array
+ // to extend the last value. This means min/max are not impacted, but we need to mask
+ // out the dummy values when we compute the line weighting.
+ vint lane_ids = vint::lane_id();
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vmask mask = lane_ids < vint(texel_count);
+ vint texel_idxs(texel_indexes + i);
+
+ vfloat data_r = gatherf(blk.data_r, texel_idxs);
+ vfloat data_g = gatherf(blk.data_g, texel_idxs);
+ vfloat data_b = gatherf(blk.data_b, texel_idxs);
+
+ vfloat uncor_param = (data_r * l_uncor_bs0)
+ + (data_g * l_uncor_bs1)
+ + (data_b * l_uncor_bs2);
+
+ uncor_loparamv = min(uncor_param, uncor_loparamv);
+ uncor_hiparamv = max(uncor_param, uncor_hiparamv);
+
+ vfloat uncor_dist0 = (l_uncor_amod0 - data_r)
+ + (uncor_param * l_uncor_bs0);
+ vfloat uncor_dist1 = (l_uncor_amod1 - data_g)
+ + (uncor_param * l_uncor_bs1);
+ vfloat uncor_dist2 = (l_uncor_amod2 - data_b)
+ + (uncor_param * l_uncor_bs2);
+
+ vfloat uncor_err = (ew_r * uncor_dist0 * uncor_dist0)
+ + (ew_g * uncor_dist1 * uncor_dist1)
+ + (ew_b * uncor_dist2 * uncor_dist2);
+
+ haccumulate(uncor_errorsumv, uncor_err, mask);
+
+ // Process samechroma data
+ vfloat samec_param = (data_r * l_samec_bs0)
+ + (data_g * l_samec_bs1)
+ + (data_b * l_samec_bs2);
+
+ samec_loparamv = min(samec_param, samec_loparamv);
+ samec_hiparamv = max(samec_param, samec_hiparamv);
+
+ vfloat samec_dist0 = samec_param * l_samec_bs0 - data_r;
+ vfloat samec_dist1 = samec_param * l_samec_bs1 - data_g;
+ vfloat samec_dist2 = samec_param * l_samec_bs2 - data_b;
+
+ vfloat samec_err = (ew_r * samec_dist0 * samec_dist0)
+ + (ew_g * samec_dist1 * samec_dist1)
+ + (ew_b * samec_dist2 * samec_dist2);
+
+ haccumulate(samec_errorsumv, samec_err, mask);
+
+ lane_ids += vint(ASTCENC_SIMD_WIDTH);
+ }
+
+ uncor_loparam = hmin_s(uncor_loparamv);
+ uncor_hiparam = hmax_s(uncor_hiparamv);
+
+ samec_loparam = hmin_s(samec_loparamv);
+ samec_hiparam = hmax_s(samec_hiparamv);
+
+ float uncor_linelen = uncor_hiparam - uncor_loparam;
+ float samec_linelen = samec_hiparam - samec_loparam;
+
+ // Turn very small numbers and NaNs into a small number
+ pl.uncor_line_len = astc::max(uncor_linelen, 1e-7f);
+ pl.samec_line_len = astc::max(samec_linelen, 1e-7f);
+ }
+
+ uncor_error = hadd_s(uncor_errorsumv);
+ samec_error = hadd_s(samec_errorsumv);
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_block_sizes.cpp b/thirdparty/astcenc/astcenc_block_sizes.cpp
new file mode 100644
index 0000000000..1c22d06a5c
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_block_sizes.cpp
@@ -0,0 +1,1184 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2023 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Functions to generate block size descriptor and decimation tables.
+ */
+
+#include "astcenc_internal.h"
+
+/**
+ * @brief Decode the properties of an encoded 2D block mode.
+ *
+ * @param block_mode The encoded block mode.
+ * @param[out] x_weights The number of weights in the X dimension.
+ * @param[out] y_weights The number of weights in the Y dimension.
+ * @param[out] is_dual_plane True if this block mode has two weight planes.
+ * @param[out] quant_mode The quantization level for the weights.
+ * @param[out] weight_bits The storage bit count for the weights.
+ *
+ * @return Returns true if a valid mode, false otherwise.
+ */
+static bool decode_block_mode_2d(
+ unsigned int block_mode,
+ unsigned int& x_weights,
+ unsigned int& y_weights,
+ bool& is_dual_plane,
+ unsigned int& quant_mode,
+ unsigned int& weight_bits
+) {
+ unsigned int base_quant_mode = (block_mode >> 4) & 1;
+ unsigned int H = (block_mode >> 9) & 1;
+ unsigned int D = (block_mode >> 10) & 1;
+ unsigned int A = (block_mode >> 5) & 0x3;
+
+ x_weights = 0;
+ y_weights = 0;
+
+ if ((block_mode & 3) != 0)
+ {
+ base_quant_mode |= (block_mode & 3) << 1;
+ unsigned int B = (block_mode >> 7) & 3;
+ switch ((block_mode >> 2) & 3)
+ {
+ case 0:
+ x_weights = B + 4;
+ y_weights = A + 2;
+ break;
+ case 1:
+ x_weights = B + 8;
+ y_weights = A + 2;
+ break;
+ case 2:
+ x_weights = A + 2;
+ y_weights = B + 8;
+ break;
+ case 3:
+ B &= 1;
+ if (block_mode & 0x100)
+ {
+ x_weights = B + 2;
+ y_weights = A + 2;
+ }
+ else
+ {
+ x_weights = A + 2;
+ y_weights = B + 6;
+ }
+ break;
+ }
+ }
+ else
+ {
+ base_quant_mode |= ((block_mode >> 2) & 3) << 1;
+ if (((block_mode >> 2) & 3) == 0)
+ {
+ return false;
+ }
+
+ unsigned int B = (block_mode >> 9) & 3;
+ switch ((block_mode >> 7) & 3)
+ {
+ case 0:
+ x_weights = 12;
+ y_weights = A + 2;
+ break;
+ case 1:
+ x_weights = A + 2;
+ y_weights = 12;
+ break;
+ case 2:
+ x_weights = A + 6;
+ y_weights = B + 6;
+ D = 0;
+ H = 0;
+ break;
+ case 3:
+ switch ((block_mode >> 5) & 3)
+ {
+ case 0:
+ x_weights = 6;
+ y_weights = 10;
+ break;
+ case 1:
+ x_weights = 10;
+ y_weights = 6;
+ break;
+ case 2:
+ case 3:
+ return false;
+ }
+ break;
+ }
+ }
+
+ unsigned int weight_count = x_weights * y_weights * (D + 1);
+ quant_mode = (base_quant_mode - 2) + 6 * H;
+ is_dual_plane = D != 0;
+
+ weight_bits = get_ise_sequence_bitcount(weight_count, static_cast<quant_method>(quant_mode));
+ return (weight_count <= BLOCK_MAX_WEIGHTS &&
+ weight_bits >= BLOCK_MIN_WEIGHT_BITS &&
+ weight_bits <= BLOCK_MAX_WEIGHT_BITS);
+}
+
+/**
+ * @brief Decode the properties of an encoded 3D block mode.
+ *
+ * @param block_mode The encoded block mode.
+ * @param[out] x_weights The number of weights in the X dimension.
+ * @param[out] y_weights The number of weights in the Y dimension.
+ * @param[out] z_weights The number of weights in the Z dimension.
+ * @param[out] is_dual_plane True if this block mode has two weight planes.
+ * @param[out] quant_mode The quantization level for the weights.
+ * @param[out] weight_bits The storage bit count for the weights.
+ *
+ * @return Returns true if a valid mode, false otherwise.
+ */
+static bool decode_block_mode_3d(
+ unsigned int block_mode,
+ unsigned int& x_weights,
+ unsigned int& y_weights,
+ unsigned int& z_weights,
+ bool& is_dual_plane,
+ unsigned int& quant_mode,
+ unsigned int& weight_bits
+) {
+ unsigned int base_quant_mode = (block_mode >> 4) & 1;
+ unsigned int H = (block_mode >> 9) & 1;
+ unsigned int D = (block_mode >> 10) & 1;
+ unsigned int A = (block_mode >> 5) & 0x3;
+
+ x_weights = 0;
+ y_weights = 0;
+ z_weights = 0;
+
+ if ((block_mode & 3) != 0)
+ {
+ base_quant_mode |= (block_mode & 3) << 1;
+ unsigned int B = (block_mode >> 7) & 3;
+ unsigned int C = (block_mode >> 2) & 0x3;
+ x_weights = A + 2;
+ y_weights = B + 2;
+ z_weights = C + 2;
+ }
+ else
+ {
+ base_quant_mode |= ((block_mode >> 2) & 3) << 1;
+ if (((block_mode >> 2) & 3) == 0)
+ {
+ return false;
+ }
+
+ int B = (block_mode >> 9) & 3;
+ if (((block_mode >> 7) & 3) != 3)
+ {
+ D = 0;
+ H = 0;
+ }
+ switch ((block_mode >> 7) & 3)
+ {
+ case 0:
+ x_weights = 6;
+ y_weights = B + 2;
+ z_weights = A + 2;
+ break;
+ case 1:
+ x_weights = A + 2;
+ y_weights = 6;
+ z_weights = B + 2;
+ break;
+ case 2:
+ x_weights = A + 2;
+ y_weights = B + 2;
+ z_weights = 6;
+ break;
+ case 3:
+ x_weights = 2;
+ y_weights = 2;
+ z_weights = 2;
+ switch ((block_mode >> 5) & 3)
+ {
+ case 0:
+ x_weights = 6;
+ break;
+ case 1:
+ y_weights = 6;
+ break;
+ case 2:
+ z_weights = 6;
+ break;
+ case 3:
+ return false;
+ }
+ break;
+ }
+ }
+
+ unsigned int weight_count = x_weights * y_weights * z_weights * (D + 1);
+ quant_mode = (base_quant_mode - 2) + 6 * H;
+ is_dual_plane = D != 0;
+
+ weight_bits = get_ise_sequence_bitcount(weight_count, static_cast<quant_method>(quant_mode));
+ return (weight_count <= BLOCK_MAX_WEIGHTS &&
+ weight_bits >= BLOCK_MIN_WEIGHT_BITS &&
+ weight_bits <= BLOCK_MAX_WEIGHT_BITS);
+}
+
+/**
+ * @brief Create a 2D decimation entry for a block-size and weight-decimation pair.
+ *
+ * @param x_texels The number of texels in the X dimension.
+ * @param y_texels The number of texels in the Y dimension.
+ * @param x_weights The number of weights in the X dimension.
+ * @param y_weights The number of weights in the Y dimension.
+ * @param[out] di The decimation info structure to populate.
+ * @param[out] wb The decimation table init scratch working buffers.
+ */
+static void init_decimation_info_2d(
+ unsigned int x_texels,
+ unsigned int y_texels,
+ unsigned int x_weights,
+ unsigned int y_weights,
+ decimation_info& di,
+ dt_init_working_buffers& wb
+) {
+ unsigned int texels_per_block = x_texels * y_texels;
+ unsigned int weights_per_block = x_weights * y_weights;
+
+ uint8_t max_texel_count_of_weight = 0;
+
+ promise(weights_per_block > 0);
+ promise(texels_per_block > 0);
+ promise(x_texels > 0);
+ promise(y_texels > 0);
+
+ for (unsigned int i = 0; i < weights_per_block; i++)
+ {
+ wb.texel_count_of_weight[i] = 0;
+ }
+
+ for (unsigned int i = 0; i < texels_per_block; i++)
+ {
+ wb.weight_count_of_texel[i] = 0;
+ }
+
+ for (unsigned int y = 0; y < y_texels; y++)
+ {
+ for (unsigned int x = 0; x < x_texels; x++)
+ {
+ unsigned int texel = y * x_texels + x;
+
+ unsigned int x_weight = (((1024 + x_texels / 2) / (x_texels - 1)) * x * (x_weights - 1) + 32) >> 6;
+ unsigned int y_weight = (((1024 + y_texels / 2) / (y_texels - 1)) * y * (y_weights - 1) + 32) >> 6;
+
+ unsigned int x_weight_frac = x_weight & 0xF;
+ unsigned int y_weight_frac = y_weight & 0xF;
+ unsigned int x_weight_int = x_weight >> 4;
+ unsigned int y_weight_int = y_weight >> 4;
+
+ unsigned int qweight[4];
+ qweight[0] = x_weight_int + y_weight_int * x_weights;
+ qweight[1] = qweight[0] + 1;
+ qweight[2] = qweight[0] + x_weights;
+ qweight[3] = qweight[2] + 1;
+
+ // Truncated-precision bilinear interpolation
+ unsigned int prod = x_weight_frac * y_weight_frac;
+
+ unsigned int weight[4];
+ weight[3] = (prod + 8) >> 4;
+ weight[1] = x_weight_frac - weight[3];
+ weight[2] = y_weight_frac - weight[3];
+ weight[0] = 16 - x_weight_frac - y_weight_frac + weight[3];
+
+ for (unsigned int i = 0; i < 4; i++)
+ {
+ if (weight[i] != 0)
+ {
+ wb.grid_weights_of_texel[texel][wb.weight_count_of_texel[texel]] = static_cast<uint8_t>(qweight[i]);
+ wb.weights_of_texel[texel][wb.weight_count_of_texel[texel]] = static_cast<uint8_t>(weight[i]);
+ wb.weight_count_of_texel[texel]++;
+ wb.texels_of_weight[qweight[i]][wb.texel_count_of_weight[qweight[i]]] = static_cast<uint8_t>(texel);
+ wb.texel_weights_of_weight[qweight[i]][wb.texel_count_of_weight[qweight[i]]] = static_cast<uint8_t>(weight[i]);
+ wb.texel_count_of_weight[qweight[i]]++;
+ max_texel_count_of_weight = astc::max(max_texel_count_of_weight, wb.texel_count_of_weight[qweight[i]]);
+ }
+ }
+ }
+ }
+
+ uint8_t max_texel_weight_count = 0;
+ for (unsigned int i = 0; i < texels_per_block; i++)
+ {
+ di.texel_weight_count[i] = wb.weight_count_of_texel[i];
+ max_texel_weight_count = astc::max(max_texel_weight_count, di.texel_weight_count[i]);
+
+ for (unsigned int j = 0; j < wb.weight_count_of_texel[i]; j++)
+ {
+ di.texel_weight_contribs_int_tr[j][i] = wb.weights_of_texel[i][j];
+ di.texel_weight_contribs_float_tr[j][i] = static_cast<float>(wb.weights_of_texel[i][j]) * (1.0f / WEIGHTS_TEXEL_SUM);
+ di.texel_weights_tr[j][i] = wb.grid_weights_of_texel[i][j];
+ }
+
+ // Init all 4 entries so we can rely on zeros for vectorization
+ for (unsigned int j = wb.weight_count_of_texel[i]; j < 4; j++)
+ {
+ di.texel_weight_contribs_int_tr[j][i] = 0;
+ di.texel_weight_contribs_float_tr[j][i] = 0.0f;
+ di.texel_weights_tr[j][i] = 0;
+ }
+ }
+
+ di.max_texel_weight_count = max_texel_weight_count;
+
+ for (unsigned int i = 0; i < weights_per_block; i++)
+ {
+ unsigned int texel_count_wt = wb.texel_count_of_weight[i];
+ di.weight_texel_count[i] = static_cast<uint8_t>(texel_count_wt);
+
+ for (unsigned int j = 0; j < texel_count_wt; j++)
+ {
+ uint8_t texel = wb.texels_of_weight[i][j];
+
+ // Create transposed versions of these for better vectorization
+ di.weight_texels_tr[j][i] = texel;
+ di.weights_texel_contribs_tr[j][i] = static_cast<float>(wb.texel_weights_of_weight[i][j]);
+
+ // Store the per-texel contribution of this weight for each texel it contributes to
+ di.texel_contrib_for_weight[j][i] = 0.0f;
+ for (unsigned int k = 0; k < 4; k++)
+ {
+ uint8_t dttw = di.texel_weights_tr[k][texel];
+ float dttwf = di.texel_weight_contribs_float_tr[k][texel];
+ if (dttw == i && dttwf != 0.0f)
+ {
+ di.texel_contrib_for_weight[j][i] = di.texel_weight_contribs_float_tr[k][texel];
+ break;
+ }
+ }
+ }
+
+ // Initialize array tail so we can over-fetch with SIMD later to avoid loop tails
+ // Match last texel in active lane in SIMD group, for better gathers
+ uint8_t last_texel = di.weight_texels_tr[texel_count_wt - 1][i];
+ for (unsigned int j = texel_count_wt; j < max_texel_count_of_weight; j++)
+ {
+ di.weight_texels_tr[j][i] = last_texel;
+ di.weights_texel_contribs_tr[j][i] = 0.0f;
+ }
+ }
+
+ // Initialize array tail so we can over-fetch with SIMD later to avoid loop tails
+ unsigned int texels_per_block_simd = round_up_to_simd_multiple_vla(texels_per_block);
+ for (unsigned int i = texels_per_block; i < texels_per_block_simd; i++)
+ {
+ di.texel_weight_count[i] = 0;
+
+ for (unsigned int j = 0; j < 4; j++)
+ {
+ di.texel_weight_contribs_float_tr[j][i] = 0;
+ di.texel_weights_tr[j][i] = 0;
+ di.texel_weight_contribs_int_tr[j][i] = 0;
+ }
+ }
+
+ // Initialize array tail so we can over-fetch with SIMD later to avoid loop tails
+ // Match last texel in active lane in SIMD group, for better gathers
+ unsigned int last_texel_count_wt = wb.texel_count_of_weight[weights_per_block - 1];
+ uint8_t last_texel = di.weight_texels_tr[last_texel_count_wt - 1][weights_per_block - 1];
+
+ unsigned int weights_per_block_simd = round_up_to_simd_multiple_vla(weights_per_block);
+ for (unsigned int i = weights_per_block; i < weights_per_block_simd; i++)
+ {
+ di.weight_texel_count[i] = 0;
+
+ for (unsigned int j = 0; j < max_texel_count_of_weight; j++)
+ {
+ di.weight_texels_tr[j][i] = last_texel;
+ di.weights_texel_contribs_tr[j][i] = 0.0f;
+ }
+ }
+
+ di.texel_count = static_cast<uint8_t>(texels_per_block);
+ di.weight_count = static_cast<uint8_t>(weights_per_block);
+ di.weight_x = static_cast<uint8_t>(x_weights);
+ di.weight_y = static_cast<uint8_t>(y_weights);
+ di.weight_z = 1;
+}
+
+/**
+ * @brief Create a 3D decimation entry for a block-size and weight-decimation pair.
+ *
+ * @param x_texels The number of texels in the X dimension.
+ * @param y_texels The number of texels in the Y dimension.
+ * @param z_texels The number of texels in the Z dimension.
+ * @param x_weights The number of weights in the X dimension.
+ * @param y_weights The number of weights in the Y dimension.
+ * @param z_weights The number of weights in the Z dimension.
+ * @param[out] di The decimation info structure to populate.
+ @param[out] wb The decimation table init scratch working buffers.
+ */
+static void init_decimation_info_3d(
+ unsigned int x_texels,
+ unsigned int y_texels,
+ unsigned int z_texels,
+ unsigned int x_weights,
+ unsigned int y_weights,
+ unsigned int z_weights,
+ decimation_info& di,
+ dt_init_working_buffers& wb
+) {
+ unsigned int texels_per_block = x_texels * y_texels * z_texels;
+ unsigned int weights_per_block = x_weights * y_weights * z_weights;
+
+ uint8_t max_texel_count_of_weight = 0;
+
+ promise(weights_per_block > 0);
+ promise(texels_per_block > 0);
+
+ for (unsigned int i = 0; i < weights_per_block; i++)
+ {
+ wb.texel_count_of_weight[i] = 0;
+ }
+
+ for (unsigned int i = 0; i < texels_per_block; i++)
+ {
+ wb.weight_count_of_texel[i] = 0;
+ }
+
+ for (unsigned int z = 0; z < z_texels; z++)
+ {
+ for (unsigned int y = 0; y < y_texels; y++)
+ {
+ for (unsigned int x = 0; x < x_texels; x++)
+ {
+ int texel = (z * y_texels + y) * x_texels + x;
+
+ int x_weight = (((1024 + x_texels / 2) / (x_texels - 1)) * x * (x_weights - 1) + 32) >> 6;
+ int y_weight = (((1024 + y_texels / 2) / (y_texels - 1)) * y * (y_weights - 1) + 32) >> 6;
+ int z_weight = (((1024 + z_texels / 2) / (z_texels - 1)) * z * (z_weights - 1) + 32) >> 6;
+
+ int x_weight_frac = x_weight & 0xF;
+ int y_weight_frac = y_weight & 0xF;
+ int z_weight_frac = z_weight & 0xF;
+ int x_weight_int = x_weight >> 4;
+ int y_weight_int = y_weight >> 4;
+ int z_weight_int = z_weight >> 4;
+ int qweight[4];
+ int weight[4];
+ qweight[0] = (z_weight_int * y_weights + y_weight_int) * x_weights + x_weight_int;
+ qweight[3] = ((z_weight_int + 1) * y_weights + (y_weight_int + 1)) * x_weights + (x_weight_int + 1);
+
+ // simplex interpolation
+ int fs = x_weight_frac;
+ int ft = y_weight_frac;
+ int fp = z_weight_frac;
+
+ int cas = ((fs > ft) << 2) + ((ft > fp) << 1) + ((fs > fp));
+ int N = x_weights;
+ int NM = x_weights * y_weights;
+
+ int s1, s2, w0, w1, w2, w3;
+ switch (cas)
+ {
+ case 7:
+ s1 = 1;
+ s2 = N;
+ w0 = 16 - fs;
+ w1 = fs - ft;
+ w2 = ft - fp;
+ w3 = fp;
+ break;
+ case 3:
+ s1 = N;
+ s2 = 1;
+ w0 = 16 - ft;
+ w1 = ft - fs;
+ w2 = fs - fp;
+ w3 = fp;
+ break;
+ case 5:
+ s1 = 1;
+ s2 = NM;
+ w0 = 16 - fs;
+ w1 = fs - fp;
+ w2 = fp - ft;
+ w3 = ft;
+ break;
+ case 4:
+ s1 = NM;
+ s2 = 1;
+ w0 = 16 - fp;
+ w1 = fp - fs;
+ w2 = fs - ft;
+ w3 = ft;
+ break;
+ case 2:
+ s1 = N;
+ s2 = NM;
+ w0 = 16 - ft;
+ w1 = ft - fp;
+ w2 = fp - fs;
+ w3 = fs;
+ break;
+ case 0:
+ s1 = NM;
+ s2 = N;
+ w0 = 16 - fp;
+ w1 = fp - ft;
+ w2 = ft - fs;
+ w3 = fs;
+ break;
+ default:
+ s1 = NM;
+ s2 = N;
+ w0 = 16 - fp;
+ w1 = fp - ft;
+ w2 = ft - fs;
+ w3 = fs;
+ break;
+ }
+
+ qweight[1] = qweight[0] + s1;
+ qweight[2] = qweight[1] + s2;
+ weight[0] = w0;
+ weight[1] = w1;
+ weight[2] = w2;
+ weight[3] = w3;
+
+ for (unsigned int i = 0; i < 4; i++)
+ {
+ if (weight[i] != 0)
+ {
+ wb.grid_weights_of_texel[texel][wb.weight_count_of_texel[texel]] = static_cast<uint8_t>(qweight[i]);
+ wb.weights_of_texel[texel][wb.weight_count_of_texel[texel]] = static_cast<uint8_t>(weight[i]);
+ wb.weight_count_of_texel[texel]++;
+ wb.texels_of_weight[qweight[i]][wb.texel_count_of_weight[qweight[i]]] = static_cast<uint8_t>(texel);
+ wb.texel_weights_of_weight[qweight[i]][wb.texel_count_of_weight[qweight[i]]] = static_cast<uint8_t>(weight[i]);
+ wb.texel_count_of_weight[qweight[i]]++;
+ max_texel_count_of_weight = astc::max(max_texel_count_of_weight, wb.texel_count_of_weight[qweight[i]]);
+ }
+ }
+ }
+ }
+ }
+
+ uint8_t max_texel_weight_count = 0;
+ for (unsigned int i = 0; i < texels_per_block; i++)
+ {
+ di.texel_weight_count[i] = wb.weight_count_of_texel[i];
+ max_texel_weight_count = astc::max(max_texel_weight_count, di.texel_weight_count[i]);
+
+ // Init all 4 entries so we can rely on zeros for vectorization
+ for (unsigned int j = 0; j < 4; j++)
+ {
+ di.texel_weight_contribs_int_tr[j][i] = 0;
+ di.texel_weight_contribs_float_tr[j][i] = 0.0f;
+ di.texel_weights_tr[j][i] = 0;
+ }
+
+ for (unsigned int j = 0; j < wb.weight_count_of_texel[i]; j++)
+ {
+ di.texel_weight_contribs_int_tr[j][i] = wb.weights_of_texel[i][j];
+ di.texel_weight_contribs_float_tr[j][i] = static_cast<float>(wb.weights_of_texel[i][j]) * (1.0f / WEIGHTS_TEXEL_SUM);
+ di.texel_weights_tr[j][i] = wb.grid_weights_of_texel[i][j];
+ }
+ }
+
+ di.max_texel_weight_count = max_texel_weight_count;
+
+ for (unsigned int i = 0; i < weights_per_block; i++)
+ {
+ unsigned int texel_count_wt = wb.texel_count_of_weight[i];
+ di.weight_texel_count[i] = static_cast<uint8_t>(texel_count_wt);
+
+ for (unsigned int j = 0; j < texel_count_wt; j++)
+ {
+ unsigned int texel = wb.texels_of_weight[i][j];
+
+ // Create transposed versions of these for better vectorization
+ di.weight_texels_tr[j][i] = static_cast<uint8_t>(texel);
+ di.weights_texel_contribs_tr[j][i] = static_cast<float>(wb.texel_weights_of_weight[i][j]);
+
+ // Store the per-texel contribution of this weight for each texel it contributes to
+ di.texel_contrib_for_weight[j][i] = 0.0f;
+ for (unsigned int k = 0; k < 4; k++)
+ {
+ uint8_t dttw = di.texel_weights_tr[k][texel];
+ float dttwf = di.texel_weight_contribs_float_tr[k][texel];
+ if (dttw == i && dttwf != 0.0f)
+ {
+ di.texel_contrib_for_weight[j][i] = di.texel_weight_contribs_float_tr[k][texel];
+ break;
+ }
+ }
+ }
+
+ // Initialize array tail so we can over-fetch with SIMD later to avoid loop tails
+ // Match last texel in active lane in SIMD group, for better gathers
+ uint8_t last_texel = di.weight_texels_tr[texel_count_wt - 1][i];
+ for (unsigned int j = texel_count_wt; j < max_texel_count_of_weight; j++)
+ {
+ di.weight_texels_tr[j][i] = last_texel;
+ di.weights_texel_contribs_tr[j][i] = 0.0f;
+ }
+ }
+
+ // Initialize array tail so we can over-fetch with SIMD later to avoid loop tails
+ unsigned int texels_per_block_simd = round_up_to_simd_multiple_vla(texels_per_block);
+ for (unsigned int i = texels_per_block; i < texels_per_block_simd; i++)
+ {
+ di.texel_weight_count[i] = 0;
+
+ for (unsigned int j = 0; j < 4; j++)
+ {
+ di.texel_weight_contribs_float_tr[j][i] = 0;
+ di.texel_weights_tr[j][i] = 0;
+ di.texel_weight_contribs_int_tr[j][i] = 0;
+ }
+ }
+
+ // Initialize array tail so we can over-fetch with SIMD later to avoid loop tails
+ // Match last texel in active lane in SIMD group, for better gathers
+ int last_texel_count_wt = wb.texel_count_of_weight[weights_per_block - 1];
+ uint8_t last_texel = di.weight_texels_tr[last_texel_count_wt - 1][weights_per_block - 1];
+
+ unsigned int weights_per_block_simd = round_up_to_simd_multiple_vla(weights_per_block);
+ for (unsigned int i = weights_per_block; i < weights_per_block_simd; i++)
+ {
+ di.weight_texel_count[i] = 0;
+
+ for (int j = 0; j < max_texel_count_of_weight; j++)
+ {
+ di.weight_texels_tr[j][i] = last_texel;
+ di.weights_texel_contribs_tr[j][i] = 0.0f;
+ }
+ }
+
+ di.texel_count = static_cast<uint8_t>(texels_per_block);
+ di.weight_count = static_cast<uint8_t>(weights_per_block);
+ di.weight_x = static_cast<uint8_t>(x_weights);
+ di.weight_y = static_cast<uint8_t>(y_weights);
+ di.weight_z = static_cast<uint8_t>(z_weights);
+}
+
+/**
+ * @brief Assign the texels to use for kmeans clustering.
+ *
+ * The max limit is @c BLOCK_MAX_KMEANS_TEXELS; above this a random selection is used.
+ * The @c bsd.texel_count is an input and must be populated beforehand.
+ *
+ * @param[in,out] bsd The block size descriptor to populate.
+ */
+static void assign_kmeans_texels(
+ block_size_descriptor& bsd
+) {
+ // Use all texels for kmeans on a small block
+ if (bsd.texel_count <= BLOCK_MAX_KMEANS_TEXELS)
+ {
+ for (uint8_t i = 0; i < bsd.texel_count; i++)
+ {
+ bsd.kmeans_texels[i] = i;
+ }
+
+ return;
+ }
+
+ // Select a random subset of BLOCK_MAX_KMEANS_TEXELS for kmeans on a large block
+ uint64_t rng_state[2];
+ astc::rand_init(rng_state);
+
+ // Initialize array used for tracking used indices
+ bool seen[BLOCK_MAX_TEXELS];
+ for (uint8_t i = 0; i < bsd.texel_count; i++)
+ {
+ seen[i] = false;
+ }
+
+ // Assign 64 random indices, retrying if we see repeats
+ unsigned int arr_elements_set = 0;
+ while (arr_elements_set < BLOCK_MAX_KMEANS_TEXELS)
+ {
+ uint8_t texel = static_cast<uint8_t>(astc::rand(rng_state));
+ texel = texel % bsd.texel_count;
+ if (!seen[texel])
+ {
+ bsd.kmeans_texels[arr_elements_set++] = texel;
+ seen[texel] = true;
+ }
+ }
+}
+
+/**
+ * @brief Allocate a single 2D decimation table entry.
+ *
+ * @param x_texels The number of texels in the X dimension.
+ * @param y_texels The number of texels in the Y dimension.
+ * @param x_weights The number of weights in the X dimension.
+ * @param y_weights The number of weights in the Y dimension.
+ * @param bsd The block size descriptor we are populating.
+ * @param wb The decimation table init scratch working buffers.
+ * @param index The packed array index to populate.
+ */
+static void construct_dt_entry_2d(
+ unsigned int x_texels,
+ unsigned int y_texels,
+ unsigned int x_weights,
+ unsigned int y_weights,
+ block_size_descriptor& bsd,
+ dt_init_working_buffers& wb,
+ unsigned int index
+) {
+ unsigned int weight_count = x_weights * y_weights;
+ assert(weight_count <= BLOCK_MAX_WEIGHTS);
+
+ bool try_2planes = (2 * weight_count) <= BLOCK_MAX_WEIGHTS;
+
+ decimation_info& di = bsd.decimation_tables[index];
+ init_decimation_info_2d(x_texels, y_texels, x_weights, y_weights, di, wb);
+
+ int maxprec_1plane = -1;
+ int maxprec_2planes = -1;
+ for (int i = 0; i < 12; i++)
+ {
+ unsigned int bits_1plane = get_ise_sequence_bitcount(weight_count, static_cast<quant_method>(i));
+ if (bits_1plane >= BLOCK_MIN_WEIGHT_BITS && bits_1plane <= BLOCK_MAX_WEIGHT_BITS)
+ {
+ maxprec_1plane = i;
+ }
+
+ if (try_2planes)
+ {
+ unsigned int bits_2planes = get_ise_sequence_bitcount(2 * weight_count, static_cast<quant_method>(i));
+ if (bits_2planes >= BLOCK_MIN_WEIGHT_BITS && bits_2planes <= BLOCK_MAX_WEIGHT_BITS)
+ {
+ maxprec_2planes = i;
+ }
+ }
+ }
+
+ // At least one of the two should be valid ...
+ assert(maxprec_1plane >= 0 || maxprec_2planes >= 0);
+ bsd.decimation_modes[index].maxprec_1plane = static_cast<int8_t>(maxprec_1plane);
+ bsd.decimation_modes[index].maxprec_2planes = static_cast<int8_t>(maxprec_2planes);
+ bsd.decimation_modes[index].refprec_1_plane = 0;
+ bsd.decimation_modes[index].refprec_2_planes = 0;
+}
+
+/**
+ * @brief Allocate block modes and decimation tables for a single 2D block size.
+ *
+ * @param x_texels The number of texels in the X dimension.
+ * @param y_texels The number of texels in the Y dimension.
+ * @param can_omit_modes Can we discard modes that astcenc won't use, even if legal?
+ * @param mode_cutoff Percentile cutoff in range [0,1]. Low values more likely to be used.
+ * @param[out] bsd The block size descriptor to populate.
+ */
+static void construct_block_size_descriptor_2d(
+ unsigned int x_texels,
+ unsigned int y_texels,
+ bool can_omit_modes,
+ float mode_cutoff,
+ block_size_descriptor& bsd
+) {
+ // Store a remap table for storing packed decimation modes.
+ // Indexing uses [Y * 16 + X] and max size for each axis is 12.
+ static const unsigned int MAX_DMI = 12 * 16 + 12;
+ int decimation_mode_index[MAX_DMI];
+
+ dt_init_working_buffers* wb = new dt_init_working_buffers;
+
+ bsd.xdim = static_cast<uint8_t>(x_texels);
+ bsd.ydim = static_cast<uint8_t>(y_texels);
+ bsd.zdim = 1;
+ bsd.texel_count = static_cast<uint8_t>(x_texels * y_texels);
+
+ for (unsigned int i = 0; i < MAX_DMI; i++)
+ {
+ decimation_mode_index[i] = -1;
+ }
+
+ // Gather all the decimation grids that can be used with the current block
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+ const float *percentiles = get_2d_percentile_table(x_texels, y_texels);
+ float always_cutoff = 0.0f;
+#else
+ // Unused in decompress-only builds
+ (void)can_omit_modes;
+ (void)mode_cutoff;
+#endif
+
+ // Construct the list of block formats referencing the decimation tables
+ unsigned int packed_bm_idx = 0;
+ unsigned int packed_dm_idx = 0;
+
+ // Trackers
+ unsigned int bm_counts[4] { 0 };
+ unsigned int dm_counts[4] { 0 };
+
+ // Clear the list to a known-bad value
+ for (unsigned int i = 0; i < WEIGHTS_MAX_BLOCK_MODES; i++)
+ {
+ bsd.block_mode_packed_index[i] = BLOCK_BAD_BLOCK_MODE;
+ }
+
+ // Iterate four times to build a usefully ordered list:
+ // - Pass 0 - keep selected single plane "always" block modes
+ // - Pass 1 - keep selected single plane "non-always" block modes
+ // - Pass 2 - keep select dual plane block modes
+ // - Pass 3 - keep everything else that's legal
+ unsigned int limit = can_omit_modes ? 3 : 4;
+ for (unsigned int j = 0; j < limit; j ++)
+ {
+ for (unsigned int i = 0; i < WEIGHTS_MAX_BLOCK_MODES; i++)
+ {
+ // Skip modes we've already included in a previous pass
+ if (bsd.block_mode_packed_index[i] != BLOCK_BAD_BLOCK_MODE)
+ {
+ continue;
+ }
+
+ // Decode parameters
+ unsigned int x_weights;
+ unsigned int y_weights;
+ bool is_dual_plane;
+ unsigned int quant_mode;
+ unsigned int weight_bits;
+ bool valid = decode_block_mode_2d(i, x_weights, y_weights, is_dual_plane, quant_mode, weight_bits);
+
+ // Always skip invalid encodings for the current block size
+ if (!valid || (x_weights > x_texels) || (y_weights > y_texels))
+ {
+ continue;
+ }
+
+ // Selectively skip dual plane encodings
+ if (((j <= 1) && is_dual_plane) || (j == 2 && !is_dual_plane))
+ {
+ continue;
+ }
+
+ // Always skip encodings we can't physically encode based on
+ // generic encoding bit availability
+ if (is_dual_plane)
+ {
+ // This is the only check we need as only support 1 partition
+ if ((109 - weight_bits) <= 0)
+ {
+ continue;
+ }
+ }
+ else
+ {
+ // This is conservative - fewer bits may be available for > 1 partition
+ if ((111 - weight_bits) <= 0)
+ {
+ continue;
+ }
+ }
+
+ // Selectively skip encodings based on percentile
+ bool percentile_hit = false;
+ #if !defined(ASTCENC_DECOMPRESS_ONLY)
+ if (j == 0)
+ {
+ percentile_hit = percentiles[i] <= always_cutoff;
+ }
+ else
+ {
+ percentile_hit = percentiles[i] <= mode_cutoff;
+ }
+ #endif
+
+ if (j != 3 && !percentile_hit)
+ {
+ continue;
+ }
+
+ // Allocate and initialize the decimation table entry if we've not used it yet
+ int decimation_mode = decimation_mode_index[y_weights * 16 + x_weights];
+ if (decimation_mode < 0)
+ {
+ construct_dt_entry_2d(x_texels, y_texels, x_weights, y_weights, bsd, *wb, packed_dm_idx);
+ decimation_mode_index[y_weights * 16 + x_weights] = packed_dm_idx;
+ decimation_mode = packed_dm_idx;
+
+ dm_counts[j]++;
+ packed_dm_idx++;
+ }
+
+ auto& bm = bsd.block_modes[packed_bm_idx];
+
+ bm.decimation_mode = static_cast<uint8_t>(decimation_mode);
+ bm.quant_mode = static_cast<uint8_t>(quant_mode);
+ bm.is_dual_plane = static_cast<uint8_t>(is_dual_plane);
+ bm.weight_bits = static_cast<uint8_t>(weight_bits);
+ bm.mode_index = static_cast<uint16_t>(i);
+
+ auto& dm = bsd.decimation_modes[decimation_mode];
+
+ if (is_dual_plane)
+ {
+ dm.set_ref_2_plane(bm.get_weight_quant_mode());
+ }
+ else
+ {
+ dm.set_ref_1_plane(bm.get_weight_quant_mode());
+ }
+
+ bsd.block_mode_packed_index[i] = static_cast<uint16_t>(packed_bm_idx);
+
+ packed_bm_idx++;
+ bm_counts[j]++;
+ }
+ }
+
+ bsd.block_mode_count_1plane_always = bm_counts[0];
+ bsd.block_mode_count_1plane_selected = bm_counts[0] + bm_counts[1];
+ bsd.block_mode_count_1plane_2plane_selected = bm_counts[0] + bm_counts[1] + bm_counts[2];
+ bsd.block_mode_count_all = bm_counts[0] + bm_counts[1] + bm_counts[2] + bm_counts[3];
+
+ bsd.decimation_mode_count_always = dm_counts[0];
+ bsd.decimation_mode_count_selected = dm_counts[0] + dm_counts[1] + dm_counts[2];
+ bsd.decimation_mode_count_all = dm_counts[0] + dm_counts[1] + dm_counts[2] + dm_counts[3];
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+ assert(bsd.block_mode_count_1plane_always > 0);
+ assert(bsd.decimation_mode_count_always > 0);
+
+ delete[] percentiles;
+#endif
+
+ // Ensure the end of the array contains valid data (should never get read)
+ for (unsigned int i = bsd.decimation_mode_count_all; i < WEIGHTS_MAX_DECIMATION_MODES; i++)
+ {
+ bsd.decimation_modes[i].maxprec_1plane = -1;
+ bsd.decimation_modes[i].maxprec_2planes = -1;
+ bsd.decimation_modes[i].refprec_1_plane = 0;
+ bsd.decimation_modes[i].refprec_2_planes = 0;
+ }
+
+ // Determine the texels to use for kmeans clustering.
+ assign_kmeans_texels(bsd);
+
+ delete wb;
+}
+
+/**
+ * @brief Allocate block modes and decimation tables for a single 3D block size.
+ *
+ * TODO: This function doesn't include all of the heuristics that we use for 2D block sizes such as
+ * the percentile mode cutoffs. If 3D becomes more widely used we should look at this.
+ *
+ * @param x_texels The number of texels in the X dimension.
+ * @param y_texels The number of texels in the Y dimension.
+ * @param z_texels The number of texels in the Z dimension.
+ * @param[out] bsd The block size descriptor to populate.
+ */
+static void construct_block_size_descriptor_3d(
+ unsigned int x_texels,
+ unsigned int y_texels,
+ unsigned int z_texels,
+ block_size_descriptor& bsd
+) {
+ // Store a remap table for storing packed decimation modes.
+ // Indexing uses [Z * 64 + Y * 8 + X] and max size for each axis is 6.
+ static constexpr unsigned int MAX_DMI = 6 * 64 + 6 * 8 + 6;
+ int decimation_mode_index[MAX_DMI];
+ unsigned int decimation_mode_count = 0;
+
+ dt_init_working_buffers* wb = new dt_init_working_buffers;
+
+ bsd.xdim = static_cast<uint8_t>(x_texels);
+ bsd.ydim = static_cast<uint8_t>(y_texels);
+ bsd.zdim = static_cast<uint8_t>(z_texels);
+ bsd.texel_count = static_cast<uint8_t>(x_texels * y_texels * z_texels);
+
+ for (unsigned int i = 0; i < MAX_DMI; i++)
+ {
+ decimation_mode_index[i] = -1;
+ }
+
+ // gather all the infill-modes that can be used with the current block size
+ for (unsigned int x_weights = 2; x_weights <= x_texels; x_weights++)
+ {
+ for (unsigned int y_weights = 2; y_weights <= y_texels; y_weights++)
+ {
+ for (unsigned int z_weights = 2; z_weights <= z_texels; z_weights++)
+ {
+ unsigned int weight_count = x_weights * y_weights * z_weights;
+ if (weight_count > BLOCK_MAX_WEIGHTS)
+ {
+ continue;
+ }
+
+ decimation_info& di = bsd.decimation_tables[decimation_mode_count];
+ decimation_mode_index[z_weights * 64 + y_weights * 8 + x_weights] = decimation_mode_count;
+ init_decimation_info_3d(x_texels, y_texels, z_texels, x_weights, y_weights, z_weights, di, *wb);
+
+ int maxprec_1plane = -1;
+ int maxprec_2planes = -1;
+ for (unsigned int i = 0; i < 12; i++)
+ {
+ unsigned int bits_1plane = get_ise_sequence_bitcount(weight_count, static_cast<quant_method>(i));
+ if (bits_1plane >= BLOCK_MIN_WEIGHT_BITS && bits_1plane <= BLOCK_MAX_WEIGHT_BITS)
+ {
+ maxprec_1plane = i;
+ }
+
+ unsigned int bits_2planes = get_ise_sequence_bitcount(2 * weight_count, static_cast<quant_method>(i));
+ if (bits_2planes >= BLOCK_MIN_WEIGHT_BITS && bits_2planes <= BLOCK_MAX_WEIGHT_BITS)
+ {
+ maxprec_2planes = i;
+ }
+ }
+
+ if ((2 * weight_count) > BLOCK_MAX_WEIGHTS)
+ {
+ maxprec_2planes = -1;
+ }
+
+ bsd.decimation_modes[decimation_mode_count].maxprec_1plane = static_cast<int8_t>(maxprec_1plane);
+ bsd.decimation_modes[decimation_mode_count].maxprec_2planes = static_cast<int8_t>(maxprec_2planes);
+ bsd.decimation_modes[decimation_mode_count].refprec_1_plane = maxprec_1plane == -1 ? 0 : 0xFFFF;
+ bsd.decimation_modes[decimation_mode_count].refprec_2_planes = maxprec_2planes == -1 ? 0 : 0xFFFF;
+ decimation_mode_count++;
+ }
+ }
+ }
+
+ // Ensure the end of the array contains valid data (should never get read)
+ for (unsigned int i = decimation_mode_count; i < WEIGHTS_MAX_DECIMATION_MODES; i++)
+ {
+ bsd.decimation_modes[i].maxprec_1plane = -1;
+ bsd.decimation_modes[i].maxprec_2planes = -1;
+ bsd.decimation_modes[i].refprec_1_plane = 0;
+ bsd.decimation_modes[i].refprec_2_planes = 0;
+ }
+
+ bsd.decimation_mode_count_always = 0; // Skipped for 3D modes
+ bsd.decimation_mode_count_selected = decimation_mode_count;
+ bsd.decimation_mode_count_all = decimation_mode_count;
+
+ // Construct the list of block formats referencing the decimation tables
+
+ // Clear the list to a known-bad value
+ for (unsigned int i = 0; i < WEIGHTS_MAX_BLOCK_MODES; i++)
+ {
+ bsd.block_mode_packed_index[i] = BLOCK_BAD_BLOCK_MODE;
+ }
+
+ unsigned int packed_idx = 0;
+ unsigned int bm_counts[2] { 0 };
+
+ // Iterate two times to build a usefully ordered list:
+ // - Pass 0 - keep valid single plane block modes
+ // - Pass 1 - keep valid dual plane block modes
+ for (unsigned int j = 0; j < 2; j++)
+ {
+ for (unsigned int i = 0; i < WEIGHTS_MAX_BLOCK_MODES; i++)
+ {
+ // Skip modes we've already included in a previous pass
+ if (bsd.block_mode_packed_index[i] != BLOCK_BAD_BLOCK_MODE)
+ {
+ continue;
+ }
+
+ unsigned int x_weights;
+ unsigned int y_weights;
+ unsigned int z_weights;
+ bool is_dual_plane;
+ unsigned int quant_mode;
+ unsigned int weight_bits;
+
+ bool valid = decode_block_mode_3d(i, x_weights, y_weights, z_weights, is_dual_plane, quant_mode, weight_bits);
+ // Skip invalid encodings
+ if (!valid || x_weights > x_texels || y_weights > y_texels || z_weights > z_texels)
+ {
+ continue;
+ }
+
+ // Skip encodings in the wrong iteration
+ if ((j == 0 && is_dual_plane) || (j == 1 && !is_dual_plane))
+ {
+ continue;
+ }
+
+ // Always skip encodings we can't physically encode based on bit availability
+ if (is_dual_plane)
+ {
+ // This is the only check we need as only support 1 partition
+ if ((109 - weight_bits) <= 0)
+ {
+ continue;
+ }
+ }
+ else
+ {
+ // This is conservative - fewer bits may be available for > 1 partition
+ if ((111 - weight_bits) <= 0)
+ {
+ continue;
+ }
+ }
+
+ int decimation_mode = decimation_mode_index[z_weights * 64 + y_weights * 8 + x_weights];
+ bsd.block_modes[packed_idx].decimation_mode = static_cast<uint8_t>(decimation_mode);
+ bsd.block_modes[packed_idx].quant_mode = static_cast<uint8_t>(quant_mode);
+ bsd.block_modes[packed_idx].weight_bits = static_cast<uint8_t>(weight_bits);
+ bsd.block_modes[packed_idx].is_dual_plane = static_cast<uint8_t>(is_dual_plane);
+ bsd.block_modes[packed_idx].mode_index = static_cast<uint16_t>(i);
+
+ bsd.block_mode_packed_index[i] = static_cast<uint16_t>(packed_idx);
+ bm_counts[j]++;
+ packed_idx++;
+ }
+ }
+
+ bsd.block_mode_count_1plane_always = 0; // Skipped for 3D modes
+ bsd.block_mode_count_1plane_selected = bm_counts[0];
+ bsd.block_mode_count_1plane_2plane_selected = bm_counts[0] + bm_counts[1];
+ bsd.block_mode_count_all = bm_counts[0] + bm_counts[1];
+
+ // Determine the texels to use for kmeans clustering.
+ assign_kmeans_texels(bsd);
+
+ delete wb;
+}
+
+/* See header for documentation. */
+void init_block_size_descriptor(
+ unsigned int x_texels,
+ unsigned int y_texels,
+ unsigned int z_texels,
+ bool can_omit_modes,
+ unsigned int partition_count_cutoff,
+ float mode_cutoff,
+ block_size_descriptor& bsd
+) {
+ if (z_texels > 1)
+ {
+ construct_block_size_descriptor_3d(x_texels, y_texels, z_texels, bsd);
+ }
+ else
+ {
+ construct_block_size_descriptor_2d(x_texels, y_texels, can_omit_modes, mode_cutoff, bsd);
+ }
+
+ init_partition_tables(bsd, can_omit_modes, partition_count_cutoff);
+}
diff --git a/thirdparty/astcenc/astcenc_color_quantize.cpp b/thirdparty/astcenc/astcenc_color_quantize.cpp
new file mode 100644
index 0000000000..edcfe4f853
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_color_quantize.cpp
@@ -0,0 +1,2071 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2021 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+
+/**
+ * @brief Functions for color quantization.
+ *
+ * The design of the color quantization functionality requires the caller to use higher level error
+ * analysis to determine the base encoding that should be used. This earlier analysis will select
+ * the basic type of the endpoint that should be used:
+ *
+ * * Mode: LDR or HDR
+ * * Quantization level
+ * * Channel count: L, LA, RGB, or RGBA
+ * * Endpoint 2 type: Direct color endcode, or scaled from endpoint 1.
+ *
+ * However, this leaves a number of decisions about exactly how to pack the endpoints open. In
+ * particular we need to determine if blue contraction can be used, or/and if delta encoding can be
+ * used. If they can be applied these will allow us to maintain higher precision in the endpoints
+ * without needing additional storage.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "astcenc_internal.h"
+
+/**
+ * @brief Determine the quantized value given a quantization level.
+ *
+ * @param quant_level The quantization level to use.
+ * @param value The value to convert. This may be outside of the 0-255 range and will be
+ * clamped before the value is looked up.
+ *
+ * @return The encoded quantized value. These are not necessarily in order; the compressor
+ * scrambles the values slightly to make hardware implementation easier.
+ */
+static inline uint8_t quant_color(
+ quant_method quant_level,
+ int value
+) {
+ return color_unquant_to_uquant_tables[quant_level - QUANT_6][value];
+}
+
+/**
+ * @brief Quantize an LDR RGB color.
+ *
+ * Since this is a fall-back encoding, we cannot actually fail but must produce a sensible result.
+ * For this encoding @c color0 cannot be larger than @c color1. If @c color0 is actually larger
+ * than @c color1, @c color0 is reduced and @c color1 is increased until the constraint is met.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as (r0, r1, g0, g1, b0, b1).
+ * @param quant_level The quantization level to use.
+ */
+static void quantize_rgb(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[6],
+ quant_method quant_level
+) {
+ float scale = 1.0f / 257.0f;
+
+ float r0 = astc::clamp255f(color0.lane<0>() * scale);
+ float g0 = astc::clamp255f(color0.lane<1>() * scale);
+ float b0 = astc::clamp255f(color0.lane<2>() * scale);
+
+ float r1 = astc::clamp255f(color1.lane<0>() * scale);
+ float g1 = astc::clamp255f(color1.lane<1>() * scale);
+ float b1 = astc::clamp255f(color1.lane<2>() * scale);
+
+ int ri0, gi0, bi0, ri1, gi1, bi1;
+ float rgb0_addon = 0.5f;
+ float rgb1_addon = 0.5f;
+ do
+ {
+ ri0 = quant_color(quant_level, astc::max(astc::flt2int_rd(r0 + rgb0_addon), 0));
+ gi0 = quant_color(quant_level, astc::max(astc::flt2int_rd(g0 + rgb0_addon), 0));
+ bi0 = quant_color(quant_level, astc::max(astc::flt2int_rd(b0 + rgb0_addon), 0));
+ ri1 = quant_color(quant_level, astc::min(astc::flt2int_rd(r1 + rgb1_addon), 255));
+ gi1 = quant_color(quant_level, astc::min(astc::flt2int_rd(g1 + rgb1_addon), 255));
+ bi1 = quant_color(quant_level, astc::min(astc::flt2int_rd(b1 + rgb1_addon), 255));
+
+ rgb0_addon -= 0.2f;
+ rgb1_addon += 0.2f;
+ } while (ri0 + gi0 + bi0 > ri1 + gi1 + bi1);
+
+ output[0] = static_cast<uint8_t>(ri0);
+ output[1] = static_cast<uint8_t>(ri1);
+ output[2] = static_cast<uint8_t>(gi0);
+ output[3] = static_cast<uint8_t>(gi1);
+ output[4] = static_cast<uint8_t>(bi0);
+ output[5] = static_cast<uint8_t>(bi1);
+}
+
+/**
+ * @brief Quantize an LDR RGBA color.
+ *
+ * Since this is a fall-back encoding, we cannot actually fail but must produce a sensible result.
+ * For this encoding @c color0.rgb cannot be larger than @c color1.rgb (this indicates blue
+ * contraction). If @c color0.rgb is actually larger than @c color1.rgb, @c color0.rgb is reduced
+ * and @c color1.rgb is increased until the constraint is met.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as (r0, r1, g0, g1, b0, b1, a0, a1).
+ * @param quant_level The quantization level to use.
+ */
+static void quantize_rgba(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[8],
+ quant_method quant_level
+) {
+ float scale = 1.0f / 257.0f;
+
+ float a0 = astc::clamp255f(color0.lane<3>() * scale);
+ float a1 = astc::clamp255f(color1.lane<3>() * scale);
+
+ output[6] = quant_color(quant_level, astc::flt2int_rtn(a0));
+ output[7] = quant_color(quant_level, astc::flt2int_rtn(a1));
+
+ quantize_rgb(color0, color1, output, quant_level);
+}
+
+/**
+ * @brief Try to quantize an LDR RGB color using blue-contraction.
+ *
+ * Blue-contraction is only usable if encoded color 1 is larger than color 0.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as (r1, r0, g1, g0, b1, b0).
+ * @param quant_level The quantization level to use.
+ *
+ * @return Returns @c false on failure, @c true on success.
+ */
+static bool try_quantize_rgb_blue_contract(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[6],
+ quant_method quant_level
+) {
+ float scale = 1.0f / 257.0f;
+
+ float r0 = color0.lane<0>() * scale;
+ float g0 = color0.lane<1>() * scale;
+ float b0 = color0.lane<2>() * scale;
+
+ float r1 = color1.lane<0>() * scale;
+ float g1 = color1.lane<1>() * scale;
+ float b1 = color1.lane<2>() * scale;
+
+ // Apply inverse blue-contraction. This can produce an overflow; which means BC cannot be used.
+ r0 += (r0 - b0);
+ g0 += (g0 - b0);
+ r1 += (r1 - b1);
+ g1 += (g1 - b1);
+
+ if (r0 < 0.0f || r0 > 255.0f || g0 < 0.0f || g0 > 255.0f || b0 < 0.0f || b0 > 255.0f ||
+ r1 < 0.0f || r1 > 255.0f || g1 < 0.0f || g1 > 255.0f || b1 < 0.0f || b1 > 255.0f)
+ {
+ return false;
+ }
+
+ // Quantize the inverse-blue-contracted color
+ int ri0 = quant_color(quant_level, astc::flt2int_rtn(r0));
+ int gi0 = quant_color(quant_level, astc::flt2int_rtn(g0));
+ int bi0 = quant_color(quant_level, astc::flt2int_rtn(b0));
+
+ int ri1 = quant_color(quant_level, astc::flt2int_rtn(r1));
+ int gi1 = quant_color(quant_level, astc::flt2int_rtn(g1));
+ int bi1 = quant_color(quant_level, astc::flt2int_rtn(b1));
+
+ // If color #1 is not larger than color #0 then blue-contraction cannot be used. Note that
+ // blue-contraction and quantization change this order, which is why we must test afterwards.
+ if (ri1 + gi1 + bi1 <= ri0 + gi0 + bi0)
+ {
+ return false;
+ }
+
+ output[0] = static_cast<uint8_t>(ri1);
+ output[1] = static_cast<uint8_t>(ri0);
+ output[2] = static_cast<uint8_t>(gi1);
+ output[3] = static_cast<uint8_t>(gi0);
+ output[4] = static_cast<uint8_t>(bi1);
+ output[5] = static_cast<uint8_t>(bi0);
+
+ return true;
+}
+
+/**
+ * @brief Try to quantize an LDR RGBA color using blue-contraction.
+ *
+ * Blue-contraction is only usable if encoded color 1 RGB is larger than color 0 RGB.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as (r1, r0, g1, g0, b1, b0, a1, a0).
+ * @param quant_level The quantization level to use.
+ *
+ * @return Returns @c false on failure, @c true on success.
+ */
+static int try_quantize_rgba_blue_contract(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[8],
+ quant_method quant_level
+) {
+ float scale = 1.0f / 257.0f;
+
+ float a0 = astc::clamp255f(color0.lane<3>() * scale);
+ float a1 = astc::clamp255f(color1.lane<3>() * scale);
+
+ output[6] = quant_color(quant_level, astc::flt2int_rtn(a1));
+ output[7] = quant_color(quant_level, astc::flt2int_rtn(a0));
+
+ return try_quantize_rgb_blue_contract(color0, color1, output, quant_level);
+}
+
+/**
+ * @brief Try to quantize an LDR RGB color using delta encoding.
+ *
+ * At decode time we move one bit from the offset to the base and seize another bit as a sign bit;
+ * we then unquantize both values as if they contain one extra bit. If the sum of the offsets is
+ * non-negative, then we encode a regular delta.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as (r0, r1, g0, g1, b0, b1).
+ * @param quant_level The quantization level to use.
+ *
+ * @return Returns @c false on failure, @c true on success.
+ */
+static bool try_quantize_rgb_delta(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[6],
+ quant_method quant_level
+) {
+ float scale = 1.0f / 257.0f;
+
+ float r0 = astc::clamp255f(color0.lane<0>() * scale);
+ float g0 = astc::clamp255f(color0.lane<1>() * scale);
+ float b0 = astc::clamp255f(color0.lane<2>() * scale);
+
+ float r1 = astc::clamp255f(color1.lane<0>() * scale);
+ float g1 = astc::clamp255f(color1.lane<1>() * scale);
+ float b1 = astc::clamp255f(color1.lane<2>() * scale);
+
+ // Transform r0 to unorm9
+ int r0a = astc::flt2int_rtn(r0);
+ int g0a = astc::flt2int_rtn(g0);
+ int b0a = astc::flt2int_rtn(b0);
+
+ r0a <<= 1;
+ g0a <<= 1;
+ b0a <<= 1;
+
+ // Mask off the top bit
+ int r0b = r0a & 0xFF;
+ int g0b = g0a & 0xFF;
+ int b0b = b0a & 0xFF;
+
+ // Quantize then unquantize in order to get a value that we take differences against
+ int r0be = quant_color(quant_level, r0b);
+ int g0be = quant_color(quant_level, g0b);
+ int b0be = quant_color(quant_level, b0b);
+
+ r0b = r0be | (r0a & 0x100);
+ g0b = g0be | (g0a & 0x100);
+ b0b = b0be | (b0a & 0x100);
+
+ // Get hold of the second value
+ int r1d = astc::flt2int_rtn(r1);
+ int g1d = astc::flt2int_rtn(g1);
+ int b1d = astc::flt2int_rtn(b1);
+
+ r1d <<= 1;
+ g1d <<= 1;
+ b1d <<= 1;
+
+ // ... and take differences
+ r1d -= r0b;
+ g1d -= g0b;
+ b1d -= b0b;
+
+ // Check if the difference is too large to be encodable
+ if (r1d > 63 || g1d > 63 || b1d > 63 || r1d < -64 || g1d < -64 || b1d < -64)
+ {
+ return false;
+ }
+
+ // Insert top bit of the base into the offset
+ r1d &= 0x7F;
+ g1d &= 0x7F;
+ b1d &= 0x7F;
+
+ r1d |= (r0b & 0x100) >> 1;
+ g1d |= (g0b & 0x100) >> 1;
+ b1d |= (b0b & 0x100) >> 1;
+
+ // Then quantize and unquantize; if this causes either top two bits to flip, then encoding fails
+ // since we have then corrupted either the top bit of the base or the sign bit of the offset
+ int r1de = quant_color(quant_level, r1d);
+ int g1de = quant_color(quant_level, g1d);
+ int b1de = quant_color(quant_level, b1d);
+
+ if (((r1d ^ r1de) | (g1d ^ g1de) | (b1d ^ b1de)) & 0xC0)
+ {
+ return false;
+ }
+
+ // If the sum of offsets triggers blue-contraction then encoding fails
+ vint4 ep0(r0be, g0be, b0be, 0);
+ vint4 ep1(r1de, g1de, b1de, 0);
+ bit_transfer_signed(ep1, ep0);
+ if (hadd_rgb_s(ep1) < 0)
+ {
+ return false;
+ }
+
+ // Check that the offsets produce legitimate sums as well
+ ep0 = ep0 + ep1;
+ if (any((ep0 < vint4(0)) | (ep0 > vint4(0xFF))))
+ {
+ return false;
+ }
+
+ output[0] = static_cast<uint8_t>(r0be);
+ output[1] = static_cast<uint8_t>(r1de);
+ output[2] = static_cast<uint8_t>(g0be);
+ output[3] = static_cast<uint8_t>(g1de);
+ output[4] = static_cast<uint8_t>(b0be);
+ output[5] = static_cast<uint8_t>(b1de);
+
+ return true;
+}
+
+static bool try_quantize_rgb_delta_blue_contract(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[6],
+ quant_method quant_level
+) {
+ // Note: Switch around endpoint colors already at start
+ float scale = 1.0f / 257.0f;
+
+ float r1 = color0.lane<0>() * scale;
+ float g1 = color0.lane<1>() * scale;
+ float b1 = color0.lane<2>() * scale;
+
+ float r0 = color1.lane<0>() * scale;
+ float g0 = color1.lane<1>() * scale;
+ float b0 = color1.lane<2>() * scale;
+
+ // Apply inverse blue-contraction. This can produce an overflow; which means BC cannot be used.
+ r0 += (r0 - b0);
+ g0 += (g0 - b0);
+ r1 += (r1 - b1);
+ g1 += (g1 - b1);
+
+ if (r0 < 0.0f || r0 > 255.0f || g0 < 0.0f || g0 > 255.0f || b0 < 0.0f || b0 > 255.0f ||
+ r1 < 0.0f || r1 > 255.0f || g1 < 0.0f || g1 > 255.0f || b1 < 0.0f || b1 > 255.0f)
+ {
+ return false;
+ }
+
+ // Transform r0 to unorm9
+ int r0a = astc::flt2int_rtn(r0);
+ int g0a = astc::flt2int_rtn(g0);
+ int b0a = astc::flt2int_rtn(b0);
+ r0a <<= 1;
+ g0a <<= 1;
+ b0a <<= 1;
+
+ // Mask off the top bit
+ int r0b = r0a & 0xFF;
+ int g0b = g0a & 0xFF;
+ int b0b = b0a & 0xFF;
+
+ // Quantize, then unquantize in order to get a value that we take differences against.
+ int r0be = quant_color(quant_level, r0b);
+ int g0be = quant_color(quant_level, g0b);
+ int b0be = quant_color(quant_level, b0b);
+
+ r0b = r0be | (r0a & 0x100);
+ g0b = g0be | (g0a & 0x100);
+ b0b = b0be | (b0a & 0x100);
+
+ // Get hold of the second value
+ int r1d = astc::flt2int_rtn(r1);
+ int g1d = astc::flt2int_rtn(g1);
+ int b1d = astc::flt2int_rtn(b1);
+
+ r1d <<= 1;
+ g1d <<= 1;
+ b1d <<= 1;
+
+ // .. and take differences!
+ r1d -= r0b;
+ g1d -= g0b;
+ b1d -= b0b;
+
+ // Check if the difference is too large to be encodable
+ if (r1d > 63 || g1d > 63 || b1d > 63 || r1d < -64 || g1d < -64 || b1d < -64)
+ {
+ return false;
+ }
+
+ // Insert top bit of the base into the offset
+ r1d &= 0x7F;
+ g1d &= 0x7F;
+ b1d &= 0x7F;
+
+ r1d |= (r0b & 0x100) >> 1;
+ g1d |= (g0b & 0x100) >> 1;
+ b1d |= (b0b & 0x100) >> 1;
+
+ // Then quantize and unquantize; if this causes any of the top two bits to flip,
+ // then encoding fails, since we have then corrupted either the top bit of the base
+ // or the sign bit of the offset.
+ int r1de = quant_color(quant_level, r1d);
+ int g1de = quant_color(quant_level, g1d);
+ int b1de = quant_color(quant_level, b1d);
+
+ if (((r1d ^ r1de) | (g1d ^ g1de) | (b1d ^ b1de)) & 0xC0)
+ {
+ return false;
+ }
+
+ // If the sum of offsets does not trigger blue-contraction then encoding fails
+ vint4 ep0(r0be, g0be, b0be, 0);
+ vint4 ep1(r1de, g1de, b1de, 0);
+ bit_transfer_signed(ep1, ep0);
+ if (hadd_rgb_s(ep1) >= 0)
+ {
+ return false;
+ }
+
+ // Check that the offsets produce legitimate sums as well
+ ep0 = ep0 + ep1;
+ if (any((ep0 < vint4(0)) | (ep0 > vint4(0xFF))))
+ {
+ return false;
+ }
+
+ output[0] = static_cast<uint8_t>(r0be);
+ output[1] = static_cast<uint8_t>(r1de);
+ output[2] = static_cast<uint8_t>(g0be);
+ output[3] = static_cast<uint8_t>(g1de);
+ output[4] = static_cast<uint8_t>(b0be);
+ output[5] = static_cast<uint8_t>(b1de);
+
+ return true;
+}
+
+/**
+ * @brief Try to quantize an LDR A color using delta encoding.
+ *
+ * At decode time we move one bit from the offset to the base and seize another bit as a sign bit;
+ * we then unquantize both values as if they contain one extra bit. If the sum of the offsets is
+ * non-negative, then we encode a regular delta.
+ *
+ * This function only compressed the alpha - the other elements in the output array are not touched.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as (x, x, x, x, x, x, a0, a1).
+ * @param quant_level The quantization level to use.
+ *
+ * @return Returns @c false on failure, @c true on success.
+ */
+static bool try_quantize_alpha_delta(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[8],
+ quant_method quant_level
+) {
+ float scale = 1.0f / 257.0f;
+
+ float a0 = astc::clamp255f(color0.lane<3>() * scale);
+ float a1 = astc::clamp255f(color1.lane<3>() * scale);
+
+ int a0a = astc::flt2int_rtn(a0);
+ a0a <<= 1;
+ int a0b = a0a & 0xFF;
+ int a0be = quant_color(quant_level, a0b);
+ a0b = a0be;
+ a0b |= a0a & 0x100;
+ int a1d = astc::flt2int_rtn(a1);
+ a1d <<= 1;
+ a1d -= a0b;
+
+ if (a1d > 63 || a1d < -64)
+ {
+ return false;
+ }
+
+ a1d &= 0x7F;
+ a1d |= (a0b & 0x100) >> 1;
+
+ int a1de = quant_color(quant_level, a1d);
+ int a1du = a1de;
+ if ((a1d ^ a1du) & 0xC0)
+ {
+ return false;
+ }
+
+ a1du &= 0x7F;
+ if (a1du & 0x40)
+ {
+ a1du -= 0x80;
+ }
+
+ a1du += a0b;
+ if (a1du < 0 || a1du > 0x1FF)
+ {
+ return false;
+ }
+
+ output[6] = static_cast<uint8_t>(a0be);
+ output[7] = static_cast<uint8_t>(a1de);
+
+ return true;
+}
+
+/**
+ * @brief Try to quantize an LDR LA color using delta encoding.
+ *
+ * At decode time we move one bit from the offset to the base and seize another bit as a sign bit;
+ * we then unquantize both values as if they contain one extra bit. If the sum of the offsets is
+ * non-negative, then we encode a regular delta.
+ *
+ * This function only compressed the alpha - the other elements in the output array are not touched.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as (l0, l1, a0, a1).
+ * @param quant_level The quantization level to use.
+ *
+ * @return Returns @c false on failure, @c true on success.
+ */
+static bool try_quantize_luminance_alpha_delta(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[4],
+ quant_method quant_level
+) {
+ float scale = 1.0f / 257.0f;
+
+ float l0 = astc::clamp255f(hadd_rgb_s(color0) * ((1.0f / 3.0f) * scale));
+ float l1 = astc::clamp255f(hadd_rgb_s(color1) * ((1.0f / 3.0f) * scale));
+
+ float a0 = astc::clamp255f(color0.lane<3>() * scale);
+ float a1 = astc::clamp255f(color1.lane<3>() * scale);
+
+ int l0a = astc::flt2int_rtn(l0);
+ int a0a = astc::flt2int_rtn(a0);
+ l0a <<= 1;
+ a0a <<= 1;
+
+ int l0b = l0a & 0xFF;
+ int a0b = a0a & 0xFF;
+ int l0be = quant_color(quant_level, l0b);
+ int a0be = quant_color(quant_level, a0b);
+ l0b = l0be;
+ a0b = a0be;
+ l0b |= l0a & 0x100;
+ a0b |= a0a & 0x100;
+
+ int l1d = astc::flt2int_rtn(l1);
+ int a1d = astc::flt2int_rtn(a1);
+ l1d <<= 1;
+ a1d <<= 1;
+ l1d -= l0b;
+ a1d -= a0b;
+
+ if (l1d > 63 || l1d < -64)
+ {
+ return false;
+ }
+
+ if (a1d > 63 || a1d < -64)
+ {
+ return false;
+ }
+
+ l1d &= 0x7F;
+ a1d &= 0x7F;
+ l1d |= (l0b & 0x100) >> 1;
+ a1d |= (a0b & 0x100) >> 1;
+
+ int l1de = quant_color(quant_level, l1d);
+ int a1de = quant_color(quant_level, a1d);
+ int l1du = l1de;
+ int a1du = a1de;
+
+ if ((l1d ^ l1du) & 0xC0)
+ {
+ return false;
+ }
+
+ if ((a1d ^ a1du) & 0xC0)
+ {
+ return false;
+ }
+
+ l1du &= 0x7F;
+ a1du &= 0x7F;
+
+ if (l1du & 0x40)
+ {
+ l1du -= 0x80;
+ }
+
+ if (a1du & 0x40)
+ {
+ a1du -= 0x80;
+ }
+
+ l1du += l0b;
+ a1du += a0b;
+
+ if (l1du < 0 || l1du > 0x1FF)
+ {
+ return false;
+ }
+
+ if (a1du < 0 || a1du > 0x1FF)
+ {
+ return false;
+ }
+
+ output[0] = static_cast<uint8_t>(l0be);
+ output[1] = static_cast<uint8_t>(l1de);
+ output[2] = static_cast<uint8_t>(a0be);
+ output[3] = static_cast<uint8_t>(a1de);
+
+ return true;
+}
+
+/**
+ * @brief Try to quantize an LDR RGBA color using delta encoding.
+ *
+ * At decode time we move one bit from the offset to the base and seize another bit as a sign bit;
+ * we then unquantize both values as if they contain one extra bit. If the sum of the offsets is
+ * non-negative, then we encode a regular delta.
+ *
+ * This function only compressed the alpha - the other elements in the output array are not touched.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as (r0, r1, b0, b1, g0, g1, a0, a1).
+ * @param quant_level The quantization level to use.
+ *
+ * @return Returns @c false on failure, @c true on success.
+ */
+static bool try_quantize_rgba_delta(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[8],
+ quant_method quant_level
+) {
+ return try_quantize_rgb_delta(color0, color1, output, quant_level) &&
+ try_quantize_alpha_delta(color0, color1, output, quant_level);
+}
+
+
+/**
+ * @brief Try to quantize an LDR RGBA color using delta and blue contract encoding.
+ *
+ * At decode time we move one bit from the offset to the base and seize another bit as a sign bit;
+ * we then unquantize both values as if they contain one extra bit. If the sum of the offsets is
+ * non-negative, then we encode a regular delta.
+ *
+ * This function only compressed the alpha - the other elements in the output array are not touched.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as (r0, r1, b0, b1, g0, g1, a0, a1).
+ * @param quant_level The quantization level to use.
+ *
+ * @return Returns @c false on failure, @c true on success.
+ */
+static bool try_quantize_rgba_delta_blue_contract(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[8],
+ quant_method quant_level
+) {
+ // Note that we swap the color0 and color1 ordering for alpha to match RGB blue-contract
+ return try_quantize_rgb_delta_blue_contract(color0, color1, output, quant_level) &&
+ try_quantize_alpha_delta(color1, color0, output, quant_level);
+}
+
+/**
+ * @brief Quantize an LDR RGB color using scale encoding.
+ *
+ * @param color The input unquantized color endpoint and scale factor.
+ * @param[out] output The output endpoints, returned as (r0, g0, b0, s).
+ * @param quant_level The quantization level to use.
+ */
+static void quantize_rgbs(
+ vfloat4 color,
+ uint8_t output[4],
+ quant_method quant_level
+) {
+ float scale = 1.0f / 257.0f;
+
+ float r = astc::clamp255f(color.lane<0>() * scale);
+ float g = astc::clamp255f(color.lane<1>() * scale);
+ float b = astc::clamp255f(color.lane<2>() * scale);
+
+ int ri = quant_color(quant_level, astc::flt2int_rtn(r));
+ int gi = quant_color(quant_level, astc::flt2int_rtn(g));
+ int bi = quant_color(quant_level, astc::flt2int_rtn(b));
+
+ float oldcolorsum = hadd_rgb_s(color) * scale;
+ float newcolorsum = static_cast<float>(ri + gi + bi);
+
+ float scalea = astc::clamp1f(color.lane<3>() * (oldcolorsum + 1e-10f) / (newcolorsum + 1e-10f));
+ int scale_idx = astc::flt2int_rtn(scalea * 256.0f);
+ scale_idx = astc::clamp(scale_idx, 0, 255);
+
+ output[0] = static_cast<uint8_t>(ri);
+ output[1] = static_cast<uint8_t>(gi);
+ output[2] = static_cast<uint8_t>(bi);
+ output[3] = quant_color(quant_level, scale_idx);
+}
+
+/**
+ * @brief Quantize an LDR RGBA color using scale encoding.
+ *
+ * @param color The input unquantized color endpoint and scale factor.
+ * @param[out] output The output endpoints, returned as (r0, g0, b0, s, a0, a1).
+ * @param quant_level The quantization level to use.
+ */
+static void quantize_rgbs_alpha(
+ vfloat4 color0,
+ vfloat4 color1,
+ vfloat4 color,
+ uint8_t output[6],
+ quant_method quant_level
+) {
+ float scale = 1.0f / 257.0f;
+
+ float a0 = astc::clamp255f(color0.lane<3>() * scale);
+ float a1 = astc::clamp255f(color1.lane<3>() * scale);
+
+ output[4] = quant_color(quant_level, astc::flt2int_rtn(a0));
+ output[5] = quant_color(quant_level, astc::flt2int_rtn(a1));
+
+ quantize_rgbs(color, output, quant_level);
+}
+
+/**
+ * @brief Quantize a LDR L color.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as (l0, l1).
+ * @param quant_level The quantization level to use.
+ */
+static void quantize_luminance(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[2],
+ quant_method quant_level
+) {
+ float scale = 1.0f / 257.0f;
+
+ color0 = color0 * scale;
+ color1 = color1 * scale;
+
+ float lum0 = astc::clamp255f(hadd_rgb_s(color0) * (1.0f / 3.0f));
+ float lum1 = astc::clamp255f(hadd_rgb_s(color1) * (1.0f / 3.0f));
+
+ if (lum0 > lum1)
+ {
+ float avg = (lum0 + lum1) * 0.5f;
+ lum0 = avg;
+ lum1 = avg;
+ }
+
+ output[0] = quant_color(quant_level, astc::flt2int_rtn(lum0));
+ output[1] = quant_color(quant_level, astc::flt2int_rtn(lum1));
+}
+
+/**
+ * @brief Quantize a LDR LA color.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as (l0, l1, a0, a1).
+ * @param quant_level The quantization level to use.
+ */
+static void quantize_luminance_alpha(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[4],
+ quant_method quant_level
+) {
+ float scale = 1.0f / 257.0f;
+
+ color0 = color0 * scale;
+ color1 = color1 * scale;
+
+ float lum0 = astc::clamp255f(hadd_rgb_s(color0) * (1.0f / 3.0f));
+ float lum1 = astc::clamp255f(hadd_rgb_s(color1) * (1.0f / 3.0f));
+
+ float a0 = astc::clamp255f(color0.lane<3>());
+ float a1 = astc::clamp255f(color1.lane<3>());
+
+ // If endpoints are close then pull apart slightly; this gives > 8 bit normal map precision.
+ if (quant_level > 18)
+ {
+ if (fabsf(lum0 - lum1) < 3.0f)
+ {
+ if (lum0 < lum1)
+ {
+ lum0 -= 0.5f;
+ lum1 += 0.5f;
+ }
+ else
+ {
+ lum0 += 0.5f;
+ lum1 -= 0.5f;
+ }
+
+ lum0 = astc::clamp255f(lum0);
+ lum1 = astc::clamp255f(lum1);
+ }
+
+ if (fabsf(a0 - a1) < 3.0f)
+ {
+ if (a0 < a1)
+ {
+ a0 -= 0.5f;
+ a1 += 0.5f;
+ }
+ else
+ {
+ a0 += 0.5f;
+ a1 -= 0.5f;
+ }
+
+ a0 = astc::clamp255f(a0);
+ a1 = astc::clamp255f(a1);
+ }
+ }
+
+ output[0] = quant_color(quant_level, astc::flt2int_rtn(lum0));
+ output[1] = quant_color(quant_level, astc::flt2int_rtn(lum1));
+ output[2] = quant_color(quant_level, astc::flt2int_rtn(a0));
+ output[3] = quant_color(quant_level, astc::flt2int_rtn(a1));
+}
+
+/**
+ * @brief Quantize and unquantize a value ensuring top two bits are the same.
+ *
+ * @param quant_level The quantization level to use.
+ * @param value The input unquantized value.
+ * @param[out] quant_value The quantized value.
+ */
+static inline void quantize_and_unquantize_retain_top_two_bits(
+ quant_method quant_level,
+ uint8_t value,
+ uint8_t& quant_value
+) {
+ int perform_loop;
+ uint8_t quantval;
+
+ do
+ {
+ quantval = quant_color(quant_level, value);
+
+ // Perform looping if the top two bits were modified by quant/unquant
+ perform_loop = (value & 0xC0) != (quantval & 0xC0);
+
+ if ((quantval & 0xC0) > (value & 0xC0))
+ {
+ // Quant/unquant rounded UP so that the top two bits changed;
+ // decrement the input in hopes that this will avoid rounding up.
+ value--;
+ }
+ else if ((quantval & 0xC0) < (value & 0xC0))
+ {
+ // Quant/unquant rounded DOWN so that the top two bits changed;
+ // decrement the input in hopes that this will avoid rounding down.
+ value--;
+ }
+ } while (perform_loop);
+
+ quant_value = quantval;
+}
+
+/**
+ * @brief Quantize and unquantize a value ensuring top four bits are the same.
+ *
+ * @param quant_level The quantization level to use.
+ * @param value The input unquantized value.
+ * @param[out] quant_value The quantized value in 0-255 range.
+ */
+static inline void quantize_and_unquantize_retain_top_four_bits(
+ quant_method quant_level,
+ uint8_t value,
+ uint8_t& quant_value
+) {
+ uint8_t perform_loop;
+ uint8_t quantval;
+
+ do
+ {
+ quantval = quant_color(quant_level, value);
+ // Perform looping if the top four bits were modified by quant/unquant
+ perform_loop = (value & 0xF0) != (quantval & 0xF0);
+
+ if ((quantval & 0xF0) > (value & 0xF0))
+ {
+ // Quant/unquant rounded UP so that the top four bits changed;
+ // decrement the input value in hopes that this will avoid rounding up.
+ value--;
+ }
+ else if ((quantval & 0xF0) < (value & 0xF0))
+ {
+ // Quant/unquant rounded DOWN so that the top four bits changed;
+ // decrement the input value in hopes that this will avoid rounding down.
+ value--;
+ }
+ } while (perform_loop);
+
+ quant_value = quantval;
+}
+
+/**
+ * @brief Quantize a HDR RGB color using RGB + offset.
+ *
+ * @param color The input unquantized color endpoint and offset.
+ * @param[out] output The output endpoints, returned as packed RGBS with some mode bits.
+ * @param quant_level The quantization level to use.
+ */
+static void quantize_hdr_rgbo(
+ vfloat4 color,
+ uint8_t output[4],
+ quant_method quant_level
+) {
+ color.set_lane<0>(color.lane<0>() + color.lane<3>());
+ color.set_lane<1>(color.lane<1>() + color.lane<3>());
+ color.set_lane<2>(color.lane<2>() + color.lane<3>());
+
+ color = clamp(0.0f, 65535.0f, color);
+
+ vfloat4 color_bak = color;
+
+ int majcomp;
+ if (color.lane<0>() > color.lane<1>() && color.lane<0>() > color.lane<2>())
+ {
+ majcomp = 0; // red is largest component
+ }
+ else if (color.lane<1>() > color.lane<2>())
+ {
+ majcomp = 1; // green is largest component
+ }
+ else
+ {
+ majcomp = 2; // blue is largest component
+ }
+
+ // swap around the red component and the largest component.
+ switch (majcomp)
+ {
+ case 1:
+ color = color.swz<1, 0, 2, 3>();
+ break;
+ case 2:
+ color = color.swz<2, 1, 0, 3>();
+ break;
+ default:
+ break;
+ }
+
+ static const int mode_bits[5][3] {
+ {11, 5, 7},
+ {11, 6, 5},
+ {10, 5, 8},
+ {9, 6, 7},
+ {8, 7, 6}
+ };
+
+ static const float mode_cutoffs[5][2] {
+ {1024, 4096},
+ {2048, 1024},
+ {2048, 16384},
+ {8192, 16384},
+ {32768, 16384}
+ };
+
+ static const float mode_rscales[5] {
+ 32.0f,
+ 32.0f,
+ 64.0f,
+ 128.0f,
+ 256.0f,
+ };
+
+ static const float mode_scales[5] {
+ 1.0f / 32.0f,
+ 1.0f / 32.0f,
+ 1.0f / 64.0f,
+ 1.0f / 128.0f,
+ 1.0f / 256.0f,
+ };
+
+ float r_base = color.lane<0>();
+ float g_base = color.lane<0>() - color.lane<1>() ;
+ float b_base = color.lane<0>() - color.lane<2>() ;
+ float s_base = color.lane<3>() ;
+
+ for (int mode = 0; mode < 5; mode++)
+ {
+ if (g_base > mode_cutoffs[mode][0] || b_base > mode_cutoffs[mode][0] || s_base > mode_cutoffs[mode][1])
+ {
+ continue;
+ }
+
+ // Encode the mode into a 4-bit vector
+ int mode_enc = mode < 4 ? (mode | (majcomp << 2)) : (majcomp | 0xC);
+
+ float mode_scale = mode_scales[mode];
+ float mode_rscale = mode_rscales[mode];
+
+ int gb_intcutoff = 1 << mode_bits[mode][1];
+ int s_intcutoff = 1 << mode_bits[mode][2];
+
+ // Quantize and unquantize R
+ int r_intval = astc::flt2int_rtn(r_base * mode_scale);
+
+ int r_lowbits = r_intval & 0x3f;
+
+ r_lowbits |= (mode_enc & 3) << 6;
+
+ uint8_t r_quantval;
+ quantize_and_unquantize_retain_top_two_bits(
+ quant_level, static_cast<uint8_t>(r_lowbits), r_quantval);
+
+ r_intval = (r_intval & ~0x3f) | (r_quantval & 0x3f);
+ float r_fval = static_cast<float>(r_intval) * mode_rscale;
+
+ // Recompute G and B, then quantize and unquantize them
+ float g_fval = r_fval - color.lane<1>() ;
+ float b_fval = r_fval - color.lane<2>() ;
+
+ g_fval = astc::clamp(g_fval, 0.0f, 65535.0f);
+ b_fval = astc::clamp(b_fval, 0.0f, 65535.0f);
+
+ int g_intval = astc::flt2int_rtn(g_fval * mode_scale);
+ int b_intval = astc::flt2int_rtn(b_fval * mode_scale);
+
+ if (g_intval >= gb_intcutoff || b_intval >= gb_intcutoff)
+ {
+ continue;
+ }
+
+ int g_lowbits = g_intval & 0x1f;
+ int b_lowbits = b_intval & 0x1f;
+
+ int bit0 = 0;
+ int bit1 = 0;
+ int bit2 = 0;
+ int bit3 = 0;
+
+ switch (mode)
+ {
+ case 0:
+ case 2:
+ bit0 = (r_intval >> 9) & 1;
+ break;
+ case 1:
+ case 3:
+ bit0 = (r_intval >> 8) & 1;
+ break;
+ case 4:
+ case 5:
+ bit0 = (g_intval >> 6) & 1;
+ break;
+ }
+
+ switch (mode)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 3:
+ bit2 = (r_intval >> 7) & 1;
+ break;
+ case 4:
+ case 5:
+ bit2 = (b_intval >> 6) & 1;
+ break;
+ }
+
+ switch (mode)
+ {
+ case 0:
+ case 2:
+ bit1 = (r_intval >> 8) & 1;
+ break;
+ case 1:
+ case 3:
+ case 4:
+ case 5:
+ bit1 = (g_intval >> 5) & 1;
+ break;
+ }
+
+ switch (mode)
+ {
+ case 0:
+ bit3 = (r_intval >> 10) & 1;
+ break;
+ case 2:
+ bit3 = (r_intval >> 6) & 1;
+ break;
+ case 1:
+ case 3:
+ case 4:
+ case 5:
+ bit3 = (b_intval >> 5) & 1;
+ break;
+ }
+
+ g_lowbits |= (mode_enc & 0x4) << 5;
+ b_lowbits |= (mode_enc & 0x8) << 4;
+
+ g_lowbits |= bit0 << 6;
+ g_lowbits |= bit1 << 5;
+ b_lowbits |= bit2 << 6;
+ b_lowbits |= bit3 << 5;
+
+ uint8_t g_quantval;
+ uint8_t b_quantval;
+
+ quantize_and_unquantize_retain_top_four_bits(
+ quant_level, static_cast<uint8_t>(g_lowbits), g_quantval);
+ quantize_and_unquantize_retain_top_four_bits(
+ quant_level, static_cast<uint8_t>(b_lowbits), b_quantval);
+
+ g_intval = (g_intval & ~0x1f) | (g_quantval & 0x1f);
+ b_intval = (b_intval & ~0x1f) | (b_quantval & 0x1f);
+
+ g_fval = static_cast<float>(g_intval) * mode_rscale;
+ b_fval = static_cast<float>(b_intval) * mode_rscale;
+
+ // Recompute the scale value, based on the errors introduced to red, green and blue
+
+ // If the error is positive, then the R,G,B errors combined have raised the color
+ // value overall; as such, the scale value needs to be increased.
+ float rgb_errorsum = (r_fval - color.lane<0>() ) + (r_fval - g_fval - color.lane<1>() ) + (r_fval - b_fval - color.lane<2>() );
+
+ float s_fval = s_base + rgb_errorsum * (1.0f / 3.0f);
+ s_fval = astc::clamp(s_fval, 0.0f, 1e9f);
+
+ int s_intval = astc::flt2int_rtn(s_fval * mode_scale);
+
+ if (s_intval >= s_intcutoff)
+ {
+ continue;
+ }
+
+ int s_lowbits = s_intval & 0x1f;
+
+ int bit4;
+ int bit5;
+ int bit6;
+ switch (mode)
+ {
+ case 1:
+ bit6 = (r_intval >> 9) & 1;
+ break;
+ default:
+ bit6 = (s_intval >> 5) & 1;
+ break;
+ }
+
+ switch (mode)
+ {
+ case 4:
+ bit5 = (r_intval >> 7) & 1;
+ break;
+ case 1:
+ bit5 = (r_intval >> 10) & 1;
+ break;
+ default:
+ bit5 = (s_intval >> 6) & 1;
+ break;
+ }
+
+ switch (mode)
+ {
+ case 2:
+ bit4 = (s_intval >> 7) & 1;
+ break;
+ default:
+ bit4 = (r_intval >> 6) & 1;
+ break;
+ }
+
+ s_lowbits |= bit6 << 5;
+ s_lowbits |= bit5 << 6;
+ s_lowbits |= bit4 << 7;
+
+ uint8_t s_quantval;
+
+ quantize_and_unquantize_retain_top_four_bits(
+ quant_level, static_cast<uint8_t>(s_lowbits), s_quantval);
+
+ output[0] = r_quantval;
+ output[1] = g_quantval;
+ output[2] = b_quantval;
+ output[3] = s_quantval;
+ return;
+ }
+
+ // Failed to encode any of the modes above? In that case encode using mode #5
+ float vals[4];
+ vals[0] = color_bak.lane<0>();
+ vals[1] = color_bak.lane<1>();
+ vals[2] = color_bak.lane<2>();
+ vals[3] = color_bak.lane<3>();
+
+ int ivals[4];
+ float cvals[3];
+
+ for (int i = 0; i < 3; i++)
+ {
+ vals[i] = astc::clamp(vals[i], 0.0f, 65020.0f);
+ ivals[i] = astc::flt2int_rtn(vals[i] * (1.0f / 512.0f));
+ cvals[i] = static_cast<float>(ivals[i]) * 512.0f;
+ }
+
+ float rgb_errorsum = (cvals[0] - vals[0]) + (cvals[1] - vals[1]) + (cvals[2] - vals[2]);
+ vals[3] += rgb_errorsum * (1.0f / 3.0f);
+
+ vals[3] = astc::clamp(vals[3], 0.0f, 65020.0f);
+ ivals[3] = astc::flt2int_rtn(vals[3] * (1.0f / 512.0f));
+
+ int encvals[4];
+ encvals[0] = (ivals[0] & 0x3f) | 0xC0;
+ encvals[1] = (ivals[1] & 0x7f) | 0x80;
+ encvals[2] = (ivals[2] & 0x7f) | 0x80;
+ encvals[3] = (ivals[3] & 0x7f) | ((ivals[0] & 0x40) << 1);
+
+ for (uint8_t i = 0; i < 4; i++)
+ {
+ quantize_and_unquantize_retain_top_four_bits(
+ quant_level, static_cast<uint8_t>(encvals[i]), output[i]);
+ }
+
+ return;
+}
+
+/**
+ * @brief Quantize a HDR RGB color using direct RGB encoding.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as packed RGB+RGB pairs with mode bits.
+ * @param quant_level The quantization level to use.
+ */
+static void quantize_hdr_rgb(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[6],
+ quant_method quant_level
+) {
+ // Note: color*.lane<3> is not used so we can ignore it
+ color0 = clamp(0.0f, 65535.0f, color0);
+ color1 = clamp(0.0f, 65535.0f, color1);
+
+ vfloat4 color0_bak = color0;
+ vfloat4 color1_bak = color1;
+
+ int majcomp;
+ if (color1.lane<0>() > color1.lane<1>() && color1.lane<0>() > color1.lane<2>())
+ {
+ majcomp = 0;
+ }
+ else if (color1.lane<1>() > color1.lane<2>())
+ {
+ majcomp = 1;
+ }
+ else
+ {
+ majcomp = 2;
+ }
+
+ // Swizzle the components
+ switch (majcomp)
+ {
+ case 1: // red-green swap
+ color0 = color0.swz<1, 0, 2, 3>();
+ color1 = color1.swz<1, 0, 2, 3>();
+ break;
+ case 2: // red-blue swap
+ color0 = color0.swz<2, 1, 0, 3>();
+ color1 = color1.swz<2, 1, 0, 3>();
+ break;
+ default:
+ break;
+ }
+
+ float a_base = color1.lane<0>();
+ a_base = astc::clamp(a_base, 0.0f, 65535.0f);
+
+ float b0_base = a_base - color1.lane<1>();
+ float b1_base = a_base - color1.lane<2>();
+ float c_base = a_base - color0.lane<0>();
+ float d0_base = a_base - b0_base - c_base - color0.lane<1>();
+ float d1_base = a_base - b1_base - c_base - color0.lane<2>();
+
+ // Number of bits in the various fields in the various modes
+ static const int mode_bits[8][4] {
+ {9, 7, 6, 7},
+ {9, 8, 6, 6},
+ {10, 6, 7, 7},
+ {10, 7, 7, 6},
+ {11, 8, 6, 5},
+ {11, 6, 8, 6},
+ {12, 7, 7, 5},
+ {12, 6, 7, 6}
+ };
+
+ // Cutoffs to use for the computed values of a,b,c,d, assuming the
+ // range 0..65535 are LNS values corresponding to fp16.
+ static const float mode_cutoffs[8][4] {
+ {16384, 8192, 8192, 8}, // mode 0: 9,7,6,7
+ {32768, 8192, 4096, 8}, // mode 1: 9,8,6,6
+ {4096, 8192, 4096, 4}, // mode 2: 10,6,7,7
+ {8192, 8192, 2048, 4}, // mode 3: 10,7,7,6
+ {8192, 2048, 512, 2}, // mode 4: 11,8,6,5
+ {2048, 8192, 1024, 2}, // mode 5: 11,6,8,6
+ {2048, 2048, 256, 1}, // mode 6: 12,7,7,5
+ {1024, 2048, 512, 1}, // mode 7: 12,6,7,6
+ };
+
+ static const float mode_scales[8] {
+ 1.0f / 128.0f,
+ 1.0f / 128.0f,
+ 1.0f / 64.0f,
+ 1.0f / 64.0f,
+ 1.0f / 32.0f,
+ 1.0f / 32.0f,
+ 1.0f / 16.0f,
+ 1.0f / 16.0f,
+ };
+
+ // Scaling factors when going from what was encoded in the mode to 16 bits.
+ static const float mode_rscales[8] {
+ 128.0f,
+ 128.0f,
+ 64.0f,
+ 64.0f,
+ 32.0f,
+ 32.0f,
+ 16.0f,
+ 16.0f
+ };
+
+ // Try modes one by one, with the highest-precision mode first.
+ for (int mode = 7; mode >= 0; mode--)
+ {
+ // For each mode, test if we can in fact accommodate the computed b, c, and d values.
+ // If we clearly can't, then we skip to the next mode.
+
+ float b_cutoff = mode_cutoffs[mode][0];
+ float c_cutoff = mode_cutoffs[mode][1];
+ float d_cutoff = mode_cutoffs[mode][2];
+
+ if (b0_base > b_cutoff || b1_base > b_cutoff || c_base > c_cutoff || fabsf(d0_base) > d_cutoff || fabsf(d1_base) > d_cutoff)
+ {
+ continue;
+ }
+
+ float mode_scale = mode_scales[mode];
+ float mode_rscale = mode_rscales[mode];
+
+ int b_intcutoff = 1 << mode_bits[mode][1];
+ int c_intcutoff = 1 << mode_bits[mode][2];
+ int d_intcutoff = 1 << (mode_bits[mode][3] - 1);
+
+ // Quantize and unquantize A, with the assumption that its high bits can be handled safely.
+ int a_intval = astc::flt2int_rtn(a_base * mode_scale);
+ int a_lowbits = a_intval & 0xFF;
+
+ int a_quantval = quant_color(quant_level, a_lowbits);
+ int a_uquantval = a_quantval;
+ a_intval = (a_intval & ~0xFF) | a_uquantval;
+ float a_fval = static_cast<float>(a_intval) * mode_rscale;
+
+ // Recompute C, then quantize and unquantize it
+ float c_fval = a_fval - color0.lane<0>();
+ c_fval = astc::clamp(c_fval, 0.0f, 65535.0f);
+
+ int c_intval = astc::flt2int_rtn(c_fval * mode_scale);
+
+ if (c_intval >= c_intcutoff)
+ {
+ continue;
+ }
+
+ int c_lowbits = c_intval & 0x3f;
+
+ c_lowbits |= (mode & 1) << 7;
+ c_lowbits |= (a_intval & 0x100) >> 2;
+
+ uint8_t c_quantval;
+
+ quantize_and_unquantize_retain_top_two_bits(
+ quant_level, static_cast<uint8_t>(c_lowbits), c_quantval);
+
+ c_intval = (c_intval & ~0x3F) | (c_quantval & 0x3F);
+ c_fval = static_cast<float>(c_intval) * mode_rscale;
+
+ // Recompute B0 and B1, then quantize and unquantize them
+ float b0_fval = a_fval - color1.lane<1>();
+ float b1_fval = a_fval - color1.lane<2>();
+
+ b0_fval = astc::clamp(b0_fval, 0.0f, 65535.0f);
+ b1_fval = astc::clamp(b1_fval, 0.0f, 65535.0f);
+ int b0_intval = astc::flt2int_rtn(b0_fval * mode_scale);
+ int b1_intval = astc::flt2int_rtn(b1_fval * mode_scale);
+
+ if (b0_intval >= b_intcutoff || b1_intval >= b_intcutoff)
+ {
+ continue;
+ }
+
+ int b0_lowbits = b0_intval & 0x3f;
+ int b1_lowbits = b1_intval & 0x3f;
+
+ int bit0 = 0;
+ int bit1 = 0;
+ switch (mode)
+ {
+ case 0:
+ case 1:
+ case 3:
+ case 4:
+ case 6:
+ bit0 = (b0_intval >> 6) & 1;
+ break;
+ case 2:
+ case 5:
+ case 7:
+ bit0 = (a_intval >> 9) & 1;
+ break;
+ }
+
+ switch (mode)
+ {
+ case 0:
+ case 1:
+ case 3:
+ case 4:
+ case 6:
+ bit1 = (b1_intval >> 6) & 1;
+ break;
+ case 2:
+ bit1 = (c_intval >> 6) & 1;
+ break;
+ case 5:
+ case 7:
+ bit1 = (a_intval >> 10) & 1;
+ break;
+ }
+
+ b0_lowbits |= bit0 << 6;
+ b1_lowbits |= bit1 << 6;
+
+ b0_lowbits |= ((mode >> 1) & 1) << 7;
+ b1_lowbits |= ((mode >> 2) & 1) << 7;
+
+ uint8_t b0_quantval;
+ uint8_t b1_quantval;
+
+ quantize_and_unquantize_retain_top_two_bits(
+ quant_level, static_cast<uint8_t>(b0_lowbits), b0_quantval);
+ quantize_and_unquantize_retain_top_two_bits(
+ quant_level, static_cast<uint8_t>(b1_lowbits), b1_quantval);
+
+ b0_intval = (b0_intval & ~0x3f) | (b0_quantval & 0x3f);
+ b1_intval = (b1_intval & ~0x3f) | (b1_quantval & 0x3f);
+ b0_fval = static_cast<float>(b0_intval) * mode_rscale;
+ b1_fval = static_cast<float>(b1_intval) * mode_rscale;
+
+ // Recompute D0 and D1, then quantize and unquantize them
+ float d0_fval = a_fval - b0_fval - c_fval - color0.lane<1>();
+ float d1_fval = a_fval - b1_fval - c_fval - color0.lane<2>();
+
+ d0_fval = astc::clamp(d0_fval, -65535.0f, 65535.0f);
+ d1_fval = astc::clamp(d1_fval, -65535.0f, 65535.0f);
+
+ int d0_intval = astc::flt2int_rtn(d0_fval * mode_scale);
+ int d1_intval = astc::flt2int_rtn(d1_fval * mode_scale);
+
+ if (abs(d0_intval) >= d_intcutoff || abs(d1_intval) >= d_intcutoff)
+ {
+ continue;
+ }
+
+ int d0_lowbits = d0_intval & 0x1f;
+ int d1_lowbits = d1_intval & 0x1f;
+
+ int bit2 = 0;
+ int bit3 = 0;
+ int bit4;
+ int bit5;
+ switch (mode)
+ {
+ case 0:
+ case 2:
+ bit2 = (d0_intval >> 6) & 1;
+ break;
+ case 1:
+ case 4:
+ bit2 = (b0_intval >> 7) & 1;
+ break;
+ case 3:
+ bit2 = (a_intval >> 9) & 1;
+ break;
+ case 5:
+ bit2 = (c_intval >> 7) & 1;
+ break;
+ case 6:
+ case 7:
+ bit2 = (a_intval >> 11) & 1;
+ break;
+ }
+ switch (mode)
+ {
+ case 0:
+ case 2:
+ bit3 = (d1_intval >> 6) & 1;
+ break;
+ case 1:
+ case 4:
+ bit3 = (b1_intval >> 7) & 1;
+ break;
+ case 3:
+ case 5:
+ case 6:
+ case 7:
+ bit3 = (c_intval >> 6) & 1;
+ break;
+ }
+
+ switch (mode)
+ {
+ case 4:
+ case 6:
+ bit4 = (a_intval >> 9) & 1;
+ bit5 = (a_intval >> 10) & 1;
+ break;
+ default:
+ bit4 = (d0_intval >> 5) & 1;
+ bit5 = (d1_intval >> 5) & 1;
+ break;
+ }
+
+ d0_lowbits |= bit2 << 6;
+ d1_lowbits |= bit3 << 6;
+ d0_lowbits |= bit4 << 5;
+ d1_lowbits |= bit5 << 5;
+
+ d0_lowbits |= (majcomp & 1) << 7;
+ d1_lowbits |= ((majcomp >> 1) & 1) << 7;
+
+ uint8_t d0_quantval;
+ uint8_t d1_quantval;
+
+ quantize_and_unquantize_retain_top_four_bits(
+ quant_level, static_cast<uint8_t>(d0_lowbits), d0_quantval);
+ quantize_and_unquantize_retain_top_four_bits(
+ quant_level, static_cast<uint8_t>(d1_lowbits), d1_quantval);
+
+ output[0] = static_cast<uint8_t>(a_quantval);
+ output[1] = c_quantval;
+ output[2] = b0_quantval;
+ output[3] = b1_quantval;
+ output[4] = d0_quantval;
+ output[5] = d1_quantval;
+ return;
+ }
+
+ // If neither of the modes fit we will use a flat representation for storing data, using 8 bits
+ // for red and green, and 7 bits for blue. This gives color accuracy roughly similar to LDR
+ // 4:4:3 which is not at all great but usable. This representation is used if the light color is
+ // more than 4x the color value of the dark color.
+ float vals[6];
+ vals[0] = color0_bak.lane<0>();
+ vals[1] = color1_bak.lane<0>();
+ vals[2] = color0_bak.lane<1>();
+ vals[3] = color1_bak.lane<1>();
+ vals[4] = color0_bak.lane<2>();
+ vals[5] = color1_bak.lane<2>();
+
+ for (int i = 0; i < 6; i++)
+ {
+ vals[i] = astc::clamp(vals[i], 0.0f, 65020.0f);
+ }
+
+ for (int i = 0; i < 4; i++)
+ {
+ int idx = astc::flt2int_rtn(vals[i] * 1.0f / 256.0f);
+ output[i] = quant_color(quant_level, idx);
+ }
+
+ for (int i = 4; i < 6; i++)
+ {
+ int idx = astc::flt2int_rtn(vals[i] * 1.0f / 512.0f) + 128;
+ quantize_and_unquantize_retain_top_two_bits(
+ quant_level, static_cast<uint8_t>(idx), output[i]);
+ }
+
+ return;
+}
+
+/**
+ * @brief Quantize a HDR RGB + LDR A color using direct RGBA encoding.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as packed RGBA+RGBA pairs with mode bits.
+ * @param quant_level The quantization level to use.
+ */
+static void quantize_hdr_rgb_ldr_alpha(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[8],
+ quant_method quant_level
+) {
+ float scale = 1.0f / 257.0f;
+
+ float a0 = astc::clamp255f(color0.lane<3>() * scale);
+ float a1 = astc::clamp255f(color1.lane<3>() * scale);
+
+ output[6] = quant_color(quant_level, astc::flt2int_rtn(a0));
+ output[7] = quant_color(quant_level, astc::flt2int_rtn(a1));
+
+ quantize_hdr_rgb(color0, color1, output, quant_level);
+}
+
+/**
+ * @brief Quantize a HDR L color using the large range encoding.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as packed (l0, l1).
+ * @param quant_level The quantization level to use.
+ */
+static void quantize_hdr_luminance_large_range(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[2],
+ quant_method quant_level
+) {
+ float lum0 = hadd_rgb_s(color0) * (1.0f / 3.0f);
+ float lum1 = hadd_rgb_s(color1) * (1.0f / 3.0f);
+
+ if (lum1 < lum0)
+ {
+ float avg = (lum0 + lum1) * 0.5f;
+ lum0 = avg;
+ lum1 = avg;
+ }
+
+ int ilum1 = astc::flt2int_rtn(lum1);
+ int ilum0 = astc::flt2int_rtn(lum0);
+
+ // Find the closest encodable point in the upper half of the code-point space
+ int upper_v0 = (ilum0 + 128) >> 8;
+ int upper_v1 = (ilum1 + 128) >> 8;
+
+ upper_v0 = astc::clamp(upper_v0, 0, 255);
+ upper_v1 = astc::clamp(upper_v1, 0, 255);
+
+ // Find the closest encodable point in the lower half of the code-point space
+ int lower_v0 = (ilum1 + 256) >> 8;
+ int lower_v1 = ilum0 >> 8;
+
+ lower_v0 = astc::clamp(lower_v0, 0, 255);
+ lower_v1 = astc::clamp(lower_v1, 0, 255);
+
+ // Determine the distance between the point in code-point space and the input value
+ int upper0_dec = upper_v0 << 8;
+ int upper1_dec = upper_v1 << 8;
+ int lower0_dec = (lower_v1 << 8) + 128;
+ int lower1_dec = (lower_v0 << 8) - 128;
+
+ int upper0_diff = upper0_dec - ilum0;
+ int upper1_diff = upper1_dec - ilum1;
+ int lower0_diff = lower0_dec - ilum0;
+ int lower1_diff = lower1_dec - ilum1;
+
+ int upper_error = (upper0_diff * upper0_diff) + (upper1_diff * upper1_diff);
+ int lower_error = (lower0_diff * lower0_diff) + (lower1_diff * lower1_diff);
+
+ int v0, v1;
+ if (upper_error < lower_error)
+ {
+ v0 = upper_v0;
+ v1 = upper_v1;
+ }
+ else
+ {
+ v0 = lower_v0;
+ v1 = lower_v1;
+ }
+
+ // OK; encode
+ output[0] = quant_color(quant_level, v0);
+ output[1] = quant_color(quant_level, v1);
+}
+
+/**
+ * @brief Quantize a HDR L color using the small range encoding.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as packed (l0, l1) with mode bits.
+ * @param quant_level The quantization level to use.
+ *
+ * @return Returns @c false on failure, @c true on success.
+ */
+static bool try_quantize_hdr_luminance_small_range(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[2],
+ quant_method quant_level
+) {
+ float lum0 = hadd_rgb_s(color0) * (1.0f / 3.0f);
+ float lum1 = hadd_rgb_s(color1) * (1.0f / 3.0f);
+
+ if (lum1 < lum0)
+ {
+ float avg = (lum0 + lum1) * 0.5f;
+ lum0 = avg;
+ lum1 = avg;
+ }
+
+ int ilum1 = astc::flt2int_rtn(lum1);
+ int ilum0 = astc::flt2int_rtn(lum0);
+
+ // Difference of more than a factor-of-2 results in immediate failure
+ if (ilum1 - ilum0 > 2048)
+ {
+ return false;
+ }
+
+ int lowval, highval, diffval;
+ int v0, v1;
+ int v0e, v1e;
+ int v0d, v1d;
+
+ // Try to encode the high-precision submode
+ lowval = (ilum0 + 16) >> 5;
+ highval = (ilum1 + 16) >> 5;
+
+ lowval = astc::clamp(lowval, 0, 2047);
+ highval = astc::clamp(highval, 0, 2047);
+
+ v0 = lowval & 0x7F;
+ v0e = quant_color(quant_level, v0);
+ v0d = v0e;
+
+ if (v0d < 0x80)
+ {
+ lowval = (lowval & ~0x7F) | v0d;
+ diffval = highval - lowval;
+ if (diffval >= 0 && diffval <= 15)
+ {
+ v1 = ((lowval >> 3) & 0xF0) | diffval;
+ v1e = quant_color(quant_level, v1);
+ v1d = v1e;
+ if ((v1d & 0xF0) == (v1 & 0xF0))
+ {
+ output[0] = static_cast<uint8_t>(v0e);
+ output[1] = static_cast<uint8_t>(v1e);
+ return true;
+ }
+ }
+ }
+
+ // Try to encode the low-precision submode
+ lowval = (ilum0 + 32) >> 6;
+ highval = (ilum1 + 32) >> 6;
+
+ lowval = astc::clamp(lowval, 0, 1023);
+ highval = astc::clamp(highval, 0, 1023);
+
+ v0 = (lowval & 0x7F) | 0x80;
+ v0e = quant_color(quant_level, v0);
+ v0d = v0e;
+ if ((v0d & 0x80) == 0)
+ {
+ return false;
+ }
+
+ lowval = (lowval & ~0x7F) | (v0d & 0x7F);
+ diffval = highval - lowval;
+ if (diffval < 0 || diffval > 31)
+ {
+ return false;
+ }
+
+ v1 = ((lowval >> 2) & 0xE0) | diffval;
+ v1e = quant_color(quant_level, v1);
+ v1d = v1e;
+ if ((v1d & 0xE0) != (v1 & 0xE0))
+ {
+ return false;
+ }
+
+ output[0] = static_cast<uint8_t>(v0e);
+ output[1] = static_cast<uint8_t>(v1e);
+ return true;
+}
+
+/**
+ * @brief Quantize a HDR A color using either delta or direct RGBA encoding.
+ *
+ * @param alpha0 The input unquantized color0 endpoint.
+ * @param alpha1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as packed RGBA+RGBA pairs with mode bits.
+ * @param quant_level The quantization level to use.
+ */
+static void quantize_hdr_alpha(
+ float alpha0,
+ float alpha1,
+ uint8_t output[2],
+ quant_method quant_level
+) {
+ alpha0 = astc::clamp(alpha0, 0.0f, 65280.0f);
+ alpha1 = astc::clamp(alpha1, 0.0f, 65280.0f);
+
+ int ialpha0 = astc::flt2int_rtn(alpha0);
+ int ialpha1 = astc::flt2int_rtn(alpha1);
+
+ int val0, val1, diffval;
+ int v6, v7;
+ int v6e, v7e;
+ int v6d, v7d;
+
+ // Try to encode one of the delta submodes, in decreasing-precision order
+ for (int i = 2; i >= 0; i--)
+ {
+ val0 = (ialpha0 + (128 >> i)) >> (8 - i);
+ val1 = (ialpha1 + (128 >> i)) >> (8 - i);
+
+ v6 = (val0 & 0x7F) | ((i & 1) << 7);
+ v6e = quant_color(quant_level, v6);
+ v6d = v6e;
+
+ if ((v6 ^ v6d) & 0x80)
+ {
+ continue;
+ }
+
+ val0 = (val0 & ~0x7f) | (v6d & 0x7f);
+ diffval = val1 - val0;
+ int cutoff = 32 >> i;
+ int mask = 2 * cutoff - 1;
+
+ if (diffval < -cutoff || diffval >= cutoff)
+ {
+ continue;
+ }
+
+ v7 = ((i & 2) << 6) | ((val0 >> 7) << (6 - i)) | (diffval & mask);
+ v7e = quant_color(quant_level, v7);
+ v7d = v7e;
+
+ static const int testbits[3] { 0xE0, 0xF0, 0xF8 };
+
+ if ((v7 ^ v7d) & testbits[i])
+ {
+ continue;
+ }
+
+ output[0] = static_cast<uint8_t>(v6e);
+ output[1] = static_cast<uint8_t>(v7e);
+ return;
+ }
+
+ // Could not encode any of the delta modes; instead encode a flat value
+ val0 = (ialpha0 + 256) >> 9;
+ val1 = (ialpha1 + 256) >> 9;
+ v6 = val0 | 0x80;
+ v7 = val1 | 0x80;
+
+ output[0] = quant_color(quant_level, v6);
+ output[1] = quant_color(quant_level, v7);
+
+ return;
+}
+
+/**
+ * @brief Quantize a HDR RGBA color using either delta or direct RGBA encoding.
+ *
+ * @param color0 The input unquantized color0 endpoint.
+ * @param color1 The input unquantized color1 endpoint.
+ * @param[out] output The output endpoints, returned as packed RGBA+RGBA pairs with mode bits.
+ * @param quant_level The quantization level to use.
+ */
+static void quantize_hdr_rgb_alpha(
+ vfloat4 color0,
+ vfloat4 color1,
+ uint8_t output[8],
+ quant_method quant_level
+) {
+ quantize_hdr_rgb(color0, color1, output, quant_level);
+ quantize_hdr_alpha(color0.lane<3>(), color1.lane<3>(), output + 6, quant_level);
+}
+
+/* See header for documentation. */
+uint8_t pack_color_endpoints(
+ vfloat4 color0,
+ vfloat4 color1,
+ vfloat4 rgbs_color,
+ vfloat4 rgbo_color,
+ int format,
+ uint8_t* output,
+ quant_method quant_level
+) {
+ assert(QUANT_6 <= quant_level && quant_level <= QUANT_256);
+
+ // We do not support negative colors
+ color0 = max(color0, 0.0f);
+ color1 = max(color1, 0.0f);
+
+ uint8_t retval = 0;
+
+ switch (format)
+ {
+ case FMT_RGB:
+ if (quant_level <= QUANT_160)
+ {
+ if (try_quantize_rgb_delta_blue_contract(color0, color1, output, quant_level))
+ {
+ retval = FMT_RGB_DELTA;
+ break;
+ }
+ if (try_quantize_rgb_delta(color0, color1, output, quant_level))
+ {
+ retval = FMT_RGB_DELTA;
+ break;
+ }
+ }
+ if (quant_level < QUANT_256 && try_quantize_rgb_blue_contract(color0, color1, output, quant_level))
+ {
+ retval = FMT_RGB;
+ break;
+ }
+ quantize_rgb(color0, color1, output, quant_level);
+ retval = FMT_RGB;
+ break;
+
+ case FMT_RGBA:
+ if (quant_level <= QUANT_160)
+ {
+ if (try_quantize_rgba_delta_blue_contract(color0, color1, output, quant_level))
+ {
+ retval = FMT_RGBA_DELTA;
+ break;
+ }
+ if (try_quantize_rgba_delta(color0, color1, output, quant_level))
+ {
+ retval = FMT_RGBA_DELTA;
+ break;
+ }
+ }
+ if (quant_level < QUANT_256 && try_quantize_rgba_blue_contract(color0, color1, output, quant_level))
+ {
+ retval = FMT_RGBA;
+ break;
+ }
+ quantize_rgba(color0, color1, output, quant_level);
+ retval = FMT_RGBA;
+ break;
+
+ case FMT_RGB_SCALE:
+ quantize_rgbs(rgbs_color, output, quant_level);
+ retval = FMT_RGB_SCALE;
+ break;
+
+ case FMT_HDR_RGB_SCALE:
+ quantize_hdr_rgbo(rgbo_color, output, quant_level);
+ retval = FMT_HDR_RGB_SCALE;
+ break;
+
+ case FMT_HDR_RGB:
+ quantize_hdr_rgb(color0, color1, output, quant_level);
+ retval = FMT_HDR_RGB;
+ break;
+
+ case FMT_RGB_SCALE_ALPHA:
+ quantize_rgbs_alpha(color0, color1, rgbs_color, output, quant_level);
+ retval = FMT_RGB_SCALE_ALPHA;
+ break;
+
+ case FMT_HDR_LUMINANCE_SMALL_RANGE:
+ case FMT_HDR_LUMINANCE_LARGE_RANGE:
+ if (try_quantize_hdr_luminance_small_range(color0, color1, output, quant_level))
+ {
+ retval = FMT_HDR_LUMINANCE_SMALL_RANGE;
+ break;
+ }
+ quantize_hdr_luminance_large_range(color0, color1, output, quant_level);
+ retval = FMT_HDR_LUMINANCE_LARGE_RANGE;
+ break;
+
+ case FMT_LUMINANCE:
+ quantize_luminance(color0, color1, output, quant_level);
+ retval = FMT_LUMINANCE;
+ break;
+
+ case FMT_LUMINANCE_ALPHA:
+ if (quant_level <= 18)
+ {
+ if (try_quantize_luminance_alpha_delta(color0, color1, output, quant_level))
+ {
+ retval = FMT_LUMINANCE_ALPHA_DELTA;
+ break;
+ }
+ }
+ quantize_luminance_alpha(color0, color1, output, quant_level);
+ retval = FMT_LUMINANCE_ALPHA;
+ break;
+
+ case FMT_HDR_RGB_LDR_ALPHA:
+ quantize_hdr_rgb_ldr_alpha(color0, color1, output, quant_level);
+ retval = FMT_HDR_RGB_LDR_ALPHA;
+ break;
+
+ case FMT_HDR_RGBA:
+ quantize_hdr_rgb_alpha(color0, color1, output, quant_level);
+ retval = FMT_HDR_RGBA;
+ break;
+ }
+
+ return retval;
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_color_unquantize.cpp b/thirdparty/astcenc/astcenc_color_unquantize.cpp
new file mode 100644
index 0000000000..d31895a627
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_color_unquantize.cpp
@@ -0,0 +1,941 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2021 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+#include <utility>
+
+/**
+ * @brief Functions for color unquantization.
+ */
+
+#include "astcenc_internal.h"
+
+/**
+ * @brief Un-blue-contract a color.
+ *
+ * This function reverses any applied blue contraction.
+ *
+ * @param input The input color that has been blue-contracted.
+ *
+ * @return The uncontracted color.
+ */
+static ASTCENC_SIMD_INLINE vint4 uncontract_color(
+ vint4 input
+) {
+ vmask4 mask(true, true, false, false);
+ vint4 bc0 = asr<1>(input + input.lane<2>());
+ return select(input, bc0, mask);
+}
+
+/**
+ * @brief Unpack an LDR RGBA color that uses delta encoding.
+ *
+ * @param input0 The packed endpoint 0 color.
+ * @param input1 The packed endpoint 1 color deltas.
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void rgba_delta_unpack(
+ vint4 input0,
+ vint4 input1,
+ vint4& output0,
+ vint4& output1
+) {
+ // Apply bit transfer
+ bit_transfer_signed(input1, input0);
+
+ // Apply blue-uncontraction if needed
+ int rgb_sum = hadd_rgb_s(input1);
+ input1 = input1 + input0;
+ if (rgb_sum < 0)
+ {
+ input0 = uncontract_color(input0);
+ input1 = uncontract_color(input1);
+ std::swap(input0, input1);
+ }
+
+ output0 = clamp(0, 255, input0);
+ output1 = clamp(0, 255, input1);
+}
+
+/**
+ * @brief Unpack an LDR RGB color that uses delta encoding.
+ *
+ * Output alpha set to 255.
+ *
+ * @param input0 The packed endpoint 0 color.
+ * @param input1 The packed endpoint 1 color deltas.
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void rgb_delta_unpack(
+ vint4 input0,
+ vint4 input1,
+ vint4& output0,
+ vint4& output1
+) {
+ rgba_delta_unpack(input0, input1, output0, output1);
+ output0.set_lane<3>(255);
+ output1.set_lane<3>(255);
+}
+
+/**
+ * @brief Unpack an LDR RGBA color that uses direct encoding.
+ *
+ * @param input0 The packed endpoint 0 color.
+ * @param input1 The packed endpoint 1 color.
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void rgba_unpack(
+ vint4 input0,
+ vint4 input1,
+ vint4& output0,
+ vint4& output1
+) {
+ // Apply blue-uncontraction if needed
+ if (hadd_rgb_s(input0) > hadd_rgb_s(input1))
+ {
+ input0 = uncontract_color(input0);
+ input1 = uncontract_color(input1);
+ std::swap(input0, input1);
+ }
+
+ output0 = input0;
+ output1 = input1;
+}
+
+/**
+ * @brief Unpack an LDR RGB color that uses direct encoding.
+ *
+ * Output alpha set to 255.
+ *
+ * @param input0 The packed endpoint 0 color.
+ * @param input1 The packed endpoint 1 color.
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void rgb_unpack(
+ vint4 input0,
+ vint4 input1,
+ vint4& output0,
+ vint4& output1
+) {
+ rgba_unpack(input0, input1, output0, output1);
+ output0.set_lane<3>(255);
+ output1.set_lane<3>(255);
+}
+
+/**
+ * @brief Unpack an LDR RGBA color that uses scaled encoding.
+ *
+ * Note only the RGB channels use the scaled encoding, alpha uses direct.
+ *
+ * @param input0 The packed endpoint 0 color.
+ * @param alpha1 The packed endpoint 1 alpha value.
+ * @param scale The packed quantized scale.
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void rgb_scale_alpha_unpack(
+ vint4 input0,
+ uint8_t alpha1,
+ uint8_t scale,
+ vint4& output0,
+ vint4& output1
+) {
+ output1 = input0;
+ output1.set_lane<3>(alpha1);
+
+ output0 = asr<8>(input0 * scale);
+ output0.set_lane<3>(input0.lane<3>());
+}
+
+/**
+ * @brief Unpack an LDR RGB color that uses scaled encoding.
+ *
+ * Output alpha is 255.
+ *
+ * @param input0 The packed endpoint 0 color.
+ * @param scale The packed scale.
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void rgb_scale_unpack(
+ vint4 input0,
+ int scale,
+ vint4& output0,
+ vint4& output1
+) {
+ output1 = input0;
+ output1.set_lane<3>(255);
+
+ output0 = asr<8>(input0 * scale);
+ output0.set_lane<3>(255);
+}
+
+/**
+ * @brief Unpack an LDR L color that uses direct encoding.
+ *
+ * Output alpha is 255.
+ *
+ * @param input The packed endpoints.
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void luminance_unpack(
+ const uint8_t input[2],
+ vint4& output0,
+ vint4& output1
+) {
+ int lum0 = input[0];
+ int lum1 = input[1];
+ output0 = vint4(lum0, lum0, lum0, 255);
+ output1 = vint4(lum1, lum1, lum1, 255);
+}
+
+/**
+ * @brief Unpack an LDR L color that uses delta encoding.
+ *
+ * Output alpha is 255.
+ *
+ * @param input The packed endpoints (L0, L1).
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void luminance_delta_unpack(
+ const uint8_t input[2],
+ vint4& output0,
+ vint4& output1
+) {
+ int v0 = input[0];
+ int v1 = input[1];
+ int l0 = (v0 >> 2) | (v1 & 0xC0);
+ int l1 = l0 + (v1 & 0x3F);
+
+ l1 = astc::min(l1, 255);
+
+ output0 = vint4(l0, l0, l0, 255);
+ output1 = vint4(l1, l1, l1, 255);
+}
+
+/**
+ * @brief Unpack an LDR LA color that uses direct encoding.
+ *
+ * @param input The packed endpoints (L0, L1, A0, A1).
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void luminance_alpha_unpack(
+ const uint8_t input[4],
+ vint4& output0,
+ vint4& output1
+) {
+ int lum0 = input[0];
+ int lum1 = input[1];
+ int alpha0 = input[2];
+ int alpha1 = input[3];
+ output0 = vint4(lum0, lum0, lum0, alpha0);
+ output1 = vint4(lum1, lum1, lum1, alpha1);
+}
+
+/**
+ * @brief Unpack an LDR LA color that uses delta encoding.
+ *
+ * @param input The packed endpoints (L0, L1, A0, A1).
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void luminance_alpha_delta_unpack(
+ const uint8_t input[4],
+ vint4& output0,
+ vint4& output1
+) {
+ int lum0 = input[0];
+ int lum1 = input[1];
+ int alpha0 = input[2];
+ int alpha1 = input[3];
+
+ lum0 |= (lum1 & 0x80) << 1;
+ alpha0 |= (alpha1 & 0x80) << 1;
+ lum1 &= 0x7F;
+ alpha1 &= 0x7F;
+
+ if (lum1 & 0x40)
+ {
+ lum1 -= 0x80;
+ }
+
+ if (alpha1 & 0x40)
+ {
+ alpha1 -= 0x80;
+ }
+
+ lum0 >>= 1;
+ lum1 >>= 1;
+ alpha0 >>= 1;
+ alpha1 >>= 1;
+ lum1 += lum0;
+ alpha1 += alpha0;
+
+ lum1 = astc::clamp(lum1, 0, 255);
+ alpha1 = astc::clamp(alpha1, 0, 255);
+
+ output0 = vint4(lum0, lum0, lum0, alpha0);
+ output1 = vint4(lum1, lum1, lum1, alpha1);
+}
+
+/**
+ * @brief Unpack an HDR RGB + offset encoding.
+ *
+ * @param input The packed endpoints (packed and modal).
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void hdr_rgbo_unpack(
+ const uint8_t input[4],
+ vint4& output0,
+ vint4& output1
+) {
+ int v0 = input[0];
+ int v1 = input[1];
+ int v2 = input[2];
+ int v3 = input[3];
+
+ int modeval = ((v0 & 0xC0) >> 6) | (((v1 & 0x80) >> 7) << 2) | (((v2 & 0x80) >> 7) << 3);
+
+ int majcomp;
+ int mode;
+ if ((modeval & 0xC) != 0xC)
+ {
+ majcomp = modeval >> 2;
+ mode = modeval & 3;
+ }
+ else if (modeval != 0xF)
+ {
+ majcomp = modeval & 3;
+ mode = 4;
+ }
+ else
+ {
+ majcomp = 0;
+ mode = 5;
+ }
+
+ int red = v0 & 0x3F;
+ int green = v1 & 0x1F;
+ int blue = v2 & 0x1F;
+ int scale = v3 & 0x1F;
+
+ int bit0 = (v1 >> 6) & 1;
+ int bit1 = (v1 >> 5) & 1;
+ int bit2 = (v2 >> 6) & 1;
+ int bit3 = (v2 >> 5) & 1;
+ int bit4 = (v3 >> 7) & 1;
+ int bit5 = (v3 >> 6) & 1;
+ int bit6 = (v3 >> 5) & 1;
+
+ int ohcomp = 1 << mode;
+
+ if (ohcomp & 0x30)
+ green |= bit0 << 6;
+ if (ohcomp & 0x3A)
+ green |= bit1 << 5;
+ if (ohcomp & 0x30)
+ blue |= bit2 << 6;
+ if (ohcomp & 0x3A)
+ blue |= bit3 << 5;
+
+ if (ohcomp & 0x3D)
+ scale |= bit6 << 5;
+ if (ohcomp & 0x2D)
+ scale |= bit5 << 6;
+ if (ohcomp & 0x04)
+ scale |= bit4 << 7;
+
+ if (ohcomp & 0x3B)
+ red |= bit4 << 6;
+ if (ohcomp & 0x04)
+ red |= bit3 << 6;
+
+ if (ohcomp & 0x10)
+ red |= bit5 << 7;
+ if (ohcomp & 0x0F)
+ red |= bit2 << 7;
+
+ if (ohcomp & 0x05)
+ red |= bit1 << 8;
+ if (ohcomp & 0x0A)
+ red |= bit0 << 8;
+
+ if (ohcomp & 0x05)
+ red |= bit0 << 9;
+ if (ohcomp & 0x02)
+ red |= bit6 << 9;
+
+ if (ohcomp & 0x01)
+ red |= bit3 << 10;
+ if (ohcomp & 0x02)
+ red |= bit5 << 10;
+
+ // expand to 12 bits.
+ static const int shamts[6] { 1, 1, 2, 3, 4, 5 };
+ int shamt = shamts[mode];
+ red <<= shamt;
+ green <<= shamt;
+ blue <<= shamt;
+ scale <<= shamt;
+
+ // on modes 0 to 4, the values stored for "green" and "blue" are differentials,
+ // not absolute values.
+ if (mode != 5)
+ {
+ green = red - green;
+ blue = red - blue;
+ }
+
+ // switch around components.
+ int temp;
+ switch (majcomp)
+ {
+ case 1:
+ temp = red;
+ red = green;
+ green = temp;
+ break;
+ case 2:
+ temp = red;
+ red = blue;
+ blue = temp;
+ break;
+ default:
+ break;
+ }
+
+ int red0 = red - scale;
+ int green0 = green - scale;
+ int blue0 = blue - scale;
+
+ // clamp to [0,0xFFF].
+ if (red < 0)
+ red = 0;
+ if (green < 0)
+ green = 0;
+ if (blue < 0)
+ blue = 0;
+
+ if (red0 < 0)
+ red0 = 0;
+ if (green0 < 0)
+ green0 = 0;
+ if (blue0 < 0)
+ blue0 = 0;
+
+ output0 = vint4(red0 << 4, green0 << 4, blue0 << 4, 0x7800);
+ output1 = vint4(red << 4, green << 4, blue << 4, 0x7800);
+}
+
+/**
+ * @brief Unpack an HDR RGB direct encoding.
+ *
+ * @param input The packed endpoints (packed and modal).
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void hdr_rgb_unpack(
+ const uint8_t input[6],
+ vint4& output0,
+ vint4& output1
+) {
+
+ int v0 = input[0];
+ int v1 = input[1];
+ int v2 = input[2];
+ int v3 = input[3];
+ int v4 = input[4];
+ int v5 = input[5];
+
+ // extract all the fixed-placement bitfields
+ int modeval = ((v1 & 0x80) >> 7) | (((v2 & 0x80) >> 7) << 1) | (((v3 & 0x80) >> 7) << 2);
+
+ int majcomp = ((v4 & 0x80) >> 7) | (((v5 & 0x80) >> 7) << 1);
+
+ if (majcomp == 3)
+ {
+ output0 = vint4(v0 << 8, v2 << 8, (v4 & 0x7F) << 9, 0x7800);
+ output1 = vint4(v1 << 8, v3 << 8, (v5 & 0x7F) << 9, 0x7800);
+ return;
+ }
+
+ int a = v0 | ((v1 & 0x40) << 2);
+ int b0 = v2 & 0x3f;
+ int b1 = v3 & 0x3f;
+ int c = v1 & 0x3f;
+ int d0 = v4 & 0x7f;
+ int d1 = v5 & 0x7f;
+
+ // get hold of the number of bits in 'd0' and 'd1'
+ static const int dbits_tab[8] { 7, 6, 7, 6, 5, 6, 5, 6 };
+ int dbits = dbits_tab[modeval];
+
+ // extract six variable-placement bits
+ int bit0 = (v2 >> 6) & 1;
+ int bit1 = (v3 >> 6) & 1;
+ int bit2 = (v4 >> 6) & 1;
+ int bit3 = (v5 >> 6) & 1;
+ int bit4 = (v4 >> 5) & 1;
+ int bit5 = (v5 >> 5) & 1;
+
+ // and prepend the variable-placement bits depending on mode.
+ int ohmod = 1 << modeval; // one-hot-mode
+ if (ohmod & 0xA4)
+ a |= bit0 << 9;
+ if (ohmod & 0x8)
+ a |= bit2 << 9;
+ if (ohmod & 0x50)
+ a |= bit4 << 9;
+
+ if (ohmod & 0x50)
+ a |= bit5 << 10;
+ if (ohmod & 0xA0)
+ a |= bit1 << 10;
+
+ if (ohmod & 0xC0)
+ a |= bit2 << 11;
+
+ if (ohmod & 0x4)
+ c |= bit1 << 6;
+ if (ohmod & 0xE8)
+ c |= bit3 << 6;
+
+ if (ohmod & 0x20)
+ c |= bit2 << 7;
+
+ if (ohmod & 0x5B)
+ {
+ b0 |= bit0 << 6;
+ b1 |= bit1 << 6;
+ }
+
+ if (ohmod & 0x12)
+ {
+ b0 |= bit2 << 7;
+ b1 |= bit3 << 7;
+ }
+
+ if (ohmod & 0xAF)
+ {
+ d0 |= bit4 << 5;
+ d1 |= bit5 << 5;
+ }
+
+ if (ohmod & 0x5)
+ {
+ d0 |= bit2 << 6;
+ d1 |= bit3 << 6;
+ }
+
+ // sign-extend 'd0' and 'd1'
+ // note: this code assumes that signed right-shift actually sign-fills, not zero-fills.
+ int32_t d0x = d0;
+ int32_t d1x = d1;
+ int sx_shamt = 32 - dbits;
+ d0x <<= sx_shamt;
+ d0x >>= sx_shamt;
+ d1x <<= sx_shamt;
+ d1x >>= sx_shamt;
+ d0 = d0x;
+ d1 = d1x;
+
+ // expand all values to 12 bits, with left-shift as needed.
+ int val_shamt = (modeval >> 1) ^ 3;
+ a <<= val_shamt;
+ b0 <<= val_shamt;
+ b1 <<= val_shamt;
+ c <<= val_shamt;
+ d0 <<= val_shamt;
+ d1 <<= val_shamt;
+
+ // then compute the actual color values.
+ int red1 = a;
+ int green1 = a - b0;
+ int blue1 = a - b1;
+ int red0 = a - c;
+ int green0 = a - b0 - c - d0;
+ int blue0 = a - b1 - c - d1;
+
+ // clamp the color components to [0,2^12 - 1]
+ red0 = astc::clamp(red0, 0, 4095);
+ green0 = astc::clamp(green0, 0, 4095);
+ blue0 = astc::clamp(blue0, 0, 4095);
+
+ red1 = astc::clamp(red1, 0, 4095);
+ green1 = astc::clamp(green1, 0, 4095);
+ blue1 = astc::clamp(blue1, 0, 4095);
+
+ // switch around the color components
+ int temp0, temp1;
+ switch (majcomp)
+ {
+ case 1: // switch around red and green
+ temp0 = red0;
+ temp1 = red1;
+ red0 = green0;
+ red1 = green1;
+ green0 = temp0;
+ green1 = temp1;
+ break;
+ case 2: // switch around red and blue
+ temp0 = red0;
+ temp1 = red1;
+ red0 = blue0;
+ red1 = blue1;
+ blue0 = temp0;
+ blue1 = temp1;
+ break;
+ case 0: // no switch
+ break;
+ }
+
+ output0 = vint4(red0 << 4, green0 << 4, blue0 << 4, 0x7800);
+ output1 = vint4(red1 << 4, green1 << 4, blue1 << 4, 0x7800);
+}
+
+/**
+ * @brief Unpack an HDR RGB + LDR A direct encoding.
+ *
+ * @param input The packed endpoints (packed and modal).
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void hdr_rgb_ldr_alpha_unpack(
+ const uint8_t input[8],
+ vint4& output0,
+ vint4& output1
+) {
+ hdr_rgb_unpack(input, output0, output1);
+
+ int v6 = input[6];
+ int v7 = input[7];
+ output0.set_lane<3>(v6);
+ output1.set_lane<3>(v7);
+}
+
+/**
+ * @brief Unpack an HDR L (small range) direct encoding.
+ *
+ * @param input The packed endpoints (packed and modal).
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void hdr_luminance_small_range_unpack(
+ const uint8_t input[2],
+ vint4& output0,
+ vint4& output1
+) {
+ int v0 = input[0];
+ int v1 = input[1];
+
+ int y0, y1;
+ if (v0 & 0x80)
+ {
+ y0 = ((v1 & 0xE0) << 4) | ((v0 & 0x7F) << 2);
+ y1 = (v1 & 0x1F) << 2;
+ }
+ else
+ {
+ y0 = ((v1 & 0xF0) << 4) | ((v0 & 0x7F) << 1);
+ y1 = (v1 & 0xF) << 1;
+ }
+
+ y1 += y0;
+ if (y1 > 0xFFF)
+ {
+ y1 = 0xFFF;
+ }
+
+ output0 = vint4(y0 << 4, y0 << 4, y0 << 4, 0x7800);
+ output1 = vint4(y1 << 4, y1 << 4, y1 << 4, 0x7800);
+}
+
+/**
+ * @brief Unpack an HDR L (large range) direct encoding.
+ *
+ * @param input The packed endpoints (packed and modal).
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void hdr_luminance_large_range_unpack(
+ const uint8_t input[2],
+ vint4& output0,
+ vint4& output1
+) {
+ int v0 = input[0];
+ int v1 = input[1];
+
+ int y0, y1;
+ if (v1 >= v0)
+ {
+ y0 = v0 << 4;
+ y1 = v1 << 4;
+ }
+ else
+ {
+ y0 = (v1 << 4) + 8;
+ y1 = (v0 << 4) - 8;
+ }
+
+ output0 = vint4(y0 << 4, y0 << 4, y0 << 4, 0x7800);
+ output1 = vint4(y1 << 4, y1 << 4, y1 << 4, 0x7800);
+}
+
+/**
+ * @brief Unpack an HDR A direct encoding.
+ *
+ * @param input The packed endpoints (packed and modal).
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void hdr_alpha_unpack(
+ const uint8_t input[2],
+ int& output0,
+ int& output1
+) {
+
+ int v6 = input[0];
+ int v7 = input[1];
+
+ int selector = ((v6 >> 7) & 1) | ((v7 >> 6) & 2);
+ v6 &= 0x7F;
+ v7 &= 0x7F;
+ if (selector == 3)
+ {
+ output0 = v6 << 5;
+ output1 = v7 << 5;
+ }
+ else
+ {
+ v6 |= (v7 << (selector + 1)) & 0x780;
+ v7 &= (0x3f >> selector);
+ v7 ^= 32 >> selector;
+ v7 -= 32 >> selector;
+ v6 <<= (4 - selector);
+ v7 <<= (4 - selector);
+ v7 += v6;
+
+ if (v7 < 0)
+ {
+ v7 = 0;
+ }
+ else if (v7 > 0xFFF)
+ {
+ v7 = 0xFFF;
+ }
+
+ output0 = v6;
+ output1 = v7;
+ }
+
+ output0 <<= 4;
+ output1 <<= 4;
+}
+
+/**
+ * @brief Unpack an HDR RGBA direct encoding.
+ *
+ * @param input The packed endpoints (packed and modal).
+ * @param[out] output0 The unpacked endpoint 0 color.
+ * @param[out] output1 The unpacked endpoint 1 color.
+ */
+static void hdr_rgb_hdr_alpha_unpack(
+ const uint8_t input[8],
+ vint4& output0,
+ vint4& output1
+) {
+ hdr_rgb_unpack(input, output0, output1);
+
+ int alpha0, alpha1;
+ hdr_alpha_unpack(input + 6, alpha0, alpha1);
+
+ output0.set_lane<3>(alpha0);
+ output1.set_lane<3>(alpha1);
+}
+
+/* See header for documentation. */
+void unpack_color_endpoints(
+ astcenc_profile decode_mode,
+ int format,
+ const uint8_t* input,
+ bool& rgb_hdr,
+ bool& alpha_hdr,
+ vint4& output0,
+ vint4& output1
+) {
+ // Assume no NaNs and LDR endpoints unless set later
+ rgb_hdr = false;
+ alpha_hdr = false;
+
+ bool alpha_hdr_default = false;
+
+ switch (format)
+ {
+ case FMT_LUMINANCE:
+ luminance_unpack(input, output0, output1);
+ break;
+
+ case FMT_LUMINANCE_DELTA:
+ luminance_delta_unpack(input, output0, output1);
+ break;
+
+ case FMT_HDR_LUMINANCE_SMALL_RANGE:
+ rgb_hdr = true;
+ alpha_hdr_default = true;
+ hdr_luminance_small_range_unpack(input, output0, output1);
+ break;
+
+ case FMT_HDR_LUMINANCE_LARGE_RANGE:
+ rgb_hdr = true;
+ alpha_hdr_default = true;
+ hdr_luminance_large_range_unpack(input, output0, output1);
+ break;
+
+ case FMT_LUMINANCE_ALPHA:
+ luminance_alpha_unpack(input, output0, output1);
+ break;
+
+ case FMT_LUMINANCE_ALPHA_DELTA:
+ luminance_alpha_delta_unpack(input, output0, output1);
+ break;
+
+ case FMT_RGB_SCALE:
+ {
+ vint4 input0q(input[0], input[1], input[2], 0);
+ uint8_t scale = input[3];
+ rgb_scale_unpack(input0q, scale, output0, output1);
+ }
+ break;
+
+ case FMT_RGB_SCALE_ALPHA:
+ {
+ vint4 input0q(input[0], input[1], input[2], input[4]);
+ uint8_t alpha1q = input[5];
+ uint8_t scaleq = input[3];
+ rgb_scale_alpha_unpack(input0q, alpha1q, scaleq, output0, output1);
+ }
+ break;
+
+ case FMT_HDR_RGB_SCALE:
+ rgb_hdr = true;
+ alpha_hdr_default = true;
+ hdr_rgbo_unpack(input, output0, output1);
+ break;
+
+ case FMT_RGB:
+ {
+ vint4 input0q(input[0], input[2], input[4], 0);
+ vint4 input1q(input[1], input[3], input[5], 0);
+ rgb_unpack(input0q, input1q, output0, output1);
+ }
+ break;
+
+ case FMT_RGB_DELTA:
+ {
+ vint4 input0q(input[0], input[2], input[4], 0);
+ vint4 input1q(input[1], input[3], input[5], 0);
+ rgb_delta_unpack(input0q, input1q, output0, output1);
+ }
+ break;
+
+ case FMT_HDR_RGB:
+ rgb_hdr = true;
+ alpha_hdr_default = true;
+ hdr_rgb_unpack(input, output0, output1);
+ break;
+
+ case FMT_RGBA:
+ {
+ vint4 input0q(input[0], input[2], input[4], input[6]);
+ vint4 input1q(input[1], input[3], input[5], input[7]);
+ rgba_unpack(input0q, input1q, output0, output1);
+ }
+ break;
+
+ case FMT_RGBA_DELTA:
+ {
+ vint4 input0q(input[0], input[2], input[4], input[6]);
+ vint4 input1q(input[1], input[3], input[5], input[7]);
+ rgba_delta_unpack(input0q, input1q, output0, output1);
+ }
+ break;
+
+ case FMT_HDR_RGB_LDR_ALPHA:
+ rgb_hdr = true;
+ hdr_rgb_ldr_alpha_unpack(input, output0, output1);
+ break;
+
+ case FMT_HDR_RGBA:
+ rgb_hdr = true;
+ alpha_hdr = true;
+ hdr_rgb_hdr_alpha_unpack(input, output0, output1);
+ break;
+ }
+
+ // Assign a correct default alpha
+ if (alpha_hdr_default)
+ {
+ if (decode_mode == ASTCENC_PRF_HDR)
+ {
+ output0.set_lane<3>(0x7800);
+ output1.set_lane<3>(0x7800);
+ alpha_hdr = true;
+ }
+ else
+ {
+ output0.set_lane<3>(0x00FF);
+ output1.set_lane<3>(0x00FF);
+ alpha_hdr = false;
+ }
+ }
+
+ vint4 ldr_scale(257);
+ vint4 hdr_scale(1);
+ vint4 output_scale = ldr_scale;
+
+ // An LDR profile image
+ if ((decode_mode == ASTCENC_PRF_LDR) ||
+ (decode_mode == ASTCENC_PRF_LDR_SRGB))
+ {
+ // Also matches HDR alpha, as cannot have HDR alpha without HDR RGB
+ if (rgb_hdr == true)
+ {
+ output0 = vint4(0xFF00, 0x0000, 0xFF00, 0xFF00);
+ output1 = vint4(0xFF00, 0x0000, 0xFF00, 0xFF00);
+ output_scale = hdr_scale;
+
+ rgb_hdr = false;
+ alpha_hdr = false;
+ }
+ }
+ // An HDR profile image
+ else
+ {
+ vmask4 hdr_lanes(rgb_hdr, rgb_hdr, rgb_hdr, alpha_hdr);
+ output_scale = select(ldr_scale, hdr_scale, hdr_lanes);
+ }
+
+ output0 = output0 * output_scale;
+ output1 = output1 * output_scale;
+}
diff --git a/thirdparty/astcenc/astcenc_compress_symbolic.cpp b/thirdparty/astcenc/astcenc_compress_symbolic.cpp
new file mode 100644
index 0000000000..afb76246e7
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_compress_symbolic.cpp
@@ -0,0 +1,1455 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2023 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+
+/**
+ * @brief Functions to compress a symbolic block.
+ */
+
+#include "astcenc_internal.h"
+#include "astcenc_diagnostic_trace.h"
+
+#include <cassert>
+
+/**
+ * @brief Merge two planes of endpoints into a single vector.
+ *
+ * @param ep_plane1 The endpoints for plane 1.
+ * @param ep_plane2 The endpoints for plane 2.
+ * @param component_plane2 The color component for plane 2.
+ * @param[out] result The merged output.
+ */
+static void merge_endpoints(
+ const endpoints& ep_plane1,
+ const endpoints& ep_plane2,
+ unsigned int component_plane2,
+ endpoints& result
+) {
+ unsigned int partition_count = ep_plane1.partition_count;
+ assert(partition_count == 1);
+
+ vmask4 sep_mask = vint4::lane_id() == vint4(component_plane2);
+
+ result.partition_count = partition_count;
+ result.endpt0[0] = select(ep_plane1.endpt0[0], ep_plane2.endpt0[0], sep_mask);
+ result.endpt1[0] = select(ep_plane1.endpt1[0], ep_plane2.endpt1[0], sep_mask);
+}
+
+/**
+ * @brief Attempt to improve weights given a chosen configuration.
+ *
+ * Given a fixed weight grid decimation and weight value quantization, iterate over all weights (per
+ * partition and per plane) and attempt to improve image quality by moving each weight up by one or
+ * down by one quantization step.
+ *
+ * This is a specialized function which only supports operating on undecimated weight grids,
+ * therefore primarily improving the performance of 4x4 and 5x5 blocks where grid decimation
+ * is needed less often.
+ *
+ * @param decode_mode The decode mode (LDR, HDR).
+ * @param bsd The block size information.
+ * @param blk The image block color data to compress.
+ * @param[out] scb The symbolic compressed block output.
+ */
+static bool realign_weights_undecimated(
+ astcenc_profile decode_mode,
+ const block_size_descriptor& bsd,
+ const image_block& blk,
+ symbolic_compressed_block& scb
+) {
+ // Get the partition descriptor
+ unsigned int partition_count = scb.partition_count;
+ const auto& pi = bsd.get_partition_info(partition_count, scb.partition_index);
+
+ // Get the quantization table
+ const block_mode& bm = bsd.get_block_mode(scb.block_mode);
+ unsigned int weight_quant_level = bm.quant_mode;
+ const quant_and_transfer_table& qat = quant_and_xfer_tables[weight_quant_level];
+
+ unsigned int max_plane = bm.is_dual_plane;
+ int plane2_component = scb.plane2_component;
+ vmask4 plane_mask = vint4::lane_id() == vint4(plane2_component);
+
+ // Decode the color endpoints
+ bool rgb_hdr;
+ bool alpha_hdr;
+ vint4 endpnt0[BLOCK_MAX_PARTITIONS];
+ vint4 endpnt1[BLOCK_MAX_PARTITIONS];
+ vfloat4 endpnt0f[BLOCK_MAX_PARTITIONS];
+ vfloat4 offset[BLOCK_MAX_PARTITIONS];
+
+ promise(partition_count > 0);
+
+ for (unsigned int pa_idx = 0; pa_idx < partition_count; pa_idx++)
+ {
+ unpack_color_endpoints(decode_mode,
+ scb.color_formats[pa_idx],
+ scb.color_values[pa_idx],
+ rgb_hdr, alpha_hdr,
+ endpnt0[pa_idx],
+ endpnt1[pa_idx]);
+ }
+
+ uint8_t* dec_weights_uquant = scb.weights;
+ bool adjustments = false;
+
+ // For each plane and partition ...
+ for (unsigned int pl_idx = 0; pl_idx <= max_plane; pl_idx++)
+ {
+ for (unsigned int pa_idx = 0; pa_idx < partition_count; pa_idx++)
+ {
+ // Compute the endpoint delta for all components in current plane
+ vint4 epd = endpnt1[pa_idx] - endpnt0[pa_idx];
+ epd = select(epd, vint4::zero(), plane_mask);
+
+ endpnt0f[pa_idx] = int_to_float(endpnt0[pa_idx]);
+ offset[pa_idx] = int_to_float(epd) * (1.0f / 64.0f);
+ }
+
+ // For each weight compute previous, current, and next errors
+ promise(bsd.texel_count > 0);
+ for (unsigned int texel = 0; texel < bsd.texel_count; texel++)
+ {
+ int uqw = dec_weights_uquant[texel];
+
+ uint32_t prev_and_next = qat.prev_next_values[uqw];
+ int uqw_down = prev_and_next & 0xFF;
+ int uqw_up = (prev_and_next >> 8) & 0xFF;
+
+ // Interpolate the colors to create the diffs
+ float weight_base = static_cast<float>(uqw);
+ float weight_down = static_cast<float>(uqw_down - uqw);
+ float weight_up = static_cast<float>(uqw_up - uqw);
+
+ unsigned int partition = pi.partition_of_texel[texel];
+ vfloat4 color_offset = offset[partition];
+ vfloat4 color_base = endpnt0f[partition];
+
+ vfloat4 color = color_base + color_offset * weight_base;
+ vfloat4 orig_color = blk.texel(texel);
+ vfloat4 error_weight = blk.channel_weight;
+
+ vfloat4 color_diff = color - orig_color;
+ vfloat4 color_diff_down = color_diff + color_offset * weight_down;
+ vfloat4 color_diff_up = color_diff + color_offset * weight_up;
+
+ float error_base = dot_s(color_diff * color_diff, error_weight);
+ float error_down = dot_s(color_diff_down * color_diff_down, error_weight);
+ float error_up = dot_s(color_diff_up * color_diff_up, error_weight);
+
+ // Check if the prev or next error is better, and if so use it
+ if ((error_up < error_base) && (error_up < error_down) && (uqw < 64))
+ {
+ dec_weights_uquant[texel] = static_cast<uint8_t>(uqw_up);
+ adjustments = true;
+ }
+ else if ((error_down < error_base) && (uqw > 0))
+ {
+ dec_weights_uquant[texel] = static_cast<uint8_t>(uqw_down);
+ adjustments = true;
+ }
+ }
+
+ // Prepare iteration for plane 2
+ dec_weights_uquant += WEIGHTS_PLANE2_OFFSET;
+ plane_mask = ~plane_mask;
+ }
+
+ return adjustments;
+}
+
+/**
+ * @brief Attempt to improve weights given a chosen configuration.
+ *
+ * Given a fixed weight grid decimation and weight value quantization, iterate over all weights (per
+ * partition and per plane) and attempt to improve image quality by moving each weight up by one or
+ * down by one quantization step.
+ *
+ * @param decode_mode The decode mode (LDR, HDR).
+ * @param bsd The block size information.
+ * @param blk The image block color data to compress.
+ * @param[out] scb The symbolic compressed block output.
+ */
+static bool realign_weights_decimated(
+ astcenc_profile decode_mode,
+ const block_size_descriptor& bsd,
+ const image_block& blk,
+ symbolic_compressed_block& scb
+) {
+ // Get the partition descriptor
+ unsigned int partition_count = scb.partition_count;
+ const auto& pi = bsd.get_partition_info(partition_count, scb.partition_index);
+
+ // Get the quantization table
+ const block_mode& bm = bsd.get_block_mode(scb.block_mode);
+ unsigned int weight_quant_level = bm.quant_mode;
+ const quant_and_transfer_table& qat = quant_and_xfer_tables[weight_quant_level];
+
+ // Get the decimation table
+ const decimation_info& di = bsd.get_decimation_info(bm.decimation_mode);
+ unsigned int weight_count = di.weight_count;
+ assert(weight_count != bsd.texel_count);
+
+ unsigned int max_plane = bm.is_dual_plane;
+ int plane2_component = scb.plane2_component;
+ vmask4 plane_mask = vint4::lane_id() == vint4(plane2_component);
+
+ // Decode the color endpoints
+ bool rgb_hdr;
+ bool alpha_hdr;
+ vint4 endpnt0[BLOCK_MAX_PARTITIONS];
+ vint4 endpnt1[BLOCK_MAX_PARTITIONS];
+ vfloat4 endpnt0f[BLOCK_MAX_PARTITIONS];
+ vfloat4 offset[BLOCK_MAX_PARTITIONS];
+
+ promise(partition_count > 0);
+ promise(weight_count > 0);
+
+ for (unsigned int pa_idx = 0; pa_idx < partition_count; pa_idx++)
+ {
+ unpack_color_endpoints(decode_mode,
+ scb.color_formats[pa_idx],
+ scb.color_values[pa_idx],
+ rgb_hdr, alpha_hdr,
+ endpnt0[pa_idx],
+ endpnt1[pa_idx]);
+ }
+
+ uint8_t* dec_weights_uquant = scb.weights;
+ bool adjustments = false;
+
+ // For each plane and partition ...
+ for (unsigned int pl_idx = 0; pl_idx <= max_plane; pl_idx++)
+ {
+ for (unsigned int pa_idx = 0; pa_idx < partition_count; pa_idx++)
+ {
+ // Compute the endpoint delta for all components in current plane
+ vint4 epd = endpnt1[pa_idx] - endpnt0[pa_idx];
+ epd = select(epd, vint4::zero(), plane_mask);
+
+ endpnt0f[pa_idx] = int_to_float(endpnt0[pa_idx]);
+ offset[pa_idx] = int_to_float(epd) * (1.0f / 64.0f);
+ }
+
+ // Create an unquantized weight grid for this decimation level
+ alignas(ASTCENC_VECALIGN) float uq_weightsf[BLOCK_MAX_WEIGHTS];
+ for (unsigned int we_idx = 0; we_idx < weight_count; we_idx += ASTCENC_SIMD_WIDTH)
+ {
+ vint unquant_value(dec_weights_uquant + we_idx);
+ vfloat unquant_valuef = int_to_float(unquant_value);
+ storea(unquant_valuef, uq_weightsf + we_idx);
+ }
+
+ // For each weight compute previous, current, and next errors
+ for (unsigned int we_idx = 0; we_idx < weight_count; we_idx++)
+ {
+ int uqw = dec_weights_uquant[we_idx];
+ uint32_t prev_and_next = qat.prev_next_values[uqw];
+
+ float uqw_base = uq_weightsf[we_idx];
+ float uqw_down = static_cast<float>(prev_and_next & 0xFF);
+ float uqw_up = static_cast<float>((prev_and_next >> 8) & 0xFF);
+
+ float uqw_diff_down = uqw_down - uqw_base;
+ float uqw_diff_up = uqw_up - uqw_base;
+
+ vfloat4 error_basev = vfloat4::zero();
+ vfloat4 error_downv = vfloat4::zero();
+ vfloat4 error_upv = vfloat4::zero();
+
+ // Interpolate the colors to create the diffs
+ unsigned int texels_to_evaluate = di.weight_texel_count[we_idx];
+ promise(texels_to_evaluate > 0);
+ for (unsigned int te_idx = 0; te_idx < texels_to_evaluate; te_idx++)
+ {
+ unsigned int texel = di.weight_texels_tr[te_idx][we_idx];
+
+ float tw_base = di.texel_contrib_for_weight[te_idx][we_idx];
+
+ float weight_base = (uq_weightsf[di.texel_weights_tr[0][texel]] * di.texel_weight_contribs_float_tr[0][texel]
+ + uq_weightsf[di.texel_weights_tr[1][texel]] * di.texel_weight_contribs_float_tr[1][texel])
+ + (uq_weightsf[di.texel_weights_tr[2][texel]] * di.texel_weight_contribs_float_tr[2][texel]
+ + uq_weightsf[di.texel_weights_tr[3][texel]] * di.texel_weight_contribs_float_tr[3][texel]);
+
+ // Ideally this is integer rounded, but IQ gain it isn't worth the overhead
+ // float weight = astc::flt_rd(weight_base + 0.5f);
+ // float weight_down = astc::flt_rd(weight_base + 0.5f + uqw_diff_down * tw_base) - weight;
+ // float weight_up = astc::flt_rd(weight_base + 0.5f + uqw_diff_up * tw_base) - weight;
+ float weight_down = weight_base + uqw_diff_down * tw_base - weight_base;
+ float weight_up = weight_base + uqw_diff_up * tw_base - weight_base;
+
+ unsigned int partition = pi.partition_of_texel[texel];
+ vfloat4 color_offset = offset[partition];
+ vfloat4 color_base = endpnt0f[partition];
+
+ vfloat4 color = color_base + color_offset * weight_base;
+ vfloat4 orig_color = blk.texel(texel);
+
+ vfloat4 color_diff = color - orig_color;
+ vfloat4 color_down_diff = color_diff + color_offset * weight_down;
+ vfloat4 color_up_diff = color_diff + color_offset * weight_up;
+
+ error_basev += color_diff * color_diff;
+ error_downv += color_down_diff * color_down_diff;
+ error_upv += color_up_diff * color_up_diff;
+ }
+
+ vfloat4 error_weight = blk.channel_weight;
+ float error_base = hadd_s(error_basev * error_weight);
+ float error_down = hadd_s(error_downv * error_weight);
+ float error_up = hadd_s(error_upv * error_weight);
+
+ // Check if the prev or next error is better, and if so use it
+ if ((error_up < error_base) && (error_up < error_down) && (uqw < 64))
+ {
+ uq_weightsf[we_idx] = uqw_up;
+ dec_weights_uquant[we_idx] = static_cast<uint8_t>(uqw_up);
+ adjustments = true;
+ }
+ else if ((error_down < error_base) && (uqw > 0))
+ {
+ uq_weightsf[we_idx] = uqw_down;
+ dec_weights_uquant[we_idx] = static_cast<uint8_t>(uqw_down);
+ adjustments = true;
+ }
+ }
+
+ // Prepare iteration for plane 2
+ dec_weights_uquant += WEIGHTS_PLANE2_OFFSET;
+ plane_mask = ~plane_mask;
+ }
+
+ return adjustments;
+}
+
+/**
+ * @brief Compress a block using a chosen partitioning and 1 plane of weights.
+ *
+ * @param config The compressor configuration.
+ * @param bsd The block size information.
+ * @param blk The image block color data to compress.
+ * @param only_always True if we only use "always" percentile block modes.
+ * @param tune_errorval_threshold The error value threshold.
+ * @param partition_count The partition count.
+ * @param partition_index The partition index if @c partition_count is 2-4.
+ * @param[out] scb The symbolic compressed block output.
+ * @param[out] tmpbuf The quantized weights for plane 1.
+ */
+static float compress_symbolic_block_for_partition_1plane(
+ const astcenc_config& config,
+ const block_size_descriptor& bsd,
+ const image_block& blk,
+ bool only_always,
+ float tune_errorval_threshold,
+ unsigned int partition_count,
+ unsigned int partition_index,
+ symbolic_compressed_block& scb,
+ compression_working_buffers& tmpbuf,
+ int quant_limit
+) {
+ promise(partition_count > 0);
+ promise(config.tune_candidate_limit > 0);
+ promise(config.tune_refinement_limit > 0);
+
+ int max_weight_quant = astc::min(static_cast<int>(QUANT_32), quant_limit);
+
+ auto compute_difference = &compute_symbolic_block_difference_1plane;
+ if ((partition_count == 1) && !(config.flags & ASTCENC_FLG_MAP_RGBM))
+ {
+ compute_difference = &compute_symbolic_block_difference_1plane_1partition;
+ }
+
+ const auto& pi = bsd.get_partition_info(partition_count, partition_index);
+
+ // Compute ideal weights and endpoint colors, with no quantization or decimation
+ endpoints_and_weights& ei = tmpbuf.ei1;
+ compute_ideal_colors_and_weights_1plane(blk, pi, ei);
+
+ // Compute ideal weights and endpoint colors for every decimation
+ float* dec_weights_ideal = tmpbuf.dec_weights_ideal;
+ uint8_t* dec_weights_uquant = tmpbuf.dec_weights_uquant;
+
+ // For each decimation mode, compute an ideal set of weights with no quantization
+ unsigned int max_decimation_modes = only_always ? bsd.decimation_mode_count_always
+ : bsd.decimation_mode_count_selected;
+ promise(max_decimation_modes > 0);
+ for (unsigned int i = 0; i < max_decimation_modes; i++)
+ {
+ const auto& dm = bsd.get_decimation_mode(i);
+ if (!dm.is_ref_1_plane(static_cast<quant_method>(max_weight_quant)))
+ {
+ continue;
+ }
+
+ const auto& di = bsd.get_decimation_info(i);
+
+ compute_ideal_weights_for_decimation(
+ ei,
+ di,
+ dec_weights_ideal + i * BLOCK_MAX_WEIGHTS);
+ }
+
+ // Compute maximum colors for the endpoints and ideal weights, then for each endpoint and ideal
+ // weight pair, compute the smallest weight that will result in a color value greater than 1
+ vfloat4 min_ep(10.0f);
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ vfloat4 ep = (vfloat4(1.0f) - ei.ep.endpt0[i]) / (ei.ep.endpt1[i] - ei.ep.endpt0[i]);
+
+ vmask4 use_ep = (ep > vfloat4(0.5f)) & (ep < min_ep);
+ min_ep = select(min_ep, ep, use_ep);
+ }
+
+ float min_wt_cutoff = hmin_s(min_ep);
+
+ // For each mode, use the angular method to compute a shift
+ compute_angular_endpoints_1plane(
+ only_always, bsd, dec_weights_ideal, max_weight_quant, tmpbuf);
+
+ float* weight_low_value = tmpbuf.weight_low_value1;
+ float* weight_high_value = tmpbuf.weight_high_value1;
+ int8_t* qwt_bitcounts = tmpbuf.qwt_bitcounts;
+ float* qwt_errors = tmpbuf.qwt_errors;
+
+ // For each mode (which specifies a decimation and a quantization):
+ // * Compute number of bits needed for the quantized weights
+ // * Generate an optimized set of quantized weights
+ // * Compute quantization errors for the mode
+
+
+ static const int8_t free_bits_for_partition_count[4] {
+ 115 - 4, 111 - 4 - PARTITION_INDEX_BITS, 108 - 4 - PARTITION_INDEX_BITS, 105 - 4 - PARTITION_INDEX_BITS
+ };
+
+ unsigned int max_block_modes = only_always ? bsd.block_mode_count_1plane_always
+ : bsd.block_mode_count_1plane_selected;
+ promise(max_block_modes > 0);
+ for (unsigned int i = 0; i < max_block_modes; i++)
+ {
+ const block_mode& bm = bsd.block_modes[i];
+
+ if (bm.quant_mode > max_weight_quant)
+ {
+ qwt_errors[i] = 1e38f;
+ continue;
+ }
+
+ assert(!bm.is_dual_plane);
+ int bitcount = free_bits_for_partition_count[partition_count - 1] - bm.weight_bits;
+ if (bitcount <= 0)
+ {
+ qwt_errors[i] = 1e38f;
+ continue;
+ }
+
+ if (weight_high_value[i] > 1.02f * min_wt_cutoff)
+ {
+ weight_high_value[i] = 1.0f;
+ }
+
+ int decimation_mode = bm.decimation_mode;
+ const auto& di = bsd.get_decimation_info(decimation_mode);
+
+ qwt_bitcounts[i] = static_cast<int8_t>(bitcount);
+
+ alignas(ASTCENC_VECALIGN) float dec_weights_uquantf[BLOCK_MAX_WEIGHTS];
+
+ // Generate the optimized set of weights for the weight mode
+ compute_quantized_weights_for_decimation(
+ di,
+ weight_low_value[i], weight_high_value[i],
+ dec_weights_ideal + BLOCK_MAX_WEIGHTS * decimation_mode,
+ dec_weights_uquantf,
+ dec_weights_uquant + BLOCK_MAX_WEIGHTS * i,
+ bm.get_weight_quant_mode());
+
+ // Compute weight quantization errors for the block mode
+ qwt_errors[i] = compute_error_of_weight_set_1plane(
+ ei,
+ di,
+ dec_weights_uquantf);
+ }
+
+ // Decide the optimal combination of color endpoint encodings and weight encodings
+ uint8_t partition_format_specifiers[TUNE_MAX_TRIAL_CANDIDATES][BLOCK_MAX_PARTITIONS];
+ int block_mode_index[TUNE_MAX_TRIAL_CANDIDATES];
+
+ quant_method color_quant_level[TUNE_MAX_TRIAL_CANDIDATES];
+ quant_method color_quant_level_mod[TUNE_MAX_TRIAL_CANDIDATES];
+
+ unsigned int candidate_count = compute_ideal_endpoint_formats(
+ pi, blk, ei.ep, qwt_bitcounts, qwt_errors,
+ config.tune_candidate_limit, 0, max_block_modes,
+ partition_format_specifiers, block_mode_index,
+ color_quant_level, color_quant_level_mod, tmpbuf);
+
+ // Iterate over the N believed-to-be-best modes to find out which one is actually best
+ float best_errorval_in_mode = ERROR_CALC_DEFAULT;
+ float best_errorval_in_scb = scb.errorval;
+
+ for (unsigned int i = 0; i < candidate_count; i++)
+ {
+ TRACE_NODE(node0, "candidate");
+
+ const int bm_packed_index = block_mode_index[i];
+ assert(bm_packed_index >= 0 && bm_packed_index < static_cast<int>(bsd.block_mode_count_1plane_selected));
+ const block_mode& qw_bm = bsd.block_modes[bm_packed_index];
+
+ int decimation_mode = qw_bm.decimation_mode;
+ const auto& di = bsd.get_decimation_info(decimation_mode);
+ promise(di.weight_count > 0);
+
+ trace_add_data("weight_x", di.weight_x);
+ trace_add_data("weight_y", di.weight_y);
+ trace_add_data("weight_z", di.weight_z);
+ trace_add_data("weight_quant", qw_bm.quant_mode);
+
+ // Recompute the ideal color endpoints before storing them
+ vfloat4 rgbs_colors[BLOCK_MAX_PARTITIONS];
+ vfloat4 rgbo_colors[BLOCK_MAX_PARTITIONS];
+
+ symbolic_compressed_block workscb;
+ endpoints workep = ei.ep;
+
+ uint8_t* u8_weight_src = dec_weights_uquant + BLOCK_MAX_WEIGHTS * bm_packed_index;
+
+ for (unsigned int j = 0; j < di.weight_count; j++)
+ {
+ workscb.weights[j] = u8_weight_src[j];
+ }
+
+ for (unsigned int l = 0; l < config.tune_refinement_limit; l++)
+ {
+ recompute_ideal_colors_1plane(
+ blk, pi, di, workscb.weights,
+ workep, rgbs_colors, rgbo_colors);
+
+ // Quantize the chosen color, tracking if worth trying the mod value
+ bool all_same = color_quant_level[i] != color_quant_level_mod[i];
+ for (unsigned int j = 0; j < partition_count; j++)
+ {
+ workscb.color_formats[j] = pack_color_endpoints(
+ workep.endpt0[j],
+ workep.endpt1[j],
+ rgbs_colors[j],
+ rgbo_colors[j],
+ partition_format_specifiers[i][j],
+ workscb.color_values[j],
+ color_quant_level[i]);
+
+ all_same = all_same && workscb.color_formats[j] == workscb.color_formats[0];
+ }
+
+ // If all the color endpoint modes are the same, we get a few more bits to store colors;
+ // let's see if we can take advantage of this: requantize all the colors and see if the
+ // endpoint modes remain the same.
+ workscb.color_formats_matched = 0;
+ if (partition_count >= 2 && all_same)
+ {
+ uint8_t colorvals[BLOCK_MAX_PARTITIONS][12];
+ uint8_t color_formats_mod[BLOCK_MAX_PARTITIONS] { 0 };
+ bool all_same_mod = true;
+ for (unsigned int j = 0; j < partition_count; j++)
+ {
+ color_formats_mod[j] = pack_color_endpoints(
+ workep.endpt0[j],
+ workep.endpt1[j],
+ rgbs_colors[j],
+ rgbo_colors[j],
+ partition_format_specifiers[i][j],
+ colorvals[j],
+ color_quant_level_mod[i]);
+
+ // Early out as soon as it's no longer possible to use mod
+ if (color_formats_mod[j] != color_formats_mod[0])
+ {
+ all_same_mod = false;
+ break;
+ }
+ }
+
+ if (all_same_mod)
+ {
+ workscb.color_formats_matched = 1;
+ for (unsigned int j = 0; j < BLOCK_MAX_PARTITIONS; j++)
+ {
+ for (unsigned int k = 0; k < 8; k++)
+ {
+ workscb.color_values[j][k] = colorvals[j][k];
+ }
+
+ workscb.color_formats[j] = color_formats_mod[j];
+ }
+ }
+ }
+
+ // Store header fields
+ workscb.partition_count = static_cast<uint8_t>(partition_count);
+ workscb.partition_index = static_cast<uint16_t>(partition_index);
+ workscb.plane2_component = -1;
+ workscb.quant_mode = workscb.color_formats_matched ? color_quant_level_mod[i] : color_quant_level[i];
+ workscb.block_mode = qw_bm.mode_index;
+ workscb.block_type = SYM_BTYPE_NONCONST;
+
+ // Pre-realign test
+ if (l == 0)
+ {
+ float errorval = compute_difference(config, bsd, workscb, blk);
+ if (errorval == -ERROR_CALC_DEFAULT)
+ {
+ errorval = -errorval;
+ workscb.block_type = SYM_BTYPE_ERROR;
+ }
+
+ trace_add_data("error_prerealign", errorval);
+ best_errorval_in_mode = astc::min(errorval, best_errorval_in_mode);
+
+ // Average refinement improvement is 3.5% per iteration (allow 4.5%), but the first
+ // iteration can help more so we give it a extra 8% leeway. Use this knowledge to
+ // drive a heuristic to skip blocks that are unlikely to catch up with the best
+ // block we have already.
+ unsigned int iters_remaining = config.tune_refinement_limit - l;
+ float threshold = (0.045f * static_cast<float>(iters_remaining)) + 1.08f;
+ if (errorval > (threshold * best_errorval_in_scb))
+ {
+ break;
+ }
+
+ if (errorval < best_errorval_in_scb)
+ {
+ best_errorval_in_scb = errorval;
+ workscb.errorval = errorval;
+ scb = workscb;
+
+ if (errorval < tune_errorval_threshold)
+ {
+ // Skip remaining candidates - this is "good enough"
+ i = candidate_count;
+ break;
+ }
+ }
+ }
+
+ bool adjustments;
+ if (di.weight_count != bsd.texel_count)
+ {
+ adjustments = realign_weights_decimated(
+ config.profile, bsd, blk, workscb);
+ }
+ else
+ {
+ adjustments = realign_weights_undecimated(
+ config.profile, bsd, blk, workscb);
+ }
+
+ // Post-realign test
+ float errorval = compute_difference(config, bsd, workscb, blk);
+ if (errorval == -ERROR_CALC_DEFAULT)
+ {
+ errorval = -errorval;
+ workscb.block_type = SYM_BTYPE_ERROR;
+ }
+
+ trace_add_data("error_postrealign", errorval);
+ best_errorval_in_mode = astc::min(errorval, best_errorval_in_mode);
+
+ // Average refinement improvement is 3.5% per iteration, so skip blocks that are
+ // unlikely to catch up with the best block we have already. Assume a 4.5% per step to
+ // give benefit of the doubt ...
+ unsigned int iters_remaining = config.tune_refinement_limit - 1 - l;
+ float threshold = (0.045f * static_cast<float>(iters_remaining)) + 1.0f;
+ if (errorval > (threshold * best_errorval_in_scb))
+ {
+ break;
+ }
+
+ if (errorval < best_errorval_in_scb)
+ {
+ best_errorval_in_scb = errorval;
+ workscb.errorval = errorval;
+ scb = workscb;
+
+ if (errorval < tune_errorval_threshold)
+ {
+ // Skip remaining candidates - this is "good enough"
+ i = candidate_count;
+ break;
+ }
+ }
+
+ if (!adjustments)
+ {
+ break;
+ }
+ }
+ }
+
+ return best_errorval_in_mode;
+}
+
+/**
+ * @brief Compress a block using a chosen partitioning and 2 planes of weights.
+ *
+ * @param config The compressor configuration.
+ * @param bsd The block size information.
+ * @param blk The image block color data to compress.
+ * @param tune_errorval_threshold The error value threshold.
+ * @param plane2_component The component index for the second plane of weights.
+ * @param[out] scb The symbolic compressed block output.
+ * @param[out] tmpbuf The quantized weights for plane 1.
+ */
+static float compress_symbolic_block_for_partition_2planes(
+ const astcenc_config& config,
+ const block_size_descriptor& bsd,
+ const image_block& blk,
+ float tune_errorval_threshold,
+ unsigned int plane2_component,
+ symbolic_compressed_block& scb,
+ compression_working_buffers& tmpbuf,
+ int quant_limit
+) {
+ promise(config.tune_candidate_limit > 0);
+ promise(config.tune_refinement_limit > 0);
+ promise(bsd.decimation_mode_count_selected > 0);
+
+ int max_weight_quant = astc::min(static_cast<int>(QUANT_32), quant_limit);
+
+ // Compute ideal weights and endpoint colors, with no quantization or decimation
+ endpoints_and_weights& ei1 = tmpbuf.ei1;
+ endpoints_and_weights& ei2 = tmpbuf.ei2;
+
+ compute_ideal_colors_and_weights_2planes(bsd, blk, plane2_component, ei1, ei2);
+
+ // Compute ideal weights and endpoint colors for every decimation
+ float* dec_weights_ideal = tmpbuf.dec_weights_ideal;
+ uint8_t* dec_weights_uquant = tmpbuf.dec_weights_uquant;
+
+ // For each decimation mode, compute an ideal set of weights with no quantization
+ for (unsigned int i = 0; i < bsd.decimation_mode_count_selected; i++)
+ {
+ const auto& dm = bsd.get_decimation_mode(i);
+ if (!dm.is_ref_2_plane(static_cast<quant_method>(max_weight_quant)))
+ {
+ continue;
+ }
+
+ const auto& di = bsd.get_decimation_info(i);
+
+ compute_ideal_weights_for_decimation(
+ ei1,
+ di,
+ dec_weights_ideal + i * BLOCK_MAX_WEIGHTS);
+
+ compute_ideal_weights_for_decimation(
+ ei2,
+ di,
+ dec_weights_ideal + i * BLOCK_MAX_WEIGHTS + WEIGHTS_PLANE2_OFFSET);
+ }
+
+ // Compute maximum colors for the endpoints and ideal weights, then for each endpoint and ideal
+ // weight pair, compute the smallest weight that will result in a color value greater than 1
+ vfloat4 min_ep1(10.0f);
+ vfloat4 min_ep2(10.0f);
+
+ vfloat4 ep1 = (vfloat4(1.0f) - ei1.ep.endpt0[0]) / (ei1.ep.endpt1[0] - ei1.ep.endpt0[0]);
+ vmask4 use_ep1 = (ep1 > vfloat4(0.5f)) & (ep1 < min_ep1);
+ min_ep1 = select(min_ep1, ep1, use_ep1);
+
+ vfloat4 ep2 = (vfloat4(1.0f) - ei2.ep.endpt0[0]) / (ei2.ep.endpt1[0] - ei2.ep.endpt0[0]);
+ vmask4 use_ep2 = (ep2 > vfloat4(0.5f)) & (ep2 < min_ep2);
+ min_ep2 = select(min_ep2, ep2, use_ep2);
+
+ vfloat4 err_max(ERROR_CALC_DEFAULT);
+ vmask4 err_mask = vint4::lane_id() == vint4(plane2_component);
+
+ // Set the plane2 component to max error in ep1
+ min_ep1 = select(min_ep1, err_max, err_mask);
+
+ float min_wt_cutoff1 = hmin_s(min_ep1);
+
+ // Set the minwt2 to the plane2 component min in ep2
+ float min_wt_cutoff2 = hmin_s(select(err_max, min_ep2, err_mask));
+
+ compute_angular_endpoints_2planes(
+ bsd, dec_weights_ideal, max_weight_quant, tmpbuf);
+
+ // For each mode (which specifies a decimation and a quantization):
+ // * Compute number of bits needed for the quantized weights
+ // * Generate an optimized set of quantized weights
+ // * Compute quantization errors for the mode
+
+ float* weight_low_value1 = tmpbuf.weight_low_value1;
+ float* weight_high_value1 = tmpbuf.weight_high_value1;
+ float* weight_low_value2 = tmpbuf.weight_low_value2;
+ float* weight_high_value2 = tmpbuf.weight_high_value2;
+
+ int8_t* qwt_bitcounts = tmpbuf.qwt_bitcounts;
+ float* qwt_errors = tmpbuf.qwt_errors;
+
+ unsigned int start_2plane = bsd.block_mode_count_1plane_selected;
+ unsigned int end_2plane = bsd.block_mode_count_1plane_2plane_selected;
+
+ for (unsigned int i = start_2plane; i < end_2plane; i++)
+ {
+ const block_mode& bm = bsd.block_modes[i];
+ assert(bm.is_dual_plane);
+
+ if (bm.quant_mode > max_weight_quant)
+ {
+ qwt_errors[i] = 1e38f;
+ continue;
+ }
+
+ qwt_bitcounts[i] = static_cast<int8_t>(109 - bm.weight_bits);
+
+ if (weight_high_value1[i] > 1.02f * min_wt_cutoff1)
+ {
+ weight_high_value1[i] = 1.0f;
+ }
+
+ if (weight_high_value2[i] > 1.02f * min_wt_cutoff2)
+ {
+ weight_high_value2[i] = 1.0f;
+ }
+
+ unsigned int decimation_mode = bm.decimation_mode;
+ const auto& di = bsd.get_decimation_info(decimation_mode);
+
+ alignas(ASTCENC_VECALIGN) float dec_weights_uquantf[BLOCK_MAX_WEIGHTS];
+
+ // Generate the optimized set of weights for the mode
+ compute_quantized_weights_for_decimation(
+ di,
+ weight_low_value1[i],
+ weight_high_value1[i],
+ dec_weights_ideal + BLOCK_MAX_WEIGHTS * decimation_mode,
+ dec_weights_uquantf,
+ dec_weights_uquant + BLOCK_MAX_WEIGHTS * i,
+ bm.get_weight_quant_mode());
+
+ compute_quantized_weights_for_decimation(
+ di,
+ weight_low_value2[i],
+ weight_high_value2[i],
+ dec_weights_ideal + BLOCK_MAX_WEIGHTS * decimation_mode + WEIGHTS_PLANE2_OFFSET,
+ dec_weights_uquantf + WEIGHTS_PLANE2_OFFSET,
+ dec_weights_uquant + BLOCK_MAX_WEIGHTS * i + WEIGHTS_PLANE2_OFFSET,
+ bm.get_weight_quant_mode());
+
+ // Compute weight quantization errors for the block mode
+ qwt_errors[i] = compute_error_of_weight_set_2planes(
+ ei1,
+ ei2,
+ di,
+ dec_weights_uquantf,
+ dec_weights_uquantf + WEIGHTS_PLANE2_OFFSET);
+ }
+
+ // Decide the optimal combination of color endpoint encodings and weight encodings
+ uint8_t partition_format_specifiers[TUNE_MAX_TRIAL_CANDIDATES][BLOCK_MAX_PARTITIONS];
+ int block_mode_index[TUNE_MAX_TRIAL_CANDIDATES];
+
+ quant_method color_quant_level[TUNE_MAX_TRIAL_CANDIDATES];
+ quant_method color_quant_level_mod[TUNE_MAX_TRIAL_CANDIDATES];
+
+ endpoints epm;
+ merge_endpoints(ei1.ep, ei2.ep, plane2_component, epm);
+
+ const auto& pi = bsd.get_partition_info(1, 0);
+ unsigned int candidate_count = compute_ideal_endpoint_formats(
+ pi, blk, epm, qwt_bitcounts, qwt_errors,
+ config.tune_candidate_limit,
+ bsd.block_mode_count_1plane_selected, bsd.block_mode_count_1plane_2plane_selected,
+ partition_format_specifiers, block_mode_index,
+ color_quant_level, color_quant_level_mod, tmpbuf);
+
+ // Iterate over the N believed-to-be-best modes to find out which one is actually best
+ float best_errorval_in_mode = ERROR_CALC_DEFAULT;
+ float best_errorval_in_scb = scb.errorval;
+
+ for (unsigned int i = 0; i < candidate_count; i++)
+ {
+ TRACE_NODE(node0, "candidate");
+
+ const int bm_packed_index = block_mode_index[i];
+ assert(bm_packed_index >= static_cast<int>(bsd.block_mode_count_1plane_selected) &&
+ bm_packed_index < static_cast<int>(bsd.block_mode_count_1plane_2plane_selected));
+ const block_mode& qw_bm = bsd.block_modes[bm_packed_index];
+
+ int decimation_mode = qw_bm.decimation_mode;
+ const auto& di = bsd.get_decimation_info(decimation_mode);
+ promise(di.weight_count > 0);
+
+ trace_add_data("weight_x", di.weight_x);
+ trace_add_data("weight_y", di.weight_y);
+ trace_add_data("weight_z", di.weight_z);
+ trace_add_data("weight_quant", qw_bm.quant_mode);
+
+ vfloat4 rgbs_color;
+ vfloat4 rgbo_color;
+
+ symbolic_compressed_block workscb;
+ endpoints workep = epm;
+
+ uint8_t* u8_weight1_src = dec_weights_uquant + BLOCK_MAX_WEIGHTS * bm_packed_index;
+ uint8_t* u8_weight2_src = dec_weights_uquant + BLOCK_MAX_WEIGHTS * bm_packed_index + WEIGHTS_PLANE2_OFFSET;
+
+ for (int j = 0; j < di.weight_count; j++)
+ {
+ workscb.weights[j] = u8_weight1_src[j];
+ workscb.weights[j + WEIGHTS_PLANE2_OFFSET] = u8_weight2_src[j];
+ }
+
+ for (unsigned int l = 0; l < config.tune_refinement_limit; l++)
+ {
+ recompute_ideal_colors_2planes(
+ blk, bsd, di,
+ workscb.weights, workscb.weights + WEIGHTS_PLANE2_OFFSET,
+ workep, rgbs_color, rgbo_color, plane2_component);
+
+ // Quantize the chosen color
+ workscb.color_formats[0] = pack_color_endpoints(
+ workep.endpt0[0],
+ workep.endpt1[0],
+ rgbs_color, rgbo_color,
+ partition_format_specifiers[i][0],
+ workscb.color_values[0],
+ color_quant_level[i]);
+
+ // Store header fields
+ workscb.partition_count = 1;
+ workscb.partition_index = 0;
+ workscb.quant_mode = color_quant_level[i];
+ workscb.color_formats_matched = 0;
+ workscb.block_mode = qw_bm.mode_index;
+ workscb.plane2_component = static_cast<int8_t>(plane2_component);
+ workscb.block_type = SYM_BTYPE_NONCONST;
+
+ // Pre-realign test
+ if (l == 0)
+ {
+ float errorval = compute_symbolic_block_difference_2plane(config, bsd, workscb, blk);
+ if (errorval == -ERROR_CALC_DEFAULT)
+ {
+ errorval = -errorval;
+ workscb.block_type = SYM_BTYPE_ERROR;
+ }
+
+ trace_add_data("error_prerealign", errorval);
+ best_errorval_in_mode = astc::min(errorval, best_errorval_in_mode);
+
+ // Average refinement improvement is 3.5% per iteration (allow 4.5%), but the first
+ // iteration can help more so we give it a extra 8% leeway. Use this knowledge to
+ // drive a heuristic to skip blocks that are unlikely to catch up with the best
+ // block we have already.
+ unsigned int iters_remaining = config.tune_refinement_limit - l;
+ float threshold = (0.045f * static_cast<float>(iters_remaining)) + 1.08f;
+ if (errorval > (threshold * best_errorval_in_scb))
+ {
+ break;
+ }
+
+ if (errorval < best_errorval_in_scb)
+ {
+ best_errorval_in_scb = errorval;
+ workscb.errorval = errorval;
+ scb = workscb;
+
+ if (errorval < tune_errorval_threshold)
+ {
+ // Skip remaining candidates - this is "good enough"
+ i = candidate_count;
+ break;
+ }
+ }
+ }
+
+ // Perform a final pass over the weights to try to improve them.
+ bool adjustments;
+ if (di.weight_count != bsd.texel_count)
+ {
+ adjustments = realign_weights_decimated(
+ config.profile, bsd, blk, workscb);
+ }
+ else
+ {
+ adjustments = realign_weights_undecimated(
+ config.profile, bsd, blk, workscb);
+ }
+
+ // Post-realign test
+ float errorval = compute_symbolic_block_difference_2plane(config, bsd, workscb, blk);
+ if (errorval == -ERROR_CALC_DEFAULT)
+ {
+ errorval = -errorval;
+ workscb.block_type = SYM_BTYPE_ERROR;
+ }
+
+ trace_add_data("error_postrealign", errorval);
+ best_errorval_in_mode = astc::min(errorval, best_errorval_in_mode);
+
+ // Average refinement improvement is 3.5% per iteration, so skip blocks that are
+ // unlikely to catch up with the best block we have already. Assume a 4.5% per step to
+ // give benefit of the doubt ...
+ unsigned int iters_remaining = config.tune_refinement_limit - 1 - l;
+ float threshold = (0.045f * static_cast<float>(iters_remaining)) + 1.0f;
+ if (errorval > (threshold * best_errorval_in_scb))
+ {
+ break;
+ }
+
+ if (errorval < best_errorval_in_scb)
+ {
+ best_errorval_in_scb = errorval;
+ workscb.errorval = errorval;
+ scb = workscb;
+
+ if (errorval < tune_errorval_threshold)
+ {
+ // Skip remaining candidates - this is "good enough"
+ i = candidate_count;
+ break;
+ }
+ }
+
+ if (!adjustments)
+ {
+ break;
+ }
+ }
+ }
+
+ return best_errorval_in_mode;
+}
+
+/**
+ * @brief Determine the lowest cross-channel correlation factor.
+ *
+ * @param texels_per_block The number of texels in a block.
+ * @param blk The image block color data to compress.
+ *
+ * @return Return the lowest correlation factor.
+ */
+static float prepare_block_statistics(
+ int texels_per_block,
+ const image_block& blk
+) {
+ // Compute covariance matrix, as a collection of 10 scalars that form the upper-triangular row
+ // of the matrix. The matrix is symmetric, so this is all we need for this use case.
+ float rs = 0.0f;
+ float gs = 0.0f;
+ float bs = 0.0f;
+ float as = 0.0f;
+ float rr_var = 0.0f;
+ float gg_var = 0.0f;
+ float bb_var = 0.0f;
+ float aa_var = 0.0f;
+ float rg_cov = 0.0f;
+ float rb_cov = 0.0f;
+ float ra_cov = 0.0f;
+ float gb_cov = 0.0f;
+ float ga_cov = 0.0f;
+ float ba_cov = 0.0f;
+
+ float weight_sum = 0.0f;
+
+ promise(texels_per_block > 0);
+ for (int i = 0; i < texels_per_block; i++)
+ {
+ float weight = hadd_s(blk.channel_weight) / 4.0f;
+ assert(weight >= 0.0f);
+ weight_sum += weight;
+
+ float r = blk.data_r[i];
+ float g = blk.data_g[i];
+ float b = blk.data_b[i];
+ float a = blk.data_a[i];
+
+ float rw = r * weight;
+ rs += rw;
+ rr_var += r * rw;
+ rg_cov += g * rw;
+ rb_cov += b * rw;
+ ra_cov += a * rw;
+
+ float gw = g * weight;
+ gs += gw;
+ gg_var += g * gw;
+ gb_cov += b * gw;
+ ga_cov += a * gw;
+
+ float bw = b * weight;
+ bs += bw;
+ bb_var += b * bw;
+ ba_cov += a * bw;
+
+ float aw = a * weight;
+ as += aw;
+ aa_var += a * aw;
+ }
+
+ float rpt = 1.0f / astc::max(weight_sum, 1e-7f);
+
+ rr_var -= rs * (rs * rpt);
+ rg_cov -= gs * (rs * rpt);
+ rb_cov -= bs * (rs * rpt);
+ ra_cov -= as * (rs * rpt);
+
+ gg_var -= gs * (gs * rpt);
+ gb_cov -= bs * (gs * rpt);
+ ga_cov -= as * (gs * rpt);
+
+ bb_var -= bs * (bs * rpt);
+ ba_cov -= as * (bs * rpt);
+
+ aa_var -= as * (as * rpt);
+
+ // These will give a NaN if a channel is constant - these are fixed up in the next step
+ rg_cov *= astc::rsqrt(rr_var * gg_var);
+ rb_cov *= astc::rsqrt(rr_var * bb_var);
+ ra_cov *= astc::rsqrt(rr_var * aa_var);
+ gb_cov *= astc::rsqrt(gg_var * bb_var);
+ ga_cov *= astc::rsqrt(gg_var * aa_var);
+ ba_cov *= astc::rsqrt(bb_var * aa_var);
+
+ if (astc::isnan(rg_cov)) rg_cov = 1.0f;
+ if (astc::isnan(rb_cov)) rb_cov = 1.0f;
+ if (astc::isnan(ra_cov)) ra_cov = 1.0f;
+ if (astc::isnan(gb_cov)) gb_cov = 1.0f;
+ if (astc::isnan(ga_cov)) ga_cov = 1.0f;
+ if (astc::isnan(ba_cov)) ba_cov = 1.0f;
+
+ float lowest_correlation = astc::min(fabsf(rg_cov), fabsf(rb_cov));
+ lowest_correlation = astc::min(lowest_correlation, fabsf(ra_cov));
+ lowest_correlation = astc::min(lowest_correlation, fabsf(gb_cov));
+ lowest_correlation = astc::min(lowest_correlation, fabsf(ga_cov));
+ lowest_correlation = astc::min(lowest_correlation, fabsf(ba_cov));
+
+ // Diagnostic trace points
+ trace_add_data("min_r", blk.data_min.lane<0>());
+ trace_add_data("max_r", blk.data_max.lane<0>());
+ trace_add_data("min_g", blk.data_min.lane<1>());
+ trace_add_data("max_g", blk.data_max.lane<1>());
+ trace_add_data("min_b", blk.data_min.lane<2>());
+ trace_add_data("max_b", blk.data_max.lane<2>());
+ trace_add_data("min_a", blk.data_min.lane<3>());
+ trace_add_data("max_a", blk.data_max.lane<3>());
+ trace_add_data("cov_rg", fabsf(rg_cov));
+ trace_add_data("cov_rb", fabsf(rb_cov));
+ trace_add_data("cov_ra", fabsf(ra_cov));
+ trace_add_data("cov_gb", fabsf(gb_cov));
+ trace_add_data("cov_ga", fabsf(ga_cov));
+ trace_add_data("cov_ba", fabsf(ba_cov));
+
+ return lowest_correlation;
+}
+
+/* See header for documentation. */
+void compress_block(
+ const astcenc_contexti& ctx,
+ const image_block& blk,
+ physical_compressed_block& pcb,
+ compression_working_buffers& tmpbuf)
+{
+ astcenc_profile decode_mode = ctx.config.profile;
+ symbolic_compressed_block scb;
+ const block_size_descriptor& bsd = *ctx.bsd;
+ float lowest_correl;
+
+ TRACE_NODE(node0, "block");
+ trace_add_data("pos_x", blk.xpos);
+ trace_add_data("pos_y", blk.ypos);
+ trace_add_data("pos_z", blk.zpos);
+
+ // Set stricter block targets for luminance data as we have more bits to play with
+ bool block_is_l = blk.is_luminance();
+ float block_is_l_scale = block_is_l ? 1.0f / 1.5f : 1.0f;
+
+ // Set slightly stricter block targets for lumalpha data as we have more bits to play with
+ bool block_is_la = blk.is_luminancealpha();
+ float block_is_la_scale = block_is_la ? 1.0f / 1.05f : 1.0f;
+
+ bool block_skip_two_plane = false;
+ int max_partitions = ctx.config.tune_partition_count_limit;
+
+ unsigned int requested_partition_indices[3] {
+ ctx.config.tune_2partition_index_limit,
+ ctx.config.tune_3partition_index_limit,
+ ctx.config.tune_4partition_index_limit
+ };
+
+ unsigned int requested_partition_trials[3] {
+ ctx.config.tune_2partitioning_candidate_limit,
+ ctx.config.tune_3partitioning_candidate_limit,
+ ctx.config.tune_4partitioning_candidate_limit
+ };
+
+#if defined(ASTCENC_DIAGNOSTICS)
+ // Do this early in diagnostic builds so we can dump uniform metrics
+ // for every block. Do it later in release builds to avoid redundant work!
+ float error_weight_sum = hadd_s(blk.channel_weight) * bsd.texel_count;
+ float error_threshold = ctx.config.tune_db_limit
+ * error_weight_sum
+ * block_is_l_scale
+ * block_is_la_scale;
+
+ lowest_correl = prepare_block_statistics(bsd.texel_count, blk);
+ trace_add_data("lowest_correl", lowest_correl);
+ trace_add_data("tune_error_threshold", error_threshold);
+#endif
+
+ // Detected a constant-color block
+ if (all(blk.data_min == blk.data_max))
+ {
+ TRACE_NODE(node1, "pass");
+ trace_add_data("partition_count", 0);
+ trace_add_data("plane_count", 1);
+
+ scb.partition_count = 0;
+
+ // Encode as FP16 if using HDR
+ if ((decode_mode == ASTCENC_PRF_HDR) ||
+ (decode_mode == ASTCENC_PRF_HDR_RGB_LDR_A))
+ {
+ scb.block_type = SYM_BTYPE_CONST_F16;
+ vint4 color_f16 = float_to_float16(blk.origin_texel);
+ store(color_f16, scb.constant_color);
+ }
+ // Encode as UNORM16 if NOT using HDR
+ else
+ {
+ scb.block_type = SYM_BTYPE_CONST_U16;
+ vfloat4 color_f32 = clamp(0.0f, 1.0f, blk.origin_texel) * 65535.0f;
+ vint4 color_u16 = float_to_int_rtn(color_f32);
+ store(color_u16, scb.constant_color);
+ }
+
+ trace_add_data("exit", "quality hit");
+
+ symbolic_to_physical(bsd, scb, pcb);
+ return;
+ }
+
+#if !defined(ASTCENC_DIAGNOSTICS)
+ float error_weight_sum = hadd_s(blk.channel_weight) * bsd.texel_count;
+ float error_threshold = ctx.config.tune_db_limit
+ * error_weight_sum
+ * block_is_l_scale
+ * block_is_la_scale;
+#endif
+
+ // Set SCB and mode errors to a very high error value
+ scb.errorval = ERROR_CALC_DEFAULT;
+ scb.block_type = SYM_BTYPE_ERROR;
+
+ float best_errorvals_for_pcount[BLOCK_MAX_PARTITIONS] {
+ ERROR_CALC_DEFAULT, ERROR_CALC_DEFAULT, ERROR_CALC_DEFAULT, ERROR_CALC_DEFAULT
+ };
+
+ float exit_thresholds_for_pcount[BLOCK_MAX_PARTITIONS] {
+ 0.0f,
+ ctx.config.tune_2_partition_early_out_limit_factor,
+ ctx.config.tune_3_partition_early_out_limit_factor,
+ 0.0f
+ };
+
+ // Trial using 1 plane of weights and 1 partition.
+
+ // Most of the time we test it twice, first with a mode cutoff of 0 and then with the specified
+ // mode cutoff. This causes an early-out that speeds up encoding of easy blocks. However, this
+ // optimization is disabled for 4x4 and 5x4 blocks where it nearly always slows down the
+ // compression and slightly reduces image quality.
+
+ float errorval_mult[2] {
+ 1.0f / ctx.config.tune_mse_overshoot,
+ 1.0f
+ };
+
+ static const float errorval_overshoot = 1.0f / ctx.config.tune_mse_overshoot;
+
+ // Only enable MODE0 fast path (trial 0) if 2D, and more than 25 texels
+ int start_trial = 1;
+ if ((bsd.texel_count >= TUNE_MIN_TEXELS_MODE0_FASTPATH) && (bsd.zdim == 1))
+ {
+ start_trial = 0;
+ }
+
+ int quant_limit = QUANT_32;
+ for (int i = start_trial; i < 2; i++)
+ {
+ TRACE_NODE(node1, "pass");
+ trace_add_data("partition_count", 1);
+ trace_add_data("plane_count", 1);
+ trace_add_data("search_mode", i);
+
+ float errorval = compress_symbolic_block_for_partition_1plane(
+ ctx.config, bsd, blk, i == 0,
+ error_threshold * errorval_mult[i] * errorval_overshoot,
+ 1, 0, scb, tmpbuf, QUANT_32);
+
+ // Record the quant level so we can use the filter later searches
+ const auto& bm = bsd.get_block_mode(scb.block_mode);
+ quant_limit = bm.get_weight_quant_mode();
+
+ best_errorvals_for_pcount[0] = astc::min(best_errorvals_for_pcount[0], errorval);
+ if (errorval < (error_threshold * errorval_mult[i]))
+ {
+ trace_add_data("exit", "quality hit");
+ goto END_OF_TESTS;
+ }
+ }
+
+#if !defined(ASTCENC_DIAGNOSTICS)
+ lowest_correl = prepare_block_statistics(bsd.texel_count, blk);
+#endif
+
+ block_skip_two_plane = lowest_correl > ctx.config.tune_2_plane_early_out_limit_correlation;
+
+ // Test the four possible 1-partition, 2-planes modes. Do this in reverse, as
+ // alpha is the most likely to be non-correlated if it is present in the data.
+ for (int i = BLOCK_MAX_COMPONENTS - 1; i >= 0; i--)
+ {
+ TRACE_NODE(node1, "pass");
+ trace_add_data("partition_count", 1);
+ trace_add_data("plane_count", 2);
+ trace_add_data("plane_component", i);
+
+ if (block_skip_two_plane)
+ {
+ trace_add_data("skip", "tune_2_plane_early_out_limit_correlation");
+ continue;
+ }
+
+ if (blk.grayscale && i != 3)
+ {
+ trace_add_data("skip", "grayscale block");
+ continue;
+ }
+
+ if (blk.is_constant_channel(i))
+ {
+ trace_add_data("skip", "constant component");
+ continue;
+ }
+
+ float errorval = compress_symbolic_block_for_partition_2planes(
+ ctx.config, bsd, blk, error_threshold * errorval_overshoot,
+ i, scb, tmpbuf, quant_limit);
+
+ // If attempting two planes is much worse than the best one plane result
+ // then further two plane searches are unlikely to help so move on ...
+ if (errorval > (best_errorvals_for_pcount[0] * 1.85f))
+ {
+ break;
+ }
+
+ if (errorval < error_threshold)
+ {
+ trace_add_data("exit", "quality hit");
+ goto END_OF_TESTS;
+ }
+ }
+
+ // Find best blocks for 2, 3 and 4 partitions
+ for (int partition_count = 2; partition_count <= max_partitions; partition_count++)
+ {
+ unsigned int partition_indices[TUNE_MAX_PARTITIONING_CANDIDATES];
+
+ unsigned int requested_indices = requested_partition_indices[partition_count - 2];
+
+ unsigned int requested_trials = requested_partition_trials[partition_count - 2];
+ requested_trials = astc::min(requested_trials, requested_indices);
+
+ unsigned int actual_trials = find_best_partition_candidates(
+ bsd, blk, partition_count, requested_indices, partition_indices, requested_trials);
+
+ float best_error_in_prev = best_errorvals_for_pcount[partition_count - 2];
+
+ for (unsigned int i = 0; i < actual_trials; i++)
+ {
+ TRACE_NODE(node1, "pass");
+ trace_add_data("partition_count", partition_count);
+ trace_add_data("partition_index", partition_indices[i]);
+ trace_add_data("plane_count", 1);
+ trace_add_data("search_mode", i);
+
+ float errorval = compress_symbolic_block_for_partition_1plane(
+ ctx.config, bsd, blk, false,
+ error_threshold * errorval_overshoot,
+ partition_count, partition_indices[i],
+ scb, tmpbuf, quant_limit);
+
+ best_errorvals_for_pcount[partition_count - 1] = astc::min(best_errorvals_for_pcount[partition_count - 1], errorval);
+
+ // If using N partitions doesn't improve much over using N-1 partitions then skip trying
+ // N+1. Error can dramatically improve if the data is correlated or non-correlated and
+ // aligns with a partitioning that suits that encoding, so for this inner loop check add
+ // a large error scale because the "other" trial could be a lot better.
+ float best_error = best_errorvals_for_pcount[partition_count - 1];
+ float best_error_scale = exit_thresholds_for_pcount[partition_count - 1] * 1.85f;
+ if (best_error > (best_error_in_prev * best_error_scale))
+ {
+ trace_add_data("skip", "tune_partition_early_out_limit_factor");
+ goto END_OF_TESTS;
+ }
+
+ if (errorval < error_threshold)
+ {
+ trace_add_data("exit", "quality hit");
+ goto END_OF_TESTS;
+ }
+ }
+
+ // If using N partitions doesn't improve much over using N-1 partitions then skip trying N+1
+ float best_error = best_errorvals_for_pcount[partition_count - 1];
+ float best_error_scale = exit_thresholds_for_pcount[partition_count - 1];
+ if (best_error > (best_error_in_prev * best_error_scale))
+ {
+ trace_add_data("skip", "tune_partition_early_out_limit_factor");
+ goto END_OF_TESTS;
+ }
+ }
+
+ trace_add_data("exit", "quality not hit");
+
+END_OF_TESTS:
+ // If we still have an error block then convert to something we can encode
+ // TODO: Do something more sensible here, such as average color block
+ if (scb.block_type == SYM_BTYPE_ERROR)
+ {
+#if defined(ASTCENC_DIAGNOSTICS)
+ static bool printed_once = false;
+ if (!printed_once)
+ {
+ printed_once = true;
+ printf("WARN: At least one block failed to find a valid encoding.\n"
+ " Try increasing compression quality settings.\n\n");
+ }
+#endif
+
+ scb.block_type = SYM_BTYPE_CONST_U16;
+ vfloat4 color_f32 = clamp(0.0f, 1.0f, blk.origin_texel) * 65535.0f;
+ vint4 color_u16 = float_to_int_rtn(color_f32);
+ store(color_u16, scb.constant_color);
+ }
+
+ // Compress to a physical block
+ symbolic_to_physical(bsd, scb, pcb);
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_compute_variance.cpp b/thirdparty/astcenc/astcenc_compute_variance.cpp
new file mode 100644
index 0000000000..48a4af8cef
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_compute_variance.cpp
@@ -0,0 +1,472 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+
+/**
+ * @brief Functions to calculate variance per component in a NxN footprint.
+ *
+ * We need N to be parametric, so the routine below uses summed area tables in order to execute in
+ * O(1) time independent of how big N is.
+ *
+ * The addition uses a Brent-Kung-based parallel prefix adder. This uses the prefix tree to first
+ * perform a binary reduction, and then distributes the results. This method means that there is no
+ * serial dependency between a given element and the next one, and also significantly improves
+ * numerical stability allowing us to use floats rather than doubles.
+ */
+
+#include "astcenc_internal.h"
+
+#include <cassert>
+
+/**
+ * @brief Generate a prefix-sum array using the Brent-Kung algorithm.
+ *
+ * This will take an input array of the form:
+ * v0, v1, v2, ...
+ * ... and modify in-place to turn it into a prefix-sum array of the form:
+ * v0, v0+v1, v0+v1+v2, ...
+ *
+ * @param d The array to prefix-sum.
+ * @param items The number of items in the array.
+ * @param stride The item spacing in the array; i.e. dense arrays should use 1.
+ */
+static void brent_kung_prefix_sum(
+ vfloat4* d,
+ size_t items,
+ int stride
+) {
+ if (items < 2)
+ return;
+
+ size_t lc_stride = 2;
+ size_t log2_stride = 1;
+
+ // The reduction-tree loop
+ do {
+ size_t step = lc_stride >> 1;
+ size_t start = lc_stride - 1;
+ size_t iters = items >> log2_stride;
+
+ vfloat4 *da = d + (start * stride);
+ ptrdiff_t ofs = -static_cast<ptrdiff_t>(step * stride);
+ size_t ofs_stride = stride << log2_stride;
+
+ while (iters)
+ {
+ *da = *da + da[ofs];
+ da += ofs_stride;
+ iters--;
+ }
+
+ log2_stride += 1;
+ lc_stride <<= 1;
+ } while (lc_stride <= items);
+
+ // The expansion-tree loop
+ do {
+ log2_stride -= 1;
+ lc_stride >>= 1;
+
+ size_t step = lc_stride >> 1;
+ size_t start = step + lc_stride - 1;
+ size_t iters = (items - step) >> log2_stride;
+
+ vfloat4 *da = d + (start * stride);
+ ptrdiff_t ofs = -static_cast<ptrdiff_t>(step * stride);
+ size_t ofs_stride = stride << log2_stride;
+
+ while (iters)
+ {
+ *da = *da + da[ofs];
+ da += ofs_stride;
+ iters--;
+ }
+ } while (lc_stride > 2);
+}
+
+/* See header for documentation. */
+void compute_pixel_region_variance(
+ astcenc_contexti& ctx,
+ const pixel_region_args& arg
+) {
+ // Unpack the memory structure into local variables
+ const astcenc_image* img = arg.img;
+ astcenc_swizzle swz = arg.swz;
+ bool have_z = arg.have_z;
+
+ int size_x = arg.size_x;
+ int size_y = arg.size_y;
+ int size_z = arg.size_z;
+
+ int offset_x = arg.offset_x;
+ int offset_y = arg.offset_y;
+ int offset_z = arg.offset_z;
+
+ int alpha_kernel_radius = arg.alpha_kernel_radius;
+
+ float* input_alpha_averages = ctx.input_alpha_averages;
+ vfloat4* work_memory = arg.work_memory;
+
+ // Compute memory sizes and dimensions that we need
+ int kernel_radius = alpha_kernel_radius;
+ int kerneldim = 2 * kernel_radius + 1;
+ int kernel_radius_xy = kernel_radius;
+ int kernel_radius_z = have_z ? kernel_radius : 0;
+
+ int padsize_x = size_x + kerneldim;
+ int padsize_y = size_y + kerneldim;
+ int padsize_z = size_z + (have_z ? kerneldim : 0);
+ int sizeprod = padsize_x * padsize_y * padsize_z;
+
+ int zd_start = have_z ? 1 : 0;
+
+ vfloat4 *varbuf1 = work_memory;
+ vfloat4 *varbuf2 = work_memory + sizeprod;
+
+ // Scaling factors to apply to Y and Z for accesses into the work buffers
+ int yst = padsize_x;
+ int zst = padsize_x * padsize_y;
+
+ // Scaling factors to apply to Y and Z for accesses into result buffers
+ int ydt = img->dim_x;
+ int zdt = img->dim_x * img->dim_y;
+
+ // Macros to act as accessor functions for the work-memory
+ #define VARBUF1(z, y, x) varbuf1[z * zst + y * yst + x]
+ #define VARBUF2(z, y, x) varbuf2[z * zst + y * yst + x]
+
+ // Load N and N^2 values into the work buffers
+ if (img->data_type == ASTCENC_TYPE_U8)
+ {
+ // Swizzle data structure 4 = ZERO, 5 = ONE
+ uint8_t data[6];
+ data[ASTCENC_SWZ_0] = 0;
+ data[ASTCENC_SWZ_1] = 255;
+
+ for (int z = zd_start; z < padsize_z; z++)
+ {
+ int z_src = (z - zd_start) + offset_z - kernel_radius_z;
+ z_src = astc::clamp(z_src, 0, static_cast<int>(img->dim_z - 1));
+ uint8_t* data8 = static_cast<uint8_t*>(img->data[z_src]);
+
+ for (int y = 1; y < padsize_y; y++)
+ {
+ int y_src = (y - 1) + offset_y - kernel_radius_xy;
+ y_src = astc::clamp(y_src, 0, static_cast<int>(img->dim_y - 1));
+
+ for (int x = 1; x < padsize_x; x++)
+ {
+ int x_src = (x - 1) + offset_x - kernel_radius_xy;
+ x_src = astc::clamp(x_src, 0, static_cast<int>(img->dim_x - 1));
+
+ data[0] = data8[(4 * img->dim_x * y_src) + (4 * x_src )];
+ data[1] = data8[(4 * img->dim_x * y_src) + (4 * x_src + 1)];
+ data[2] = data8[(4 * img->dim_x * y_src) + (4 * x_src + 2)];
+ data[3] = data8[(4 * img->dim_x * y_src) + (4 * x_src + 3)];
+
+ uint8_t r = data[swz.r];
+ uint8_t g = data[swz.g];
+ uint8_t b = data[swz.b];
+ uint8_t a = data[swz.a];
+
+ vfloat4 d = vfloat4 (r * (1.0f / 255.0f),
+ g * (1.0f / 255.0f),
+ b * (1.0f / 255.0f),
+ a * (1.0f / 255.0f));
+
+ VARBUF1(z, y, x) = d;
+ VARBUF2(z, y, x) = d * d;
+ }
+ }
+ }
+ }
+ else if (img->data_type == ASTCENC_TYPE_F16)
+ {
+ // Swizzle data structure 4 = ZERO, 5 = ONE (in FP16)
+ uint16_t data[6];
+ data[ASTCENC_SWZ_0] = 0;
+ data[ASTCENC_SWZ_1] = 0x3C00;
+
+ for (int z = zd_start; z < padsize_z; z++)
+ {
+ int z_src = (z - zd_start) + offset_z - kernel_radius_z;
+ z_src = astc::clamp(z_src, 0, static_cast<int>(img->dim_z - 1));
+ uint16_t* data16 = static_cast<uint16_t*>(img->data[z_src]);
+
+ for (int y = 1; y < padsize_y; y++)
+ {
+ int y_src = (y - 1) + offset_y - kernel_radius_xy;
+ y_src = astc::clamp(y_src, 0, static_cast<int>(img->dim_y - 1));
+
+ for (int x = 1; x < padsize_x; x++)
+ {
+ int x_src = (x - 1) + offset_x - kernel_radius_xy;
+ x_src = astc::clamp(x_src, 0, static_cast<int>(img->dim_x - 1));
+
+ data[0] = data16[(4 * img->dim_x * y_src) + (4 * x_src )];
+ data[1] = data16[(4 * img->dim_x * y_src) + (4 * x_src + 1)];
+ data[2] = data16[(4 * img->dim_x * y_src) + (4 * x_src + 2)];
+ data[3] = data16[(4 * img->dim_x * y_src) + (4 * x_src + 3)];
+
+ vint4 di(data[swz.r], data[swz.g], data[swz.b], data[swz.a]);
+ vfloat4 d = float16_to_float(di);
+
+ VARBUF1(z, y, x) = d;
+ VARBUF2(z, y, x) = d * d;
+ }
+ }
+ }
+ }
+ else // if (img->data_type == ASTCENC_TYPE_F32)
+ {
+ assert(img->data_type == ASTCENC_TYPE_F32);
+
+ // Swizzle data structure 4 = ZERO, 5 = ONE (in FP16)
+ float data[6];
+ data[ASTCENC_SWZ_0] = 0.0f;
+ data[ASTCENC_SWZ_1] = 1.0f;
+
+ for (int z = zd_start; z < padsize_z; z++)
+ {
+ int z_src = (z - zd_start) + offset_z - kernel_radius_z;
+ z_src = astc::clamp(z_src, 0, static_cast<int>(img->dim_z - 1));
+ float* data32 = static_cast<float*>(img->data[z_src]);
+
+ for (int y = 1; y < padsize_y; y++)
+ {
+ int y_src = (y - 1) + offset_y - kernel_radius_xy;
+ y_src = astc::clamp(y_src, 0, static_cast<int>(img->dim_y - 1));
+
+ for (int x = 1; x < padsize_x; x++)
+ {
+ int x_src = (x - 1) + offset_x - kernel_radius_xy;
+ x_src = astc::clamp(x_src, 0, static_cast<int>(img->dim_x - 1));
+
+ data[0] = data32[(4 * img->dim_x * y_src) + (4 * x_src )];
+ data[1] = data32[(4 * img->dim_x * y_src) + (4 * x_src + 1)];
+ data[2] = data32[(4 * img->dim_x * y_src) + (4 * x_src + 2)];
+ data[3] = data32[(4 * img->dim_x * y_src) + (4 * x_src + 3)];
+
+ float r = data[swz.r];
+ float g = data[swz.g];
+ float b = data[swz.b];
+ float a = data[swz.a];
+
+ vfloat4 d(r, g, b, a);
+
+ VARBUF1(z, y, x) = d;
+ VARBUF2(z, y, x) = d * d;
+ }
+ }
+ }
+ }
+
+ // Pad with an extra layer of 0s; this forms the edge of the SAT tables
+ vfloat4 vbz = vfloat4::zero();
+ for (int z = 0; z < padsize_z; z++)
+ {
+ for (int y = 0; y < padsize_y; y++)
+ {
+ VARBUF1(z, y, 0) = vbz;
+ VARBUF2(z, y, 0) = vbz;
+ }
+
+ for (int x = 0; x < padsize_x; x++)
+ {
+ VARBUF1(z, 0, x) = vbz;
+ VARBUF2(z, 0, x) = vbz;
+ }
+ }
+
+ if (have_z)
+ {
+ for (int y = 0; y < padsize_y; y++)
+ {
+ for (int x = 0; x < padsize_x; x++)
+ {
+ VARBUF1(0, y, x) = vbz;
+ VARBUF2(0, y, x) = vbz;
+ }
+ }
+ }
+
+ // Generate summed-area tables for N and N^2; this is done in-place, using
+ // a Brent-Kung parallel-prefix based algorithm to minimize precision loss
+ for (int z = zd_start; z < padsize_z; z++)
+ {
+ for (int y = 1; y < padsize_y; y++)
+ {
+ brent_kung_prefix_sum(&(VARBUF1(z, y, 1)), padsize_x - 1, 1);
+ brent_kung_prefix_sum(&(VARBUF2(z, y, 1)), padsize_x - 1, 1);
+ }
+ }
+
+ for (int z = zd_start; z < padsize_z; z++)
+ {
+ for (int x = 1; x < padsize_x; x++)
+ {
+ brent_kung_prefix_sum(&(VARBUF1(z, 1, x)), padsize_y - 1, yst);
+ brent_kung_prefix_sum(&(VARBUF2(z, 1, x)), padsize_y - 1, yst);
+ }
+ }
+
+ if (have_z)
+ {
+ for (int y = 1; y < padsize_y; y++)
+ {
+ for (int x = 1; x < padsize_x; x++)
+ {
+ brent_kung_prefix_sum(&(VARBUF1(1, y, x)), padsize_z - 1, zst);
+ brent_kung_prefix_sum(&(VARBUF2(1, y, x)), padsize_z - 1, zst);
+ }
+ }
+ }
+
+ // Compute a few constants used in the variance-calculation.
+ float alpha_kdim = static_cast<float>(2 * alpha_kernel_radius + 1);
+ float alpha_rsamples;
+
+ if (have_z)
+ {
+ alpha_rsamples = 1.0f / (alpha_kdim * alpha_kdim * alpha_kdim);
+ }
+ else
+ {
+ alpha_rsamples = 1.0f / (alpha_kdim * alpha_kdim);
+ }
+
+ // Use the summed-area tables to compute variance for each neighborhood
+ if (have_z)
+ {
+ for (int z = 0; z < size_z; z++)
+ {
+ int z_src = z + kernel_radius_z;
+ int z_dst = z + offset_z;
+ int z_low = z_src - alpha_kernel_radius;
+ int z_high = z_src + alpha_kernel_radius + 1;
+
+ for (int y = 0; y < size_y; y++)
+ {
+ int y_src = y + kernel_radius_xy;
+ int y_dst = y + offset_y;
+ int y_low = y_src - alpha_kernel_radius;
+ int y_high = y_src + alpha_kernel_radius + 1;
+
+ for (int x = 0; x < size_x; x++)
+ {
+ int x_src = x + kernel_radius_xy;
+ int x_dst = x + offset_x;
+ int x_low = x_src - alpha_kernel_radius;
+ int x_high = x_src + alpha_kernel_radius + 1;
+
+ // Summed-area table lookups for alpha average
+ float vasum = ( VARBUF1(z_high, y_low, x_low).lane<3>()
+ - VARBUF1(z_high, y_low, x_high).lane<3>()
+ - VARBUF1(z_high, y_high, x_low).lane<3>()
+ + VARBUF1(z_high, y_high, x_high).lane<3>()) -
+ ( VARBUF1(z_low, y_low, x_low).lane<3>()
+ - VARBUF1(z_low, y_low, x_high).lane<3>()
+ - VARBUF1(z_low, y_high, x_low).lane<3>()
+ + VARBUF1(z_low, y_high, x_high).lane<3>());
+
+ int out_index = z_dst * zdt + y_dst * ydt + x_dst;
+ input_alpha_averages[out_index] = (vasum * alpha_rsamples);
+ }
+ }
+ }
+ }
+ else
+ {
+ for (int y = 0; y < size_y; y++)
+ {
+ int y_src = y + kernel_radius_xy;
+ int y_dst = y + offset_y;
+ int y_low = y_src - alpha_kernel_radius;
+ int y_high = y_src + alpha_kernel_radius + 1;
+
+ for (int x = 0; x < size_x; x++)
+ {
+ int x_src = x + kernel_radius_xy;
+ int x_dst = x + offset_x;
+ int x_low = x_src - alpha_kernel_radius;
+ int x_high = x_src + alpha_kernel_radius + 1;
+
+ // Summed-area table lookups for alpha average
+ float vasum = VARBUF1(0, y_low, x_low).lane<3>()
+ - VARBUF1(0, y_low, x_high).lane<3>()
+ - VARBUF1(0, y_high, x_low).lane<3>()
+ + VARBUF1(0, y_high, x_high).lane<3>();
+
+ int out_index = y_dst * ydt + x_dst;
+ input_alpha_averages[out_index] = (vasum * alpha_rsamples);
+ }
+ }
+ }
+}
+
+/* See header for documentation. */
+unsigned int init_compute_averages(
+ const astcenc_image& img,
+ unsigned int alpha_kernel_radius,
+ const astcenc_swizzle& swz,
+ avg_args& ag
+) {
+ unsigned int size_x = img.dim_x;
+ unsigned int size_y = img.dim_y;
+ unsigned int size_z = img.dim_z;
+
+ // Compute maximum block size and from that the working memory buffer size
+ unsigned int kernel_radius = alpha_kernel_radius;
+ unsigned int kerneldim = 2 * kernel_radius + 1;
+
+ bool have_z = (size_z > 1);
+ unsigned int max_blk_size_xy = have_z ? 16 : 32;
+ unsigned int max_blk_size_z = astc::min(size_z, have_z ? 16u : 1u);
+
+ unsigned int max_padsize_xy = max_blk_size_xy + kerneldim;
+ unsigned int max_padsize_z = max_blk_size_z + (have_z ? kerneldim : 0);
+
+ // Perform block-wise averages calculations across the image
+ // Initialize fields which are not populated until later
+ ag.arg.size_x = 0;
+ ag.arg.size_y = 0;
+ ag.arg.size_z = 0;
+ ag.arg.offset_x = 0;
+ ag.arg.offset_y = 0;
+ ag.arg.offset_z = 0;
+ ag.arg.work_memory = nullptr;
+
+ ag.arg.img = &img;
+ ag.arg.swz = swz;
+ ag.arg.have_z = have_z;
+ ag.arg.alpha_kernel_radius = alpha_kernel_radius;
+
+ ag.img_size_x = size_x;
+ ag.img_size_y = size_y;
+ ag.img_size_z = size_z;
+ ag.blk_size_xy = max_blk_size_xy;
+ ag.blk_size_z = max_blk_size_z;
+ ag.work_memory_size = 2 * max_padsize_xy * max_padsize_xy * max_padsize_z;
+
+ // The parallel task count
+ unsigned int z_tasks = (size_z + max_blk_size_z - 1) / max_blk_size_z;
+ unsigned int y_tasks = (size_y + max_blk_size_xy - 1) / max_blk_size_xy;
+ return z_tasks * y_tasks;
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_decompress_symbolic.cpp b/thirdparty/astcenc/astcenc_decompress_symbolic.cpp
new file mode 100644
index 0000000000..39e5525c3b
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_decompress_symbolic.cpp
@@ -0,0 +1,623 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2023 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Functions to decompress a symbolic block.
+ */
+
+#include "astcenc_internal.h"
+
+#include <stdio.h>
+#include <assert.h>
+
+/**
+ * @brief Compute the integer linear interpolation of two color endpoints.
+ *
+ * @param decode_mode The ASTC profile (linear or sRGB)
+ * @param color0 The endpoint0 color.
+ * @param color1 The endpoint1 color.
+ * @param weights The interpolation weight (between 0 and 64).
+ *
+ * @return The interpolated color.
+ */
+static vint4 lerp_color_int(
+ astcenc_profile decode_mode,
+ vint4 color0,
+ vint4 color1,
+ vint4 weights
+) {
+ vint4 weight1 = weights;
+ vint4 weight0 = vint4(64) - weight1;
+
+ if (decode_mode == ASTCENC_PRF_LDR_SRGB)
+ {
+ color0 = asr<8>(color0);
+ color1 = asr<8>(color1);
+ }
+
+ vint4 color = (color0 * weight0) + (color1 * weight1) + vint4(32);
+ color = asr<6>(color);
+
+ if (decode_mode == ASTCENC_PRF_LDR_SRGB)
+ {
+ color = color * vint4(257);
+ }
+
+ return color;
+}
+
+
+/**
+ * @brief Convert integer color value into a float value for the decoder.
+ *
+ * @param data The integer color value post-interpolation.
+ * @param lns_mask If set treat lane as HDR (LNS) else LDR (unorm16).
+ *
+ * @return The float color value.
+ */
+static inline vfloat4 decode_texel(
+ vint4 data,
+ vmask4 lns_mask
+) {
+ vint4 color_lns = vint4::zero();
+ vint4 color_unorm = vint4::zero();
+
+ if (any(lns_mask))
+ {
+ color_lns = lns_to_sf16(data);
+ }
+
+ if (!all(lns_mask))
+ {
+ color_unorm = unorm16_to_sf16(data);
+ }
+
+ // Pick components and then convert to FP16
+ vint4 datai = select(color_unorm, color_lns, lns_mask);
+ return float16_to_float(datai);
+}
+
+/* See header for documentation. */
+void unpack_weights(
+ const block_size_descriptor& bsd,
+ const symbolic_compressed_block& scb,
+ const decimation_info& di,
+ bool is_dual_plane,
+ int weights_plane1[BLOCK_MAX_TEXELS],
+ int weights_plane2[BLOCK_MAX_TEXELS]
+) {
+ // Safe to overshoot as all arrays are allocated to full size
+ if (!is_dual_plane)
+ {
+ // Build full 64-entry weight lookup table
+ vint4 tab0(reinterpret_cast<const int*>(scb.weights + 0));
+ vint4 tab1(reinterpret_cast<const int*>(scb.weights + 16));
+ vint4 tab2(reinterpret_cast<const int*>(scb.weights + 32));
+ vint4 tab3(reinterpret_cast<const int*>(scb.weights + 48));
+
+ vint tab0p, tab1p, tab2p, tab3p;
+ vtable_prepare(tab0, tab1, tab2, tab3, tab0p, tab1p, tab2p, tab3p);
+
+ for (unsigned int i = 0; i < bsd.texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vint summed_value(8);
+ vint weight_count(di.texel_weight_count + i);
+ int max_weight_count = hmax(weight_count).lane<0>();
+
+ promise(max_weight_count > 0);
+ for (int j = 0; j < max_weight_count; j++)
+ {
+ vint texel_weights(di.texel_weights_tr[j] + i);
+ vint texel_weights_int(di.texel_weight_contribs_int_tr[j] + i);
+
+ summed_value += vtable_8bt_32bi(tab0p, tab1p, tab2p, tab3p, texel_weights) * texel_weights_int;
+ }
+
+ store(lsr<4>(summed_value), weights_plane1 + i);
+ }
+ }
+ else
+ {
+ // Build a 32-entry weight lookup table per plane
+ // Plane 1
+ vint4 tab0_plane1(reinterpret_cast<const int*>(scb.weights + 0));
+ vint4 tab1_plane1(reinterpret_cast<const int*>(scb.weights + 16));
+ vint tab0_plane1p, tab1_plane1p;
+ vtable_prepare(tab0_plane1, tab1_plane1, tab0_plane1p, tab1_plane1p);
+
+ // Plane 2
+ vint4 tab0_plane2(reinterpret_cast<const int*>(scb.weights + 32));
+ vint4 tab1_plane2(reinterpret_cast<const int*>(scb.weights + 48));
+ vint tab0_plane2p, tab1_plane2p;
+ vtable_prepare(tab0_plane2, tab1_plane2, tab0_plane2p, tab1_plane2p);
+
+ for (unsigned int i = 0; i < bsd.texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vint sum_plane1(8);
+ vint sum_plane2(8);
+
+ vint weight_count(di.texel_weight_count + i);
+ int max_weight_count = hmax(weight_count).lane<0>();
+
+ promise(max_weight_count > 0);
+ for (int j = 0; j < max_weight_count; j++)
+ {
+ vint texel_weights(di.texel_weights_tr[j] + i);
+ vint texel_weights_int(di.texel_weight_contribs_int_tr[j] + i);
+
+ sum_plane1 += vtable_8bt_32bi(tab0_plane1p, tab1_plane1p, texel_weights) * texel_weights_int;
+ sum_plane2 += vtable_8bt_32bi(tab0_plane2p, tab1_plane2p, texel_weights) * texel_weights_int;
+ }
+
+ store(lsr<4>(sum_plane1), weights_plane1 + i);
+ store(lsr<4>(sum_plane2), weights_plane2 + i);
+ }
+ }
+}
+
+/**
+ * @brief Return an FP32 NaN value for use in error colors.
+ *
+ * This NaN encoding will turn into 0xFFFF when converted to an FP16 NaN.
+ *
+ * @return The float color value.
+ */
+static float error_color_nan()
+{
+ if32 v;
+ v.u = 0xFFFFE000U;
+ return v.f;
+}
+
+/* See header for documentation. */
+void decompress_symbolic_block(
+ astcenc_profile decode_mode,
+ const block_size_descriptor& bsd,
+ int xpos,
+ int ypos,
+ int zpos,
+ const symbolic_compressed_block& scb,
+ image_block& blk
+) {
+ blk.xpos = xpos;
+ blk.ypos = ypos;
+ blk.zpos = zpos;
+
+ blk.data_min = vfloat4::zero();
+ blk.data_mean = vfloat4::zero();
+ blk.data_max = vfloat4::zero();
+ blk.grayscale = false;
+
+ // If we detected an error-block, blow up immediately.
+ if (scb.block_type == SYM_BTYPE_ERROR)
+ {
+ for (unsigned int i = 0; i < bsd.texel_count; i++)
+ {
+ blk.data_r[i] = error_color_nan();
+ blk.data_g[i] = error_color_nan();
+ blk.data_b[i] = error_color_nan();
+ blk.data_a[i] = error_color_nan();
+ blk.rgb_lns[i] = 0;
+ blk.alpha_lns[i] = 0;
+ }
+
+ return;
+ }
+
+ if ((scb.block_type == SYM_BTYPE_CONST_F16) ||
+ (scb.block_type == SYM_BTYPE_CONST_U16))
+ {
+ vfloat4 color;
+ uint8_t use_lns = 0;
+
+ // UNORM16 constant color block
+ if (scb.block_type == SYM_BTYPE_CONST_U16)
+ {
+ vint4 colori(scb.constant_color);
+
+ // For sRGB decoding a real decoder would just use the top 8 bits for color conversion.
+ // We don't color convert, so rescale the top 8 bits into the full 16 bit dynamic range.
+ if (decode_mode == ASTCENC_PRF_LDR_SRGB)
+ {
+ colori = asr<8>(colori) * 257;
+ }
+
+ vint4 colorf16 = unorm16_to_sf16(colori);
+ color = float16_to_float(colorf16);
+ }
+ // FLOAT16 constant color block
+ else
+ {
+ switch (decode_mode)
+ {
+ case ASTCENC_PRF_LDR_SRGB:
+ case ASTCENC_PRF_LDR:
+ color = vfloat4(error_color_nan());
+ break;
+ case ASTCENC_PRF_HDR_RGB_LDR_A:
+ case ASTCENC_PRF_HDR:
+ // Constant-color block; unpack from FP16 to FP32.
+ color = float16_to_float(vint4(scb.constant_color));
+ use_lns = 1;
+ break;
+ }
+ }
+
+ for (unsigned int i = 0; i < bsd.texel_count; i++)
+ {
+ blk.data_r[i] = color.lane<0>();
+ blk.data_g[i] = color.lane<1>();
+ blk.data_b[i] = color.lane<2>();
+ blk.data_a[i] = color.lane<3>();
+ blk.rgb_lns[i] = use_lns;
+ blk.alpha_lns[i] = use_lns;
+ }
+
+ return;
+ }
+
+ // Get the appropriate partition-table entry
+ int partition_count = scb.partition_count;
+ const auto& pi = bsd.get_partition_info(partition_count, scb.partition_index);
+
+ // Get the appropriate block descriptors
+ const auto& bm = bsd.get_block_mode(scb.block_mode);
+ const auto& di = bsd.get_decimation_info(bm.decimation_mode);
+
+ bool is_dual_plane = static_cast<bool>(bm.is_dual_plane);
+
+ // Unquantize and undecimate the weights
+ int plane1_weights[BLOCK_MAX_TEXELS];
+ int plane2_weights[BLOCK_MAX_TEXELS];
+ unpack_weights(bsd, scb, di, is_dual_plane, plane1_weights, plane2_weights);
+
+ // Now that we have endpoint colors and weights, we can unpack texel colors
+ int plane2_component = scb.plane2_component;
+ vmask4 plane2_mask = vint4::lane_id() == vint4(plane2_component);
+
+ for (int i = 0; i < partition_count; i++)
+ {
+ // Decode the color endpoints for this partition
+ vint4 ep0;
+ vint4 ep1;
+ bool rgb_lns;
+ bool a_lns;
+
+ unpack_color_endpoints(decode_mode,
+ scb.color_formats[i],
+ scb.color_values[i],
+ rgb_lns, a_lns,
+ ep0, ep1);
+
+ vmask4 lns_mask(rgb_lns, rgb_lns, rgb_lns, a_lns);
+
+ int texel_count = pi.partition_texel_count[i];
+ for (int j = 0; j < texel_count; j++)
+ {
+ int tix = pi.texels_of_partition[i][j];
+ vint4 weight = select(vint4(plane1_weights[tix]), vint4(plane2_weights[tix]), plane2_mask);
+ vint4 color = lerp_color_int(decode_mode, ep0, ep1, weight);
+ vfloat4 colorf = decode_texel(color, lns_mask);
+
+ blk.data_r[tix] = colorf.lane<0>();
+ blk.data_g[tix] = colorf.lane<1>();
+ blk.data_b[tix] = colorf.lane<2>();
+ blk.data_a[tix] = colorf.lane<3>();
+ }
+ }
+}
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+
+/* See header for documentation. */
+float compute_symbolic_block_difference_2plane(
+ const astcenc_config& config,
+ const block_size_descriptor& bsd,
+ const symbolic_compressed_block& scb,
+ const image_block& blk
+) {
+ // If we detected an error-block, blow up immediately.
+ if (scb.block_type == SYM_BTYPE_ERROR)
+ {
+ return ERROR_CALC_DEFAULT;
+ }
+
+ assert(scb.block_mode >= 0);
+ assert(scb.partition_count == 1);
+ assert(bsd.get_block_mode(scb.block_mode).is_dual_plane == 1);
+
+ // Get the appropriate block descriptor
+ const block_mode& bm = bsd.get_block_mode(scb.block_mode);
+ const decimation_info& di = bsd.get_decimation_info(bm.decimation_mode);
+
+ // Unquantize and undecimate the weights
+ int plane1_weights[BLOCK_MAX_TEXELS];
+ int plane2_weights[BLOCK_MAX_TEXELS];
+ unpack_weights(bsd, scb, di, true, plane1_weights, plane2_weights);
+
+ vmask4 plane2_mask = vint4::lane_id() == vint4(scb.plane2_component);
+
+ vfloat4 summa = vfloat4::zero();
+
+ // Decode the color endpoints for this partition
+ vint4 ep0;
+ vint4 ep1;
+ bool rgb_lns;
+ bool a_lns;
+
+ unpack_color_endpoints(config.profile,
+ scb.color_formats[0],
+ scb.color_values[0],
+ rgb_lns, a_lns,
+ ep0, ep1);
+
+ // Unpack and compute error for each texel in the partition
+ unsigned int texel_count = bsd.texel_count;
+ for (unsigned int i = 0; i < texel_count; i++)
+ {
+ vint4 weight = select(vint4(plane1_weights[i]), vint4(plane2_weights[i]), plane2_mask);
+ vint4 colori = lerp_color_int(config.profile, ep0, ep1, weight);
+
+ vfloat4 color = int_to_float(colori);
+ vfloat4 oldColor = blk.texel(i);
+
+ // Compare error using a perceptual decode metric for RGBM textures
+ if (config.flags & ASTCENC_FLG_MAP_RGBM)
+ {
+ // Fail encodings that result in zero weight M pixels. Note that this can cause
+ // "interesting" artifacts if we reject all useful encodings - we typically get max
+ // brightness encodings instead which look just as bad. We recommend users apply a
+ // bias to their stored M value, limiting the lower value to 16 or 32 to avoid
+ // getting small M values post-quantization, but we can't prove it would never
+ // happen, especially at low bit rates ...
+ if (color.lane<3>() == 0.0f)
+ {
+ return -ERROR_CALC_DEFAULT;
+ }
+
+ // Compute error based on decoded RGBM color
+ color = vfloat4(
+ color.lane<0>() * color.lane<3>() * config.rgbm_m_scale,
+ color.lane<1>() * color.lane<3>() * config.rgbm_m_scale,
+ color.lane<2>() * color.lane<3>() * config.rgbm_m_scale,
+ 1.0f
+ );
+
+ oldColor = vfloat4(
+ oldColor.lane<0>() * oldColor.lane<3>() * config.rgbm_m_scale,
+ oldColor.lane<1>() * oldColor.lane<3>() * config.rgbm_m_scale,
+ oldColor.lane<2>() * oldColor.lane<3>() * config.rgbm_m_scale,
+ 1.0f
+ );
+ }
+
+ vfloat4 error = oldColor - color;
+ error = min(abs(error), 1e15f);
+ error = error * error;
+
+ summa += min(dot(error, blk.channel_weight), ERROR_CALC_DEFAULT);
+ }
+
+ return summa.lane<0>();
+}
+
+/* See header for documentation. */
+float compute_symbolic_block_difference_1plane(
+ const astcenc_config& config,
+ const block_size_descriptor& bsd,
+ const symbolic_compressed_block& scb,
+ const image_block& blk
+) {
+ assert(bsd.get_block_mode(scb.block_mode).is_dual_plane == 0);
+
+ // If we detected an error-block, blow up immediately.
+ if (scb.block_type == SYM_BTYPE_ERROR)
+ {
+ return ERROR_CALC_DEFAULT;
+ }
+
+ assert(scb.block_mode >= 0);
+
+ // Get the appropriate partition-table entry
+ unsigned int partition_count = scb.partition_count;
+ const auto& pi = bsd.get_partition_info(partition_count, scb.partition_index);
+
+ // Get the appropriate block descriptor
+ const block_mode& bm = bsd.get_block_mode(scb.block_mode);
+ const decimation_info& di = bsd.get_decimation_info(bm.decimation_mode);
+
+ // Unquantize and undecimate the weights
+ int plane1_weights[BLOCK_MAX_TEXELS];
+ unpack_weights(bsd, scb, di, false, plane1_weights, nullptr);
+
+ vfloat4 summa = vfloat4::zero();
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ // Decode the color endpoints for this partition
+ vint4 ep0;
+ vint4 ep1;
+ bool rgb_lns;
+ bool a_lns;
+
+ unpack_color_endpoints(config.profile,
+ scb.color_formats[i],
+ scb.color_values[i],
+ rgb_lns, a_lns,
+ ep0, ep1);
+
+ // Unpack and compute error for each texel in the partition
+ unsigned int texel_count = pi.partition_texel_count[i];
+ for (unsigned int j = 0; j < texel_count; j++)
+ {
+ unsigned int tix = pi.texels_of_partition[i][j];
+ vint4 colori = lerp_color_int(config.profile, ep0, ep1,
+ vint4(plane1_weights[tix]));
+
+ vfloat4 color = int_to_float(colori);
+ vfloat4 oldColor = blk.texel(tix);
+
+ // Compare error using a perceptual decode metric for RGBM textures
+ if (config.flags & ASTCENC_FLG_MAP_RGBM)
+ {
+ // Fail encodings that result in zero weight M pixels. Note that this can cause
+ // "interesting" artifacts if we reject all useful encodings - we typically get max
+ // brightness encodings instead which look just as bad. We recommend users apply a
+ // bias to their stored M value, limiting the lower value to 16 or 32 to avoid
+ // getting small M values post-quantization, but we can't prove it would never
+ // happen, especially at low bit rates ...
+ if (color.lane<3>() == 0.0f)
+ {
+ return -ERROR_CALC_DEFAULT;
+ }
+
+ // Compute error based on decoded RGBM color
+ color = vfloat4(
+ color.lane<0>() * color.lane<3>() * config.rgbm_m_scale,
+ color.lane<1>() * color.lane<3>() * config.rgbm_m_scale,
+ color.lane<2>() * color.lane<3>() * config.rgbm_m_scale,
+ 1.0f
+ );
+
+ oldColor = vfloat4(
+ oldColor.lane<0>() * oldColor.lane<3>() * config.rgbm_m_scale,
+ oldColor.lane<1>() * oldColor.lane<3>() * config.rgbm_m_scale,
+ oldColor.lane<2>() * oldColor.lane<3>() * config.rgbm_m_scale,
+ 1.0f
+ );
+ }
+
+ vfloat4 error = oldColor - color;
+ error = min(abs(error), 1e15f);
+ error = error * error;
+
+ summa += min(dot(error, blk.channel_weight), ERROR_CALC_DEFAULT);
+ }
+ }
+
+ return summa.lane<0>();
+}
+
+/* See header for documentation. */
+float compute_symbolic_block_difference_1plane_1partition(
+ const astcenc_config& config,
+ const block_size_descriptor& bsd,
+ const symbolic_compressed_block& scb,
+ const image_block& blk
+) {
+ // If we detected an error-block, blow up immediately.
+ if (scb.block_type == SYM_BTYPE_ERROR)
+ {
+ return ERROR_CALC_DEFAULT;
+ }
+
+ assert(scb.block_mode >= 0);
+ assert(bsd.get_partition_info(scb.partition_count, scb.partition_index).partition_count == 1);
+
+ // Get the appropriate block descriptor
+ const block_mode& bm = bsd.get_block_mode(scb.block_mode);
+ const decimation_info& di = bsd.get_decimation_info(bm.decimation_mode);
+
+ // Unquantize and undecimate the weights
+ alignas(ASTCENC_VECALIGN) int plane1_weights[BLOCK_MAX_TEXELS];
+ unpack_weights(bsd, scb, di, false, plane1_weights, nullptr);
+
+ // Decode the color endpoints for this partition
+ vint4 ep0;
+ vint4 ep1;
+ bool rgb_lns;
+ bool a_lns;
+
+ unpack_color_endpoints(config.profile,
+ scb.color_formats[0],
+ scb.color_values[0],
+ rgb_lns, a_lns,
+ ep0, ep1);
+
+
+ // Pre-shift sRGB so things round correctly
+ if (config.profile == ASTCENC_PRF_LDR_SRGB)
+ {
+ ep0 = asr<8>(ep0);
+ ep1 = asr<8>(ep1);
+ }
+
+ // Unpack and compute error for each texel in the partition
+ vfloatacc summav = vfloatacc::zero();
+
+ vint lane_id = vint::lane_id();
+ vint srgb_scale(config.profile == ASTCENC_PRF_LDR_SRGB ? 257 : 1);
+
+ unsigned int texel_count = bsd.texel_count;
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ // Compute EP1 contribution
+ vint weight1 = vint::loada(plane1_weights + i);
+ vint ep1_r = vint(ep1.lane<0>()) * weight1;
+ vint ep1_g = vint(ep1.lane<1>()) * weight1;
+ vint ep1_b = vint(ep1.lane<2>()) * weight1;
+ vint ep1_a = vint(ep1.lane<3>()) * weight1;
+
+ // Compute EP0 contribution
+ vint weight0 = vint(64) - weight1;
+ vint ep0_r = vint(ep0.lane<0>()) * weight0;
+ vint ep0_g = vint(ep0.lane<1>()) * weight0;
+ vint ep0_b = vint(ep0.lane<2>()) * weight0;
+ vint ep0_a = vint(ep0.lane<3>()) * weight0;
+
+ // Shift so things round correctly
+ vint colori_r = asr<6>(ep0_r + ep1_r + vint(32)) * srgb_scale;
+ vint colori_g = asr<6>(ep0_g + ep1_g + vint(32)) * srgb_scale;
+ vint colori_b = asr<6>(ep0_b + ep1_b + vint(32)) * srgb_scale;
+ vint colori_a = asr<6>(ep0_a + ep1_a + vint(32)) * srgb_scale;
+
+ // Compute color diff
+ vfloat color_r = int_to_float(colori_r);
+ vfloat color_g = int_to_float(colori_g);
+ vfloat color_b = int_to_float(colori_b);
+ vfloat color_a = int_to_float(colori_a);
+
+ vfloat color_orig_r = loada(blk.data_r + i);
+ vfloat color_orig_g = loada(blk.data_g + i);
+ vfloat color_orig_b = loada(blk.data_b + i);
+ vfloat color_orig_a = loada(blk.data_a + i);
+
+ vfloat color_error_r = min(abs(color_orig_r - color_r), vfloat(1e15f));
+ vfloat color_error_g = min(abs(color_orig_g - color_g), vfloat(1e15f));
+ vfloat color_error_b = min(abs(color_orig_b - color_b), vfloat(1e15f));
+ vfloat color_error_a = min(abs(color_orig_a - color_a), vfloat(1e15f));
+
+ // Compute squared error metric
+ color_error_r = color_error_r * color_error_r;
+ color_error_g = color_error_g * color_error_g;
+ color_error_b = color_error_b * color_error_b;
+ color_error_a = color_error_a * color_error_a;
+
+ vfloat metric = color_error_r * blk.channel_weight.lane<0>()
+ + color_error_g * blk.channel_weight.lane<1>()
+ + color_error_b * blk.channel_weight.lane<2>()
+ + color_error_a * blk.channel_weight.lane<3>();
+
+ // Mask off bad lanes
+ vmask mask = lane_id < vint(texel_count);
+ lane_id += vint(ASTCENC_SIMD_WIDTH);
+ haccumulate(summav, metric, mask);
+ }
+
+ return hadd_s(summav);
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_diagnostic_trace.cpp b/thirdparty/astcenc/astcenc_diagnostic_trace.cpp
new file mode 100644
index 0000000000..7fa7ab1a8b
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_diagnostic_trace.cpp
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2021-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Functions for the library entrypoint.
+ */
+
+#if defined(ASTCENC_DIAGNOSTICS)
+
+#include <cassert>
+#include <cstdarg>
+#include <cstdio>
+#include <string>
+
+#include "astcenc_diagnostic_trace.h"
+
+/** @brief The global trace logger. */
+static TraceLog* g_TraceLog = nullptr;
+
+/** @brief The JSON indentation level. */
+static const size_t g_trace_indent = 2;
+
+TraceLog::TraceLog(
+ const char* file_name):
+ m_file(file_name, std::ofstream::out | std::ofstream::binary)
+{
+ assert(!g_TraceLog);
+ g_TraceLog = this;
+ m_root = new TraceNode("root");
+}
+
+/* See header for documentation. */
+TraceNode* TraceLog::get_current_leaf()
+{
+ if (m_stack.size())
+ {
+ return m_stack.back();
+ }
+
+ return nullptr;
+}
+
+/* See header for documentation. */
+size_t TraceLog::get_depth()
+{
+ return m_stack.size();
+}
+
+/* See header for documentation. */
+TraceLog::~TraceLog()
+{
+ assert(g_TraceLog == this);
+ delete m_root;
+ g_TraceLog = nullptr;
+}
+
+/* See header for documentation. */
+TraceNode::TraceNode(
+ const char* format,
+ ...
+) {
+ // Format the name string
+ constexpr size_t bufsz = 256;
+ char buffer[bufsz];
+
+ va_list args;
+ va_start (args, format);
+ vsnprintf (buffer, bufsz, format, args);
+ va_end (args);
+
+ // Guarantee there is a nul terminator
+ buffer[bufsz - 1] = 0;
+
+ // Generate the node
+ TraceNode* parent = g_TraceLog->get_current_leaf();
+ size_t depth = g_TraceLog->get_depth();
+ g_TraceLog->m_stack.push_back(this);
+
+ bool comma = parent && parent->m_attrib_count;
+ auto& out = g_TraceLog->m_file;
+
+ if (parent)
+ {
+ parent->m_attrib_count++;
+ }
+
+ if (comma)
+ {
+ out << ',';
+ }
+
+ if (depth)
+ {
+ out << '\n';
+ }
+
+ size_t out_indent = (depth * 2) * g_trace_indent;
+ size_t in_indent = (depth * 2 + 1) * g_trace_indent;
+
+ std::string out_indents("");
+ if (out_indent)
+ {
+ out_indents = std::string(out_indent, ' ');
+ }
+
+ std::string in_indents(in_indent, ' ');
+
+ out << out_indents << "[ \"node\", \"" << buffer << "\",\n";
+ out << in_indents << "[";
+}
+
+/* See header for documentation. */
+void TraceNode::add_attrib(
+ std::string type,
+ std::string key,
+ std::string value
+) {
+ (void)type;
+
+ size_t depth = g_TraceLog->get_depth();
+ size_t indent = (depth * 2) * g_trace_indent;
+ auto& out = g_TraceLog->m_file;
+ bool comma = m_attrib_count;
+ m_attrib_count++;
+
+ if (comma)
+ {
+ out << ',';
+ }
+
+ out << '\n';
+ out << std::string(indent, ' ') << "[ "
+ << "\"" << key << "\", "
+ << value << " ]";
+}
+
+/* See header for documentation. */
+TraceNode::~TraceNode()
+{
+ g_TraceLog->m_stack.pop_back();
+
+ auto& out = g_TraceLog->m_file;
+ size_t depth = g_TraceLog->get_depth();
+ size_t out_indent = (depth * 2) * g_trace_indent;
+ size_t in_indent = (depth * 2 + 1) * g_trace_indent;
+
+ std::string out_indents("");
+ if (out_indent)
+ {
+ out_indents = std::string(out_indent, ' ');
+ }
+
+ std::string in_indents(in_indent, ' ');
+
+ if (m_attrib_count)
+ {
+ out << "\n" << in_indents;
+ }
+ out << "]\n";
+
+ out << out_indents << "]";
+}
+
+/* See header for documentation. */
+void trace_add_data(
+ const char* key,
+ const char* format,
+ ...
+) {
+ constexpr size_t bufsz = 256;
+ char buffer[bufsz];
+
+ va_list args;
+ va_start (args, format);
+ vsnprintf (buffer, bufsz, format, args);
+ va_end (args);
+
+ // Guarantee there is a nul terminator
+ buffer[bufsz - 1] = 0;
+
+ std::string value = "\"" + std::string(buffer) + "\"";
+
+ TraceNode* node = g_TraceLog->get_current_leaf();
+ node->add_attrib("str", key, value);
+}
+
+/* See header for documentation. */
+void trace_add_data(
+ const char* key,
+ float value
+) {
+ char buffer[256];
+ sprintf(buffer, "%.20g", (double)value);
+ TraceNode* node = g_TraceLog->get_current_leaf();
+ node->add_attrib("float", key, buffer);
+}
+
+/* See header for documentation. */
+void trace_add_data(
+ const char* key,
+ int value
+) {
+ TraceNode* node = g_TraceLog->get_current_leaf();
+ node->add_attrib("int", key, std::to_string(value));
+}
+
+/* See header for documentation. */
+void trace_add_data(
+ const char* key,
+ unsigned int value
+) {
+ TraceNode* node = g_TraceLog->get_current_leaf();
+ node->add_attrib("int", key, std::to_string(value));
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_diagnostic_trace.h b/thirdparty/astcenc/astcenc_diagnostic_trace.h
new file mode 100644
index 0000000000..f5586b0ad5
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_diagnostic_trace.h
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2021-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief This module provides a set of diagnostic tracing utilities.
+ *
+ * Overview
+ * ========
+ *
+ * The built-in diagnostic trace tool generates a hierarchical JSON tree structure. The tree
+ * hierarchy contains three levels:
+ *
+ * - block
+ * - pass
+ * - candidate
+ *
+ * One block node exists for each compressed block in the image. One pass node exists for each major
+ * pass (N partition, M planes, O components) applied to a block. One candidate node exists for each
+ * encoding candidate trialed for a pass.
+ *
+ * Each node contains both the hierarchy but also a number of attributes which explain the behavior.
+ * For example, the block node contains the block coordinates in the image, the pass explains the
+ * pass configuration, and the candidate will explain the candidate encoding such as weight
+ * decimation, refinement error, etc.
+ *
+ * Trace Nodes are designed as scope-managed C++ objects with stack-like push/pop behavior.
+ * Constructing a trace node on the stack will automatically add it to the current node as a child,
+ * and then make it the current node. Destroying the current node will pop the stack and set the
+ * parent to the current node. This provides a robust mechanism for ensuring reliable nesting in the
+ * tree structure.
+ *
+ * A set of utility macros are provided to add attribute annotations to the current trace node.
+ *
+ * Usage
+ * =====
+ *
+ * Create Trace Nodes on the stack using the @c TRACE_NODE() macro. This will compile-out completely
+ * in builds with diagnostics disabled.
+ *
+ * Add annotations to the current trace node using the @c trace_add_data() macro. This will
+ * similarly compile out completely in builds with diagnostics disabled.
+ *
+ * If you need to add additional code to support diagnostics-only behavior wrap
+ * it in preprocessor guards:
+ *
+ * #if defined(ASTCENC_DIAGNOSTICS)
+ * #endif
+ */
+
+#ifndef ASTCENC_DIAGNOSTIC_TRACE_INCLUDED
+#define ASTCENC_DIAGNOSTIC_TRACE_INCLUDED
+
+#if defined(ASTCENC_DIAGNOSTICS)
+
+#include <iostream>
+#include <fstream>
+#include <vector>
+
+/**
+ * @brief Class representing a single node in the trace hierarchy.
+ */
+class TraceNode
+{
+public:
+ /**
+ * @brief Construct a new node.
+ *
+ * Constructing a node will push to the the top of the stack, automatically making it a child of
+ * the current node, and then setting it to become the current node.
+ *
+ * @param format The format template for the node name.
+ * @param ... The format parameters.
+ */
+ TraceNode(const char* format, ...);
+
+ /**
+ * @brief Add an attribute to this node.
+ *
+ * Note that no quoting is applied to the @c value, so if quoting is needed it must be done by
+ * the caller.
+ *
+ * @param type The type of the attribute.
+ * @param key The key of the attribute.
+ * @param value The value of the attribute.
+ */
+ void add_attrib(std::string type, std::string key, std::string value);
+
+ /**
+ * @brief Destroy this node.
+ *
+ * Destroying a node will pop it from the top of the stack, making its parent the current node.
+ * It is invalid behavior to destroy a node that is not the current node; usage must conform to
+ * stack push-pop semantics.
+ */
+ ~TraceNode();
+
+ /**
+ * @brief The number of attributes and child nodes in this node.
+ */
+ unsigned int m_attrib_count { 0 };
+};
+
+/**
+ * @brief Class representing the trace log file being written.
+ */
+class TraceLog
+{
+public:
+ /**
+ * @brief Create a new trace log.
+ *
+ * The trace log is global; there can be only one at a time.
+ *
+ * @param file_name The name of the file to write.
+ */
+ TraceLog(const char* file_name);
+
+ /**
+ * @brief Detroy the trace log.
+ *
+ * Trace logs MUST be cleanly destroyed to ensure the file gets written.
+ */
+ ~TraceLog();
+
+ /**
+ * @brief Get the current child node.
+ *
+ * @return The current leaf node.
+ */
+ TraceNode* get_current_leaf();
+
+ /**
+ * @brief Get the stack depth of the current child node.
+ *
+ * @return The current leaf node stack depth.
+ */
+ size_t get_depth();
+
+ /**
+ * @brief The file stream to write to.
+ */
+ std::ofstream m_file;
+
+ /**
+ * @brief The stack of nodes (newest at the back).
+ */
+ std::vector<TraceNode*> m_stack;
+
+private:
+ /**
+ * @brief The root node in the JSON file.
+ */
+ TraceNode* m_root;
+};
+
+/**
+ * @brief Utility macro to create a trace node on the stack.
+ *
+ * @param name The variable name to use.
+ * @param ... The name template and format parameters.
+ */
+#define TRACE_NODE(name, ...) TraceNode name(__VA_ARGS__);
+
+/**
+ * @brief Add a string annotation to the current node.
+ *
+ * @param key The name of the attribute.
+ * @param format The format template for the attribute value.
+ * @param ... The format parameters.
+ */
+void trace_add_data(const char* key, const char* format, ...);
+
+/**
+ * @brief Add a float annotation to the current node.
+ *
+ * @param key The name of the attribute.
+ * @param value The value of the attribute.
+ */
+void trace_add_data(const char* key, float value);
+
+/**
+ * @brief Add an integer annotation to the current node.
+ *
+ * @param key The name of the attribute.
+ * @param value The value of the attribute.
+ */
+void trace_add_data(const char* key, int value);
+
+/**
+ * @brief Add an unsigned integer annotation to the current node.
+ *
+ * @param key The name of the attribute.
+ * @param value The value of the attribute.
+ */
+void trace_add_data(const char* key, unsigned int value);
+
+#else
+
+#define TRACE_NODE(name, ...)
+
+#define trace_add_data(...)
+
+#endif
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_entry.cpp b/thirdparty/astcenc/astcenc_entry.cpp
new file mode 100644
index 0000000000..e59f1fe61a
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_entry.cpp
@@ -0,0 +1,1427 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2023 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Functions for the library entrypoint.
+ */
+
+#include <array>
+#include <cstring>
+#include <new>
+
+#include "astcenc.h"
+#include "astcenc_internal_entry.h"
+#include "astcenc_diagnostic_trace.h"
+
+/**
+ * @brief Record of the quality tuning parameter values.
+ *
+ * See the @c astcenc_config structure for detailed parameter documentation.
+ *
+ * Note that the mse_overshoot entries are scaling factors relative to the base MSE to hit db_limit.
+ * A 20% overshoot is harder to hit for a higher base db_limit, so we may actually use lower ratios
+ * for the more through search presets because the underlying db_limit is so much higher.
+ */
+struct astcenc_preset_config
+{
+ float quality;
+ unsigned int tune_partition_count_limit;
+ unsigned int tune_2partition_index_limit;
+ unsigned int tune_3partition_index_limit;
+ unsigned int tune_4partition_index_limit;
+ unsigned int tune_block_mode_limit;
+ unsigned int tune_refinement_limit;
+ unsigned int tune_candidate_limit;
+ unsigned int tune_2partitioning_candidate_limit;
+ unsigned int tune_3partitioning_candidate_limit;
+ unsigned int tune_4partitioning_candidate_limit;
+ float tune_db_limit_a_base;
+ float tune_db_limit_b_base;
+ float tune_mse_overshoot;
+ float tune_2_partition_early_out_limit_factor;
+ float tune_3_partition_early_out_limit_factor;
+ float tune_2_plane_early_out_limit_correlation;
+};
+
+/**
+ * @brief The static presets for high bandwidth encodings (x < 25 texels per block).
+ */
+static const std::array<astcenc_preset_config, 6> preset_configs_high {{
+ {
+ ASTCENC_PRE_FASTEST,
+ 2, 10, 6, 4, 43, 2, 2, 2, 2, 2, 85.2f, 63.2f, 3.5f, 1.0f, 1.0f, 0.85f
+ }, {
+ ASTCENC_PRE_FAST,
+ 3, 18, 10, 8, 55, 3, 3, 2, 2, 2, 85.2f, 63.2f, 3.5f, 1.0f, 1.0f, 0.90f
+ }, {
+ ASTCENC_PRE_MEDIUM,
+ 4, 34, 28, 16, 77, 3, 3, 2, 2, 2, 95.0f, 70.0f, 2.5f, 1.1f, 1.05f, 0.95f
+ }, {
+ ASTCENC_PRE_THOROUGH,
+ 4, 82, 60, 30, 94, 4, 4, 3, 2, 2, 105.0f, 77.0f, 10.0f, 1.35f, 1.15f, 0.97f
+ }, {
+ ASTCENC_PRE_VERYTHOROUGH,
+ 4, 256, 128, 64, 98, 4, 6, 20, 14, 8, 200.0f, 200.0f, 10.0f, 1.6f, 1.4f, 0.98f
+ }, {
+ ASTCENC_PRE_EXHAUSTIVE,
+ 4, 512, 512, 512, 100, 4, 8, 32, 32, 32, 200.0f, 200.0f, 10.0f, 2.0f, 2.0f, 0.99f
+ }
+}};
+
+/**
+ * @brief The static presets for medium bandwidth encodings (25 <= x < 64 texels per block).
+ */
+static const std::array<astcenc_preset_config, 6> preset_configs_mid {{
+ {
+ ASTCENC_PRE_FASTEST,
+ 2, 10, 6, 4, 43, 2, 2, 2, 2, 2, 85.2f, 63.2f, 3.5f, 1.0f, 1.0f, 0.80f
+ }, {
+ ASTCENC_PRE_FAST,
+ 3, 18, 12, 10, 55, 3, 3, 2, 2, 2, 85.2f, 63.2f, 3.5f, 1.0f, 1.0f, 0.85f
+ }, {
+ ASTCENC_PRE_MEDIUM,
+ 4, 34, 28, 16, 77, 3, 3, 2, 2, 2, 95.0f, 70.0f, 3.0f, 1.1f, 1.05f, 0.90f
+ }, {
+ ASTCENC_PRE_THOROUGH,
+ 4, 82, 60, 30, 94, 4, 4, 3, 2, 2, 105.0f, 77.0f, 10.0f, 1.4f, 1.2f, 0.95f
+ }, {
+ ASTCENC_PRE_VERYTHOROUGH,
+ 4, 256, 128, 64, 98, 4, 6, 12, 8, 3, 200.0f, 200.0f, 10.0f, 1.6f, 1.4f, 0.98f
+ }, {
+ ASTCENC_PRE_EXHAUSTIVE,
+ 4, 256, 256, 256, 100, 4, 8, 32, 32, 32, 200.0f, 200.0f, 10.0f, 2.0f, 2.0f, 0.99f
+ }
+}};
+
+/**
+ * @brief The static presets for low bandwidth encodings (64 <= x texels per block).
+ */
+static const std::array<astcenc_preset_config, 6> preset_configs_low {{
+ {
+ ASTCENC_PRE_FASTEST,
+ 2, 10, 6, 4, 40, 2, 2, 2, 2, 2, 85.0f, 63.0f, 3.5f, 1.0f, 1.0f, 0.80f
+ }, {
+ ASTCENC_PRE_FAST,
+ 2, 18, 12, 10, 55, 3, 3, 2, 2, 2, 85.0f, 63.0f, 3.5f, 1.0f, 1.0f, 0.85f
+ }, {
+ ASTCENC_PRE_MEDIUM,
+ 3, 34, 28, 16, 77, 3, 3, 2, 2, 2, 95.0f, 70.0f, 3.5f, 1.1f, 1.05f, 0.90f
+ }, {
+ ASTCENC_PRE_THOROUGH,
+ 4, 82, 60, 30, 93, 4, 4, 3, 2, 2, 105.0f, 77.0f, 10.0f, 1.3f, 1.2f, 0.97f
+ }, {
+ ASTCENC_PRE_VERYTHOROUGH,
+ 4, 256, 128, 64, 98, 4, 6, 9, 5, 2, 200.0f, 200.0f, 10.0f, 1.6f, 1.4f, 0.98f
+ }, {
+ ASTCENC_PRE_EXHAUSTIVE,
+ 4, 256, 256, 256, 100, 4, 8, 32, 32, 32, 200.0f, 200.0f, 10.0f, 2.0f, 2.0f, 0.99f
+ }
+}};
+
+/**
+ * @brief Validate CPU floating point meets assumptions made in the codec.
+ *
+ * The codec is written with the assumption that a float threaded through the @c if32 union will be
+ * stored and reloaded as a 32-bit IEEE-754 float with round-to-nearest rounding. This is always the
+ * case in an IEEE-754 compliant system, however not every system or compilation mode is actually
+ * IEEE-754 compliant. This normally fails if the code is compiled with fast math enabled.
+ *
+ * @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.
+ */
+static astcenc_error validate_cpu_float()
+{
+ if32 p;
+ volatile float xprec_testval = 2.51f;
+ p.f = xprec_testval + 12582912.0f;
+ float q = p.f - 12582912.0f;
+
+ if (q != 3.0f)
+ {
+ return ASTCENC_ERR_BAD_CPU_FLOAT;
+ }
+
+ return ASTCENC_SUCCESS;
+}
+
+/**
+ * @brief Validate CPU ISA support meets the requirements of this build of the library.
+ *
+ * Each library build is statically compiled for a particular set of CPU ISA features, such as the
+ * SIMD support or other ISA extensions such as POPCNT. This function checks that the host CPU
+ * actually supports everything this build needs.
+ *
+ * @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.
+ */
+static astcenc_error validate_cpu_isa()
+{
+ #if ASTCENC_SSE >= 41
+ if (!cpu_supports_sse41())
+ {
+ return ASTCENC_ERR_BAD_CPU_ISA;
+ }
+ #endif
+
+ #if ASTCENC_POPCNT >= 1
+ if (!cpu_supports_popcnt())
+ {
+ return ASTCENC_ERR_BAD_CPU_ISA;
+ }
+ #endif
+
+ #if ASTCENC_F16C >= 1
+ if (!cpu_supports_f16c())
+ {
+ return ASTCENC_ERR_BAD_CPU_ISA;
+ }
+ #endif
+
+ #if ASTCENC_AVX >= 2
+ if (!cpu_supports_avx2())
+ {
+ return ASTCENC_ERR_BAD_CPU_ISA;
+ }
+ #endif
+
+ return ASTCENC_SUCCESS;
+}
+
+/**
+ * @brief Validate config profile.
+ *
+ * @param profile The profile to check.
+ *
+ * @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.
+ */
+static astcenc_error validate_profile(
+ astcenc_profile profile
+) {
+ // Values in this enum are from an external user, so not guaranteed to be
+ // bounded to the enum values
+ switch (static_cast<int>(profile))
+ {
+ case ASTCENC_PRF_LDR_SRGB:
+ case ASTCENC_PRF_LDR:
+ case ASTCENC_PRF_HDR_RGB_LDR_A:
+ case ASTCENC_PRF_HDR:
+ return ASTCENC_SUCCESS;
+ default:
+ return ASTCENC_ERR_BAD_PROFILE;
+ }
+}
+
+/**
+ * @brief Validate block size.
+ *
+ * @param block_x The block x dimensions.
+ * @param block_y The block y dimensions.
+ * @param block_z The block z dimensions.
+ *
+ * @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.
+ */
+static astcenc_error validate_block_size(
+ unsigned int block_x,
+ unsigned int block_y,
+ unsigned int block_z
+) {
+ // Test if this is a legal block size at all
+ bool is_legal = (((block_z <= 1) && is_legal_2d_block_size(block_x, block_y)) ||
+ ((block_z >= 2) && is_legal_3d_block_size(block_x, block_y, block_z)));
+ if (!is_legal)
+ {
+ return ASTCENC_ERR_BAD_BLOCK_SIZE;
+ }
+
+ // Test if this build has sufficient capacity for this block size
+ bool have_capacity = (block_x * block_y * block_z) <= BLOCK_MAX_TEXELS;
+ if (!have_capacity)
+ {
+ return ASTCENC_ERR_NOT_IMPLEMENTED;
+ }
+
+ return ASTCENC_SUCCESS;
+}
+
+/**
+ * @brief Validate flags.
+ *
+ * @param flags The flags to check.
+ *
+ * @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.
+ */
+static astcenc_error validate_flags(
+ unsigned int flags
+) {
+ // Flags field must not contain any unknown flag bits
+ unsigned int exMask = ~ASTCENC_ALL_FLAGS;
+ if (popcount(flags & exMask) != 0)
+ {
+ return ASTCENC_ERR_BAD_FLAGS;
+ }
+
+ // Flags field must only contain at most a single map type
+ exMask = ASTCENC_FLG_MAP_NORMAL
+ | ASTCENC_FLG_MAP_RGBM;
+ if (popcount(flags & exMask) > 1)
+ {
+ return ASTCENC_ERR_BAD_FLAGS;
+ }
+
+ return ASTCENC_SUCCESS;
+}
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+
+/**
+ * @brief Validate single channel compression swizzle.
+ *
+ * @param swizzle The swizzle to check.
+ *
+ * @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.
+ */
+static astcenc_error validate_compression_swz(
+ astcenc_swz swizzle
+) {
+ // Not all enum values are handled; SWZ_Z is invalid for compression
+ switch (static_cast<int>(swizzle))
+ {
+ case ASTCENC_SWZ_R:
+ case ASTCENC_SWZ_G:
+ case ASTCENC_SWZ_B:
+ case ASTCENC_SWZ_A:
+ case ASTCENC_SWZ_0:
+ case ASTCENC_SWZ_1:
+ return ASTCENC_SUCCESS;
+ default:
+ return ASTCENC_ERR_BAD_SWIZZLE;
+ }
+}
+
+/**
+ * @brief Validate overall compression swizzle.
+ *
+ * @param swizzle The swizzle to check.
+ *
+ * @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.
+ */
+static astcenc_error validate_compression_swizzle(
+ const astcenc_swizzle& swizzle
+) {
+ if (validate_compression_swz(swizzle.r) ||
+ validate_compression_swz(swizzle.g) ||
+ validate_compression_swz(swizzle.b) ||
+ validate_compression_swz(swizzle.a))
+ {
+ return ASTCENC_ERR_BAD_SWIZZLE;
+ }
+
+ return ASTCENC_SUCCESS;
+}
+#endif
+
+/**
+ * @brief Validate single channel decompression swizzle.
+ *
+ * @param swizzle The swizzle to check.
+ *
+ * @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.
+ */
+static astcenc_error validate_decompression_swz(
+ astcenc_swz swizzle
+) {
+ // Values in this enum are from an external user, so not guaranteed to be
+ // bounded to the enum values
+ switch (static_cast<int>(swizzle))
+ {
+ case ASTCENC_SWZ_R:
+ case ASTCENC_SWZ_G:
+ case ASTCENC_SWZ_B:
+ case ASTCENC_SWZ_A:
+ case ASTCENC_SWZ_0:
+ case ASTCENC_SWZ_1:
+ case ASTCENC_SWZ_Z:
+ return ASTCENC_SUCCESS;
+ default:
+ return ASTCENC_ERR_BAD_SWIZZLE;
+ }
+}
+
+/**
+ * @brief Validate overall decompression swizzle.
+ *
+ * @param swizzle The swizzle to check.
+ *
+ * @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.
+ */
+static astcenc_error validate_decompression_swizzle(
+ const astcenc_swizzle& swizzle
+) {
+ if (validate_decompression_swz(swizzle.r) ||
+ validate_decompression_swz(swizzle.g) ||
+ validate_decompression_swz(swizzle.b) ||
+ validate_decompression_swz(swizzle.a))
+ {
+ return ASTCENC_ERR_BAD_SWIZZLE;
+ }
+
+ return ASTCENC_SUCCESS;
+}
+
+/**
+ * Validate that an incoming configuration is in-spec.
+ *
+ * This function can respond in two ways:
+ *
+ * * Numerical inputs that have valid ranges are clamped to those valid ranges. No error is thrown
+ * for out-of-range inputs in this case.
+ * * Numerical inputs and logic inputs are are logically invalid and which make no sense
+ * algorithmically will return an error.
+ *
+ * @param[in,out] config The input compressor configuration.
+ *
+ * @return Return @c ASTCENC_SUCCESS if validated, otherwise an error on failure.
+ */
+static astcenc_error validate_config(
+ astcenc_config &config
+) {
+ astcenc_error status;
+
+ status = validate_profile(config.profile);
+ if (status != ASTCENC_SUCCESS)
+ {
+ return status;
+ }
+
+ status = validate_flags(config.flags);
+ if (status != ASTCENC_SUCCESS)
+ {
+ return status;
+ }
+
+ status = validate_block_size(config.block_x, config.block_y, config.block_z);
+ if (status != ASTCENC_SUCCESS)
+ {
+ return status;
+ }
+
+#if defined(ASTCENC_DECOMPRESS_ONLY)
+ // Decompress-only builds only support decompress-only contexts
+ if (!(config.flags & ASTCENC_FLG_DECOMPRESS_ONLY))
+ {
+ return ASTCENC_ERR_BAD_PARAM;
+ }
+#endif
+
+ config.rgbm_m_scale = astc::max(config.rgbm_m_scale, 1.0f);
+
+ config.tune_partition_count_limit = astc::clamp(config.tune_partition_count_limit, 1u, 4u);
+ config.tune_2partition_index_limit = astc::clamp(config.tune_2partition_index_limit, 1u, BLOCK_MAX_PARTITIONINGS);
+ config.tune_3partition_index_limit = astc::clamp(config.tune_3partition_index_limit, 1u, BLOCK_MAX_PARTITIONINGS);
+ config.tune_4partition_index_limit = astc::clamp(config.tune_4partition_index_limit, 1u, BLOCK_MAX_PARTITIONINGS);
+ config.tune_block_mode_limit = astc::clamp(config.tune_block_mode_limit, 1u, 100u);
+ config.tune_refinement_limit = astc::max(config.tune_refinement_limit, 1u);
+ config.tune_candidate_limit = astc::clamp(config.tune_candidate_limit, 1u, TUNE_MAX_TRIAL_CANDIDATES);
+ config.tune_2partitioning_candidate_limit = astc::clamp(config.tune_2partitioning_candidate_limit, 1u, TUNE_MAX_PARTITIONING_CANDIDATES);
+ config.tune_3partitioning_candidate_limit = astc::clamp(config.tune_3partitioning_candidate_limit, 1u, TUNE_MAX_PARTITIONING_CANDIDATES);
+ config.tune_4partitioning_candidate_limit = astc::clamp(config.tune_4partitioning_candidate_limit, 1u, TUNE_MAX_PARTITIONING_CANDIDATES);
+ config.tune_db_limit = astc::max(config.tune_db_limit, 0.0f);
+ config.tune_mse_overshoot = astc::max(config.tune_mse_overshoot, 1.0f);
+ config.tune_2_partition_early_out_limit_factor = astc::max(config.tune_2_partition_early_out_limit_factor, 0.0f);
+ config.tune_3_partition_early_out_limit_factor = astc::max(config.tune_3_partition_early_out_limit_factor, 0.0f);
+ config.tune_2_plane_early_out_limit_correlation = astc::max(config.tune_2_plane_early_out_limit_correlation, 0.0f);
+
+ // Specifying a zero weight color component is not allowed; force to small value
+ float max_weight = astc::max(astc::max(config.cw_r_weight, config.cw_g_weight),
+ astc::max(config.cw_b_weight, config.cw_a_weight));
+ if (max_weight > 0.0f)
+ {
+ max_weight /= 1000.0f;
+ config.cw_r_weight = astc::max(config.cw_r_weight, max_weight);
+ config.cw_g_weight = astc::max(config.cw_g_weight, max_weight);
+ config.cw_b_weight = astc::max(config.cw_b_weight, max_weight);
+ config.cw_a_weight = astc::max(config.cw_a_weight, max_weight);
+ }
+ // If all color components error weights are zero then return an error
+ else
+ {
+ return ASTCENC_ERR_BAD_PARAM;
+ }
+
+ return ASTCENC_SUCCESS;
+}
+
+/* See header for documentation. */
+astcenc_error astcenc_config_init(
+ astcenc_profile profile,
+ unsigned int block_x,
+ unsigned int block_y,
+ unsigned int block_z,
+ float quality,
+ unsigned int flags,
+ astcenc_config* configp
+) {
+ astcenc_error status;
+
+ // Check basic library compatibility options here so they are checked early. Note, these checks
+ // are repeated in context_alloc for cases where callers use a manually defined config struct
+ status = validate_cpu_isa();
+ if (status != ASTCENC_SUCCESS)
+ {
+ return status;
+ }
+
+ status = validate_cpu_float();
+ if (status != ASTCENC_SUCCESS)
+ {
+ return status;
+ }
+
+ // Zero init all config fields; although most of will be over written
+ astcenc_config& config = *configp;
+ std::memset(&config, 0, sizeof(config));
+
+ // Process the block size
+ block_z = astc::max(block_z, 1u); // For 2D blocks Z==0 is accepted, but convert to 1
+ status = validate_block_size(block_x, block_y, block_z);
+ if (status != ASTCENC_SUCCESS)
+ {
+ return status;
+ }
+
+ config.block_x = block_x;
+ config.block_y = block_y;
+ config.block_z = block_z;
+
+ float texels = static_cast<float>(block_x * block_y * block_z);
+ float ltexels = logf(texels) / logf(10.0f);
+
+ // Process the performance quality level or preset; note that this must be done before we
+ // process any additional settings, such as color profile and flags, which may replace some of
+ // these settings with more use case tuned values
+ if (quality < ASTCENC_PRE_FASTEST ||
+ quality > ASTCENC_PRE_EXHAUSTIVE)
+ {
+ return ASTCENC_ERR_BAD_QUALITY;
+ }
+
+ static const std::array<astcenc_preset_config, 6>* preset_configs;
+ int texels_int = block_x * block_y * block_z;
+ if (texels_int < 25)
+ {
+ preset_configs = &preset_configs_high;
+ }
+ else if (texels_int < 64)
+ {
+ preset_configs = &preset_configs_mid;
+ }
+ else
+ {
+ preset_configs = &preset_configs_low;
+ }
+
+ // Determine which preset to use, or which pair to interpolate
+ size_t start;
+ size_t end;
+ for (end = 0; end < preset_configs->size(); end++)
+ {
+ if ((*preset_configs)[end].quality >= quality)
+ {
+ break;
+ }
+ }
+
+ start = end == 0 ? 0 : end - 1;
+
+ // Start and end node are the same - so just transfer the values.
+ if (start == end)
+ {
+ config.tune_partition_count_limit = (*preset_configs)[start].tune_partition_count_limit;
+ config.tune_2partition_index_limit = (*preset_configs)[start].tune_2partition_index_limit;
+ config.tune_3partition_index_limit = (*preset_configs)[start].tune_3partition_index_limit;
+ config.tune_4partition_index_limit = (*preset_configs)[start].tune_4partition_index_limit;
+ config.tune_block_mode_limit = (*preset_configs)[start].tune_block_mode_limit;
+ config.tune_refinement_limit = (*preset_configs)[start].tune_refinement_limit;
+ config.tune_candidate_limit = astc::min((*preset_configs)[start].tune_candidate_limit, TUNE_MAX_TRIAL_CANDIDATES);
+ config.tune_2partitioning_candidate_limit = astc::min((*preset_configs)[start].tune_2partitioning_candidate_limit, TUNE_MAX_PARTITIONING_CANDIDATES);
+ config.tune_3partitioning_candidate_limit = astc::min((*preset_configs)[start].tune_3partitioning_candidate_limit, TUNE_MAX_PARTITIONING_CANDIDATES);
+ config.tune_4partitioning_candidate_limit = astc::min((*preset_configs)[start].tune_4partitioning_candidate_limit, TUNE_MAX_PARTITIONING_CANDIDATES);
+ config.tune_db_limit = astc::max((*preset_configs)[start].tune_db_limit_a_base - 35 * ltexels,
+ (*preset_configs)[start].tune_db_limit_b_base - 19 * ltexels);
+
+ config.tune_mse_overshoot = (*preset_configs)[start].tune_mse_overshoot;
+
+ config.tune_2_partition_early_out_limit_factor = (*preset_configs)[start].tune_2_partition_early_out_limit_factor;
+ config.tune_3_partition_early_out_limit_factor =(*preset_configs)[start].tune_3_partition_early_out_limit_factor;
+ config.tune_2_plane_early_out_limit_correlation = (*preset_configs)[start].tune_2_plane_early_out_limit_correlation;
+ }
+ // Start and end node are not the same - so interpolate between them
+ else
+ {
+ auto& node_a = (*preset_configs)[start];
+ auto& node_b = (*preset_configs)[end];
+
+ float wt_range = node_b.quality - node_a.quality;
+ assert(wt_range > 0);
+
+ // Compute interpolation factors
+ float wt_node_a = (node_b.quality - quality) / wt_range;
+ float wt_node_b = (quality - node_a.quality) / wt_range;
+
+ #define LERP(param) ((node_a.param * wt_node_a) + (node_b.param * wt_node_b))
+ #define LERPI(param) astc::flt2int_rtn(\
+ (static_cast<float>(node_a.param) * wt_node_a) + \
+ (static_cast<float>(node_b.param) * wt_node_b))
+ #define LERPUI(param) static_cast<unsigned int>(LERPI(param))
+
+ config.tune_partition_count_limit = LERPI(tune_partition_count_limit);
+ config.tune_2partition_index_limit = LERPI(tune_2partition_index_limit);
+ config.tune_3partition_index_limit = LERPI(tune_3partition_index_limit);
+ config.tune_4partition_index_limit = LERPI(tune_4partition_index_limit);
+ config.tune_block_mode_limit = LERPI(tune_block_mode_limit);
+ config.tune_refinement_limit = LERPI(tune_refinement_limit);
+ config.tune_candidate_limit = astc::min(LERPUI(tune_candidate_limit),
+ TUNE_MAX_TRIAL_CANDIDATES);
+ config.tune_2partitioning_candidate_limit = astc::min(LERPUI(tune_2partitioning_candidate_limit),
+ BLOCK_MAX_PARTITIONINGS);
+ config.tune_3partitioning_candidate_limit = astc::min(LERPUI(tune_3partitioning_candidate_limit),
+ BLOCK_MAX_PARTITIONINGS);
+ config.tune_4partitioning_candidate_limit = astc::min(LERPUI(tune_4partitioning_candidate_limit),
+ BLOCK_MAX_PARTITIONINGS);
+ config.tune_db_limit = astc::max(LERP(tune_db_limit_a_base) - 35 * ltexels,
+ LERP(tune_db_limit_b_base) - 19 * ltexels);
+
+ config.tune_mse_overshoot = LERP(tune_mse_overshoot);
+
+ config.tune_2_partition_early_out_limit_factor = LERP(tune_2_partition_early_out_limit_factor);
+ config.tune_3_partition_early_out_limit_factor = LERP(tune_3_partition_early_out_limit_factor);
+ config.tune_2_plane_early_out_limit_correlation = LERP(tune_2_plane_early_out_limit_correlation);
+ #undef LERP
+ #undef LERPI
+ #undef LERPUI
+ }
+
+ // Set heuristics to the defaults for each color profile
+ config.cw_r_weight = 1.0f;
+ config.cw_g_weight = 1.0f;
+ config.cw_b_weight = 1.0f;
+ config.cw_a_weight = 1.0f;
+
+ config.a_scale_radius = 0;
+
+ config.rgbm_m_scale = 0.0f;
+
+ config.profile = profile;
+
+ // Values in this enum are from an external user, so not guaranteed to be
+ // bounded to the enum values
+ switch (static_cast<int>(profile))
+ {
+ case ASTCENC_PRF_LDR:
+ case ASTCENC_PRF_LDR_SRGB:
+ break;
+ case ASTCENC_PRF_HDR_RGB_LDR_A:
+ case ASTCENC_PRF_HDR:
+ config.tune_db_limit = 999.0f;
+ break;
+ default:
+ return ASTCENC_ERR_BAD_PROFILE;
+ }
+
+ // Flags field must not contain any unknown flag bits
+ status = validate_flags(flags);
+ if (status != ASTCENC_SUCCESS)
+ {
+ return status;
+ }
+
+ if (flags & ASTCENC_FLG_MAP_NORMAL)
+ {
+ // Normal map encoding uses L+A blocks, so allow one more partitioning
+ // than normal. We need need fewer bits for endpoints, so more likely
+ // to be able to use more partitions than an RGB/RGBA block
+ config.tune_partition_count_limit = astc::min(config.tune_partition_count_limit + 1u, 4u);
+
+ config.cw_g_weight = 0.0f;
+ config.cw_b_weight = 0.0f;
+ config.tune_2_partition_early_out_limit_factor *= 1.5f;
+ config.tune_3_partition_early_out_limit_factor *= 1.5f;
+ config.tune_2_plane_early_out_limit_correlation = 0.99f;
+
+ // Normals are prone to blocking artifacts on smooth curves
+ // so force compressor to try harder here ...
+ config.tune_db_limit *= 1.03f;
+ }
+ else if (flags & ASTCENC_FLG_MAP_RGBM)
+ {
+ config.rgbm_m_scale = 5.0f;
+ config.cw_a_weight = 2.0f * config.rgbm_m_scale;
+ }
+ else // (This is color data)
+ {
+ // This is a very basic perceptual metric for RGB color data, which weights error
+ // significance by the perceptual luminance contribution of each color channel. For
+ // luminance the usual weights to compute luminance from a linear RGB value are as
+ // follows:
+ //
+ // l = r * 0.3 + g * 0.59 + b * 0.11
+ //
+ // ... but we scale these up to keep a better balance between color and alpha. Note
+ // that if the content is using alpha we'd recommend using the -a option to weight
+ // the color contribution by the alpha transparency.
+ if (flags & ASTCENC_FLG_USE_PERCEPTUAL)
+ {
+ config.cw_r_weight = 0.30f * 2.25f;
+ config.cw_g_weight = 0.59f * 2.25f;
+ config.cw_b_weight = 0.11f * 2.25f;
+ }
+ }
+ config.flags = flags;
+
+ return ASTCENC_SUCCESS;
+}
+
+/* See header for documentation. */
+astcenc_error astcenc_context_alloc(
+ const astcenc_config* configp,
+ unsigned int thread_count,
+ astcenc_context** context
+) {
+ astcenc_error status;
+ const astcenc_config& config = *configp;
+
+ status = validate_cpu_isa();
+ if (status != ASTCENC_SUCCESS)
+ {
+ return status;
+ }
+
+ status = validate_cpu_float();
+ if (status != ASTCENC_SUCCESS)
+ {
+ return status;
+ }
+
+ if (thread_count == 0)
+ {
+ return ASTCENC_ERR_BAD_PARAM;
+ }
+
+#if defined(ASTCENC_DIAGNOSTICS)
+ // Force single threaded compressor use in diagnostic mode.
+ if (thread_count != 1)
+ {
+ return ASTCENC_ERR_BAD_PARAM;
+ }
+#endif
+
+ astcenc_context* ctxo = new astcenc_context;
+ astcenc_contexti* ctx = &ctxo->context;
+ ctx->thread_count = thread_count;
+ ctx->config = config;
+ ctx->working_buffers = nullptr;
+
+ // These are allocated per-compress, as they depend on image size
+ ctx->input_alpha_averages = nullptr;
+
+ // Copy the config first and validate the copy (we may modify it)
+ status = validate_config(ctx->config);
+ if (status != ASTCENC_SUCCESS)
+ {
+ delete ctxo;
+ return status;
+ }
+
+ ctx->bsd = aligned_malloc<block_size_descriptor>(sizeof(block_size_descriptor), ASTCENC_VECALIGN);
+ bool can_omit_modes = static_cast<bool>(config.flags & ASTCENC_FLG_SELF_DECOMPRESS_ONLY);
+ init_block_size_descriptor(config.block_x, config.block_y, config.block_z,
+ can_omit_modes,
+ config.tune_partition_count_limit,
+ static_cast<float>(config.tune_block_mode_limit) / 100.0f,
+ *ctx->bsd);
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+ // Do setup only needed by compression
+ if (!(status & ASTCENC_FLG_DECOMPRESS_ONLY))
+ {
+ // Turn a dB limit into a per-texel error for faster use later
+ if ((ctx->config.profile == ASTCENC_PRF_LDR) || (ctx->config.profile == ASTCENC_PRF_LDR_SRGB))
+ {
+ ctx->config.tune_db_limit = astc::pow(0.1f, ctx->config.tune_db_limit * 0.1f) * 65535.0f * 65535.0f;
+ }
+ else
+ {
+ ctx->config.tune_db_limit = 0.0f;
+ }
+
+ size_t worksize = sizeof(compression_working_buffers) * thread_count;
+ ctx->working_buffers = aligned_malloc<compression_working_buffers>(worksize, ASTCENC_VECALIGN);
+ static_assert((sizeof(compression_working_buffers) % ASTCENC_VECALIGN) == 0,
+ "compression_working_buffers size must be multiple of vector alignment");
+ if (!ctx->working_buffers)
+ {
+ aligned_free<block_size_descriptor>(ctx->bsd);
+ delete ctxo;
+ *context = nullptr;
+ return ASTCENC_ERR_OUT_OF_MEM;
+ }
+ }
+#endif
+
+#if defined(ASTCENC_DIAGNOSTICS)
+ ctx->trace_log = new TraceLog(ctx->config.trace_file_path);
+ if (!ctx->trace_log->m_file)
+ {
+ return ASTCENC_ERR_DTRACE_FAILURE;
+ }
+
+ trace_add_data("block_x", config.block_x);
+ trace_add_data("block_y", config.block_y);
+ trace_add_data("block_z", config.block_z);
+#endif
+
+ *context = ctxo;
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+ prepare_angular_tables();
+#endif
+
+ return ASTCENC_SUCCESS;
+}
+
+/* See header dor documentation. */
+void astcenc_context_free(
+ astcenc_context* ctxo
+) {
+ if (ctxo)
+ {
+ astcenc_contexti* ctx = &ctxo->context;
+ aligned_free<compression_working_buffers>(ctx->working_buffers);
+ aligned_free<block_size_descriptor>(ctx->bsd);
+#if defined(ASTCENC_DIAGNOSTICS)
+ delete ctx->trace_log;
+#endif
+ delete ctxo;
+ }
+}
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+
+/**
+ * @brief Compress an image, after any preflight has completed.
+ *
+ * @param[out] ctxo The compressor context.
+ * @param thread_index The thread index.
+ * @param image The intput image.
+ * @param swizzle The input swizzle.
+ * @param[out] buffer The output array for the compressed data.
+ */
+static void compress_image(
+ astcenc_context& ctxo,
+ unsigned int thread_index,
+ const astcenc_image& image,
+ const astcenc_swizzle& swizzle,
+ uint8_t* buffer
+) {
+ astcenc_contexti& ctx = ctxo.context;
+ const block_size_descriptor& bsd = *ctx.bsd;
+ astcenc_profile decode_mode = ctx.config.profile;
+
+ image_block blk;
+
+ int block_x = bsd.xdim;
+ int block_y = bsd.ydim;
+ int block_z = bsd.zdim;
+ blk.texel_count = static_cast<uint8_t>(block_x * block_y * block_z);
+
+ int dim_x = image.dim_x;
+ int dim_y = image.dim_y;
+ int dim_z = image.dim_z;
+
+ int xblocks = (dim_x + block_x - 1) / block_x;
+ int yblocks = (dim_y + block_y - 1) / block_y;
+ int zblocks = (dim_z + block_z - 1) / block_z;
+ int block_count = zblocks * yblocks * xblocks;
+
+ int row_blocks = xblocks;
+ int plane_blocks = xblocks * yblocks;
+
+ // Populate the block channel weights
+ blk.channel_weight = vfloat4(ctx.config.cw_r_weight,
+ ctx.config.cw_g_weight,
+ ctx.config.cw_b_weight,
+ ctx.config.cw_a_weight);
+
+ // Use preallocated scratch buffer
+ auto& temp_buffers = ctx.working_buffers[thread_index];
+
+ // Only the first thread actually runs the initializer
+ ctxo.manage_compress.init(block_count);
+
+ // Determine if we can use an optimized load function
+ bool needs_swz = (swizzle.r != ASTCENC_SWZ_R) || (swizzle.g != ASTCENC_SWZ_G) ||
+ (swizzle.b != ASTCENC_SWZ_B) || (swizzle.a != ASTCENC_SWZ_A);
+
+ bool needs_hdr = (decode_mode == ASTCENC_PRF_HDR) ||
+ (decode_mode == ASTCENC_PRF_HDR_RGB_LDR_A);
+
+ bool use_fast_load = !needs_swz && !needs_hdr &&
+ block_z == 1 && image.data_type == ASTCENC_TYPE_U8;
+
+ auto load_func = load_image_block;
+ if (use_fast_load)
+ {
+ load_func = load_image_block_fast_ldr;
+ }
+
+ // All threads run this processing loop until there is no work remaining
+ while (true)
+ {
+ unsigned int count;
+ unsigned int base = ctxo.manage_compress.get_task_assignment(16, count);
+ if (!count)
+ {
+ break;
+ }
+
+ for (unsigned int i = base; i < base + count; i++)
+ {
+ // Decode i into x, y, z block indices
+ int z = i / plane_blocks;
+ unsigned int rem = i - (z * plane_blocks);
+ int y = rem / row_blocks;
+ int x = rem - (y * row_blocks);
+
+ // Test if we can apply some basic alpha-scale RDO
+ bool use_full_block = true;
+ if (ctx.config.a_scale_radius != 0 && block_z == 1)
+ {
+ int start_x = x * block_x;
+ int end_x = astc::min(dim_x, start_x + block_x);
+
+ int start_y = y * block_y;
+ int end_y = astc::min(dim_y, start_y + block_y);
+
+ // SATs accumulate error, so don't test exactly zero. Test for
+ // less than 1 alpha in the expanded block footprint that
+ // includes the alpha radius.
+ int x_footprint = block_x + 2 * (ctx.config.a_scale_radius - 1);
+
+ int y_footprint = block_y + 2 * (ctx.config.a_scale_radius - 1);
+
+ float footprint = static_cast<float>(x_footprint * y_footprint);
+ float threshold = 0.9f / (255.0f * footprint);
+
+ // Do we have any alpha values?
+ use_full_block = false;
+ for (int ay = start_y; ay < end_y; ay++)
+ {
+ for (int ax = start_x; ax < end_x; ax++)
+ {
+ float a_avg = ctx.input_alpha_averages[ay * dim_x + ax];
+ if (a_avg > threshold)
+ {
+ use_full_block = true;
+ ax = end_x;
+ ay = end_y;
+ }
+ }
+ }
+ }
+
+ // Fetch the full block for compression
+ if (use_full_block)
+ {
+ load_func(decode_mode, image, blk, bsd, x * block_x, y * block_y, z * block_z, swizzle);
+
+ // Scale RGB error contribution by the maximum alpha in the block
+ // This encourages preserving alpha accuracy in regions with high
+ // transparency, and can buy up to 0.5 dB PSNR.
+ if (ctx.config.flags & ASTCENC_FLG_USE_ALPHA_WEIGHT)
+ {
+ float alpha_scale = blk.data_max.lane<3>() * (1.0f / 65535.0f);
+ blk.channel_weight = vfloat4(ctx.config.cw_r_weight * alpha_scale,
+ ctx.config.cw_g_weight * alpha_scale,
+ ctx.config.cw_b_weight * alpha_scale,
+ ctx.config.cw_a_weight);
+ }
+ }
+ // Apply alpha scale RDO - substitute constant color block
+ else
+ {
+ blk.origin_texel = vfloat4::zero();
+ blk.data_min = vfloat4::zero();
+ blk.data_mean = vfloat4::zero();
+ blk.data_max = vfloat4::zero();
+ blk.grayscale = true;
+ }
+
+ int offset = ((z * yblocks + y) * xblocks + x) * 16;
+ uint8_t *bp = buffer + offset;
+ physical_compressed_block* pcb = reinterpret_cast<physical_compressed_block*>(bp);
+ compress_block(ctx, blk, *pcb, temp_buffers);
+ }
+
+ ctxo.manage_compress.complete_task_assignment(count);
+ }
+}
+
+/**
+ * @brief Compute regional averages in an image.
+ *
+ * This function can be called by multiple threads, but only after a single
+ * thread calls the setup function @c init_compute_averages().
+ *
+ * Results are written back into @c img->input_alpha_averages.
+ *
+ * @param[out] ctx The context.
+ * @param ag The average and variance arguments created during setup.
+ */
+static void compute_averages(
+ astcenc_context& ctx,
+ const avg_args &ag
+) {
+ pixel_region_args arg = ag.arg;
+ arg.work_memory = new vfloat4[ag.work_memory_size];
+
+ int size_x = ag.img_size_x;
+ int size_y = ag.img_size_y;
+ int size_z = ag.img_size_z;
+
+ int step_xy = ag.blk_size_xy;
+ int step_z = ag.blk_size_z;
+
+ int y_tasks = (size_y + step_xy - 1) / step_xy;
+
+ // All threads run this processing loop until there is no work remaining
+ while (true)
+ {
+ unsigned int count;
+ unsigned int base = ctx.manage_avg.get_task_assignment(16, count);
+ if (!count)
+ {
+ break;
+ }
+
+ for (unsigned int i = base; i < base + count; i++)
+ {
+ int z = (i / (y_tasks)) * step_z;
+ int y = (i - (z * y_tasks)) * step_xy;
+
+ arg.size_z = astc::min(step_z, size_z - z);
+ arg.offset_z = z;
+
+ arg.size_y = astc::min(step_xy, size_y - y);
+ arg.offset_y = y;
+
+ for (int x = 0; x < size_x; x += step_xy)
+ {
+ arg.size_x = astc::min(step_xy, size_x - x);
+ arg.offset_x = x;
+ compute_pixel_region_variance(ctx.context, arg);
+ }
+ }
+
+ ctx.manage_avg.complete_task_assignment(count);
+ }
+
+ delete[] arg.work_memory;
+}
+
+#endif
+
+/* See header for documentation. */
+astcenc_error astcenc_compress_image(
+ astcenc_context* ctxo,
+ astcenc_image* imagep,
+ const astcenc_swizzle* swizzle,
+ uint8_t* data_out,
+ size_t data_len,
+ unsigned int thread_index
+) {
+#if defined(ASTCENC_DECOMPRESS_ONLY)
+ (void)ctxo;
+ (void)imagep;
+ (void)swizzle;
+ (void)data_out;
+ (void)data_len;
+ (void)thread_index;
+ return ASTCENC_ERR_BAD_CONTEXT;
+#else
+ astcenc_contexti* ctx = &ctxo->context;
+ astcenc_error status;
+ astcenc_image& image = *imagep;
+
+ if (ctx->config.flags & ASTCENC_FLG_DECOMPRESS_ONLY)
+ {
+ return ASTCENC_ERR_BAD_CONTEXT;
+ }
+
+ status = validate_compression_swizzle(*swizzle);
+ if (status != ASTCENC_SUCCESS)
+ {
+ return status;
+ }
+
+ if (thread_index >= ctx->thread_count)
+ {
+ return ASTCENC_ERR_BAD_PARAM;
+ }
+
+ unsigned int block_x = ctx->config.block_x;
+ unsigned int block_y = ctx->config.block_y;
+ unsigned int block_z = ctx->config.block_z;
+
+ unsigned int xblocks = (image.dim_x + block_x - 1) / block_x;
+ unsigned int yblocks = (image.dim_y + block_y - 1) / block_y;
+ unsigned int zblocks = (image.dim_z + block_z - 1) / block_z;
+
+ // Check we have enough output space (16 bytes per block)
+ size_t size_needed = xblocks * yblocks * zblocks * 16;
+ if (data_len < size_needed)
+ {
+ return ASTCENC_ERR_OUT_OF_MEM;
+ }
+
+ // If context thread count is one then implicitly reset
+ if (ctx->thread_count == 1)
+ {
+ astcenc_compress_reset(ctxo);
+ }
+
+ if (ctx->config.a_scale_radius != 0)
+ {
+ // First thread to enter will do setup, other threads will subsequently
+ // enter the critical section but simply skip over the initialization
+ auto init_avg = [ctx, &image, swizzle]() {
+ // Perform memory allocations for the destination buffers
+ size_t texel_count = image.dim_x * image.dim_y * image.dim_z;
+ ctx->input_alpha_averages = new float[texel_count];
+
+ return init_compute_averages(
+ image, ctx->config.a_scale_radius, *swizzle,
+ ctx->avg_preprocess_args);
+ };
+
+ // Only the first thread actually runs the initializer
+ ctxo->manage_avg.init(init_avg);
+
+ // All threads will enter this function and dynamically grab work
+ compute_averages(*ctxo, ctx->avg_preprocess_args);
+ }
+
+ // Wait for compute_averages to complete before compressing
+ ctxo->manage_avg.wait();
+
+ compress_image(*ctxo, thread_index, image, *swizzle, data_out);
+
+ // Wait for compress to complete before freeing memory
+ ctxo->manage_compress.wait();
+
+ auto term_compress = [ctx]() {
+ delete[] ctx->input_alpha_averages;
+ ctx->input_alpha_averages = nullptr;
+ };
+
+ // Only the first thread to arrive actually runs the term
+ ctxo->manage_compress.term(term_compress);
+
+ return ASTCENC_SUCCESS;
+#endif
+}
+
+/* See header for documentation. */
+astcenc_error astcenc_compress_reset(
+ astcenc_context* ctxo
+) {
+#if defined(ASTCENC_DECOMPRESS_ONLY)
+ (void)ctxo;
+ return ASTCENC_ERR_BAD_CONTEXT;
+#else
+ astcenc_contexti* ctx = &ctxo->context;
+ if (ctx->config.flags & ASTCENC_FLG_DECOMPRESS_ONLY)
+ {
+ return ASTCENC_ERR_BAD_CONTEXT;
+ }
+
+ ctxo->manage_avg.reset();
+ ctxo->manage_compress.reset();
+ return ASTCENC_SUCCESS;
+#endif
+}
+
+/* See header for documentation. */
+astcenc_error astcenc_decompress_image(
+ astcenc_context* ctxo,
+ const uint8_t* data,
+ size_t data_len,
+ astcenc_image* image_outp,
+ const astcenc_swizzle* swizzle,
+ unsigned int thread_index
+) {
+ astcenc_error status;
+ astcenc_image& image_out = *image_outp;
+ astcenc_contexti* ctx = &ctxo->context;
+
+ // Today this doesn't matter (working set on stack) but might in future ...
+ if (thread_index >= ctx->thread_count)
+ {
+ return ASTCENC_ERR_BAD_PARAM;
+ }
+
+ status = validate_decompression_swizzle(*swizzle);
+ if (status != ASTCENC_SUCCESS)
+ {
+ return status;
+ }
+
+ unsigned int block_x = ctx->config.block_x;
+ unsigned int block_y = ctx->config.block_y;
+ unsigned int block_z = ctx->config.block_z;
+
+ unsigned int xblocks = (image_out.dim_x + block_x - 1) / block_x;
+ unsigned int yblocks = (image_out.dim_y + block_y - 1) / block_y;
+ unsigned int zblocks = (image_out.dim_z + block_z - 1) / block_z;
+
+ int row_blocks = xblocks;
+ int plane_blocks = xblocks * yblocks;
+
+ // Check we have enough output space (16 bytes per block)
+ size_t size_needed = xblocks * yblocks * zblocks * 16;
+ if (data_len < size_needed)
+ {
+ return ASTCENC_ERR_OUT_OF_MEM;
+ }
+
+ image_block blk;
+ blk.texel_count = static_cast<uint8_t>(block_x * block_y * block_z);
+
+ // If context thread count is one then implicitly reset
+ if (ctx->thread_count == 1)
+ {
+ astcenc_decompress_reset(ctxo);
+ }
+
+ // Only the first thread actually runs the initializer
+ ctxo->manage_decompress.init(zblocks * yblocks * xblocks);
+
+ // All threads run this processing loop until there is no work remaining
+ while (true)
+ {
+ unsigned int count;
+ unsigned int base = ctxo->manage_decompress.get_task_assignment(128, count);
+ if (!count)
+ {
+ break;
+ }
+
+ for (unsigned int i = base; i < base + count; i++)
+ {
+ // Decode i into x, y, z block indices
+ int z = i / plane_blocks;
+ unsigned int rem = i - (z * plane_blocks);
+ int y = rem / row_blocks;
+ int x = rem - (y * row_blocks);
+
+ unsigned int offset = (((z * yblocks + y) * xblocks) + x) * 16;
+ const uint8_t* bp = data + offset;
+
+ const physical_compressed_block& pcb = *reinterpret_cast<const physical_compressed_block*>(bp);
+ symbolic_compressed_block scb;
+
+ physical_to_symbolic(*ctx->bsd, pcb, scb);
+
+ decompress_symbolic_block(ctx->config.profile, *ctx->bsd,
+ x * block_x, y * block_y, z * block_z,
+ scb, blk);
+
+ store_image_block(image_out, blk, *ctx->bsd,
+ x * block_x, y * block_y, z * block_z, *swizzle);
+ }
+
+ ctxo->manage_decompress.complete_task_assignment(count);
+ }
+
+ return ASTCENC_SUCCESS;
+}
+
+/* See header for documentation. */
+astcenc_error astcenc_decompress_reset(
+ astcenc_context* ctxo
+) {
+ ctxo->manage_decompress.reset();
+ return ASTCENC_SUCCESS;
+}
+
+/* See header for documentation. */
+astcenc_error astcenc_get_block_info(
+ astcenc_context* ctxo,
+ const uint8_t data[16],
+ astcenc_block_info* info
+) {
+#if defined(ASTCENC_DECOMPRESS_ONLY)
+ (void)ctxo;
+ (void)data;
+ (void)info;
+ return ASTCENC_ERR_BAD_CONTEXT;
+#else
+ astcenc_contexti* ctx = &ctxo->context;
+
+ // Decode the compressed data into a symbolic form
+ const physical_compressed_block&pcb = *reinterpret_cast<const physical_compressed_block*>(data);
+ symbolic_compressed_block scb;
+ physical_to_symbolic(*ctx->bsd, pcb, scb);
+
+ // Fetch the appropriate partition and decimation tables
+ block_size_descriptor& bsd = *ctx->bsd;
+
+ // Start from a clean slate
+ memset(info, 0, sizeof(*info));
+
+ // Basic info we can always populate
+ info->profile = ctx->config.profile;
+
+ info->block_x = ctx->config.block_x;
+ info->block_y = ctx->config.block_y;
+ info->block_z = ctx->config.block_z;
+ info->texel_count = bsd.texel_count;
+
+ // Check for error blocks first
+ info->is_error_block = scb.block_type == SYM_BTYPE_ERROR;
+ if (info->is_error_block)
+ {
+ return ASTCENC_SUCCESS;
+ }
+
+ // Check for constant color blocks second
+ info->is_constant_block = scb.block_type == SYM_BTYPE_CONST_F16 ||
+ scb.block_type == SYM_BTYPE_CONST_U16;
+ if (info->is_constant_block)
+ {
+ return ASTCENC_SUCCESS;
+ }
+
+ // Otherwise handle a full block ; known to be valid after conditions above have been checked
+ int partition_count = scb.partition_count;
+ const auto& pi = bsd.get_partition_info(partition_count, scb.partition_index);
+
+ const block_mode& bm = bsd.get_block_mode(scb.block_mode);
+ const decimation_info& di = bsd.get_decimation_info(bm.decimation_mode);
+
+ info->weight_x = di.weight_x;
+ info->weight_y = di.weight_y;
+ info->weight_z = di.weight_z;
+
+ info->is_dual_plane_block = bm.is_dual_plane != 0;
+
+ info->partition_count = scb.partition_count;
+ info->partition_index = scb.partition_index;
+ info->dual_plane_component = scb.plane2_component;
+
+ info->color_level_count = get_quant_level(scb.get_color_quant_mode());
+ info->weight_level_count = get_quant_level(bm.get_weight_quant_mode());
+
+ // Unpack color endpoints for each active partition
+ for (unsigned int i = 0; i < scb.partition_count; i++)
+ {
+ bool rgb_hdr;
+ bool a_hdr;
+ vint4 endpnt[2];
+
+ unpack_color_endpoints(ctx->config.profile,
+ scb.color_formats[i],
+ scb.color_values[i],
+ rgb_hdr, a_hdr,
+ endpnt[0], endpnt[1]);
+
+ // Store the color endpoint mode info
+ info->color_endpoint_modes[i] = scb.color_formats[i];
+ info->is_hdr_block = info->is_hdr_block || rgb_hdr || a_hdr;
+
+ // Store the unpacked and decoded color endpoint
+ vmask4 hdr_mask(rgb_hdr, rgb_hdr, rgb_hdr, a_hdr);
+ for (int j = 0; j < 2; j++)
+ {
+ vint4 color_lns = lns_to_sf16(endpnt[j]);
+ vint4 color_unorm = unorm16_to_sf16(endpnt[j]);
+ vint4 datai = select(color_unorm, color_lns, hdr_mask);
+ store(float16_to_float(datai), info->color_endpoints[i][j]);
+ }
+ }
+
+ // Unpack weights for each texel
+ int weight_plane1[BLOCK_MAX_TEXELS];
+ int weight_plane2[BLOCK_MAX_TEXELS];
+
+ unpack_weights(bsd, scb, di, bm.is_dual_plane, weight_plane1, weight_plane2);
+ for (unsigned int i = 0; i < bsd.texel_count; i++)
+ {
+ info->weight_values_plane1[i] = static_cast<float>(weight_plane1[i]) * (1.0f / WEIGHTS_TEXEL_SUM);
+ if (info->is_dual_plane_block)
+ {
+ info->weight_values_plane2[i] = static_cast<float>(weight_plane2[i]) * (1.0f / WEIGHTS_TEXEL_SUM);
+ }
+ }
+
+ // Unpack partition assignments for each texel
+ for (unsigned int i = 0; i < bsd.texel_count; i++)
+ {
+ info->partition_assignment[i] = pi.partition_of_texel[i];
+ }
+
+ return ASTCENC_SUCCESS;
+#endif
+}
+
+/* See header for documentation. */
+const char* astcenc_get_error_string(
+ astcenc_error status
+) {
+ // Values in this enum are from an external user, so not guaranteed to be
+ // bounded to the enum values
+ switch (static_cast<int>(status))
+ {
+ case ASTCENC_SUCCESS:
+ return "ASTCENC_SUCCESS";
+ case ASTCENC_ERR_OUT_OF_MEM:
+ return "ASTCENC_ERR_OUT_OF_MEM";
+ case ASTCENC_ERR_BAD_CPU_FLOAT:
+ return "ASTCENC_ERR_BAD_CPU_FLOAT";
+ case ASTCENC_ERR_BAD_CPU_ISA:
+ return "ASTCENC_ERR_BAD_CPU_ISA";
+ case ASTCENC_ERR_BAD_PARAM:
+ return "ASTCENC_ERR_BAD_PARAM";
+ case ASTCENC_ERR_BAD_BLOCK_SIZE:
+ return "ASTCENC_ERR_BAD_BLOCK_SIZE";
+ case ASTCENC_ERR_BAD_PROFILE:
+ return "ASTCENC_ERR_BAD_PROFILE";
+ case ASTCENC_ERR_BAD_QUALITY:
+ return "ASTCENC_ERR_BAD_QUALITY";
+ case ASTCENC_ERR_BAD_FLAGS:
+ return "ASTCENC_ERR_BAD_FLAGS";
+ case ASTCENC_ERR_BAD_SWIZZLE:
+ return "ASTCENC_ERR_BAD_SWIZZLE";
+ case ASTCENC_ERR_BAD_CONTEXT:
+ return "ASTCENC_ERR_BAD_CONTEXT";
+ case ASTCENC_ERR_NOT_IMPLEMENTED:
+ return "ASTCENC_ERR_NOT_IMPLEMENTED";
+#if defined(ASTCENC_DIAGNOSTICS)
+ case ASTCENC_ERR_DTRACE_FAILURE:
+ return "ASTCENC_ERR_DTRACE_FAILURE";
+#endif
+ default:
+ return nullptr;
+ }
+}
diff --git a/thirdparty/astcenc/astcenc_find_best_partitioning.cpp b/thirdparty/astcenc/astcenc_find_best_partitioning.cpp
new file mode 100644
index 0000000000..ffde3c7060
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_find_best_partitioning.cpp
@@ -0,0 +1,780 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2023 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+
+/**
+ * @brief Functions for finding best partition for a block.
+ *
+ * The partition search operates in two stages. The first pass uses kmeans clustering to group
+ * texels into an ideal partitioning for the requested partition count, and then compares that
+ * against the 1024 partitionings generated by the ASTC partition hash function. The generated
+ * partitions are then ranked by the number of texels in the wrong partition, compared to the ideal
+ * clustering. All 1024 partitions are tested for similarity and ranked, apart from duplicates and
+ * partitionings that actually generate fewer than the requested partition count, but only the top
+ * N candidates are actually put through a more detailed search. N is determined by the compressor
+ * quality preset.
+ *
+ * For the detailed search, each candidate is checked against two possible encoding methods:
+ *
+ * - The best partitioning assuming different chroma colors (RGB + RGB or RGB + delta endpoints).
+ * - The best partitioning assuming same chroma colors (RGB + scale endpoints).
+ *
+ * This is implemented by computing the compute mean color and dominant direction for each
+ * partition. This defines two lines, both of which go through the mean color value.
+ *
+ * - One line has a direction defined by the dominant direction; this is used to assess the error
+ * from using an uncorrelated color representation.
+ * - The other line goes through (0,0,0,1) and is used to assess the error from using a same chroma
+ * (RGB + scale) color representation.
+ *
+ * The best candidate is selected by computing the squared-errors that result from using these
+ * lines for endpoint selection.
+ */
+
+#include <limits>
+#include "astcenc_internal.h"
+
+/**
+ * @brief Pick some initial kmeans cluster centers.
+ *
+ * @param blk The image block color data to compress.
+ * @param texel_count The number of texels in the block.
+ * @param partition_count The number of partitions in the block.
+ * @param[out] cluster_centers The initial partition cluster center colors.
+ */
+static void kmeans_init(
+ const image_block& blk,
+ unsigned int texel_count,
+ unsigned int partition_count,
+ vfloat4 cluster_centers[BLOCK_MAX_PARTITIONS]
+) {
+ promise(texel_count > 0);
+ promise(partition_count > 0);
+
+ unsigned int clusters_selected = 0;
+ float distances[BLOCK_MAX_TEXELS];
+
+ // Pick a random sample as first cluster center; 145897 from random.org
+ unsigned int sample = 145897 % texel_count;
+ vfloat4 center_color = blk.texel(sample);
+ cluster_centers[clusters_selected] = center_color;
+ clusters_selected++;
+
+ // Compute the distance to the first cluster center
+ float distance_sum = 0.0f;
+ for (unsigned int i = 0; i < texel_count; i++)
+ {
+ vfloat4 color = blk.texel(i);
+ vfloat4 diff = color - center_color;
+ float distance = dot_s(diff * diff, blk.channel_weight);
+ distance_sum += distance;
+ distances[i] = distance;
+ }
+
+ // More numbers from random.org for weighted-random center selection
+ const float cluster_cutoffs[9] {
+ 0.626220f, 0.932770f, 0.275454f,
+ 0.318558f, 0.240113f, 0.009190f,
+ 0.347661f, 0.731960f, 0.156391f
+ };
+
+ unsigned int cutoff = (clusters_selected - 1) + 3 * (partition_count - 2);
+
+ // Pick the remaining samples as needed
+ while (true)
+ {
+ // Pick the next center in a weighted-random fashion.
+ float summa = 0.0f;
+ float distance_cutoff = distance_sum * cluster_cutoffs[cutoff++];
+ for (sample = 0; sample < texel_count; sample++)
+ {
+ summa += distances[sample];
+ if (summa >= distance_cutoff)
+ {
+ break;
+ }
+ }
+
+ // Clamp to a valid range and store the selected cluster center
+ sample = astc::min(sample, texel_count - 1);
+
+ center_color = blk.texel(sample);
+ cluster_centers[clusters_selected++] = center_color;
+ if (clusters_selected >= partition_count)
+ {
+ break;
+ }
+
+ // Compute the distance to the new cluster center, keep the min dist
+ distance_sum = 0.0f;
+ for (unsigned int i = 0; i < texel_count; i++)
+ {
+ vfloat4 color = blk.texel(i);
+ vfloat4 diff = color - center_color;
+ float distance = dot_s(diff * diff, blk.channel_weight);
+ distance = astc::min(distance, distances[i]);
+ distance_sum += distance;
+ distances[i] = distance;
+ }
+ }
+}
+
+/**
+ * @brief Assign texels to clusters, based on a set of chosen center points.
+ *
+ * @param blk The image block color data to compress.
+ * @param texel_count The number of texels in the block.
+ * @param partition_count The number of partitions in the block.
+ * @param cluster_centers The partition cluster center colors.
+ * @param[out] partition_of_texel The partition assigned for each texel.
+ */
+static void kmeans_assign(
+ const image_block& blk,
+ unsigned int texel_count,
+ unsigned int partition_count,
+ const vfloat4 cluster_centers[BLOCK_MAX_PARTITIONS],
+ uint8_t partition_of_texel[BLOCK_MAX_TEXELS]
+) {
+ promise(texel_count > 0);
+ promise(partition_count > 0);
+
+ uint8_t partition_texel_count[BLOCK_MAX_PARTITIONS] { 0 };
+
+ // Find the best partition for every texel
+ for (unsigned int i = 0; i < texel_count; i++)
+ {
+ float best_distance = std::numeric_limits<float>::max();
+ unsigned int best_partition = 0;
+
+ vfloat4 color = blk.texel(i);
+ for (unsigned int j = 0; j < partition_count; j++)
+ {
+ vfloat4 diff = color - cluster_centers[j];
+ float distance = dot_s(diff * diff, blk.channel_weight);
+ if (distance < best_distance)
+ {
+ best_distance = distance;
+ best_partition = j;
+ }
+ }
+
+ partition_of_texel[i] = static_cast<uint8_t>(best_partition);
+ partition_texel_count[best_partition]++;
+ }
+
+ // It is possible to get a situation where a partition ends up without any texels. In this case,
+ // assign texel N to partition N. This is silly, but ensures that every partition retains at
+ // least one texel. Reassigning a texel in this manner may cause another partition to go empty,
+ // so if we actually did a reassignment, run the whole loop over again.
+ bool problem_case;
+ do
+ {
+ problem_case = false;
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ if (partition_texel_count[i] == 0)
+ {
+ partition_texel_count[partition_of_texel[i]]--;
+ partition_texel_count[i]++;
+ partition_of_texel[i] = static_cast<uint8_t>(i);
+ problem_case = true;
+ }
+ }
+ } while (problem_case);
+}
+
+/**
+ * @brief Compute new cluster centers based on their center of gravity.
+ *
+ * @param blk The image block color data to compress.
+ * @param texel_count The number of texels in the block.
+ * @param partition_count The number of partitions in the block.
+ * @param[out] cluster_centers The new cluster center colors.
+ * @param partition_of_texel The partition assigned for each texel.
+ */
+static void kmeans_update(
+ const image_block& blk,
+ unsigned int texel_count,
+ unsigned int partition_count,
+ vfloat4 cluster_centers[BLOCK_MAX_PARTITIONS],
+ const uint8_t partition_of_texel[BLOCK_MAX_TEXELS]
+) {
+ promise(texel_count > 0);
+ promise(partition_count > 0);
+
+ vfloat4 color_sum[BLOCK_MAX_PARTITIONS] {
+ vfloat4::zero(),
+ vfloat4::zero(),
+ vfloat4::zero(),
+ vfloat4::zero()
+ };
+
+ uint8_t partition_texel_count[BLOCK_MAX_PARTITIONS] { 0 };
+
+ // Find the center-of-gravity in each cluster
+ for (unsigned int i = 0; i < texel_count; i++)
+ {
+ uint8_t partition = partition_of_texel[i];
+ color_sum[partition] += blk.texel(i);
+ partition_texel_count[partition]++;
+ }
+
+ // Set the center of gravity to be the new cluster center
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ float scale = 1.0f / static_cast<float>(partition_texel_count[i]);
+ cluster_centers[i] = color_sum[i] * scale;
+ }
+}
+
+/**
+ * @brief Compute bit-mismatch for partitioning in 2-partition mode.
+ *
+ * @param a The texel assignment bitvector for the block.
+ * @param b The texel assignment bitvector for the partition table.
+ *
+ * @return The number of bit mismatches.
+ */
+static inline unsigned int partition_mismatch2(
+ const uint64_t a[2],
+ const uint64_t b[2]
+) {
+ int v1 = popcount(a[0] ^ b[0]) + popcount(a[1] ^ b[1]);
+ int v2 = popcount(a[0] ^ b[1]) + popcount(a[1] ^ b[0]);
+ return astc::min(v1, v2);
+}
+
+/**
+ * @brief Compute bit-mismatch for partitioning in 3-partition mode.
+ *
+ * @param a The texel assignment bitvector for the block.
+ * @param b The texel assignment bitvector for the partition table.
+ *
+ * @return The number of bit mismatches.
+ */
+static inline unsigned int partition_mismatch3(
+ const uint64_t a[3],
+ const uint64_t b[3]
+) {
+ int p00 = popcount(a[0] ^ b[0]);
+ int p01 = popcount(a[0] ^ b[1]);
+ int p02 = popcount(a[0] ^ b[2]);
+
+ int p10 = popcount(a[1] ^ b[0]);
+ int p11 = popcount(a[1] ^ b[1]);
+ int p12 = popcount(a[1] ^ b[2]);
+
+ int p20 = popcount(a[2] ^ b[0]);
+ int p21 = popcount(a[2] ^ b[1]);
+ int p22 = popcount(a[2] ^ b[2]);
+
+ int s0 = p11 + p22;
+ int s1 = p12 + p21;
+ int v0 = astc::min(s0, s1) + p00;
+
+ int s2 = p10 + p22;
+ int s3 = p12 + p20;
+ int v1 = astc::min(s2, s3) + p01;
+
+ int s4 = p10 + p21;
+ int s5 = p11 + p20;
+ int v2 = astc::min(s4, s5) + p02;
+
+ return astc::min(v0, v1, v2);
+}
+
+/**
+ * @brief Compute bit-mismatch for partitioning in 4-partition mode.
+ *
+ * @param a The texel assignment bitvector for the block.
+ * @param b The texel assignment bitvector for the partition table.
+ *
+ * @return The number of bit mismatches.
+ */
+static inline unsigned int partition_mismatch4(
+ const uint64_t a[4],
+ const uint64_t b[4]
+) {
+ int p00 = popcount(a[0] ^ b[0]);
+ int p01 = popcount(a[0] ^ b[1]);
+ int p02 = popcount(a[0] ^ b[2]);
+ int p03 = popcount(a[0] ^ b[3]);
+
+ int p10 = popcount(a[1] ^ b[0]);
+ int p11 = popcount(a[1] ^ b[1]);
+ int p12 = popcount(a[1] ^ b[2]);
+ int p13 = popcount(a[1] ^ b[3]);
+
+ int p20 = popcount(a[2] ^ b[0]);
+ int p21 = popcount(a[2] ^ b[1]);
+ int p22 = popcount(a[2] ^ b[2]);
+ int p23 = popcount(a[2] ^ b[3]);
+
+ int p30 = popcount(a[3] ^ b[0]);
+ int p31 = popcount(a[3] ^ b[1]);
+ int p32 = popcount(a[3] ^ b[2]);
+ int p33 = popcount(a[3] ^ b[3]);
+
+ int mx23 = astc::min(p22 + p33, p23 + p32);
+ int mx13 = astc::min(p21 + p33, p23 + p31);
+ int mx12 = astc::min(p21 + p32, p22 + p31);
+ int mx03 = astc::min(p20 + p33, p23 + p30);
+ int mx02 = astc::min(p20 + p32, p22 + p30);
+ int mx01 = astc::min(p21 + p30, p20 + p31);
+
+ int v0 = p00 + astc::min(p11 + mx23, p12 + mx13, p13 + mx12);
+ int v1 = p01 + astc::min(p10 + mx23, p12 + mx03, p13 + mx02);
+ int v2 = p02 + astc::min(p11 + mx03, p10 + mx13, p13 + mx01);
+ int v3 = p03 + astc::min(p11 + mx02, p12 + mx01, p10 + mx12);
+
+ return astc::min(v0, v1, v2, v3);
+}
+
+using mismatch_dispatch = unsigned int (*)(const uint64_t*, const uint64_t*);
+
+/**
+ * @brief Count the partition table mismatches vs the data clustering.
+ *
+ * @param bsd The block size information.
+ * @param partition_count The number of partitions in the block.
+ * @param bitmaps The block texel partition assignment patterns.
+ * @param[out] mismatch_counts The array storing per partitioning mismatch counts.
+ */
+static void count_partition_mismatch_bits(
+ const block_size_descriptor& bsd,
+ unsigned int partition_count,
+ const uint64_t bitmaps[BLOCK_MAX_PARTITIONS],
+ unsigned int mismatch_counts[BLOCK_MAX_PARTITIONINGS]
+) {
+ unsigned int active_count = bsd.partitioning_count_selected[partition_count - 1];
+ promise(active_count > 0);
+
+ if (partition_count == 2)
+ {
+ for (unsigned int i = 0; i < active_count; i++)
+ {
+ mismatch_counts[i] = partition_mismatch2(bitmaps, bsd.coverage_bitmaps_2[i]);
+ }
+ }
+ else if (partition_count == 3)
+ {
+ for (unsigned int i = 0; i < active_count; i++)
+ {
+ mismatch_counts[i] = partition_mismatch3(bitmaps, bsd.coverage_bitmaps_3[i]);
+ }
+ }
+ else
+ {
+ for (unsigned int i = 0; i < active_count; i++)
+ {
+ mismatch_counts[i] = partition_mismatch4(bitmaps, bsd.coverage_bitmaps_4[i]);
+ }
+ }
+}
+
+/**
+ * @brief Use counting sort on the mismatch array to sort partition candidates.
+ *
+ * @param partitioning_count The number of packed partitionings.
+ * @param mismatch_count Partitioning mismatch counts, in index order.
+ * @param[out] partition_ordering Partition index values, in mismatch order.
+ *
+ * @return The number of active partitions in this selection.
+ */
+static unsigned int get_partition_ordering_by_mismatch_bits(
+ unsigned int partitioning_count,
+ const unsigned int mismatch_count[BLOCK_MAX_PARTITIONINGS],
+ unsigned int partition_ordering[BLOCK_MAX_PARTITIONINGS]
+) {
+ promise(partitioning_count > 0);
+ unsigned int mscount[256] { 0 };
+
+ // Create the histogram of mismatch counts
+ for (unsigned int i = 0; i < partitioning_count; i++)
+ {
+ mscount[mismatch_count[i]]++;
+ }
+
+ unsigned int active_count = partitioning_count - mscount[255];
+
+ // Create a running sum from the histogram array
+ // Cells store previous values only; i.e. exclude self after sum
+ unsigned int summa = 0;
+ for (unsigned int i = 0; i < 256; i++)
+ {
+ unsigned int cnt = mscount[i];
+ mscount[i] = summa;
+ summa += cnt;
+ }
+
+ // Use the running sum as the index, incrementing after read to allow
+ // sequential entries with the same count
+ for (unsigned int i = 0; i < partitioning_count; i++)
+ {
+ unsigned int idx = mscount[mismatch_count[i]]++;
+ partition_ordering[idx] = i;
+ }
+
+ return active_count;
+}
+
+/**
+ * @brief Use k-means clustering to compute a partition ordering for a block..
+ *
+ * @param bsd The block size information.
+ * @param blk The image block color data to compress.
+ * @param partition_count The desired number of partitions in the block.
+ * @param[out] partition_ordering The list of recommended partition indices, in priority order.
+ *
+ * @return The number of active partitionings in this selection.
+ */
+static unsigned int compute_kmeans_partition_ordering(
+ const block_size_descriptor& bsd,
+ const image_block& blk,
+ unsigned int partition_count,
+ unsigned int partition_ordering[BLOCK_MAX_PARTITIONINGS]
+) {
+ vfloat4 cluster_centers[BLOCK_MAX_PARTITIONS];
+ uint8_t texel_partitions[BLOCK_MAX_TEXELS];
+
+ // Use three passes of k-means clustering to partition the block data
+ for (unsigned int i = 0; i < 3; i++)
+ {
+ if (i == 0)
+ {
+ kmeans_init(blk, bsd.texel_count, partition_count, cluster_centers);
+ }
+ else
+ {
+ kmeans_update(blk, bsd.texel_count, partition_count, cluster_centers, texel_partitions);
+ }
+
+ kmeans_assign(blk, bsd.texel_count, partition_count, cluster_centers, texel_partitions);
+ }
+
+ // Construct the block bitmaps of texel assignments to each partition
+ uint64_t bitmaps[BLOCK_MAX_PARTITIONS] { 0 };
+ unsigned int texels_to_process = astc::min(bsd.texel_count, BLOCK_MAX_KMEANS_TEXELS);
+ promise(texels_to_process > 0);
+ for (unsigned int i = 0; i < texels_to_process; i++)
+ {
+ unsigned int idx = bsd.kmeans_texels[i];
+ bitmaps[texel_partitions[idx]] |= 1ULL << i;
+ }
+
+ // Count the mismatch between the block and the format's partition tables
+ unsigned int mismatch_counts[BLOCK_MAX_PARTITIONINGS];
+ count_partition_mismatch_bits(bsd, partition_count, bitmaps, mismatch_counts);
+
+ // Sort the partitions based on the number of mismatched bits
+ return get_partition_ordering_by_mismatch_bits(
+ bsd.partitioning_count_selected[partition_count - 1],
+ mismatch_counts, partition_ordering);
+}
+
+/**
+ * @brief Insert a partitioning into an order list of results, sorted by error.
+ *
+ * @param max_values The max number of entries in the best result arrays.
+ * @param this_error The error of the new entry.
+ * @param this_partition The partition ID of the new entry.
+ * @param[out] best_errors The array of best error values.
+ * @param[out] best_partitions The array of best partition values.
+ */
+static void insert_result(
+ unsigned int max_values,
+ float this_error,
+ unsigned int this_partition,
+ float* best_errors,
+ unsigned int* best_partitions)
+{
+ promise(max_values > 0);
+
+ // Don't bother searching if the current worst error beats the new error
+ if (this_error >= best_errors[max_values - 1])
+ {
+ return;
+ }
+
+ // Else insert into the list in error-order
+ for (unsigned int i = 0; i < max_values; i++)
+ {
+ // Existing result is better - move on ...
+ if (this_error > best_errors[i])
+ {
+ continue;
+ }
+
+ // Move existing results down one
+ for (unsigned int j = max_values - 1; j > i; j--)
+ {
+ best_errors[j] = best_errors[j - 1];
+ best_partitions[j] = best_partitions[j - 1];
+ }
+
+ // Insert new result
+ best_errors[i] = this_error;
+ best_partitions[i] = this_partition;
+ break;
+ }
+}
+
+/* See header for documentation. */
+unsigned int find_best_partition_candidates(
+ const block_size_descriptor& bsd,
+ const image_block& blk,
+ unsigned int partition_count,
+ unsigned int partition_search_limit,
+ unsigned int best_partitions[TUNE_MAX_PARTITIONING_CANDIDATES],
+ unsigned int requested_candidates
+) {
+ // Constant used to estimate quantization error for a given partitioning; the optimal value for
+ // this depends on bitrate. These values have been determined empirically.
+ unsigned int texels_per_block = bsd.texel_count;
+ float weight_imprecision_estim = 0.055f;
+ if (texels_per_block <= 20)
+ {
+ weight_imprecision_estim = 0.03f;
+ }
+ else if (texels_per_block <= 31)
+ {
+ weight_imprecision_estim = 0.04f;
+ }
+ else if (texels_per_block <= 41)
+ {
+ weight_imprecision_estim = 0.05f;
+ }
+
+ promise(partition_count > 0);
+ promise(partition_search_limit > 0);
+
+ weight_imprecision_estim = weight_imprecision_estim * weight_imprecision_estim;
+
+ unsigned int partition_sequence[BLOCK_MAX_PARTITIONINGS];
+ unsigned int sequence_len = compute_kmeans_partition_ordering(bsd, blk, partition_count, partition_sequence);
+ partition_search_limit = astc::min(partition_search_limit, sequence_len);
+ requested_candidates = astc::min(partition_search_limit, requested_candidates);
+
+ bool uses_alpha = !blk.is_constant_channel(3);
+
+ // Partitioning errors assuming uncorrelated-chrominance endpoints
+ float uncor_best_errors[TUNE_MAX_PARTITIONING_CANDIDATES];
+ unsigned int uncor_best_partitions[TUNE_MAX_PARTITIONING_CANDIDATES];
+
+ // Partitioning errors assuming same-chrominance endpoints
+ float samec_best_errors[TUNE_MAX_PARTITIONING_CANDIDATES];
+ unsigned int samec_best_partitions[TUNE_MAX_PARTITIONING_CANDIDATES];
+
+ for (unsigned int i = 0; i < requested_candidates; i++)
+ {
+ uncor_best_errors[i] = ERROR_CALC_DEFAULT;
+ samec_best_errors[i] = ERROR_CALC_DEFAULT;
+ }
+
+ if (uses_alpha)
+ {
+ for (unsigned int i = 0; i < partition_search_limit; i++)
+ {
+ unsigned int partition = partition_sequence[i];
+ const auto& pi = bsd.get_raw_partition_info(partition_count, partition);
+
+ // Compute weighting to give to each component in each partition
+ partition_metrics pms[BLOCK_MAX_PARTITIONS];
+
+ compute_avgs_and_dirs_4_comp(pi, blk, pms);
+
+ line4 uncor_lines[BLOCK_MAX_PARTITIONS];
+ line4 samec_lines[BLOCK_MAX_PARTITIONS];
+
+ processed_line4 uncor_plines[BLOCK_MAX_PARTITIONS];
+ processed_line4 samec_plines[BLOCK_MAX_PARTITIONS];
+
+ float uncor_line_lens[BLOCK_MAX_PARTITIONS];
+ float samec_line_lens[BLOCK_MAX_PARTITIONS];
+
+ for (unsigned int j = 0; j < partition_count; j++)
+ {
+ partition_metrics& pm = pms[j];
+
+ uncor_lines[j].a = pm.avg;
+ uncor_lines[j].b = normalize_safe(pm.dir, unit4());
+
+ uncor_plines[j].amod = uncor_lines[j].a - uncor_lines[j].b * dot(uncor_lines[j].a, uncor_lines[j].b);
+ uncor_plines[j].bs = uncor_lines[j].b;
+
+ samec_lines[j].a = vfloat4::zero();
+ samec_lines[j].b = normalize_safe(pm.avg, unit4());
+
+ samec_plines[j].amod = vfloat4::zero();
+ samec_plines[j].bs = samec_lines[j].b;
+ }
+
+ float uncor_error = 0.0f;
+ float samec_error = 0.0f;
+
+ compute_error_squared_rgba(pi,
+ blk,
+ uncor_plines,
+ samec_plines,
+ uncor_line_lens,
+ samec_line_lens,
+ uncor_error,
+ samec_error);
+
+ // Compute an estimate of error introduced by weight quantization imprecision.
+ // This error is computed as follows, for each partition
+ // 1: compute the principal-axis vector (full length) in error-space
+ // 2: convert the principal-axis vector to regular RGB-space
+ // 3: scale the vector by a constant that estimates average quantization error
+ // 4: for each texel, square the vector, then do a dot-product with the texel's
+ // error weight; sum up the results across all texels.
+ // 4(optimized): square the vector once, then do a dot-product with the average
+ // texel error, then multiply by the number of texels.
+
+ for (unsigned int j = 0; j < partition_count; j++)
+ {
+ float tpp = static_cast<float>(pi.partition_texel_count[j]);
+ vfloat4 error_weights(tpp * weight_imprecision_estim);
+
+ vfloat4 uncor_vector = uncor_lines[j].b * uncor_line_lens[j];
+ vfloat4 samec_vector = samec_lines[j].b * samec_line_lens[j];
+
+ uncor_error += dot_s(uncor_vector * uncor_vector, error_weights);
+ samec_error += dot_s(samec_vector * samec_vector, error_weights);
+ }
+
+ insert_result(requested_candidates, uncor_error, partition, uncor_best_errors, uncor_best_partitions);
+ insert_result(requested_candidates, samec_error, partition, samec_best_errors, samec_best_partitions);
+ }
+ }
+ else
+ {
+ for (unsigned int i = 0; i < partition_search_limit; i++)
+ {
+ unsigned int partition = partition_sequence[i];
+ const auto& pi = bsd.get_raw_partition_info(partition_count, partition);
+
+ // Compute weighting to give to each component in each partition
+ partition_metrics pms[BLOCK_MAX_PARTITIONS];
+ compute_avgs_and_dirs_3_comp_rgb(pi, blk, pms);
+
+ partition_lines3 plines[BLOCK_MAX_PARTITIONS];
+
+ for (unsigned int j = 0; j < partition_count; j++)
+ {
+ partition_metrics& pm = pms[j];
+ partition_lines3& pl = plines[j];
+
+ pl.uncor_line.a = pm.avg;
+ pl.uncor_line.b = normalize_safe(pm.dir, unit3());
+
+ pl.samec_line.a = vfloat4::zero();
+ pl.samec_line.b = normalize_safe(pm.avg, unit3());
+
+ pl.uncor_pline.amod = pl.uncor_line.a - pl.uncor_line.b * dot3(pl.uncor_line.a, pl.uncor_line.b);
+ pl.uncor_pline.bs = pl.uncor_line.b;
+
+ pl.samec_pline.amod = vfloat4::zero();
+ pl.samec_pline.bs = pl.samec_line.b;
+ }
+
+ float uncor_error = 0.0f;
+ float samec_error = 0.0f;
+
+ compute_error_squared_rgb(pi,
+ blk,
+ plines,
+ uncor_error,
+ samec_error);
+
+ // Compute an estimate of error introduced by weight quantization imprecision.
+ // This error is computed as follows, for each partition
+ // 1: compute the principal-axis vector (full length) in error-space
+ // 2: convert the principal-axis vector to regular RGB-space
+ // 3: scale the vector by a constant that estimates average quantization error
+ // 4: for each texel, square the vector, then do a dot-product with the texel's
+ // error weight; sum up the results across all texels.
+ // 4(optimized): square the vector once, then do a dot-product with the average
+ // texel error, then multiply by the number of texels.
+
+ for (unsigned int j = 0; j < partition_count; j++)
+ {
+ partition_lines3& pl = plines[j];
+
+ float tpp = static_cast<float>(pi.partition_texel_count[j]);
+ vfloat4 error_weights(tpp * weight_imprecision_estim);
+
+ vfloat4 uncor_vector = pl.uncor_line.b * pl.uncor_line_len;
+ vfloat4 samec_vector = pl.samec_line.b * pl.samec_line_len;
+
+ uncor_error += dot3_s(uncor_vector * uncor_vector, error_weights);
+ samec_error += dot3_s(samec_vector * samec_vector, error_weights);
+ }
+
+ insert_result(requested_candidates, uncor_error, partition, uncor_best_errors, uncor_best_partitions);
+ insert_result(requested_candidates, samec_error, partition, samec_best_errors, samec_best_partitions);
+ }
+ }
+
+ bool best_is_uncor = uncor_best_partitions[0] > samec_best_partitions[0];
+
+ unsigned int interleave[2 * TUNE_MAX_PARTITIONING_CANDIDATES];
+ for (unsigned int i = 0; i < requested_candidates; i++)
+ {
+ if (best_is_uncor)
+ {
+ interleave[2 * i] = bsd.get_raw_partition_info(partition_count, uncor_best_partitions[i]).partition_index;
+ interleave[2 * i + 1] = bsd.get_raw_partition_info(partition_count, samec_best_partitions[i]).partition_index;
+ }
+ else
+ {
+ interleave[2 * i] = bsd.get_raw_partition_info(partition_count, samec_best_partitions[i]).partition_index;
+ interleave[2 * i + 1] = bsd.get_raw_partition_info(partition_count, uncor_best_partitions[i]).partition_index;
+ }
+ }
+
+ uint64_t bitmasks[1024/64] { 0 };
+ unsigned int emitted = 0;
+
+ // Deduplicate the first "requested" entries
+ for (unsigned int i = 0; i < requested_candidates * 2; i++)
+ {
+ unsigned int partition = interleave[i];
+
+ unsigned int word = partition / 64;
+ unsigned int bit = partition % 64;
+
+ bool written = bitmasks[word] & (1ull << bit);
+
+ if (!written)
+ {
+ best_partitions[emitted] = partition;
+ bitmasks[word] |= 1ull << bit;
+ emitted++;
+
+ if (emitted == requested_candidates)
+ {
+ break;
+ }
+ }
+ }
+
+ return emitted;
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_ideal_endpoints_and_weights.cpp b/thirdparty/astcenc/astcenc_ideal_endpoints_and_weights.cpp
new file mode 100644
index 0000000000..5145e08693
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_ideal_endpoints_and_weights.cpp
@@ -0,0 +1,1663 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2023 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+
+/**
+ * @brief Functions for computing color endpoints and texel weights.
+ */
+
+#include <cassert>
+
+#include "astcenc_internal.h"
+#include "astcenc_vecmathlib.h"
+
+/**
+ * @brief Compute the infilled weight for N texel indices in a decimated grid.
+ *
+ * @param di The weight grid decimation to use.
+ * @param weights The decimated weight values to use.
+ * @param index The first texel index to interpolate.
+ *
+ * @return The interpolated weight for the given set of SIMD_WIDTH texels.
+ */
+static vfloat bilinear_infill_vla(
+ const decimation_info& di,
+ const float* weights,
+ unsigned int index
+) {
+ // Load the bilinear filter texel weight indexes in the decimated grid
+ vint weight_idx0 = vint(di.texel_weights_tr[0] + index);
+ vint weight_idx1 = vint(di.texel_weights_tr[1] + index);
+ vint weight_idx2 = vint(di.texel_weights_tr[2] + index);
+ vint weight_idx3 = vint(di.texel_weights_tr[3] + index);
+
+ // Load the bilinear filter weights from the decimated grid
+ vfloat weight_val0 = gatherf(weights, weight_idx0);
+ vfloat weight_val1 = gatherf(weights, weight_idx1);
+ vfloat weight_val2 = gatherf(weights, weight_idx2);
+ vfloat weight_val3 = gatherf(weights, weight_idx3);
+
+ // Load the weight contribution factors for each decimated weight
+ vfloat tex_weight_float0 = loada(di.texel_weight_contribs_float_tr[0] + index);
+ vfloat tex_weight_float1 = loada(di.texel_weight_contribs_float_tr[1] + index);
+ vfloat tex_weight_float2 = loada(di.texel_weight_contribs_float_tr[2] + index);
+ vfloat tex_weight_float3 = loada(di.texel_weight_contribs_float_tr[3] + index);
+
+ // Compute the bilinear interpolation to generate the per-texel weight
+ return (weight_val0 * tex_weight_float0 + weight_val1 * tex_weight_float1) +
+ (weight_val2 * tex_weight_float2 + weight_val3 * tex_weight_float3);
+}
+
+/**
+ * @brief Compute the infilled weight for N texel indices in a decimated grid.
+ *
+ * This is specialized version which computes only two weights per texel for
+ * encodings that are only decimated in a single axis.
+ *
+ * @param di The weight grid decimation to use.
+ * @param weights The decimated weight values to use.
+ * @param index The first texel index to interpolate.
+ *
+ * @return The interpolated weight for the given set of SIMD_WIDTH texels.
+ */
+static vfloat bilinear_infill_vla_2(
+ const decimation_info& di,
+ const float* weights,
+ unsigned int index
+) {
+ // Load the bilinear filter texel weight indexes in the decimated grid
+ vint weight_idx0 = vint(di.texel_weights_tr[0] + index);
+ vint weight_idx1 = vint(di.texel_weights_tr[1] + index);
+
+ // Load the bilinear filter weights from the decimated grid
+ vfloat weight_val0 = gatherf(weights, weight_idx0);
+ vfloat weight_val1 = gatherf(weights, weight_idx1);
+
+ // Load the weight contribution factors for each decimated weight
+ vfloat tex_weight_float0 = loada(di.texel_weight_contribs_float_tr[0] + index);
+ vfloat tex_weight_float1 = loada(di.texel_weight_contribs_float_tr[1] + index);
+
+ // Compute the bilinear interpolation to generate the per-texel weight
+ return (weight_val0 * tex_weight_float0 + weight_val1 * tex_weight_float1);
+}
+
+/**
+ * @brief Compute the ideal endpoints and weights for 1 color component.
+ *
+ * @param blk The image block color data to compress.
+ * @param pi The partition info for the current trial.
+ * @param[out] ei The computed ideal endpoints and weights.
+ * @param component The color component to compute.
+ */
+static void compute_ideal_colors_and_weights_1_comp(
+ const image_block& blk,
+ const partition_info& pi,
+ endpoints_and_weights& ei,
+ unsigned int component
+) {
+ unsigned int partition_count = pi.partition_count;
+ ei.ep.partition_count = partition_count;
+ promise(partition_count > 0);
+
+ unsigned int texel_count = blk.texel_count;
+ promise(texel_count > 0);
+
+ float error_weight;
+ const float* data_vr = nullptr;
+
+ assert(component < BLOCK_MAX_COMPONENTS);
+ switch (component)
+ {
+ case 0:
+ error_weight = blk.channel_weight.lane<0>();
+ data_vr = blk.data_r;
+ break;
+ case 1:
+ error_weight = blk.channel_weight.lane<1>();
+ data_vr = blk.data_g;
+ break;
+ case 2:
+ error_weight = blk.channel_weight.lane<2>();
+ data_vr = blk.data_b;
+ break;
+ default:
+ assert(component == 3);
+ error_weight = blk.channel_weight.lane<3>();
+ data_vr = blk.data_a;
+ break;
+ }
+
+ vmask4 sep_mask = vint4::lane_id() == vint4(component);
+ bool is_constant_wes { true };
+ float partition0_len_sq { 0.0f };
+
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ float lowvalue { 1e10f };
+ float highvalue { -1e10f };
+
+ unsigned int partition_texel_count = pi.partition_texel_count[i];
+ for (unsigned int j = 0; j < partition_texel_count; j++)
+ {
+ unsigned int tix = pi.texels_of_partition[i][j];
+ float value = data_vr[tix];
+ lowvalue = astc::min(value, lowvalue);
+ highvalue = astc::max(value, highvalue);
+ }
+
+ if (highvalue <= lowvalue)
+ {
+ lowvalue = 0.0f;
+ highvalue = 1e-7f;
+ }
+
+ float length = highvalue - lowvalue;
+ float length_squared = length * length;
+ float scale = 1.0f / length;
+
+ if (i == 0)
+ {
+ partition0_len_sq = length_squared;
+ }
+ else
+ {
+ is_constant_wes = is_constant_wes && length_squared == partition0_len_sq;
+ }
+
+ for (unsigned int j = 0; j < partition_texel_count; j++)
+ {
+ unsigned int tix = pi.texels_of_partition[i][j];
+ float value = (data_vr[tix] - lowvalue) * scale;
+ value = astc::clamp1f(value);
+
+ ei.weights[tix] = value;
+ ei.weight_error_scale[tix] = length_squared * error_weight;
+ assert(!astc::isnan(ei.weight_error_scale[tix]));
+ }
+
+ ei.ep.endpt0[i] = select(blk.data_min, vfloat4(lowvalue), sep_mask);
+ ei.ep.endpt1[i] = select(blk.data_max, vfloat4(highvalue), sep_mask);
+ }
+
+ // Zero initialize any SIMD over-fetch
+ unsigned int texel_count_simd = round_up_to_simd_multiple_vla(texel_count);
+ for (unsigned int i = texel_count; i < texel_count_simd; i++)
+ {
+ ei.weights[i] = 0.0f;
+ ei.weight_error_scale[i] = 0.0f;
+ }
+
+ ei.is_constant_weight_error_scale = is_constant_wes;
+}
+
+/**
+ * @brief Compute the ideal endpoints and weights for 2 color components.
+ *
+ * @param blk The image block color data to compress.
+ * @param pi The partition info for the current trial.
+ * @param[out] ei The computed ideal endpoints and weights.
+ * @param component1 The first color component to compute.
+ * @param component2 The second color component to compute.
+ */
+static void compute_ideal_colors_and_weights_2_comp(
+ const image_block& blk,
+ const partition_info& pi,
+ endpoints_and_weights& ei,
+ int component1,
+ int component2
+) {
+ unsigned int partition_count = pi.partition_count;
+ ei.ep.partition_count = partition_count;
+ promise(partition_count > 0);
+
+ unsigned int texel_count = blk.texel_count;
+ promise(texel_count > 0);
+
+ partition_metrics pms[BLOCK_MAX_PARTITIONS];
+
+ float error_weight;
+ const float* data_vr = nullptr;
+ const float* data_vg = nullptr;
+
+ if (component1 == 0 && component2 == 1)
+ {
+ error_weight = hadd_s(blk.channel_weight.swz<0, 1>()) / 2.0f;
+
+ data_vr = blk.data_r;
+ data_vg = blk.data_g;
+ }
+ else if (component1 == 0 && component2 == 2)
+ {
+ error_weight = hadd_s(blk.channel_weight.swz<0, 2>()) / 2.0f;
+
+ data_vr = blk.data_r;
+ data_vg = blk.data_b;
+ }
+ else // (component1 == 1 && component2 == 2)
+ {
+ assert(component1 == 1 && component2 == 2);
+
+ error_weight = hadd_s(blk.channel_weight.swz<1, 2>()) / 2.0f;
+
+ data_vr = blk.data_g;
+ data_vg = blk.data_b;
+ }
+
+ compute_avgs_and_dirs_2_comp(pi, blk, component1, component2, pms);
+
+ bool is_constant_wes { true };
+ float partition0_len_sq { 0.0f };
+
+ vmask4 comp1_mask = vint4::lane_id() == vint4(component1);
+ vmask4 comp2_mask = vint4::lane_id() == vint4(component2);
+
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ vfloat4 dir = pms[i].dir;
+ if (hadd_s(dir) < 0.0f)
+ {
+ dir = vfloat4::zero() - dir;
+ }
+
+ line2 line { pms[i].avg, normalize_safe(dir, unit2()) };
+ float lowparam { 1e10f };
+ float highparam { -1e10f };
+
+ unsigned int partition_texel_count = pi.partition_texel_count[i];
+ for (unsigned int j = 0; j < partition_texel_count; j++)
+ {
+ unsigned int tix = pi.texels_of_partition[i][j];
+ vfloat4 point = vfloat2(data_vr[tix], data_vg[tix]);
+ float param = dot_s(point - line.a, line.b);
+ ei.weights[tix] = param;
+
+ lowparam = astc::min(param, lowparam);
+ highparam = astc::max(param, highparam);
+ }
+
+ // It is possible for a uniform-color partition to produce length=0;
+ // this causes NaN issues so set to small value to avoid this problem
+ if (highparam <= lowparam)
+ {
+ lowparam = 0.0f;
+ highparam = 1e-7f;
+ }
+
+ float length = highparam - lowparam;
+ float length_squared = length * length;
+ float scale = 1.0f / length;
+
+ if (i == 0)
+ {
+ partition0_len_sq = length_squared;
+ }
+ else
+ {
+ is_constant_wes = is_constant_wes && length_squared == partition0_len_sq;
+ }
+
+ for (unsigned int j = 0; j < partition_texel_count; j++)
+ {
+ unsigned int tix = pi.texels_of_partition[i][j];
+ float idx = (ei.weights[tix] - lowparam) * scale;
+ idx = astc::clamp1f(idx);
+
+ ei.weights[tix] = idx;
+ ei.weight_error_scale[tix] = length_squared * error_weight;
+ assert(!astc::isnan(ei.weight_error_scale[tix]));
+ }
+
+ vfloat4 lowvalue = line.a + line.b * lowparam;
+ vfloat4 highvalue = line.a + line.b * highparam;
+
+ vfloat4 ep0 = select(blk.data_min, vfloat4(lowvalue.lane<0>()), comp1_mask);
+ vfloat4 ep1 = select(blk.data_max, vfloat4(highvalue.lane<0>()), comp1_mask);
+
+ ei.ep.endpt0[i] = select(ep0, vfloat4(lowvalue.lane<1>()), comp2_mask);
+ ei.ep.endpt1[i] = select(ep1, vfloat4(highvalue.lane<1>()), comp2_mask);
+ }
+
+ // Zero initialize any SIMD over-fetch
+ unsigned int texel_count_simd = round_up_to_simd_multiple_vla(texel_count);
+ for (unsigned int i = texel_count; i < texel_count_simd; i++)
+ {
+ ei.weights[i] = 0.0f;
+ ei.weight_error_scale[i] = 0.0f;
+ }
+
+ ei.is_constant_weight_error_scale = is_constant_wes;
+}
+
+/**
+ * @brief Compute the ideal endpoints and weights for 3 color components.
+ *
+ * @param blk The image block color data to compress.
+ * @param pi The partition info for the current trial.
+ * @param[out] ei The computed ideal endpoints and weights.
+ * @param omitted_component The color component excluded from the calculation.
+ */
+static void compute_ideal_colors_and_weights_3_comp(
+ const image_block& blk,
+ const partition_info& pi,
+ endpoints_and_weights& ei,
+ unsigned int omitted_component
+) {
+ unsigned int partition_count = pi.partition_count;
+ ei.ep.partition_count = partition_count;
+ promise(partition_count > 0);
+
+ unsigned int texel_count = blk.texel_count;
+ promise(texel_count > 0);
+
+ partition_metrics pms[BLOCK_MAX_PARTITIONS];
+
+ float error_weight;
+ const float* data_vr = nullptr;
+ const float* data_vg = nullptr;
+ const float* data_vb = nullptr;
+ if (omitted_component == 0)
+ {
+ error_weight = hadd_s(blk.channel_weight.swz<0, 1, 2>());
+ data_vr = blk.data_g;
+ data_vg = blk.data_b;
+ data_vb = blk.data_a;
+ }
+ else if (omitted_component == 1)
+ {
+ error_weight = hadd_s(blk.channel_weight.swz<0, 2, 3>());
+ data_vr = blk.data_r;
+ data_vg = blk.data_b;
+ data_vb = blk.data_a;
+ }
+ else if (omitted_component == 2)
+ {
+ error_weight = hadd_s(blk.channel_weight.swz<0, 1, 3>());
+ data_vr = blk.data_r;
+ data_vg = blk.data_g;
+ data_vb = blk.data_a;
+ }
+ else
+ {
+ assert(omitted_component == 3);
+
+ error_weight = hadd_s(blk.channel_weight.swz<0, 1, 2>());
+ data_vr = blk.data_r;
+ data_vg = blk.data_g;
+ data_vb = blk.data_b;
+ }
+
+ error_weight = error_weight * (1.0f / 3.0f);
+
+ if (omitted_component == 3)
+ {
+ compute_avgs_and_dirs_3_comp_rgb(pi, blk, pms);
+ }
+ else
+ {
+ compute_avgs_and_dirs_3_comp(pi, blk, omitted_component, pms);
+ }
+
+ bool is_constant_wes { true };
+ float partition0_len_sq { 0.0f };
+
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ vfloat4 dir = pms[i].dir;
+ if (hadd_rgb_s(dir) < 0.0f)
+ {
+ dir = vfloat4::zero() - dir;
+ }
+
+ line3 line { pms[i].avg, normalize_safe(dir, unit3()) };
+ float lowparam { 1e10f };
+ float highparam { -1e10f };
+
+ unsigned int partition_texel_count = pi.partition_texel_count[i];
+ for (unsigned int j = 0; j < partition_texel_count; j++)
+ {
+ unsigned int tix = pi.texels_of_partition[i][j];
+ vfloat4 point = vfloat3(data_vr[tix], data_vg[tix], data_vb[tix]);
+ float param = dot3_s(point - line.a, line.b);
+ ei.weights[tix] = param;
+
+ lowparam = astc::min(param, lowparam);
+ highparam = astc::max(param, highparam);
+ }
+
+ // It is possible for a uniform-color partition to produce length=0;
+ // this causes NaN issues so set to small value to avoid this problem
+ if (highparam <= lowparam)
+ {
+ lowparam = 0.0f;
+ highparam = 1e-7f;
+ }
+
+ float length = highparam - lowparam;
+ float length_squared = length * length;
+ float scale = 1.0f / length;
+
+ if (i == 0)
+ {
+ partition0_len_sq = length_squared;
+ }
+ else
+ {
+ is_constant_wes = is_constant_wes && length_squared == partition0_len_sq;
+ }
+
+ for (unsigned int j = 0; j < partition_texel_count; j++)
+ {
+ unsigned int tix = pi.texels_of_partition[i][j];
+ float idx = (ei.weights[tix] - lowparam) * scale;
+ idx = astc::clamp1f(idx);
+
+ ei.weights[tix] = idx;
+ ei.weight_error_scale[tix] = length_squared * error_weight;
+ assert(!astc::isnan(ei.weight_error_scale[tix]));
+ }
+
+ vfloat4 ep0 = line.a + line.b * lowparam;
+ vfloat4 ep1 = line.a + line.b * highparam;
+
+ vfloat4 bmin = blk.data_min;
+ vfloat4 bmax = blk.data_max;
+
+ assert(omitted_component < BLOCK_MAX_COMPONENTS);
+ switch (omitted_component)
+ {
+ case 0:
+ ei.ep.endpt0[i] = vfloat4(bmin.lane<0>(), ep0.lane<0>(), ep0.lane<1>(), ep0.lane<2>());
+ ei.ep.endpt1[i] = vfloat4(bmax.lane<0>(), ep1.lane<0>(), ep1.lane<1>(), ep1.lane<2>());
+ break;
+ case 1:
+ ei.ep.endpt0[i] = vfloat4(ep0.lane<0>(), bmin.lane<1>(), ep0.lane<1>(), ep0.lane<2>());
+ ei.ep.endpt1[i] = vfloat4(ep1.lane<0>(), bmax.lane<1>(), ep1.lane<1>(), ep1.lane<2>());
+ break;
+ case 2:
+ ei.ep.endpt0[i] = vfloat4(ep0.lane<0>(), ep0.lane<1>(), bmin.lane<2>(), ep0.lane<2>());
+ ei.ep.endpt1[i] = vfloat4(ep1.lane<0>(), ep1.lane<1>(), bmax.lane<2>(), ep1.lane<2>());
+ break;
+ default:
+ ei.ep.endpt0[i] = vfloat4(ep0.lane<0>(), ep0.lane<1>(), ep0.lane<2>(), bmin.lane<3>());
+ ei.ep.endpt1[i] = vfloat4(ep1.lane<0>(), ep1.lane<1>(), ep1.lane<2>(), bmax.lane<3>());
+ break;
+ }
+ }
+
+ // Zero initialize any SIMD over-fetch
+ unsigned int texel_count_simd = round_up_to_simd_multiple_vla(texel_count);
+ for (unsigned int i = texel_count; i < texel_count_simd; i++)
+ {
+ ei.weights[i] = 0.0f;
+ ei.weight_error_scale[i] = 0.0f;
+ }
+
+ ei.is_constant_weight_error_scale = is_constant_wes;
+}
+
+/**
+ * @brief Compute the ideal endpoints and weights for 4 color components.
+ *
+ * @param blk The image block color data to compress.
+ * @param pi The partition info for the current trial.
+ * @param[out] ei The computed ideal endpoints and weights.
+ */
+static void compute_ideal_colors_and_weights_4_comp(
+ const image_block& blk,
+ const partition_info& pi,
+ endpoints_and_weights& ei
+) {
+ const float error_weight = hadd_s(blk.channel_weight) / 4.0f;
+
+ unsigned int partition_count = pi.partition_count;
+
+ unsigned int texel_count = blk.texel_count;
+ promise(texel_count > 0);
+ promise(partition_count > 0);
+
+ partition_metrics pms[BLOCK_MAX_PARTITIONS];
+
+ compute_avgs_and_dirs_4_comp(pi, blk, pms);
+
+ bool is_constant_wes { true };
+ float partition0_len_sq { 0.0f };
+
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ vfloat4 dir = pms[i].dir;
+ if (hadd_rgb_s(dir) < 0.0f)
+ {
+ dir = vfloat4::zero() - dir;
+ }
+
+ line4 line { pms[i].avg, normalize_safe(dir, unit4()) };
+ float lowparam { 1e10f };
+ float highparam { -1e10f };
+
+ unsigned int partition_texel_count = pi.partition_texel_count[i];
+ for (unsigned int j = 0; j < partition_texel_count; j++)
+ {
+ unsigned int tix = pi.texels_of_partition[i][j];
+ vfloat4 point = blk.texel(tix);
+ float param = dot_s(point - line.a, line.b);
+ ei.weights[tix] = param;
+
+ lowparam = astc::min(param, lowparam);
+ highparam = astc::max(param, highparam);
+ }
+
+ // It is possible for a uniform-color partition to produce length=0;
+ // this causes NaN issues so set to small value to avoid this problem
+ if (highparam <= lowparam)
+ {
+ lowparam = 0.0f;
+ highparam = 1e-7f;
+ }
+
+ float length = highparam - lowparam;
+ float length_squared = length * length;
+ float scale = 1.0f / length;
+
+ if (i == 0)
+ {
+ partition0_len_sq = length_squared;
+ }
+ else
+ {
+ is_constant_wes = is_constant_wes && length_squared == partition0_len_sq;
+ }
+
+ ei.ep.endpt0[i] = line.a + line.b * lowparam;
+ ei.ep.endpt1[i] = line.a + line.b * highparam;
+
+ for (unsigned int j = 0; j < partition_texel_count; j++)
+ {
+ unsigned int tix = pi.texels_of_partition[i][j];
+ float idx = (ei.weights[tix] - lowparam) * scale;
+ idx = astc::clamp1f(idx);
+
+ ei.weights[tix] = idx;
+ ei.weight_error_scale[tix] = length_squared * error_weight;
+ assert(!astc::isnan(ei.weight_error_scale[tix]));
+ }
+ }
+
+ // Zero initialize any SIMD over-fetch
+ unsigned int texel_count_simd = round_up_to_simd_multiple_vla(texel_count);
+ for (unsigned int i = texel_count; i < texel_count_simd; i++)
+ {
+ ei.weights[i] = 0.0f;
+ ei.weight_error_scale[i] = 0.0f;
+ }
+
+ ei.is_constant_weight_error_scale = is_constant_wes;
+}
+
+/* See header for documentation. */
+void compute_ideal_colors_and_weights_1plane(
+ const image_block& blk,
+ const partition_info& pi,
+ endpoints_and_weights& ei
+) {
+ bool uses_alpha = !blk.is_constant_channel(3);
+
+ if (uses_alpha)
+ {
+ compute_ideal_colors_and_weights_4_comp(blk, pi, ei);
+ }
+ else
+ {
+ compute_ideal_colors_and_weights_3_comp(blk, pi, ei, 3);
+ }
+}
+
+/* See header for documentation. */
+void compute_ideal_colors_and_weights_2planes(
+ const block_size_descriptor& bsd,
+ const image_block& blk,
+ unsigned int plane2_component,
+ endpoints_and_weights& ei1,
+ endpoints_and_weights& ei2
+) {
+ const auto& pi = bsd.get_partition_info(1, 0);
+ bool uses_alpha = !blk.is_constant_channel(3);
+
+ assert(plane2_component < BLOCK_MAX_COMPONENTS);
+ switch (plane2_component)
+ {
+ case 0: // Separate weights for red
+ if (uses_alpha)
+ {
+ compute_ideal_colors_and_weights_3_comp(blk, pi, ei1, 0);
+ }
+ else
+ {
+ compute_ideal_colors_and_weights_2_comp(blk, pi, ei1, 1, 2);
+ }
+ compute_ideal_colors_and_weights_1_comp(blk, pi, ei2, 0);
+ break;
+
+ case 1: // Separate weights for green
+ if (uses_alpha)
+ {
+ compute_ideal_colors_and_weights_3_comp(blk, pi, ei1, 1);
+ }
+ else
+ {
+ compute_ideal_colors_and_weights_2_comp(blk, pi, ei1, 0, 2);
+ }
+ compute_ideal_colors_and_weights_1_comp(blk, pi, ei2, 1);
+ break;
+
+ case 2: // Separate weights for blue
+ if (uses_alpha)
+ {
+ compute_ideal_colors_and_weights_3_comp(blk, pi, ei1, 2);
+ }
+ else
+ {
+ compute_ideal_colors_and_weights_2_comp(blk, pi, ei1, 0, 1);
+ }
+ compute_ideal_colors_and_weights_1_comp(blk, pi, ei2, 2);
+ break;
+
+ default: // Separate weights for alpha
+ assert(uses_alpha);
+ compute_ideal_colors_and_weights_3_comp(blk, pi, ei1, 3);
+ compute_ideal_colors_and_weights_1_comp(blk, pi, ei2, 3);
+ break;
+ }
+}
+
+/* See header for documentation. */
+float compute_error_of_weight_set_1plane(
+ const endpoints_and_weights& eai,
+ const decimation_info& di,
+ const float* dec_weight_quant_uvalue
+) {
+ vfloatacc error_summav = vfloatacc::zero();
+ unsigned int texel_count = di.texel_count;
+ promise(texel_count > 0);
+
+ // Process SIMD-width chunks, safe to over-fetch - the extra space is zero initialized
+ if (di.max_texel_weight_count > 2)
+ {
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ // Compute the bilinear interpolation of the decimated weight grid
+ vfloat current_values = bilinear_infill_vla(di, dec_weight_quant_uvalue, i);
+
+ // Compute the error between the computed value and the ideal weight
+ vfloat actual_values = loada(eai.weights + i);
+ vfloat diff = current_values - actual_values;
+ vfloat significance = loada(eai.weight_error_scale + i);
+ vfloat error = diff * diff * significance;
+
+ haccumulate(error_summav, error);
+ }
+ }
+ else if (di.max_texel_weight_count > 1)
+ {
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ // Compute the bilinear interpolation of the decimated weight grid
+ vfloat current_values = bilinear_infill_vla_2(di, dec_weight_quant_uvalue, i);
+
+ // Compute the error between the computed value and the ideal weight
+ vfloat actual_values = loada(eai.weights + i);
+ vfloat diff = current_values - actual_values;
+ vfloat significance = loada(eai.weight_error_scale + i);
+ vfloat error = diff * diff * significance;
+
+ haccumulate(error_summav, error);
+ }
+ }
+ else
+ {
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ // Load the weight set directly, without interpolation
+ vfloat current_values = loada(dec_weight_quant_uvalue + i);
+
+ // Compute the error between the computed value and the ideal weight
+ vfloat actual_values = loada(eai.weights + i);
+ vfloat diff = current_values - actual_values;
+ vfloat significance = loada(eai.weight_error_scale + i);
+ vfloat error = diff * diff * significance;
+
+ haccumulate(error_summav, error);
+ }
+ }
+
+ // Resolve the final scalar accumulator sum
+ return hadd_s(error_summav);
+}
+
+/* See header for documentation. */
+float compute_error_of_weight_set_2planes(
+ const endpoints_and_weights& eai1,
+ const endpoints_and_weights& eai2,
+ const decimation_info& di,
+ const float* dec_weight_quant_uvalue_plane1,
+ const float* dec_weight_quant_uvalue_plane2
+) {
+ vfloatacc error_summav = vfloatacc::zero();
+ unsigned int texel_count = di.texel_count;
+ promise(texel_count > 0);
+
+ // Process SIMD-width chunks, safe to over-fetch - the extra space is zero initialized
+ if (di.max_texel_weight_count > 2)
+ {
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ // Plane 1
+ // Compute the bilinear interpolation of the decimated weight grid
+ vfloat current_values1 = bilinear_infill_vla(di, dec_weight_quant_uvalue_plane1, i);
+
+ // Compute the error between the computed value and the ideal weight
+ vfloat actual_values1 = loada(eai1.weights + i);
+ vfloat diff = current_values1 - actual_values1;
+ vfloat error1 = diff * diff * loada(eai1.weight_error_scale + i);
+
+ // Plane 2
+ // Compute the bilinear interpolation of the decimated weight grid
+ vfloat current_values2 = bilinear_infill_vla(di, dec_weight_quant_uvalue_plane2, i);
+
+ // Compute the error between the computed value and the ideal weight
+ vfloat actual_values2 = loada(eai2.weights + i);
+ diff = current_values2 - actual_values2;
+ vfloat error2 = diff * diff * loada(eai2.weight_error_scale + i);
+
+ haccumulate(error_summav, error1 + error2);
+ }
+ }
+ else if (di.max_texel_weight_count > 1)
+ {
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ // Plane 1
+ // Compute the bilinear interpolation of the decimated weight grid
+ vfloat current_values1 = bilinear_infill_vla_2(di, dec_weight_quant_uvalue_plane1, i);
+
+ // Compute the error between the computed value and the ideal weight
+ vfloat actual_values1 = loada(eai1.weights + i);
+ vfloat diff = current_values1 - actual_values1;
+ vfloat error1 = diff * diff * loada(eai1.weight_error_scale + i);
+
+ // Plane 2
+ // Compute the bilinear interpolation of the decimated weight grid
+ vfloat current_values2 = bilinear_infill_vla_2(di, dec_weight_quant_uvalue_plane2, i);
+
+ // Compute the error between the computed value and the ideal weight
+ vfloat actual_values2 = loada(eai2.weights + i);
+ diff = current_values2 - actual_values2;
+ vfloat error2 = diff * diff * loada(eai2.weight_error_scale + i);
+
+ haccumulate(error_summav, error1 + error2);
+ }
+ }
+ else
+ {
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ // Plane 1
+ // Load the weight set directly, without interpolation
+ vfloat current_values1 = loada(dec_weight_quant_uvalue_plane1 + i);
+
+ // Compute the error between the computed value and the ideal weight
+ vfloat actual_values1 = loada(eai1.weights + i);
+ vfloat diff = current_values1 - actual_values1;
+ vfloat error1 = diff * diff * loada(eai1.weight_error_scale + i);
+
+ // Plane 2
+ // Load the weight set directly, without interpolation
+ vfloat current_values2 = loada(dec_weight_quant_uvalue_plane2 + i);
+
+ // Compute the error between the computed value and the ideal weight
+ vfloat actual_values2 = loada(eai2.weights + i);
+ diff = current_values2 - actual_values2;
+ vfloat error2 = diff * diff * loada(eai2.weight_error_scale + i);
+
+ haccumulate(error_summav, error1 + error2);
+ }
+ }
+
+ // Resolve the final scalar accumulator sum
+ return hadd_s(error_summav);
+}
+
+/* See header for documentation. */
+void compute_ideal_weights_for_decimation(
+ const endpoints_and_weights& ei,
+ const decimation_info& di,
+ float* dec_weight_ideal_value
+) {
+ unsigned int texel_count = di.texel_count;
+ unsigned int weight_count = di.weight_count;
+ bool is_direct = texel_count == weight_count;
+ promise(texel_count > 0);
+ promise(weight_count > 0);
+
+ // Ensure that the end of the output arrays that are used for SIMD paths later are filled so we
+ // can safely run SIMD elsewhere without a loop tail. Note that this is always safe as weight
+ // arrays always contain space for 64 elements
+ unsigned int prev_weight_count_simd = round_down_to_simd_multiple_vla(weight_count - 1);
+ storea(vfloat::zero(), dec_weight_ideal_value + prev_weight_count_simd);
+
+ // If we have a 1:1 mapping just shortcut the computation. Transfer enough to also copy the
+ // zero-initialized SIMD over-fetch region
+ if (is_direct)
+ {
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat weight(ei.weights + i);
+ storea(weight, dec_weight_ideal_value + i);
+ }
+
+ return;
+ }
+
+ // Otherwise compute an estimate and perform single refinement iteration
+ alignas(ASTCENC_VECALIGN) float infilled_weights[BLOCK_MAX_TEXELS];
+
+ // Compute an initial average for each decimated weight
+ bool constant_wes = ei.is_constant_weight_error_scale;
+ vfloat weight_error_scale(ei.weight_error_scale[0]);
+
+ // This overshoots - this is OK as we initialize the array tails in the
+ // decimation table structures to safe values ...
+ for (unsigned int i = 0; i < weight_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ // Start with a small value to avoid div-by-zero later
+ vfloat weight_weight(1e-10f);
+ vfloat initial_weight = vfloat::zero();
+
+ // Accumulate error weighting of all the texels using this weight
+ vint weight_texel_count(di.weight_texel_count + i);
+ unsigned int max_texel_count = hmax(weight_texel_count).lane<0>();
+ promise(max_texel_count > 0);
+
+ for (unsigned int j = 0; j < max_texel_count; j++)
+ {
+ vint texel(di.weight_texels_tr[j] + i);
+ vfloat weight = loada(di.weights_texel_contribs_tr[j] + i);
+
+ if (!constant_wes)
+ {
+ weight_error_scale = gatherf(ei.weight_error_scale, texel);
+ }
+
+ vfloat contrib_weight = weight * weight_error_scale;
+
+ weight_weight += contrib_weight;
+ initial_weight += gatherf(ei.weights, texel) * contrib_weight;
+ }
+
+ storea(initial_weight / weight_weight, dec_weight_ideal_value + i);
+ }
+
+ // Populate the interpolated weight grid based on the initial average
+ // Process SIMD-width texel coordinates at at time while we can. Safe to
+ // over-process full SIMD vectors - the tail is zeroed.
+ if (di.max_texel_weight_count <= 2)
+ {
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat weight = bilinear_infill_vla_2(di, dec_weight_ideal_value, i);
+ storea(weight, infilled_weights + i);
+ }
+ }
+ else
+ {
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat weight = bilinear_infill_vla(di, dec_weight_ideal_value, i);
+ storea(weight, infilled_weights + i);
+ }
+ }
+
+ // Perform a single iteration of refinement
+ // Empirically determined step size; larger values don't help but smaller drops image quality
+ constexpr float stepsize = 0.25f;
+ constexpr float chd_scale = -WEIGHTS_TEXEL_SUM;
+
+ for (unsigned int i = 0; i < weight_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat weight_val = loada(dec_weight_ideal_value + i);
+
+ // Accumulate error weighting of all the texels using this weight
+ // Start with a small value to avoid div-by-zero later
+ vfloat error_change0(1e-10f);
+ vfloat error_change1(0.0f);
+
+ // Accumulate error weighting of all the texels using this weight
+ vint weight_texel_count(di.weight_texel_count + i);
+ unsigned int max_texel_count = hmax(weight_texel_count).lane<0>();
+ promise(max_texel_count > 0);
+
+ for (unsigned int j = 0; j < max_texel_count; j++)
+ {
+ vint texel(di.weight_texels_tr[j] + i);
+ vfloat contrib_weight = loada(di.weights_texel_contribs_tr[j] + i);
+
+ if (!constant_wes)
+ {
+ weight_error_scale = gatherf(ei.weight_error_scale, texel);
+ }
+
+ vfloat scale = weight_error_scale * contrib_weight;
+ vfloat old_weight = gatherf(infilled_weights, texel);
+ vfloat ideal_weight = gatherf(ei.weights, texel);
+
+ error_change0 += contrib_weight * scale;
+ error_change1 += (old_weight - ideal_weight) * scale;
+ }
+
+ vfloat step = (error_change1 * chd_scale) / error_change0;
+ step = clamp(-stepsize, stepsize, step);
+
+ // Update the weight; note this can store negative values
+ storea(weight_val + step, dec_weight_ideal_value + i);
+ }
+}
+
+/* See header for documentation. */
+void compute_quantized_weights_for_decimation(
+ const decimation_info& di,
+ float low_bound,
+ float high_bound,
+ const float* dec_weight_ideal_value,
+ float* weight_set_out,
+ uint8_t* quantized_weight_set,
+ quant_method quant_level
+) {
+ int weight_count = di.weight_count;
+ promise(weight_count > 0);
+ const quant_and_transfer_table& qat = quant_and_xfer_tables[quant_level];
+
+ // The available quant levels, stored with a minus 1 bias
+ static const float quant_levels_m1[12] {
+ 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 7.0f, 9.0f, 11.0f, 15.0f, 19.0f, 23.0f, 31.0f
+ };
+
+ vint steps_m1(get_quant_level(quant_level) - 1);
+ float quant_level_m1 = quant_levels_m1[quant_level];
+
+ // Quantize the weight set using both the specified low/high bounds and standard 0..1 bounds
+
+ // TODO: Oddity to investigate; triggered by test in issue #265.
+ if (high_bound <= low_bound)
+ {
+ low_bound = 0.0f;
+ high_bound = 1.0f;
+ }
+
+ float rscale = high_bound - low_bound;
+ float scale = 1.0f / rscale;
+
+ float scaled_low_bound = low_bound * scale;
+ rscale *= 1.0f / 64.0f;
+
+ vfloat scalev(scale);
+ vfloat scaled_low_boundv(scaled_low_bound);
+ vfloat quant_level_m1v(quant_level_m1);
+ vfloat rscalev(rscale);
+ vfloat low_boundv(low_bound);
+
+ // This runs to the rounded-up SIMD size, which is safe as the loop tail is filled with known
+ // safe data in compute_ideal_weights_for_decimation and arrays are always 64 elements
+ if (get_quant_level(quant_level) <= 16)
+ {
+ vint4 tab0(reinterpret_cast<const int*>(qat.quant_to_unquant));
+ vint tab0p;
+ vtable_prepare(tab0, tab0p);
+
+ for (int i = 0; i < weight_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat ix = loada(dec_weight_ideal_value + i) * scalev - scaled_low_boundv;
+ ix = clampzo(ix);
+
+ // Look up the two closest indexes and return the one that was closest
+ vfloat ix1 = ix * quant_level_m1v;
+
+ vint weightl = float_to_int(ix1);
+ vint weighth = min(weightl + vint(1), steps_m1);
+
+ vint ixli = vtable_8bt_32bi(tab0p, weightl);
+ vint ixhi = vtable_8bt_32bi(tab0p, weighth);
+
+ vfloat ixl = int_to_float(ixli);
+ vfloat ixh = int_to_float(ixhi);
+
+ vmask mask = (ixl + ixh) < (vfloat(128.0f) * ix);
+ vint weight = select(ixli, ixhi, mask);
+ ixl = select(ixl, ixh, mask);
+
+ // Invert the weight-scaling that was done initially
+ storea(ixl * rscalev + low_boundv, weight_set_out + i);
+ vint scn = pack_low_bytes(weight);
+ store_nbytes(scn, quantized_weight_set + i);
+ }
+ }
+ else
+ {
+ vint4 tab0(reinterpret_cast<const int*>(qat.quant_to_unquant));
+ vint4 tab1(reinterpret_cast<const int*>(qat.quant_to_unquant + 16));
+ vint tab0p, tab1p;
+ vtable_prepare(tab0, tab1, tab0p, tab1p);
+
+ for (int i = 0; i < weight_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat ix = loada(dec_weight_ideal_value + i) * scalev - scaled_low_boundv;
+ ix = clampzo(ix);
+
+ // Look up the two closest indexes and return the one that was closest
+ vfloat ix1 = ix * quant_level_m1v;
+
+ vint weightl = float_to_int(ix1);
+ vint weighth = min(weightl + vint(1), steps_m1);
+
+ vint ixli = vtable_8bt_32bi(tab0p, tab1p, weightl);
+ vint ixhi = vtable_8bt_32bi(tab0p, tab1p, weighth);
+
+ vfloat ixl = int_to_float(ixli);
+ vfloat ixh = int_to_float(ixhi);
+
+ vmask mask = (ixl + ixh) < (vfloat(128.0f) * ix);
+ vint weight = select(ixli, ixhi, mask);
+ ixl = select(ixl, ixh, mask);
+
+ // Invert the weight-scaling that was done initially
+ storea(ixl * rscalev + low_boundv, weight_set_out + i);
+ vint scn = pack_low_bytes(weight);
+ store_nbytes(scn, quantized_weight_set + i);
+ }
+ }
+}
+
+/**
+ * @brief Compute the RGB + offset for a HDR endpoint mode #7.
+ *
+ * Since the matrix needed has a regular structure we can simplify the inverse calculation. This
+ * gives us ~24 multiplications vs. 96 for a generic inverse.
+ *
+ * mat[0] = vfloat4(rgba_ws.x, 0.0f, 0.0f, wght_ws.x);
+ * mat[1] = vfloat4( 0.0f, rgba_ws.y, 0.0f, wght_ws.y);
+ * mat[2] = vfloat4( 0.0f, 0.0f, rgba_ws.z, wght_ws.z);
+ * mat[3] = vfloat4(wght_ws.x, wght_ws.y, wght_ws.z, psum);
+ * mat = invert(mat);
+ *
+ * @param rgba_weight_sum Sum of partition component error weights.
+ * @param weight_weight_sum Sum of partition component error weights * texel weight.
+ * @param rgbq_sum Sum of partition component error weights * texel weight * color data.
+ * @param psum Sum of RGB color weights * texel weight^2.
+ */
+static inline vfloat4 compute_rgbo_vector(
+ vfloat4 rgba_weight_sum,
+ vfloat4 weight_weight_sum,
+ vfloat4 rgbq_sum,
+ float psum
+) {
+ float X = rgba_weight_sum.lane<0>();
+ float Y = rgba_weight_sum.lane<1>();
+ float Z = rgba_weight_sum.lane<2>();
+ float P = weight_weight_sum.lane<0>();
+ float Q = weight_weight_sum.lane<1>();
+ float R = weight_weight_sum.lane<2>();
+ float S = psum;
+
+ float PP = P * P;
+ float QQ = Q * Q;
+ float RR = R * R;
+
+ float SZmRR = S * Z - RR;
+ float DT = SZmRR * Y - Z * QQ;
+ float YP = Y * P;
+ float QX = Q * X;
+ float YX = Y * X;
+ float mZYP = -Z * YP;
+ float mZQX = -Z * QX;
+ float mRYX = -R * YX;
+ float ZQP = Z * Q * P;
+ float RYP = R * YP;
+ float RQX = R * QX;
+
+ // Compute the reciprocal of matrix determinant
+ float rdet = 1.0f / (DT * X + mZYP * P);
+
+ // Actually compute the adjugate, and then apply 1/det separately
+ vfloat4 mat0(DT, ZQP, RYP, mZYP);
+ vfloat4 mat1(ZQP, SZmRR * X - Z * PP, RQX, mZQX);
+ vfloat4 mat2(RYP, RQX, (S * Y - QQ) * X - Y * PP, mRYX);
+ vfloat4 mat3(mZYP, mZQX, mRYX, Z * YX);
+ vfloat4 vect = rgbq_sum * rdet;
+
+ return vfloat4(dot_s(mat0, vect),
+ dot_s(mat1, vect),
+ dot_s(mat2, vect),
+ dot_s(mat3, vect));
+}
+
+/* See header for documentation. */
+void recompute_ideal_colors_1plane(
+ const image_block& blk,
+ const partition_info& pi,
+ const decimation_info& di,
+ const uint8_t* dec_weights_uquant,
+ endpoints& ep,
+ vfloat4 rgbs_vectors[BLOCK_MAX_PARTITIONS],
+ vfloat4 rgbo_vectors[BLOCK_MAX_PARTITIONS]
+) {
+ unsigned int weight_count = di.weight_count;
+ unsigned int total_texel_count = blk.texel_count;
+ unsigned int partition_count = pi.partition_count;
+
+ promise(weight_count > 0);
+ promise(total_texel_count > 0);
+ promise(partition_count > 0);
+
+ alignas(ASTCENC_VECALIGN) float dec_weight[BLOCK_MAX_WEIGHTS];
+ for (unsigned int i = 0; i < weight_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vint unquant_value(dec_weights_uquant + i);
+ vfloat unquant_valuef = int_to_float(unquant_value) * vfloat(1.0f / 64.0f);
+ storea(unquant_valuef, dec_weight + i);
+ }
+
+ alignas(ASTCENC_VECALIGN) float undec_weight[BLOCK_MAX_TEXELS];
+ float* undec_weight_ref;
+ if (di.max_texel_weight_count == 1)
+ {
+ undec_weight_ref = dec_weight;
+ }
+ else if (di.max_texel_weight_count <= 2)
+ {
+ for (unsigned int i = 0; i < total_texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat weight = bilinear_infill_vla_2(di, dec_weight, i);
+ storea(weight, undec_weight + i);
+ }
+
+ undec_weight_ref = undec_weight;
+ }
+ else
+ {
+ for (unsigned int i = 0; i < total_texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat weight = bilinear_infill_vla(di, dec_weight, i);
+ storea(weight, undec_weight + i);
+ }
+
+ undec_weight_ref = undec_weight;
+ }
+
+ vfloat4 rgba_sum(blk.data_mean * static_cast<float>(blk.texel_count));
+
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ unsigned int texel_count = pi.partition_texel_count[i];
+ const uint8_t *texel_indexes = pi.texels_of_partition[i];
+
+ // Only compute a partition mean if more than one partition
+ if (partition_count > 1)
+ {
+ rgba_sum = vfloat4::zero();
+ promise(texel_count > 0);
+ for (unsigned int j = 0; j < texel_count; j++)
+ {
+ unsigned int tix = texel_indexes[j];
+ rgba_sum += blk.texel(tix);
+ }
+ }
+
+ rgba_sum = rgba_sum * blk.channel_weight;
+ vfloat4 rgba_weight_sum = max(blk.channel_weight * static_cast<float>(texel_count), 1e-17f);
+ vfloat4 scale_dir = normalize((rgba_sum / rgba_weight_sum).swz<0, 1, 2>());
+
+ float scale_max = 0.0f;
+ float scale_min = 1e10f;
+
+ float wmin1 = 1.0f;
+ float wmax1 = 0.0f;
+
+ float left_sum_s = 0.0f;
+ float middle_sum_s = 0.0f;
+ float right_sum_s = 0.0f;
+
+ vfloat4 color_vec_x = vfloat4::zero();
+ vfloat4 color_vec_y = vfloat4::zero();
+
+ vfloat4 scale_vec = vfloat4::zero();
+
+ float weight_weight_sum_s = 1e-17f;
+
+ vfloat4 color_weight = blk.channel_weight;
+ float ls_weight = hadd_rgb_s(color_weight);
+
+ for (unsigned int j = 0; j < texel_count; j++)
+ {
+ unsigned int tix = texel_indexes[j];
+ vfloat4 rgba = blk.texel(tix);
+
+ float idx0 = undec_weight_ref[tix];
+
+ float om_idx0 = 1.0f - idx0;
+ wmin1 = astc::min(idx0, wmin1);
+ wmax1 = astc::max(idx0, wmax1);
+
+ float scale = dot3_s(scale_dir, rgba);
+ scale_min = astc::min(scale, scale_min);
+ scale_max = astc::max(scale, scale_max);
+
+ left_sum_s += om_idx0 * om_idx0;
+ middle_sum_s += om_idx0 * idx0;
+ right_sum_s += idx0 * idx0;
+ weight_weight_sum_s += idx0;
+
+ vfloat4 color_idx(idx0);
+ vfloat4 cwprod = rgba;
+ vfloat4 cwiprod = cwprod * color_idx;
+
+ color_vec_y += cwiprod;
+ color_vec_x += cwprod - cwiprod;
+
+ scale_vec += vfloat2(om_idx0, idx0) * (scale * ls_weight);
+ }
+
+ vfloat4 left_sum = vfloat4(left_sum_s) * color_weight;
+ vfloat4 middle_sum = vfloat4(middle_sum_s) * color_weight;
+ vfloat4 right_sum = vfloat4(right_sum_s) * color_weight;
+ vfloat4 lmrs_sum = vfloat3(left_sum_s, middle_sum_s, right_sum_s) * ls_weight;
+
+ color_vec_x = color_vec_x * color_weight;
+ color_vec_y = color_vec_y * color_weight;
+
+ // Initialize the luminance and scale vectors with a reasonable default
+ float scalediv = scale_min / astc::max(scale_max, 1e-10f);
+ scalediv = astc::clamp1f(scalediv);
+
+ vfloat4 sds = scale_dir * scale_max;
+
+ rgbs_vectors[i] = vfloat4(sds.lane<0>(), sds.lane<1>(), sds.lane<2>(), scalediv);
+
+ if (wmin1 >= wmax1 * 0.999f)
+ {
+ // If all weights in the partition were equal, then just take average of all colors in
+ // the partition and use that as both endpoint colors
+ vfloat4 avg = (color_vec_x + color_vec_y) / rgba_weight_sum;
+
+ vmask4 notnan_mask = avg == avg;
+ ep.endpt0[i] = select(ep.endpt0[i], avg, notnan_mask);
+ ep.endpt1[i] = select(ep.endpt1[i], avg, notnan_mask);
+
+ rgbs_vectors[i] = vfloat4(sds.lane<0>(), sds.lane<1>(), sds.lane<2>(), 1.0f);
+ }
+ else
+ {
+ // Otherwise, complete the analytic calculation of ideal-endpoint-values for the given
+ // set of texel weights and pixel colors
+ vfloat4 color_det1 = (left_sum * right_sum) - (middle_sum * middle_sum);
+ vfloat4 color_rdet1 = 1.0f / color_det1;
+
+ float ls_det1 = (lmrs_sum.lane<0>() * lmrs_sum.lane<2>()) - (lmrs_sum.lane<1>() * lmrs_sum.lane<1>());
+ float ls_rdet1 = 1.0f / ls_det1;
+
+ vfloat4 color_mss1 = (left_sum * left_sum)
+ + (2.0f * middle_sum * middle_sum)
+ + (right_sum * right_sum);
+
+ float ls_mss1 = (lmrs_sum.lane<0>() * lmrs_sum.lane<0>())
+ + (2.0f * lmrs_sum.lane<1>() * lmrs_sum.lane<1>())
+ + (lmrs_sum.lane<2>() * lmrs_sum.lane<2>());
+
+ vfloat4 ep0 = (right_sum * color_vec_x - middle_sum * color_vec_y) * color_rdet1;
+ vfloat4 ep1 = (left_sum * color_vec_y - middle_sum * color_vec_x) * color_rdet1;
+
+ vmask4 det_mask = abs(color_det1) > (color_mss1 * 1e-4f);
+ vmask4 notnan_mask = (ep0 == ep0) & (ep1 == ep1);
+ vmask4 full_mask = det_mask & notnan_mask;
+
+ ep.endpt0[i] = select(ep.endpt0[i], ep0, full_mask);
+ ep.endpt1[i] = select(ep.endpt1[i], ep1, full_mask);
+
+ float scale_ep0 = (lmrs_sum.lane<2>() * scale_vec.lane<0>() - lmrs_sum.lane<1>() * scale_vec.lane<1>()) * ls_rdet1;
+ float scale_ep1 = (lmrs_sum.lane<0>() * scale_vec.lane<1>() - lmrs_sum.lane<1>() * scale_vec.lane<0>()) * ls_rdet1;
+
+ if (fabsf(ls_det1) > (ls_mss1 * 1e-4f) && scale_ep0 == scale_ep0 && scale_ep1 == scale_ep1 && scale_ep0 < scale_ep1)
+ {
+ float scalediv2 = scale_ep0 / scale_ep1;
+ vfloat4 sdsm = scale_dir * scale_ep1;
+ rgbs_vectors[i] = vfloat4(sdsm.lane<0>(), sdsm.lane<1>(), sdsm.lane<2>(), scalediv2);
+ }
+ }
+
+ // Calculations specific to mode #7, the HDR RGB-scale mode - skip if known LDR
+ if (blk.rgb_lns[0] || blk.alpha_lns[0])
+ {
+ vfloat4 weight_weight_sum = vfloat4(weight_weight_sum_s) * color_weight;
+ float psum = right_sum_s * hadd_rgb_s(color_weight);
+
+ vfloat4 rgbq_sum = color_vec_x + color_vec_y;
+ rgbq_sum.set_lane<3>(hadd_rgb_s(color_vec_y));
+
+ vfloat4 rgbovec = compute_rgbo_vector(rgba_weight_sum, weight_weight_sum, rgbq_sum, psum);
+ rgbo_vectors[i] = rgbovec;
+
+ // We can get a failure due to the use of a singular (non-invertible) matrix
+ // If it failed, compute rgbo_vectors[] with a different method ...
+ if (astc::isnan(dot_s(rgbovec, rgbovec)))
+ {
+ vfloat4 v0 = ep.endpt0[i];
+ vfloat4 v1 = ep.endpt1[i];
+
+ float avgdif = hadd_rgb_s(v1 - v0) * (1.0f / 3.0f);
+ avgdif = astc::max(avgdif, 0.0f);
+
+ vfloat4 avg = (v0 + v1) * 0.5f;
+ vfloat4 ep0 = avg - vfloat4(avgdif) * 0.5f;
+ rgbo_vectors[i] = vfloat4(ep0.lane<0>(), ep0.lane<1>(), ep0.lane<2>(), avgdif);
+ }
+ }
+ }
+}
+
+/* See header for documentation. */
+void recompute_ideal_colors_2planes(
+ const image_block& blk,
+ const block_size_descriptor& bsd,
+ const decimation_info& di,
+ const uint8_t* dec_weights_uquant_plane1,
+ const uint8_t* dec_weights_uquant_plane2,
+ endpoints& ep,
+ vfloat4& rgbs_vector,
+ vfloat4& rgbo_vector,
+ int plane2_component
+) {
+ unsigned int weight_count = di.weight_count;
+ unsigned int total_texel_count = blk.texel_count;
+
+ promise(total_texel_count > 0);
+ promise(weight_count > 0);
+
+ alignas(ASTCENC_VECALIGN) float dec_weight_plane1[BLOCK_MAX_WEIGHTS_2PLANE];
+ alignas(ASTCENC_VECALIGN) float dec_weight_plane2[BLOCK_MAX_WEIGHTS_2PLANE];
+
+ assert(weight_count <= BLOCK_MAX_WEIGHTS_2PLANE);
+
+ for (unsigned int i = 0; i < weight_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vint unquant_value1(dec_weights_uquant_plane1 + i);
+ vfloat unquant_value1f = int_to_float(unquant_value1) * vfloat(1.0f / 64.0f);
+ storea(unquant_value1f, dec_weight_plane1 + i);
+
+ vint unquant_value2(dec_weights_uquant_plane2 + i);
+ vfloat unquant_value2f = int_to_float(unquant_value2) * vfloat(1.0f / 64.0f);
+ storea(unquant_value2f, dec_weight_plane2 + i);
+ }
+
+ alignas(ASTCENC_VECALIGN) float undec_weight_plane1[BLOCK_MAX_TEXELS];
+ alignas(ASTCENC_VECALIGN) float undec_weight_plane2[BLOCK_MAX_TEXELS];
+
+ float* undec_weight_plane1_ref;
+ float* undec_weight_plane2_ref;
+
+ if (di.max_texel_weight_count == 1)
+ {
+ undec_weight_plane1_ref = dec_weight_plane1;
+ undec_weight_plane2_ref = dec_weight_plane2;
+ }
+ else if (di.max_texel_weight_count <= 2)
+ {
+ for (unsigned int i = 0; i < total_texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat weight = bilinear_infill_vla_2(di, dec_weight_plane1, i);
+ storea(weight, undec_weight_plane1 + i);
+
+ weight = bilinear_infill_vla_2(di, dec_weight_plane2, i);
+ storea(weight, undec_weight_plane2 + i);
+ }
+
+ undec_weight_plane1_ref = undec_weight_plane1;
+ undec_weight_plane2_ref = undec_weight_plane2;
+ }
+ else
+ {
+ for (unsigned int i = 0; i < total_texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat weight = bilinear_infill_vla(di, dec_weight_plane1, i);
+ storea(weight, undec_weight_plane1 + i);
+
+ weight = bilinear_infill_vla(di, dec_weight_plane2, i);
+ storea(weight, undec_weight_plane2 + i);
+ }
+
+ undec_weight_plane1_ref = undec_weight_plane1;
+ undec_weight_plane2_ref = undec_weight_plane2;
+ }
+
+ unsigned int texel_count = bsd.texel_count;
+ vfloat4 rgba_weight_sum = max(blk.channel_weight * static_cast<float>(texel_count), 1e-17f);
+ vfloat4 scale_dir = normalize(blk.data_mean.swz<0, 1, 2>());
+
+ float scale_max = 0.0f;
+ float scale_min = 1e10f;
+
+ float wmin1 = 1.0f;
+ float wmax1 = 0.0f;
+
+ float wmin2 = 1.0f;
+ float wmax2 = 0.0f;
+
+ float left1_sum_s = 0.0f;
+ float middle1_sum_s = 0.0f;
+ float right1_sum_s = 0.0f;
+
+ float left2_sum_s = 0.0f;
+ float middle2_sum_s = 0.0f;
+ float right2_sum_s = 0.0f;
+
+ vfloat4 color_vec_x = vfloat4::zero();
+ vfloat4 color_vec_y = vfloat4::zero();
+
+ vfloat4 scale_vec = vfloat4::zero();
+
+ vfloat4 weight_weight_sum = vfloat4(1e-17f);
+
+ vmask4 p2_mask = vint4::lane_id() == vint4(plane2_component);
+ vfloat4 color_weight = blk.channel_weight;
+ float ls_weight = hadd_rgb_s(color_weight);
+
+ for (unsigned int j = 0; j < texel_count; j++)
+ {
+ vfloat4 rgba = blk.texel(j);
+
+ float idx0 = undec_weight_plane1_ref[j];
+
+ float om_idx0 = 1.0f - idx0;
+ wmin1 = astc::min(idx0, wmin1);
+ wmax1 = astc::max(idx0, wmax1);
+
+ float scale = dot3_s(scale_dir, rgba);
+ scale_min = astc::min(scale, scale_min);
+ scale_max = astc::max(scale, scale_max);
+
+ left1_sum_s += om_idx0 * om_idx0;
+ middle1_sum_s += om_idx0 * idx0;
+ right1_sum_s += idx0 * idx0;
+
+ float idx1 = undec_weight_plane2_ref[j];
+
+ float om_idx1 = 1.0f - idx1;
+ wmin2 = astc::min(idx1, wmin2);
+ wmax2 = astc::max(idx1, wmax2);
+
+ left2_sum_s += om_idx1 * om_idx1;
+ middle2_sum_s += om_idx1 * idx1;
+ right2_sum_s += idx1 * idx1;
+
+ vfloat4 color_idx = select(vfloat4(idx0), vfloat4(idx1), p2_mask);
+
+ vfloat4 cwprod = rgba;
+ vfloat4 cwiprod = cwprod * color_idx;
+
+ color_vec_y += cwiprod;
+ color_vec_x += cwprod - cwiprod;
+
+ scale_vec += vfloat2(om_idx0, idx0) * (ls_weight * scale);
+ weight_weight_sum += color_idx;
+ }
+
+ vfloat4 left1_sum = vfloat4(left1_sum_s) * color_weight;
+ vfloat4 middle1_sum = vfloat4(middle1_sum_s) * color_weight;
+ vfloat4 right1_sum = vfloat4(right1_sum_s) * color_weight;
+ vfloat4 lmrs_sum = vfloat3(left1_sum_s, middle1_sum_s, right1_sum_s) * ls_weight;
+
+ vfloat4 left2_sum = vfloat4(left2_sum_s) * color_weight;
+ vfloat4 middle2_sum = vfloat4(middle2_sum_s) * color_weight;
+ vfloat4 right2_sum = vfloat4(right2_sum_s) * color_weight;
+
+ color_vec_x = color_vec_x * color_weight;
+ color_vec_y = color_vec_y * color_weight;
+
+ // Initialize the luminance and scale vectors with a reasonable default
+ float scalediv = scale_min / astc::max(scale_max, 1e-10f);
+ scalediv = astc::clamp1f(scalediv);
+
+ vfloat4 sds = scale_dir * scale_max;
+
+ rgbs_vector = vfloat4(sds.lane<0>(), sds.lane<1>(), sds.lane<2>(), scalediv);
+
+ if (wmin1 >= wmax1 * 0.999f)
+ {
+ // If all weights in the partition were equal, then just take average of all colors in
+ // the partition and use that as both endpoint colors
+ vfloat4 avg = (color_vec_x + color_vec_y) / rgba_weight_sum;
+
+ vmask4 p1_mask = vint4::lane_id() != vint4(plane2_component);
+ vmask4 notnan_mask = avg == avg;
+ vmask4 full_mask = p1_mask & notnan_mask;
+
+ ep.endpt0[0] = select(ep.endpt0[0], avg, full_mask);
+ ep.endpt1[0] = select(ep.endpt1[0], avg, full_mask);
+
+ rgbs_vector = vfloat4(sds.lane<0>(), sds.lane<1>(), sds.lane<2>(), 1.0f);
+ }
+ else
+ {
+ // Otherwise, complete the analytic calculation of ideal-endpoint-values for the given
+ // set of texel weights and pixel colors
+ vfloat4 color_det1 = (left1_sum * right1_sum) - (middle1_sum * middle1_sum);
+ vfloat4 color_rdet1 = 1.0f / color_det1;
+
+ float ls_det1 = (lmrs_sum.lane<0>() * lmrs_sum.lane<2>()) - (lmrs_sum.lane<1>() * lmrs_sum.lane<1>());
+ float ls_rdet1 = 1.0f / ls_det1;
+
+ vfloat4 color_mss1 = (left1_sum * left1_sum)
+ + (2.0f * middle1_sum * middle1_sum)
+ + (right1_sum * right1_sum);
+
+ float ls_mss1 = (lmrs_sum.lane<0>() * lmrs_sum.lane<0>())
+ + (2.0f * lmrs_sum.lane<1>() * lmrs_sum.lane<1>())
+ + (lmrs_sum.lane<2>() * lmrs_sum.lane<2>());
+
+ vfloat4 ep0 = (right1_sum * color_vec_x - middle1_sum * color_vec_y) * color_rdet1;
+ vfloat4 ep1 = (left1_sum * color_vec_y - middle1_sum * color_vec_x) * color_rdet1;
+
+ float scale_ep0 = (lmrs_sum.lane<2>() * scale_vec.lane<0>() - lmrs_sum.lane<1>() * scale_vec.lane<1>()) * ls_rdet1;
+ float scale_ep1 = (lmrs_sum.lane<0>() * scale_vec.lane<1>() - lmrs_sum.lane<1>() * scale_vec.lane<0>()) * ls_rdet1;
+
+ vmask4 p1_mask = vint4::lane_id() != vint4(plane2_component);
+ vmask4 det_mask = abs(color_det1) > (color_mss1 * 1e-4f);
+ vmask4 notnan_mask = (ep0 == ep0) & (ep1 == ep1);
+ vmask4 full_mask = p1_mask & det_mask & notnan_mask;
+
+ ep.endpt0[0] = select(ep.endpt0[0], ep0, full_mask);
+ ep.endpt1[0] = select(ep.endpt1[0], ep1, full_mask);
+
+ if (fabsf(ls_det1) > (ls_mss1 * 1e-4f) && scale_ep0 == scale_ep0 && scale_ep1 == scale_ep1 && scale_ep0 < scale_ep1)
+ {
+ float scalediv2 = scale_ep0 / scale_ep1;
+ vfloat4 sdsm = scale_dir * scale_ep1;
+ rgbs_vector = vfloat4(sdsm.lane<0>(), sdsm.lane<1>(), sdsm.lane<2>(), scalediv2);
+ }
+ }
+
+ if (wmin2 >= wmax2 * 0.999f)
+ {
+ // If all weights in the partition were equal, then just take average of all colors in
+ // the partition and use that as both endpoint colors
+ vfloat4 avg = (color_vec_x + color_vec_y) / rgba_weight_sum;
+
+ vmask4 notnan_mask = avg == avg;
+ vmask4 full_mask = p2_mask & notnan_mask;
+
+ ep.endpt0[0] = select(ep.endpt0[0], avg, full_mask);
+ ep.endpt1[0] = select(ep.endpt1[0], avg, full_mask);
+ }
+ else
+ {
+ // Otherwise, complete the analytic calculation of ideal-endpoint-values for the given
+ // set of texel weights and pixel colors
+ vfloat4 color_det2 = (left2_sum * right2_sum) - (middle2_sum * middle2_sum);
+ vfloat4 color_rdet2 = 1.0f / color_det2;
+
+ vfloat4 color_mss2 = (left2_sum * left2_sum)
+ + (2.0f * middle2_sum * middle2_sum)
+ + (right2_sum * right2_sum);
+
+ vfloat4 ep0 = (right2_sum * color_vec_x - middle2_sum * color_vec_y) * color_rdet2;
+ vfloat4 ep1 = (left2_sum * color_vec_y - middle2_sum * color_vec_x) * color_rdet2;
+
+ vmask4 det_mask = abs(color_det2) > (color_mss2 * 1e-4f);
+ vmask4 notnan_mask = (ep0 == ep0) & (ep1 == ep1);
+ vmask4 full_mask = p2_mask & det_mask & notnan_mask;
+
+ ep.endpt0[0] = select(ep.endpt0[0], ep0, full_mask);
+ ep.endpt1[0] = select(ep.endpt1[0], ep1, full_mask);
+ }
+
+ // Calculations specific to mode #7, the HDR RGB-scale mode - skip if known LDR
+ if (blk.rgb_lns[0] || blk.alpha_lns[0])
+ {
+ weight_weight_sum = weight_weight_sum * color_weight;
+ float psum = dot3_s(select(right1_sum, right2_sum, p2_mask), color_weight);
+
+ vfloat4 rgbq_sum = color_vec_x + color_vec_y;
+ rgbq_sum.set_lane<3>(hadd_rgb_s(color_vec_y));
+
+ rgbo_vector = compute_rgbo_vector(rgba_weight_sum, weight_weight_sum, rgbq_sum, psum);
+
+ // We can get a failure due to the use of a singular (non-invertible) matrix
+ // If it failed, compute rgbo_vectors[] with a different method ...
+ if (astc::isnan(dot_s(rgbo_vector, rgbo_vector)))
+ {
+ vfloat4 v0 = ep.endpt0[0];
+ vfloat4 v1 = ep.endpt1[0];
+
+ float avgdif = hadd_rgb_s(v1 - v0) * (1.0f / 3.0f);
+ avgdif = astc::max(avgdif, 0.0f);
+
+ vfloat4 avg = (v0 + v1) * 0.5f;
+ vfloat4 ep0 = avg - vfloat4(avgdif) * 0.5f;
+
+ rgbo_vector = vfloat4(ep0.lane<0>(), ep0.lane<1>(), ep0.lane<2>(), avgdif);
+ }
+ }
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_image.cpp b/thirdparty/astcenc/astcenc_image.cpp
new file mode 100644
index 0000000000..9c0d6727d0
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_image.cpp
@@ -0,0 +1,558 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Functions for creating in-memory ASTC image structures.
+ */
+
+#include <cassert>
+#include <cstring>
+
+#include "astcenc_internal.h"
+
+/**
+ * @brief Loader pipeline function type for data fetch from memory.
+ */
+using pixel_loader = vfloat4(*)(const void*, int);
+
+/**
+ * @brief Loader pipeline function type for swizzling data in a vector.
+ */
+using pixel_swizzler = vfloat4(*)(vfloat4, const astcenc_swizzle&);
+
+/**
+ * @brief Loader pipeline function type for converting data in a vector to LNS.
+ */
+using pixel_converter = vfloat4(*)(vfloat4, vmask4);
+
+/**
+ * @brief Load a 8-bit UNORM texel from a data array.
+ *
+ * @param data The data pointer.
+ * @param base_offset The index offset to the start of the pixel.
+ */
+static vfloat4 load_texel_u8(
+ const void* data,
+ int base_offset
+) {
+ const uint8_t* data8 = static_cast<const uint8_t*>(data);
+ return int_to_float(vint4(data8 + base_offset)) / 255.0f;
+}
+
+/**
+ * @brief Load a 16-bit fp16 texel from a data array.
+ *
+ * @param data The data pointer.
+ * @param base_offset The index offset to the start of the pixel.
+ */
+static vfloat4 load_texel_f16(
+ const void* data,
+ int base_offset
+) {
+ const uint16_t* data16 = static_cast<const uint16_t*>(data);
+ int r = data16[base_offset ];
+ int g = data16[base_offset + 1];
+ int b = data16[base_offset + 2];
+ int a = data16[base_offset + 3];
+ return float16_to_float(vint4(r, g, b, a));
+}
+
+/**
+ * @brief Load a 32-bit float texel from a data array.
+ *
+ * @param data The data pointer.
+ * @param base_offset The index offset to the start of the pixel.
+ */
+static vfloat4 load_texel_f32(
+ const void* data,
+ int base_offset
+) {
+ const float* data32 = static_cast<const float*>(data);
+ return vfloat4(data32 + base_offset);
+}
+
+/**
+ * @brief Dummy no-op swizzle function.
+ *
+ * @param data The source RGBA vector to swizzle.
+ * @param swz The swizzle to use.
+ */
+static vfloat4 swz_texel_skip(
+ vfloat4 data,
+ const astcenc_swizzle& swz
+) {
+ (void)swz;
+ return data;
+}
+
+/**
+ * @brief Swizzle a texel into a new arrangement.
+ *
+ * @param data The source RGBA vector to swizzle.
+ * @param swz The swizzle to use.
+ */
+static vfloat4 swz_texel(
+ vfloat4 data,
+ const astcenc_swizzle& swz
+) {
+ alignas(16) float datas[6];
+
+ storea(data, datas);
+ datas[ASTCENC_SWZ_0] = 0.0f;
+ datas[ASTCENC_SWZ_1] = 1.0f;
+
+ return vfloat4(datas[swz.r], datas[swz.g], datas[swz.b], datas[swz.a]);
+}
+
+/**
+ * @brief Encode a texel that is entirely LDR linear.
+ *
+ * @param data The RGBA data to encode.
+ * @param lns_mask The mask for the HDR channels than need LNS encoding.
+ */
+static vfloat4 encode_texel_unorm(
+ vfloat4 data,
+ vmask4 lns_mask
+) {
+ (void)lns_mask;
+ return data * 65535.0f;
+}
+
+/**
+ * @brief Encode a texel that includes at least some HDR LNS texels.
+ *
+ * @param data The RGBA data to encode.
+ * @param lns_mask The mask for the HDR channels than need LNS encoding.
+ */
+static vfloat4 encode_texel_lns(
+ vfloat4 data,
+ vmask4 lns_mask
+) {
+ vfloat4 datav_unorm = data * 65535.0f;
+ vfloat4 datav_lns = float_to_lns(data);
+ return select(datav_unorm, datav_lns, lns_mask);
+}
+
+/* See header for documentation. */
+void load_image_block(
+ astcenc_profile decode_mode,
+ const astcenc_image& img,
+ image_block& blk,
+ const block_size_descriptor& bsd,
+ unsigned int xpos,
+ unsigned int ypos,
+ unsigned int zpos,
+ const astcenc_swizzle& swz
+) {
+ unsigned int xsize = img.dim_x;
+ unsigned int ysize = img.dim_y;
+ unsigned int zsize = img.dim_z;
+
+ blk.xpos = xpos;
+ blk.ypos = ypos;
+ blk.zpos = zpos;
+
+ // True if any non-identity swizzle
+ bool needs_swz = (swz.r != ASTCENC_SWZ_R) || (swz.g != ASTCENC_SWZ_G) ||
+ (swz.b != ASTCENC_SWZ_B) || (swz.a != ASTCENC_SWZ_A);
+
+ int idx = 0;
+
+ vfloat4 data_min(1e38f);
+ vfloat4 data_mean(0.0f);
+ vfloat4 data_mean_scale(1.0f / static_cast<float>(bsd.texel_count));
+ vfloat4 data_max(-1e38f);
+ vmask4 grayscalev(true);
+
+ // This works because we impose the same choice everywhere during encode
+ uint8_t rgb_lns = (decode_mode == ASTCENC_PRF_HDR) ||
+ (decode_mode == ASTCENC_PRF_HDR_RGB_LDR_A) ? 1 : 0;
+ uint8_t a_lns = decode_mode == ASTCENC_PRF_HDR ? 1 : 0;
+ vint4 use_lns(rgb_lns, rgb_lns, rgb_lns, a_lns);
+ vmask4 lns_mask = use_lns != vint4::zero();
+
+ // Set up the function pointers for loading pipeline as needed
+ pixel_loader loader = load_texel_u8;
+ if (img.data_type == ASTCENC_TYPE_F16)
+ {
+ loader = load_texel_f16;
+ }
+ else if (img.data_type == ASTCENC_TYPE_F32)
+ {
+ loader = load_texel_f32;
+ }
+
+ pixel_swizzler swizzler = swz_texel_skip;
+ if (needs_swz)
+ {
+ swizzler = swz_texel;
+ }
+
+ pixel_converter converter = encode_texel_unorm;
+ if (any(lns_mask))
+ {
+ converter = encode_texel_lns;
+ }
+
+ for (unsigned int z = 0; z < bsd.zdim; z++)
+ {
+ unsigned int zi = astc::min(zpos + z, zsize - 1);
+ void* plane = img.data[zi];
+
+ for (unsigned int y = 0; y < bsd.ydim; y++)
+ {
+ unsigned int yi = astc::min(ypos + y, ysize - 1);
+
+ for (unsigned int x = 0; x < bsd.xdim; x++)
+ {
+ unsigned int xi = astc::min(xpos + x, xsize - 1);
+
+ vfloat4 datav = loader(plane, (4 * xsize * yi) + (4 * xi));
+ datav = swizzler(datav, swz);
+ datav = converter(datav, lns_mask);
+
+ // Compute block metadata
+ data_min = min(data_min, datav);
+ data_mean += datav * data_mean_scale;
+ data_max = max(data_max, datav);
+
+ grayscalev = grayscalev & (datav.swz<0,0,0,0>() == datav.swz<1,1,2,2>());
+
+ blk.data_r[idx] = datav.lane<0>();
+ blk.data_g[idx] = datav.lane<1>();
+ blk.data_b[idx] = datav.lane<2>();
+ blk.data_a[idx] = datav.lane<3>();
+
+ blk.rgb_lns[idx] = rgb_lns;
+ blk.alpha_lns[idx] = a_lns;
+
+ idx++;
+ }
+ }
+ }
+
+ // Reverse the encoding so we store origin block in the original format
+ vfloat4 data_enc = blk.texel(0);
+ vfloat4 data_enc_unorm = data_enc / 65535.0f;
+ vfloat4 data_enc_lns = vfloat4::zero();
+
+ if (rgb_lns || a_lns)
+ {
+ data_enc_lns = float16_to_float(lns_to_sf16(float_to_int(data_enc)));
+ }
+
+ blk.origin_texel = select(data_enc_unorm, data_enc_lns, lns_mask);
+
+ // Store block metadata
+ blk.data_min = data_min;
+ blk.data_mean = data_mean;
+ blk.data_max = data_max;
+ blk.grayscale = all(grayscalev);
+}
+
+/* See header for documentation. */
+void load_image_block_fast_ldr(
+ astcenc_profile decode_mode,
+ const astcenc_image& img,
+ image_block& blk,
+ const block_size_descriptor& bsd,
+ unsigned int xpos,
+ unsigned int ypos,
+ unsigned int zpos,
+ const astcenc_swizzle& swz
+) {
+ (void)swz;
+ (void)decode_mode;
+
+ unsigned int xsize = img.dim_x;
+ unsigned int ysize = img.dim_y;
+
+ blk.xpos = xpos;
+ blk.ypos = ypos;
+ blk.zpos = zpos;
+
+ vfloat4 data_min(1e38f);
+ vfloat4 data_mean = vfloat4::zero();
+ vfloat4 data_max(-1e38f);
+ vmask4 grayscalev(true);
+ int idx = 0;
+
+ const uint8_t* plane = static_cast<const uint8_t*>(img.data[0]);
+ for (unsigned int y = ypos; y < ypos + bsd.ydim; y++)
+ {
+ unsigned int yi = astc::min(y, ysize - 1);
+
+ for (unsigned int x = xpos; x < xpos + bsd.xdim; x++)
+ {
+ unsigned int xi = astc::min(x, xsize - 1);
+
+ vint4 datavi = vint4(plane + (4 * xsize * yi) + (4 * xi));
+ vfloat4 datav = int_to_float(datavi) * (65535.0f / 255.0f);
+
+ // Compute block metadata
+ data_min = min(data_min, datav);
+ data_mean += datav;
+ data_max = max(data_max, datav);
+
+ grayscalev = grayscalev & (datav.swz<0,0,0,0>() == datav.swz<1,1,2,2>());
+
+ blk.data_r[idx] = datav.lane<0>();
+ blk.data_g[idx] = datav.lane<1>();
+ blk.data_b[idx] = datav.lane<2>();
+ blk.data_a[idx] = datav.lane<3>();
+
+ idx++;
+ }
+ }
+
+ // Reverse the encoding so we store origin block in the original format
+ blk.origin_texel = blk.texel(0) / 65535.0f;
+
+ // Store block metadata
+ blk.rgb_lns[0] = 0;
+ blk.alpha_lns[0] = 0;
+ blk.data_min = data_min;
+ blk.data_mean = data_mean / static_cast<float>(bsd.texel_count);
+ blk.data_max = data_max;
+ blk.grayscale = all(grayscalev);
+}
+
+/* See header for documentation. */
+void store_image_block(
+ astcenc_image& img,
+ const image_block& blk,
+ const block_size_descriptor& bsd,
+ unsigned int xpos,
+ unsigned int ypos,
+ unsigned int zpos,
+ const astcenc_swizzle& swz
+) {
+ unsigned int x_size = img.dim_x;
+ unsigned int x_start = xpos;
+ unsigned int x_end = astc::min(x_size, xpos + bsd.xdim);
+ unsigned int x_count = x_end - x_start;
+ unsigned int x_nudge = bsd.xdim - x_count;
+
+ unsigned int y_size = img.dim_y;
+ unsigned int y_start = ypos;
+ unsigned int y_end = astc::min(y_size, ypos + bsd.ydim);
+ unsigned int y_count = y_end - y_start;
+ unsigned int y_nudge = (bsd.ydim - y_count) * bsd.xdim;
+
+ unsigned int z_size = img.dim_z;
+ unsigned int z_start = zpos;
+ unsigned int z_end = astc::min(z_size, zpos + bsd.zdim);
+
+ // True if any non-identity swizzle
+ bool needs_swz = (swz.r != ASTCENC_SWZ_R) || (swz.g != ASTCENC_SWZ_G) ||
+ (swz.b != ASTCENC_SWZ_B) || (swz.a != ASTCENC_SWZ_A);
+
+ // True if any swizzle uses Z reconstruct
+ bool needs_z = (swz.r == ASTCENC_SWZ_Z) || (swz.g == ASTCENC_SWZ_Z) ||
+ (swz.b == ASTCENC_SWZ_Z) || (swz.a == ASTCENC_SWZ_Z);
+
+ int idx = 0;
+ if (img.data_type == ASTCENC_TYPE_U8)
+ {
+ for (unsigned int z = z_start; z < z_end; z++)
+ {
+ // Fetch the image plane
+ uint8_t* data8 = static_cast<uint8_t*>(img.data[z]);
+
+ for (unsigned int y = y_start; y < y_end; y++)
+ {
+ uint8_t* data8_row = data8 + (4 * x_size * y) + (4 * x_start);
+
+ for (unsigned int x = 0; x < x_count; x += ASTCENC_SIMD_WIDTH)
+ {
+ unsigned int max_texels = ASTCENC_SIMD_WIDTH;
+ unsigned int used_texels = astc::min(x_count - x, max_texels);
+
+ // Unaligned load as rows are not always SIMD_WIDTH long
+ vfloat data_r(blk.data_r + idx);
+ vfloat data_g(blk.data_g + idx);
+ vfloat data_b(blk.data_b + idx);
+ vfloat data_a(blk.data_a + idx);
+
+ vint data_ri = float_to_int_rtn(min(data_r, 1.0f) * 255.0f);
+ vint data_gi = float_to_int_rtn(min(data_g, 1.0f) * 255.0f);
+ vint data_bi = float_to_int_rtn(min(data_b, 1.0f) * 255.0f);
+ vint data_ai = float_to_int_rtn(min(data_a, 1.0f) * 255.0f);
+
+ if (needs_swz)
+ {
+ vint swizzle_table[7];
+ swizzle_table[ASTCENC_SWZ_0] = vint(0);
+ swizzle_table[ASTCENC_SWZ_1] = vint(255);
+ swizzle_table[ASTCENC_SWZ_R] = data_ri;
+ swizzle_table[ASTCENC_SWZ_G] = data_gi;
+ swizzle_table[ASTCENC_SWZ_B] = data_bi;
+ swizzle_table[ASTCENC_SWZ_A] = data_ai;
+
+ if (needs_z)
+ {
+ vfloat data_x = (data_r * vfloat(2.0f)) - vfloat(1.0f);
+ vfloat data_y = (data_a * vfloat(2.0f)) - vfloat(1.0f);
+ vfloat data_z = vfloat(1.0f) - (data_x * data_x) - (data_y * data_y);
+ data_z = max(data_z, 0.0f);
+ data_z = (sqrt(data_z) * vfloat(0.5f)) + vfloat(0.5f);
+
+ swizzle_table[ASTCENC_SWZ_Z] = float_to_int_rtn(min(data_z, 1.0f) * 255.0f);
+ }
+
+ data_ri = swizzle_table[swz.r];
+ data_gi = swizzle_table[swz.g];
+ data_bi = swizzle_table[swz.b];
+ data_ai = swizzle_table[swz.a];
+ }
+
+ // Errors are NaN encoded - convert to magenta error color
+ // Branch is OK here - it is almost never true so predicts well
+ vmask nan_mask = data_r != data_r;
+ if (any(nan_mask))
+ {
+ data_ri = select(data_ri, vint(0xFF), nan_mask);
+ data_gi = select(data_gi, vint(0x00), nan_mask);
+ data_bi = select(data_bi, vint(0xFF), nan_mask);
+ data_ai = select(data_ai, vint(0xFF), nan_mask);
+ }
+
+ vint data_rgbai = interleave_rgba8(data_ri, data_gi, data_bi, data_ai);
+ vmask store_mask = vint::lane_id() < vint(used_texels);
+ store_lanes_masked(reinterpret_cast<int*>(data8_row), data_rgbai, store_mask);
+
+ data8_row += ASTCENC_SIMD_WIDTH * 4;
+ idx += used_texels;
+ }
+ idx += x_nudge;
+ }
+ idx += y_nudge;
+ }
+ }
+ else if (img.data_type == ASTCENC_TYPE_F16)
+ {
+ for (unsigned int z = z_start; z < z_end; z++)
+ {
+ // Fetch the image plane
+ uint16_t* data16 = static_cast<uint16_t*>(img.data[z]);
+
+ for (unsigned int y = y_start; y < y_end; y++)
+ {
+ uint16_t* data16_row = data16 + (4 * x_size * y) + (4 * x_start);
+
+ for (unsigned int x = 0; x < x_count; x++)
+ {
+ vint4 color;
+
+ // NaNs are handled inline - no need to special case
+ if (needs_swz)
+ {
+ float data[7];
+ data[ASTCENC_SWZ_0] = 0.0f;
+ data[ASTCENC_SWZ_1] = 1.0f;
+ data[ASTCENC_SWZ_R] = blk.data_r[idx];
+ data[ASTCENC_SWZ_G] = blk.data_g[idx];
+ data[ASTCENC_SWZ_B] = blk.data_b[idx];
+ data[ASTCENC_SWZ_A] = blk.data_a[idx];
+
+ if (needs_z)
+ {
+ float xN = (data[0] * 2.0f) - 1.0f;
+ float yN = (data[3] * 2.0f) - 1.0f;
+ float zN = 1.0f - xN * xN - yN * yN;
+ if (zN < 0.0f)
+ {
+ zN = 0.0f;
+ }
+ data[ASTCENC_SWZ_Z] = (astc::sqrt(zN) * 0.5f) + 0.5f;
+ }
+
+ vfloat4 colorf(data[swz.r], data[swz.g], data[swz.b], data[swz.a]);
+ color = float_to_float16(colorf);
+ }
+ else
+ {
+ vfloat4 colorf = blk.texel(idx);
+ color = float_to_float16(colorf);
+ }
+
+ // TODO: Vectorize with store N shorts?
+ data16_row[0] = static_cast<uint16_t>(color.lane<0>());
+ data16_row[1] = static_cast<uint16_t>(color.lane<1>());
+ data16_row[2] = static_cast<uint16_t>(color.lane<2>());
+ data16_row[3] = static_cast<uint16_t>(color.lane<3>());
+ data16_row += 4;
+ idx++;
+ }
+ idx += x_nudge;
+ }
+ idx += y_nudge;
+ }
+ }
+ else // if (img.data_type == ASTCENC_TYPE_F32)
+ {
+ assert(img.data_type == ASTCENC_TYPE_F32);
+
+ for (unsigned int z = z_start; z < z_end; z++)
+ {
+ // Fetch the image plane
+ float* data32 = static_cast<float*>(img.data[z]);
+
+ for (unsigned int y = y_start; y < y_end; y++)
+ {
+ float* data32_row = data32 + (4 * x_size * y) + (4 * x_start);
+
+ for (unsigned int x = 0; x < x_count; x++)
+ {
+ vfloat4 color = blk.texel(idx);
+
+ // NaNs are handled inline - no need to special case
+ if (needs_swz)
+ {
+ float data[7];
+ data[ASTCENC_SWZ_0] = 0.0f;
+ data[ASTCENC_SWZ_1] = 1.0f;
+ data[ASTCENC_SWZ_R] = color.lane<0>();
+ data[ASTCENC_SWZ_G] = color.lane<1>();
+ data[ASTCENC_SWZ_B] = color.lane<2>();
+ data[ASTCENC_SWZ_A] = color.lane<3>();
+
+ if (needs_z)
+ {
+ float xN = (data[0] * 2.0f) - 1.0f;
+ float yN = (data[3] * 2.0f) - 1.0f;
+ float zN = 1.0f - xN * xN - yN * yN;
+ if (zN < 0.0f)
+ {
+ zN = 0.0f;
+ }
+ data[ASTCENC_SWZ_Z] = (astc::sqrt(zN) * 0.5f) + 0.5f;
+ }
+
+ color = vfloat4(data[swz.r], data[swz.g], data[swz.b], data[swz.a]);
+ }
+
+ store(color, data32_row);
+ data32_row += 4;
+ idx++;
+ }
+ idx += x_nudge;
+ }
+ idx += y_nudge;
+ }
+ }
+}
diff --git a/thirdparty/astcenc/astcenc_integer_sequence.cpp b/thirdparty/astcenc/astcenc_integer_sequence.cpp
new file mode 100644
index 0000000000..416750374d
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_integer_sequence.cpp
@@ -0,0 +1,739 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2021 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Functions for encoding/decoding Bounded Integer Sequence Encoding.
+ */
+
+#include "astcenc_internal.h"
+
+#include <array>
+
+/** @brief Unpacked quint triplets <low,middle,high> for each packed value */
+// TODO: Bitpack these into a uint16_t?
+static const uint8_t quints_of_integer[128][3] {
+ {0, 0, 0}, {1, 0, 0}, {2, 0, 0}, {3, 0, 0},
+ {4, 0, 0}, {0, 4, 0}, {4, 4, 0}, {4, 4, 4},
+ {0, 1, 0}, {1, 1, 0}, {2, 1, 0}, {3, 1, 0},
+ {4, 1, 0}, {1, 4, 0}, {4, 4, 1}, {4, 4, 4},
+ {0, 2, 0}, {1, 2, 0}, {2, 2, 0}, {3, 2, 0},
+ {4, 2, 0}, {2, 4, 0}, {4, 4, 2}, {4, 4, 4},
+ {0, 3, 0}, {1, 3, 0}, {2, 3, 0}, {3, 3, 0},
+ {4, 3, 0}, {3, 4, 0}, {4, 4, 3}, {4, 4, 4},
+ {0, 0, 1}, {1, 0, 1}, {2, 0, 1}, {3, 0, 1},
+ {4, 0, 1}, {0, 4, 1}, {4, 0, 4}, {0, 4, 4},
+ {0, 1, 1}, {1, 1, 1}, {2, 1, 1}, {3, 1, 1},
+ {4, 1, 1}, {1, 4, 1}, {4, 1, 4}, {1, 4, 4},
+ {0, 2, 1}, {1, 2, 1}, {2, 2, 1}, {3, 2, 1},
+ {4, 2, 1}, {2, 4, 1}, {4, 2, 4}, {2, 4, 4},
+ {0, 3, 1}, {1, 3, 1}, {2, 3, 1}, {3, 3, 1},
+ {4, 3, 1}, {3, 4, 1}, {4, 3, 4}, {3, 4, 4},
+ {0, 0, 2}, {1, 0, 2}, {2, 0, 2}, {3, 0, 2},
+ {4, 0, 2}, {0, 4, 2}, {2, 0, 4}, {3, 0, 4},
+ {0, 1, 2}, {1, 1, 2}, {2, 1, 2}, {3, 1, 2},
+ {4, 1, 2}, {1, 4, 2}, {2, 1, 4}, {3, 1, 4},
+ {0, 2, 2}, {1, 2, 2}, {2, 2, 2}, {3, 2, 2},
+ {4, 2, 2}, {2, 4, 2}, {2, 2, 4}, {3, 2, 4},
+ {0, 3, 2}, {1, 3, 2}, {2, 3, 2}, {3, 3, 2},
+ {4, 3, 2}, {3, 4, 2}, {2, 3, 4}, {3, 3, 4},
+ {0, 0, 3}, {1, 0, 3}, {2, 0, 3}, {3, 0, 3},
+ {4, 0, 3}, {0, 4, 3}, {0, 0, 4}, {1, 0, 4},
+ {0, 1, 3}, {1, 1, 3}, {2, 1, 3}, {3, 1, 3},
+ {4, 1, 3}, {1, 4, 3}, {0, 1, 4}, {1, 1, 4},
+ {0, 2, 3}, {1, 2, 3}, {2, 2, 3}, {3, 2, 3},
+ {4, 2, 3}, {2, 4, 3}, {0, 2, 4}, {1, 2, 4},
+ {0, 3, 3}, {1, 3, 3}, {2, 3, 3}, {3, 3, 3},
+ {4, 3, 3}, {3, 4, 3}, {0, 3, 4}, {1, 3, 4}
+};
+
+/** @brief Packed quint values for each unpacked value, indexed [hi][mid][lo]. */
+static const uint8_t integer_of_quints[5][5][5] {
+ {
+ {0, 1, 2, 3, 4},
+ {8, 9, 10, 11, 12},
+ {16, 17, 18, 19, 20},
+ {24, 25, 26, 27, 28},
+ {5, 13, 21, 29, 6}
+ },
+ {
+ {32, 33, 34, 35, 36},
+ {40, 41, 42, 43, 44},
+ {48, 49, 50, 51, 52},
+ {56, 57, 58, 59, 60},
+ {37, 45, 53, 61, 14}
+ },
+ {
+ {64, 65, 66, 67, 68},
+ {72, 73, 74, 75, 76},
+ {80, 81, 82, 83, 84},
+ {88, 89, 90, 91, 92},
+ {69, 77, 85, 93, 22}
+ },
+ {
+ {96, 97, 98, 99, 100},
+ {104, 105, 106, 107, 108},
+ {112, 113, 114, 115, 116},
+ {120, 121, 122, 123, 124},
+ {101, 109, 117, 125, 30}
+ },
+ {
+ {102, 103, 70, 71, 38},
+ {110, 111, 78, 79, 46},
+ {118, 119, 86, 87, 54},
+ {126, 127, 94, 95, 62},
+ {39, 47, 55, 63, 31}
+ }
+};
+
+/** @brief Unpacked trit quintuplets <low,...,high> for each packed value */
+// TODO: Bitpack these into a uint16_t?
+static const uint8_t trits_of_integer[256][5] {
+ {0, 0, 0, 0, 0}, {1, 0, 0, 0, 0}, {2, 0, 0, 0, 0}, {0, 0, 2, 0, 0},
+ {0, 1, 0, 0, 0}, {1, 1, 0, 0, 0}, {2, 1, 0, 0, 0}, {1, 0, 2, 0, 0},
+ {0, 2, 0, 0, 0}, {1, 2, 0, 0, 0}, {2, 2, 0, 0, 0}, {2, 0, 2, 0, 0},
+ {0, 2, 2, 0, 0}, {1, 2, 2, 0, 0}, {2, 2, 2, 0, 0}, {2, 0, 2, 0, 0},
+ {0, 0, 1, 0, 0}, {1, 0, 1, 0, 0}, {2, 0, 1, 0, 0}, {0, 1, 2, 0, 0},
+ {0, 1, 1, 0, 0}, {1, 1, 1, 0, 0}, {2, 1, 1, 0, 0}, {1, 1, 2, 0, 0},
+ {0, 2, 1, 0, 0}, {1, 2, 1, 0, 0}, {2, 2, 1, 0, 0}, {2, 1, 2, 0, 0},
+ {0, 0, 0, 2, 2}, {1, 0, 0, 2, 2}, {2, 0, 0, 2, 2}, {0, 0, 2, 2, 2},
+ {0, 0, 0, 1, 0}, {1, 0, 0, 1, 0}, {2, 0, 0, 1, 0}, {0, 0, 2, 1, 0},
+ {0, 1, 0, 1, 0}, {1, 1, 0, 1, 0}, {2, 1, 0, 1, 0}, {1, 0, 2, 1, 0},
+ {0, 2, 0, 1, 0}, {1, 2, 0, 1, 0}, {2, 2, 0, 1, 0}, {2, 0, 2, 1, 0},
+ {0, 2, 2, 1, 0}, {1, 2, 2, 1, 0}, {2, 2, 2, 1, 0}, {2, 0, 2, 1, 0},
+ {0, 0, 1, 1, 0}, {1, 0, 1, 1, 0}, {2, 0, 1, 1, 0}, {0, 1, 2, 1, 0},
+ {0, 1, 1, 1, 0}, {1, 1, 1, 1, 0}, {2, 1, 1, 1, 0}, {1, 1, 2, 1, 0},
+ {0, 2, 1, 1, 0}, {1, 2, 1, 1, 0}, {2, 2, 1, 1, 0}, {2, 1, 2, 1, 0},
+ {0, 1, 0, 2, 2}, {1, 1, 0, 2, 2}, {2, 1, 0, 2, 2}, {1, 0, 2, 2, 2},
+ {0, 0, 0, 2, 0}, {1, 0, 0, 2, 0}, {2, 0, 0, 2, 0}, {0, 0, 2, 2, 0},
+ {0, 1, 0, 2, 0}, {1, 1, 0, 2, 0}, {2, 1, 0, 2, 0}, {1, 0, 2, 2, 0},
+ {0, 2, 0, 2, 0}, {1, 2, 0, 2, 0}, {2, 2, 0, 2, 0}, {2, 0, 2, 2, 0},
+ {0, 2, 2, 2, 0}, {1, 2, 2, 2, 0}, {2, 2, 2, 2, 0}, {2, 0, 2, 2, 0},
+ {0, 0, 1, 2, 0}, {1, 0, 1, 2, 0}, {2, 0, 1, 2, 0}, {0, 1, 2, 2, 0},
+ {0, 1, 1, 2, 0}, {1, 1, 1, 2, 0}, {2, 1, 1, 2, 0}, {1, 1, 2, 2, 0},
+ {0, 2, 1, 2, 0}, {1, 2, 1, 2, 0}, {2, 2, 1, 2, 0}, {2, 1, 2, 2, 0},
+ {0, 2, 0, 2, 2}, {1, 2, 0, 2, 2}, {2, 2, 0, 2, 2}, {2, 0, 2, 2, 2},
+ {0, 0, 0, 0, 2}, {1, 0, 0, 0, 2}, {2, 0, 0, 0, 2}, {0, 0, 2, 0, 2},
+ {0, 1, 0, 0, 2}, {1, 1, 0, 0, 2}, {2, 1, 0, 0, 2}, {1, 0, 2, 0, 2},
+ {0, 2, 0, 0, 2}, {1, 2, 0, 0, 2}, {2, 2, 0, 0, 2}, {2, 0, 2, 0, 2},
+ {0, 2, 2, 0, 2}, {1, 2, 2, 0, 2}, {2, 2, 2, 0, 2}, {2, 0, 2, 0, 2},
+ {0, 0, 1, 0, 2}, {1, 0, 1, 0, 2}, {2, 0, 1, 0, 2}, {0, 1, 2, 0, 2},
+ {0, 1, 1, 0, 2}, {1, 1, 1, 0, 2}, {2, 1, 1, 0, 2}, {1, 1, 2, 0, 2},
+ {0, 2, 1, 0, 2}, {1, 2, 1, 0, 2}, {2, 2, 1, 0, 2}, {2, 1, 2, 0, 2},
+ {0, 2, 2, 2, 2}, {1, 2, 2, 2, 2}, {2, 2, 2, 2, 2}, {2, 0, 2, 2, 2},
+ {0, 0, 0, 0, 1}, {1, 0, 0, 0, 1}, {2, 0, 0, 0, 1}, {0, 0, 2, 0, 1},
+ {0, 1, 0, 0, 1}, {1, 1, 0, 0, 1}, {2, 1, 0, 0, 1}, {1, 0, 2, 0, 1},
+ {0, 2, 0, 0, 1}, {1, 2, 0, 0, 1}, {2, 2, 0, 0, 1}, {2, 0, 2, 0, 1},
+ {0, 2, 2, 0, 1}, {1, 2, 2, 0, 1}, {2, 2, 2, 0, 1}, {2, 0, 2, 0, 1},
+ {0, 0, 1, 0, 1}, {1, 0, 1, 0, 1}, {2, 0, 1, 0, 1}, {0, 1, 2, 0, 1},
+ {0, 1, 1, 0, 1}, {1, 1, 1, 0, 1}, {2, 1, 1, 0, 1}, {1, 1, 2, 0, 1},
+ {0, 2, 1, 0, 1}, {1, 2, 1, 0, 1}, {2, 2, 1, 0, 1}, {2, 1, 2, 0, 1},
+ {0, 0, 1, 2, 2}, {1, 0, 1, 2, 2}, {2, 0, 1, 2, 2}, {0, 1, 2, 2, 2},
+ {0, 0, 0, 1, 1}, {1, 0, 0, 1, 1}, {2, 0, 0, 1, 1}, {0, 0, 2, 1, 1},
+ {0, 1, 0, 1, 1}, {1, 1, 0, 1, 1}, {2, 1, 0, 1, 1}, {1, 0, 2, 1, 1},
+ {0, 2, 0, 1, 1}, {1, 2, 0, 1, 1}, {2, 2, 0, 1, 1}, {2, 0, 2, 1, 1},
+ {0, 2, 2, 1, 1}, {1, 2, 2, 1, 1}, {2, 2, 2, 1, 1}, {2, 0, 2, 1, 1},
+ {0, 0, 1, 1, 1}, {1, 0, 1, 1, 1}, {2, 0, 1, 1, 1}, {0, 1, 2, 1, 1},
+ {0, 1, 1, 1, 1}, {1, 1, 1, 1, 1}, {2, 1, 1, 1, 1}, {1, 1, 2, 1, 1},
+ {0, 2, 1, 1, 1}, {1, 2, 1, 1, 1}, {2, 2, 1, 1, 1}, {2, 1, 2, 1, 1},
+ {0, 1, 1, 2, 2}, {1, 1, 1, 2, 2}, {2, 1, 1, 2, 2}, {1, 1, 2, 2, 2},
+ {0, 0, 0, 2, 1}, {1, 0, 0, 2, 1}, {2, 0, 0, 2, 1}, {0, 0, 2, 2, 1},
+ {0, 1, 0, 2, 1}, {1, 1, 0, 2, 1}, {2, 1, 0, 2, 1}, {1, 0, 2, 2, 1},
+ {0, 2, 0, 2, 1}, {1, 2, 0, 2, 1}, {2, 2, 0, 2, 1}, {2, 0, 2, 2, 1},
+ {0, 2, 2, 2, 1}, {1, 2, 2, 2, 1}, {2, 2, 2, 2, 1}, {2, 0, 2, 2, 1},
+ {0, 0, 1, 2, 1}, {1, 0, 1, 2, 1}, {2, 0, 1, 2, 1}, {0, 1, 2, 2, 1},
+ {0, 1, 1, 2, 1}, {1, 1, 1, 2, 1}, {2, 1, 1, 2, 1}, {1, 1, 2, 2, 1},
+ {0, 2, 1, 2, 1}, {1, 2, 1, 2, 1}, {2, 2, 1, 2, 1}, {2, 1, 2, 2, 1},
+ {0, 2, 1, 2, 2}, {1, 2, 1, 2, 2}, {2, 2, 1, 2, 2}, {2, 1, 2, 2, 2},
+ {0, 0, 0, 1, 2}, {1, 0, 0, 1, 2}, {2, 0, 0, 1, 2}, {0, 0, 2, 1, 2},
+ {0, 1, 0, 1, 2}, {1, 1, 0, 1, 2}, {2, 1, 0, 1, 2}, {1, 0, 2, 1, 2},
+ {0, 2, 0, 1, 2}, {1, 2, 0, 1, 2}, {2, 2, 0, 1, 2}, {2, 0, 2, 1, 2},
+ {0, 2, 2, 1, 2}, {1, 2, 2, 1, 2}, {2, 2, 2, 1, 2}, {2, 0, 2, 1, 2},
+ {0, 0, 1, 1, 2}, {1, 0, 1, 1, 2}, {2, 0, 1, 1, 2}, {0, 1, 2, 1, 2},
+ {0, 1, 1, 1, 2}, {1, 1, 1, 1, 2}, {2, 1, 1, 1, 2}, {1, 1, 2, 1, 2},
+ {0, 2, 1, 1, 2}, {1, 2, 1, 1, 2}, {2, 2, 1, 1, 2}, {2, 1, 2, 1, 2},
+ {0, 2, 2, 2, 2}, {1, 2, 2, 2, 2}, {2, 2, 2, 2, 2}, {2, 1, 2, 2, 2}
+};
+
+/** @brief Packed trit values for each unpacked value, indexed [hi][][][][lo]. */
+static const uint8_t integer_of_trits[3][3][3][3][3] {
+ {
+ {
+ {
+ {0, 1, 2},
+ {4, 5, 6},
+ {8, 9, 10}
+ },
+ {
+ {16, 17, 18},
+ {20, 21, 22},
+ {24, 25, 26}
+ },
+ {
+ {3, 7, 15},
+ {19, 23, 27},
+ {12, 13, 14}
+ }
+ },
+ {
+ {
+ {32, 33, 34},
+ {36, 37, 38},
+ {40, 41, 42}
+ },
+ {
+ {48, 49, 50},
+ {52, 53, 54},
+ {56, 57, 58}
+ },
+ {
+ {35, 39, 47},
+ {51, 55, 59},
+ {44, 45, 46}
+ }
+ },
+ {
+ {
+ {64, 65, 66},
+ {68, 69, 70},
+ {72, 73, 74}
+ },
+ {
+ {80, 81, 82},
+ {84, 85, 86},
+ {88, 89, 90}
+ },
+ {
+ {67, 71, 79},
+ {83, 87, 91},
+ {76, 77, 78}
+ }
+ }
+ },
+ {
+ {
+ {
+ {128, 129, 130},
+ {132, 133, 134},
+ {136, 137, 138}
+ },
+ {
+ {144, 145, 146},
+ {148, 149, 150},
+ {152, 153, 154}
+ },
+ {
+ {131, 135, 143},
+ {147, 151, 155},
+ {140, 141, 142}
+ }
+ },
+ {
+ {
+ {160, 161, 162},
+ {164, 165, 166},
+ {168, 169, 170}
+ },
+ {
+ {176, 177, 178},
+ {180, 181, 182},
+ {184, 185, 186}
+ },
+ {
+ {163, 167, 175},
+ {179, 183, 187},
+ {172, 173, 174}
+ }
+ },
+ {
+ {
+ {192, 193, 194},
+ {196, 197, 198},
+ {200, 201, 202}
+ },
+ {
+ {208, 209, 210},
+ {212, 213, 214},
+ {216, 217, 218}
+ },
+ {
+ {195, 199, 207},
+ {211, 215, 219},
+ {204, 205, 206}
+ }
+ }
+ },
+ {
+ {
+ {
+ {96, 97, 98},
+ {100, 101, 102},
+ {104, 105, 106}
+ },
+ {
+ {112, 113, 114},
+ {116, 117, 118},
+ {120, 121, 122}
+ },
+ {
+ {99, 103, 111},
+ {115, 119, 123},
+ {108, 109, 110}
+ }
+ },
+ {
+ {
+ {224, 225, 226},
+ {228, 229, 230},
+ {232, 233, 234}
+ },
+ {
+ {240, 241, 242},
+ {244, 245, 246},
+ {248, 249, 250}
+ },
+ {
+ {227, 231, 239},
+ {243, 247, 251},
+ {236, 237, 238}
+ }
+ },
+ {
+ {
+ {28, 29, 30},
+ {60, 61, 62},
+ {92, 93, 94}
+ },
+ {
+ {156, 157, 158},
+ {188, 189, 190},
+ {220, 221, 222}
+ },
+ {
+ {31, 63, 127},
+ {159, 191, 255},
+ {252, 253, 254}
+ }
+ }
+ }
+};
+
+/**
+ * @brief The number of bits, trits, and quints needed for a quant level.
+ */
+struct btq_count
+{
+ /** @brief The number of bits. */
+ uint8_t bits:6;
+
+ /** @brief The number of trits. */
+ uint8_t trits:1;
+
+ /** @brief The number of quints. */
+ uint8_t quints:1;
+};
+
+/**
+ * @brief The table of bits, trits, and quints needed for a quant encode.
+ */
+static const std::array<btq_count, 21> btq_counts {{
+ { 1, 0, 0 }, // QUANT_2
+ { 0, 1, 0 }, // QUANT_3
+ { 2, 0, 0 }, // QUANT_4
+ { 0, 0, 1 }, // QUANT_5
+ { 1, 1, 0 }, // QUANT_6
+ { 3, 0, 0 }, // QUANT_8
+ { 1, 0, 1 }, // QUANT_10
+ { 2, 1, 0 }, // QUANT_12
+ { 4, 0, 0 }, // QUANT_16
+ { 2, 0, 1 }, // QUANT_20
+ { 3, 1, 0 }, // QUANT_24
+ { 5, 0, 0 }, // QUANT_32
+ { 3, 0, 1 }, // QUANT_40
+ { 4, 1, 0 }, // QUANT_48
+ { 6, 0, 0 }, // QUANT_64
+ { 4, 0, 1 }, // QUANT_80
+ { 5, 1, 0 }, // QUANT_96
+ { 7, 0, 0 }, // QUANT_128
+ { 5, 0, 1 }, // QUANT_160
+ { 6, 1, 0 }, // QUANT_192
+ { 8, 0, 0 } // QUANT_256
+}};
+
+/**
+ * @brief The sequence scale, round, and divisors needed to compute sizing.
+ *
+ * The length of a quantized sequence in bits is:
+ * (scale * <sequence_len> + round) / divisor
+ */
+struct ise_size
+{
+ /** @brief The scaling parameter. */
+ uint8_t scale:6;
+
+ /** @brief The divisor parameter. */
+ uint8_t divisor:2;
+};
+
+/**
+ * @brief The table of scale, round, and divisors needed for quant sizing.
+ */
+static const std::array<ise_size, 21> ise_sizes {{
+ { 1, 0 }, // QUANT_2
+ { 8, 2 }, // QUANT_3
+ { 2, 0 }, // QUANT_4
+ { 7, 1 }, // QUANT_5
+ { 13, 2 }, // QUANT_6
+ { 3, 0 }, // QUANT_8
+ { 10, 1 }, // QUANT_10
+ { 18, 2 }, // QUANT_12
+ { 4, 0 }, // QUANT_16
+ { 13, 1 }, // QUANT_20
+ { 23, 2 }, // QUANT_24
+ { 5, 0 }, // QUANT_32
+ { 16, 1 }, // QUANT_40
+ { 28, 2 }, // QUANT_48
+ { 6, 0 }, // QUANT_64
+ { 19, 1 }, // QUANT_80
+ { 33, 2 }, // QUANT_96
+ { 7, 0 }, // QUANT_128
+ { 22, 1 }, // QUANT_160
+ { 38, 2 }, // QUANT_192
+ { 8, 0 } // QUANT_256
+}};
+
+/* See header for documentation. */
+unsigned int get_ise_sequence_bitcount(
+ unsigned int character_count,
+ quant_method quant_level
+) {
+ // Cope with out-of bounds values - input might be invalid
+ if (static_cast<size_t>(quant_level) >= ise_sizes.size())
+ {
+ // Arbitrary large number that's more than an ASTC block can hold
+ return 1024;
+ }
+
+ auto& entry = ise_sizes[quant_level];
+ unsigned int divisor = (entry.divisor << 1) + 1;
+ return (entry.scale * character_count + divisor - 1) / divisor;
+}
+
+/**
+ * @brief Write up to 8 bits at an arbitrary bit offset.
+ *
+ * The stored value is at most 8 bits, but can be stored at an offset of between 0 and 7 bits so may
+ * span two separate bytes in memory.
+ *
+ * @param value The value to write.
+ * @param bitcount The number of bits to write, starting from LSB.
+ * @param bitoffset The bit offset to store at, between 0 and 7.
+ * @param[in,out] ptr The data pointer to write to.
+ */
+static inline void write_bits(
+ unsigned int value,
+ unsigned int bitcount,
+ unsigned int bitoffset,
+ uint8_t ptr[2]
+) {
+ unsigned int mask = (1 << bitcount) - 1;
+ value &= mask;
+ ptr += bitoffset >> 3;
+ bitoffset &= 7;
+ value <<= bitoffset;
+ mask <<= bitoffset;
+ mask = ~mask;
+
+ ptr[0] &= mask;
+ ptr[0] |= value;
+ ptr[1] &= mask >> 8;
+ ptr[1] |= value >> 8;
+}
+
+/**
+ * @brief Read up to 8 bits at an arbitrary bit offset.
+ *
+ * The stored value is at most 8 bits, but can be stored at an offset of between 0 and 7 bits so may
+ * span two separate bytes in memory.
+ *
+ * @param bitcount The number of bits to read.
+ * @param bitoffset The bit offset to read from, between 0 and 7.
+ * @param[in,out] ptr The data pointer to read from.
+ *
+ * @return The read value.
+ */
+static inline unsigned int read_bits(
+ unsigned int bitcount,
+ unsigned int bitoffset,
+ const uint8_t* ptr
+) {
+ unsigned int mask = (1 << bitcount) - 1;
+ ptr += bitoffset >> 3;
+ bitoffset &= 7;
+ unsigned int value = ptr[0] | (ptr[1] << 8);
+ value >>= bitoffset;
+ value &= mask;
+ return value;
+}
+
+/* See header for documentation. */
+void encode_ise(
+ quant_method quant_level,
+ unsigned int character_count,
+ const uint8_t* input_data,
+ uint8_t* output_data,
+ unsigned int bit_offset
+) {
+ promise(character_count > 0);
+
+ unsigned int bits = btq_counts[quant_level].bits;
+ unsigned int trits = btq_counts[quant_level].trits;
+ unsigned int quints = btq_counts[quant_level].quints;
+ unsigned int mask = (1 << bits) - 1;
+
+ // Write out trits and bits
+ if (trits)
+ {
+ unsigned int i = 0;
+ unsigned int full_trit_blocks = character_count / 5;
+
+ for (unsigned int j = 0; j < full_trit_blocks; j++)
+ {
+ unsigned int i4 = input_data[i + 4] >> bits;
+ unsigned int i3 = input_data[i + 3] >> bits;
+ unsigned int i2 = input_data[i + 2] >> bits;
+ unsigned int i1 = input_data[i + 1] >> bits;
+ unsigned int i0 = input_data[i + 0] >> bits;
+
+ uint8_t T = integer_of_trits[i4][i3][i2][i1][i0];
+
+ // The max size of a trit bit count is 6, so we can always safely
+ // pack a single MX value with the following 1 or 2 T bits.
+ uint8_t pack;
+
+ // Element 0 + T0 + T1
+ pack = (input_data[i++] & mask) | (((T >> 0) & 0x3) << bits);
+ write_bits(pack, bits + 2, bit_offset, output_data);
+ bit_offset += bits + 2;
+
+ // Element 1 + T2 + T3
+ pack = (input_data[i++] & mask) | (((T >> 2) & 0x3) << bits);
+ write_bits(pack, bits + 2, bit_offset, output_data);
+ bit_offset += bits + 2;
+
+ // Element 2 + T4
+ pack = (input_data[i++] & mask) | (((T >> 4) & 0x1) << bits);
+ write_bits(pack, bits + 1, bit_offset, output_data);
+ bit_offset += bits + 1;
+
+ // Element 3 + T5 + T6
+ pack = (input_data[i++] & mask) | (((T >> 5) & 0x3) << bits);
+ write_bits(pack, bits + 2, bit_offset, output_data);
+ bit_offset += bits + 2;
+
+ // Element 4 + T7
+ pack = (input_data[i++] & mask) | (((T >> 7) & 0x1) << bits);
+ write_bits(pack, bits + 1, bit_offset, output_data);
+ bit_offset += bits + 1;
+ }
+
+ // Loop tail for a partial block
+ if (i != character_count)
+ {
+ // i4 cannot be present - we know the block is partial
+ // i0 must be present - we know the block isn't empty
+ unsigned int i4 = 0;
+ unsigned int i3 = i + 3 >= character_count ? 0 : input_data[i + 3] >> bits;
+ unsigned int i2 = i + 2 >= character_count ? 0 : input_data[i + 2] >> bits;
+ unsigned int i1 = i + 1 >= character_count ? 0 : input_data[i + 1] >> bits;
+ unsigned int i0 = input_data[i + 0] >> bits;
+
+ uint8_t T = integer_of_trits[i4][i3][i2][i1][i0];
+
+ for (unsigned int j = 0; i < character_count; i++, j++)
+ {
+ // Truncated table as this iteration is always partital
+ static const uint8_t tbits[4] { 2, 2, 1, 2 };
+ static const uint8_t tshift[4] { 0, 2, 4, 5 };
+
+ uint8_t pack = (input_data[i] & mask) |
+ (((T >> tshift[j]) & ((1 << tbits[j]) - 1)) << bits);
+
+ write_bits(pack, bits + tbits[j], bit_offset, output_data);
+ bit_offset += bits + tbits[j];
+ }
+ }
+ }
+ // Write out quints and bits
+ else if (quints)
+ {
+ unsigned int i = 0;
+ unsigned int full_quint_blocks = character_count / 3;
+
+ for (unsigned int j = 0; j < full_quint_blocks; j++)
+ {
+ unsigned int i2 = input_data[i + 2] >> bits;
+ unsigned int i1 = input_data[i + 1] >> bits;
+ unsigned int i0 = input_data[i + 0] >> bits;
+
+ uint8_t T = integer_of_quints[i2][i1][i0];
+
+ // The max size of a quint bit count is 5, so we can always safely
+ // pack a single M value with the following 2 or 3 T bits.
+ uint8_t pack;
+
+ // Element 0
+ pack = (input_data[i++] & mask) | (((T >> 0) & 0x7) << bits);
+ write_bits(pack, bits + 3, bit_offset, output_data);
+ bit_offset += bits + 3;
+
+ // Element 1
+ pack = (input_data[i++] & mask) | (((T >> 3) & 0x3) << bits);
+ write_bits(pack, bits + 2, bit_offset, output_data);
+ bit_offset += bits + 2;
+
+ // Element 2
+ pack = (input_data[i++] & mask) | (((T >> 5) & 0x3) << bits);
+ write_bits(pack, bits + 2, bit_offset, output_data);
+ bit_offset += bits + 2;
+ }
+
+ // Loop tail for a partial block
+ if (i != character_count)
+ {
+ // i2 cannot be present - we know the block is partial
+ // i0 must be present - we know the block isn't empty
+ unsigned int i2 = 0;
+ unsigned int i1 = i + 1 >= character_count ? 0 : input_data[i + 1] >> bits;
+ unsigned int i0 = input_data[i + 0] >> bits;
+
+ uint8_t T = integer_of_quints[i2][i1][i0];
+
+ for (unsigned int j = 0; i < character_count; i++, j++)
+ {
+ // Truncated table as this iteration is always partital
+ static const uint8_t tbits[2] { 3, 2 };
+ static const uint8_t tshift[2] { 0, 3 };
+
+ uint8_t pack = (input_data[i] & mask) |
+ (((T >> tshift[j]) & ((1 << tbits[j]) - 1)) << bits);
+
+ write_bits(pack, bits + tbits[j], bit_offset, output_data);
+ bit_offset += bits + tbits[j];
+ }
+ }
+ }
+ // Write out just bits
+ else
+ {
+ for (unsigned int i = 0; i < character_count; i++)
+ {
+ write_bits(input_data[i], bits, bit_offset, output_data);
+ bit_offset += bits;
+ }
+ }
+}
+
+/* See header for documentation. */
+void decode_ise(
+ quant_method quant_level,
+ unsigned int character_count,
+ const uint8_t* input_data,
+ uint8_t* output_data,
+ unsigned int bit_offset
+) {
+ promise(character_count > 0);
+
+ // Note: due to how the trit/quint-block unpacking is done in this function, we may write more
+ // temporary results than the number of outputs. The maximum actual number of results is 64 bit,
+ // but we keep 4 additional character_count of padding.
+ uint8_t results[68];
+ uint8_t tq_blocks[22] { 0 }; // Trit-blocks or quint-blocks, must be zeroed
+
+ unsigned int bits = btq_counts[quant_level].bits;
+ unsigned int trits = btq_counts[quant_level].trits;
+ unsigned int quints = btq_counts[quant_level].quints;
+
+ unsigned int lcounter = 0;
+ unsigned int hcounter = 0;
+
+ // Collect bits for each element, as well as bits for any trit-blocks and quint-blocks.
+ for (unsigned int i = 0; i < character_count; i++)
+ {
+ results[i] = static_cast<uint8_t>(read_bits(bits, bit_offset, input_data));
+ bit_offset += bits;
+
+ if (trits)
+ {
+ static const uint8_t bits_to_read[5] { 2, 2, 1, 2, 1 };
+ static const uint8_t block_shift[5] { 0, 2, 4, 5, 7 };
+ static const uint8_t next_lcounter[5] { 1, 2, 3, 4, 0 };
+ static const uint8_t hcounter_incr[5] { 0, 0, 0, 0, 1 };
+ unsigned int tdata = read_bits(bits_to_read[lcounter], bit_offset, input_data);
+ bit_offset += bits_to_read[lcounter];
+ tq_blocks[hcounter] |= tdata << block_shift[lcounter];
+ hcounter += hcounter_incr[lcounter];
+ lcounter = next_lcounter[lcounter];
+ }
+
+ if (quints)
+ {
+ static const uint8_t bits_to_read[3] { 3, 2, 2 };
+ static const uint8_t block_shift[3] { 0, 3, 5 };
+ static const uint8_t next_lcounter[3] { 1, 2, 0 };
+ static const uint8_t hcounter_incr[3] { 0, 0, 1 };
+ unsigned int tdata = read_bits(bits_to_read[lcounter], bit_offset, input_data);
+ bit_offset += bits_to_read[lcounter];
+ tq_blocks[hcounter] |= tdata << block_shift[lcounter];
+ hcounter += hcounter_incr[lcounter];
+ lcounter = next_lcounter[lcounter];
+ }
+ }
+
+ // Unpack trit-blocks or quint-blocks as needed
+ if (trits)
+ {
+ unsigned int trit_blocks = (character_count + 4) / 5;
+ promise(trit_blocks > 0);
+ for (unsigned int i = 0; i < trit_blocks; i++)
+ {
+ const uint8_t *tritptr = trits_of_integer[tq_blocks[i]];
+ results[5 * i ] |= tritptr[0] << bits;
+ results[5 * i + 1] |= tritptr[1] << bits;
+ results[5 * i + 2] |= tritptr[2] << bits;
+ results[5 * i + 3] |= tritptr[3] << bits;
+ results[5 * i + 4] |= tritptr[4] << bits;
+ }
+ }
+
+ if (quints)
+ {
+ unsigned int quint_blocks = (character_count + 2) / 3;
+ promise(quint_blocks > 0);
+ for (unsigned int i = 0; i < quint_blocks; i++)
+ {
+ const uint8_t *quintptr = quints_of_integer[tq_blocks[i]];
+ results[3 * i ] |= quintptr[0] << bits;
+ results[3 * i + 1] |= quintptr[1] << bits;
+ results[3 * i + 2] |= quintptr[2] << bits;
+ }
+ }
+
+ for (unsigned int i = 0; i < character_count; i++)
+ {
+ output_data[i] = results[i];
+ }
+}
diff --git a/thirdparty/astcenc/astcenc_internal.h b/thirdparty/astcenc/astcenc_internal.h
new file mode 100644
index 0000000000..0aa8fa0f81
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_internal.h
@@ -0,0 +1,2196 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2023 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Functions and data declarations.
+ */
+
+#ifndef ASTCENC_INTERNAL_INCLUDED
+#define ASTCENC_INTERNAL_INCLUDED
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#if defined(ASTCENC_DIAGNOSTICS)
+ #include <cstdio>
+#endif
+#include <cstdlib>
+
+#include "astcenc.h"
+#include "astcenc_mathlib.h"
+#include "astcenc_vecmathlib.h"
+
+/**
+ * @brief Make a promise to the compiler's optimizer.
+ *
+ * A promise is an expression that the optimizer is can assume is true for to help it generate
+ * faster code. Common use cases for this are to promise that a for loop will iterate more than
+ * once, or that the loop iteration count is a multiple of a vector length, which avoids pre-loop
+ * checks and can avoid loop tails if loops are unrolled by the auto-vectorizer.
+ */
+#if defined(NDEBUG)
+ #if !defined(__clang__) && defined(_MSC_VER)
+ #define promise(cond) __assume(cond)
+ #elif defined(__clang__)
+ #if __has_builtin(__builtin_assume)
+ #define promise(cond) __builtin_assume(cond)
+ #elif __has_builtin(__builtin_unreachable)
+ #define promise(cond) if (!(cond)) { __builtin_unreachable(); }
+ #else
+ #define promise(cond)
+ #endif
+ #else // Assume GCC
+ #define promise(cond) if (!(cond)) { __builtin_unreachable(); }
+ #endif
+#else
+ #define promise(cond) assert(cond)
+#endif
+
+/* ============================================================================
+ Constants
+============================================================================ */
+#if !defined(ASTCENC_BLOCK_MAX_TEXELS)
+ #define ASTCENC_BLOCK_MAX_TEXELS 216 // A 3D 6x6x6 block
+#endif
+
+/** @brief The maximum number of texels a block can support (6x6x6 block). */
+static constexpr unsigned int BLOCK_MAX_TEXELS { ASTCENC_BLOCK_MAX_TEXELS };
+
+/** @brief The maximum number of components a block can support. */
+static constexpr unsigned int BLOCK_MAX_COMPONENTS { 4 };
+
+/** @brief The maximum number of partitions a block can support. */
+static constexpr unsigned int BLOCK_MAX_PARTITIONS { 4 };
+
+/** @brief The number of partitionings, per partition count, suported by the ASTC format. */
+static constexpr unsigned int BLOCK_MAX_PARTITIONINGS { 1024 };
+
+/** @brief The maximum number of weights used during partition selection for texel clustering. */
+static constexpr uint8_t BLOCK_MAX_KMEANS_TEXELS { 64 };
+
+/** @brief The maximum number of weights a block can support. */
+static constexpr unsigned int BLOCK_MAX_WEIGHTS { 64 };
+
+/** @brief The maximum number of weights a block can support per plane in 2 plane mode. */
+static constexpr unsigned int BLOCK_MAX_WEIGHTS_2PLANE { BLOCK_MAX_WEIGHTS / 2 };
+
+/** @brief The minimum number of weight bits a candidate encoding must encode. */
+static constexpr unsigned int BLOCK_MIN_WEIGHT_BITS { 24 };
+
+/** @brief The maximum number of weight bits a candidate encoding can encode. */
+static constexpr unsigned int BLOCK_MAX_WEIGHT_BITS { 96 };
+
+/** @brief The index indicating a bad (unused) block mode in the remap array. */
+static constexpr uint16_t BLOCK_BAD_BLOCK_MODE { 0xFFFFu };
+
+/** @brief The index indicating a bad (unused) partitioning in the remap array. */
+static constexpr uint16_t BLOCK_BAD_PARTITIONING { 0xFFFFu };
+
+/** @brief The number of partition index bits supported by the ASTC format . */
+static constexpr unsigned int PARTITION_INDEX_BITS { 10 };
+
+/** @brief The offset of the plane 2 weights in shared weight arrays. */
+static constexpr unsigned int WEIGHTS_PLANE2_OFFSET { BLOCK_MAX_WEIGHTS_2PLANE };
+
+/** @brief The sum of quantized weights for one texel. */
+static constexpr float WEIGHTS_TEXEL_SUM { 16.0f };
+
+/** @brief The number of block modes supported by the ASTC format. */
+static constexpr unsigned int WEIGHTS_MAX_BLOCK_MODES { 2048 };
+
+/** @brief The number of weight grid decimation modes supported by the ASTC format. */
+static constexpr unsigned int WEIGHTS_MAX_DECIMATION_MODES { 87 };
+
+/** @brief The high default error used to initialize error trackers. */
+static constexpr float ERROR_CALC_DEFAULT { 1e30f };
+
+/**
+ * @brief The minimum texel count for a block to use the one partition fast path.
+ *
+ * This setting skips 4x4 and 5x4 block sizes.
+ */
+static constexpr unsigned int TUNE_MIN_TEXELS_MODE0_FASTPATH { 24 };
+
+/**
+ * @brief The maximum number of candidate encodings tested for each encoding mode.
+ *
+ * This can be dynamically reduced by the compression quality preset.
+ */
+static constexpr unsigned int TUNE_MAX_TRIAL_CANDIDATES { 8 };
+
+/**
+ * @brief The maximum number of candidate partitionings tested for each encoding mode.
+ *
+ * This can be dynamically reduced by the compression quality preset.
+ */
+static constexpr unsigned int TUNE_MAX_PARTITIONING_CANDIDATES { 32 };
+
+/**
+ * @brief The maximum quant level using full angular endpoint search method.
+ *
+ * The angular endpoint search is used to find the min/max weight that should
+ * be used for a given quantization level. It is effective but expensive, so
+ * we only use it where it has the most value - low quant levels with wide
+ * spacing. It is used below TUNE_MAX_ANGULAR_QUANT (inclusive). Above this we
+ * assume the min weight is 0.0f, and the max weight is 1.0f.
+ *
+ * Note the angular algorithm is vectorized, and using QUANT_12 exactly fills
+ * one 8-wide vector. Decreasing by one doesn't buy much performance, and
+ * increasing by one is disproportionately expensive.
+ */
+static constexpr unsigned int TUNE_MAX_ANGULAR_QUANT { 7 }; /* QUANT_12 */
+
+static_assert((BLOCK_MAX_TEXELS % ASTCENC_SIMD_WIDTH) == 0,
+ "BLOCK_MAX_TEXELS must be multiple of ASTCENC_SIMD_WIDTH");
+
+static_assert(BLOCK_MAX_TEXELS <= 216,
+ "BLOCK_MAX_TEXELS must not be greater than 216");
+
+static_assert((BLOCK_MAX_WEIGHTS % ASTCENC_SIMD_WIDTH) == 0,
+ "BLOCK_MAX_WEIGHTS must be multiple of ASTCENC_SIMD_WIDTH");
+
+static_assert((WEIGHTS_MAX_BLOCK_MODES % ASTCENC_SIMD_WIDTH) == 0,
+ "WEIGHTS_MAX_BLOCK_MODES must be multiple of ASTCENC_SIMD_WIDTH");
+
+
+/* ============================================================================
+ Commonly used data structures
+============================================================================ */
+
+/**
+ * @brief The ASTC endpoint formats.
+ *
+ * Note, the values here are used directly in the encoding in the format so do not rearrange.
+ */
+enum endpoint_formats
+{
+ FMT_LUMINANCE = 0,
+ FMT_LUMINANCE_DELTA = 1,
+ FMT_HDR_LUMINANCE_LARGE_RANGE = 2,
+ FMT_HDR_LUMINANCE_SMALL_RANGE = 3,
+ FMT_LUMINANCE_ALPHA = 4,
+ FMT_LUMINANCE_ALPHA_DELTA = 5,
+ FMT_RGB_SCALE = 6,
+ FMT_HDR_RGB_SCALE = 7,
+ FMT_RGB = 8,
+ FMT_RGB_DELTA = 9,
+ FMT_RGB_SCALE_ALPHA = 10,
+ FMT_HDR_RGB = 11,
+ FMT_RGBA = 12,
+ FMT_RGBA_DELTA = 13,
+ FMT_HDR_RGB_LDR_ALPHA = 14,
+ FMT_HDR_RGBA = 15
+};
+
+/**
+ * @brief The ASTC quantization methods.
+ *
+ * Note, the values here are used directly in the encoding in the format so do not rearrange.
+ */
+enum quant_method
+{
+ QUANT_2 = 0,
+ QUANT_3 = 1,
+ QUANT_4 = 2,
+ QUANT_5 = 3,
+ QUANT_6 = 4,
+ QUANT_8 = 5,
+ QUANT_10 = 6,
+ QUANT_12 = 7,
+ QUANT_16 = 8,
+ QUANT_20 = 9,
+ QUANT_24 = 10,
+ QUANT_32 = 11,
+ QUANT_40 = 12,
+ QUANT_48 = 13,
+ QUANT_64 = 14,
+ QUANT_80 = 15,
+ QUANT_96 = 16,
+ QUANT_128 = 17,
+ QUANT_160 = 18,
+ QUANT_192 = 19,
+ QUANT_256 = 20
+};
+
+/**
+ * @brief The number of levels use by an ASTC quantization method.
+ *
+ * @param method The quantization method
+ *
+ * @return The number of levels used by @c method.
+ */
+static inline unsigned int get_quant_level(quant_method method)
+{
+ switch (method)
+ {
+ case QUANT_2: return 2;
+ case QUANT_3: return 3;
+ case QUANT_4: return 4;
+ case QUANT_5: return 5;
+ case QUANT_6: return 6;
+ case QUANT_8: return 8;
+ case QUANT_10: return 10;
+ case QUANT_12: return 12;
+ case QUANT_16: return 16;
+ case QUANT_20: return 20;
+ case QUANT_24: return 24;
+ case QUANT_32: return 32;
+ case QUANT_40: return 40;
+ case QUANT_48: return 48;
+ case QUANT_64: return 64;
+ case QUANT_80: return 80;
+ case QUANT_96: return 96;
+ case QUANT_128: return 128;
+ case QUANT_160: return 160;
+ case QUANT_192: return 192;
+ case QUANT_256: return 256;
+ }
+
+ // Unreachable - the enum is fully described
+ return 0;
+}
+
+/**
+ * @brief Computed metrics about a partition in a block.
+ */
+struct partition_metrics
+{
+ /** @brief The error-weighted average color in the partition. */
+ vfloat4 avg;
+
+ /** @brief The dominant error-weighted direction in the partition. */
+ vfloat4 dir;
+};
+
+/**
+ * @brief Computed lines for a a three component analysis.
+ */
+struct partition_lines3
+{
+ /** @brief Line for uncorrelated chroma. */
+ line3 uncor_line;
+
+ /** @brief Line for correlated chroma, passing though the origin. */
+ line3 samec_line;
+
+ /** @brief Post-processed line for uncorrelated chroma. */
+ processed_line3 uncor_pline;
+
+ /** @brief Post-processed line for correlated chroma, passing though the origin. */
+ processed_line3 samec_pline;
+
+ /** @brief The length of the line for uncorrelated chroma. */
+ float uncor_line_len;
+
+ /** @brief The length of the line for correlated chroma. */
+ float samec_line_len;
+};
+
+/**
+ * @brief The partition information for a single partition.
+ *
+ * ASTC has a total of 1024 candidate partitions for each of 2/3/4 partition counts, although this
+ * 1024 includes seeds that generate duplicates of other seeds and seeds that generate completely
+ * empty partitions. These are both valid encodings, but astcenc will skip both during compression
+ * as they are not useful.
+ */
+struct partition_info
+{
+ /** @brief The number of partitions in this partitioning. */
+ uint16_t partition_count;
+
+ /** @brief The index (seed) of this partitioning. */
+ uint16_t partition_index;
+
+ /**
+ * @brief The number of texels in each partition.
+ *
+ * Note that some seeds result in zero texels assigned to a partition are valid, but are skipped
+ * by this compressor as there is no point spending bits encoding an unused color endpoint.
+ */
+ uint8_t partition_texel_count[BLOCK_MAX_PARTITIONS];
+
+ /** @brief The partition of each texel in the block. */
+ uint8_t partition_of_texel[BLOCK_MAX_TEXELS];
+
+ /** @brief The list of texels in each partition. */
+ uint8_t texels_of_partition[BLOCK_MAX_PARTITIONS][BLOCK_MAX_TEXELS];
+};
+
+/**
+ * @brief The weight grid information for a single decimation pattern.
+ *
+ * ASTC can store one weight per texel, but is also capable of storing lower resolution weight grids
+ * that are interpolated during decompression to assign a with to a texel. Storing fewer weights
+ * can free up a substantial amount of bits that we can then spend on more useful things, such as
+ * more accurate endpoints and weights, or additional partitions.
+ *
+ * This data structure is used to store information about a single weight grid decimation pattern,
+ * for a single block size.
+ */
+struct decimation_info
+{
+ /** @brief The total number of texels in the block. */
+ uint8_t texel_count;
+
+ /** @brief The maximum number of stored weights that contribute to each texel, between 1 and 4. */
+ uint8_t max_texel_weight_count;
+
+ /** @brief The total number of weights stored. */
+ uint8_t weight_count;
+
+ /** @brief The number of stored weights in the X dimension. */
+ uint8_t weight_x;
+
+ /** @brief The number of stored weights in the Y dimension. */
+ uint8_t weight_y;
+
+ /** @brief The number of stored weights in the Z dimension. */
+ uint8_t weight_z;
+
+ /**
+ * @brief The number of weights that contribute to each texel.
+ * Value is between 1 and 4.
+ */
+ uint8_t texel_weight_count[BLOCK_MAX_TEXELS];
+
+ /**
+ * @brief The weight index of the N weights that are interpolated for each texel.
+ * Stored transposed to improve vectorization.
+ */
+ uint8_t texel_weights_tr[4][BLOCK_MAX_TEXELS];
+
+ /**
+ * @brief The bilinear contribution of the N weights that are interpolated for each texel.
+ * Value is between 0 and 16, stored transposed to improve vectorization.
+ */
+ uint8_t texel_weight_contribs_int_tr[4][BLOCK_MAX_TEXELS];
+
+ /**
+ * @brief The bilinear contribution of the N weights that are interpolated for each texel.
+ * Value is between 0 and 1, stored transposed to improve vectorization.
+ */
+ alignas(ASTCENC_VECALIGN) float texel_weight_contribs_float_tr[4][BLOCK_MAX_TEXELS];
+
+ /** @brief The number of texels that each stored weight contributes to. */
+ uint8_t weight_texel_count[BLOCK_MAX_WEIGHTS];
+
+ /**
+ * @brief The list of texels that use a specific weight index.
+ * Stored transposed to improve vectorization.
+ */
+ uint8_t weight_texels_tr[BLOCK_MAX_TEXELS][BLOCK_MAX_WEIGHTS];
+
+ /**
+ * @brief The bilinear contribution to the N texels that use each weight.
+ * Value is between 0 and 1, stored transposed to improve vectorization.
+ */
+ alignas(ASTCENC_VECALIGN) float weights_texel_contribs_tr[BLOCK_MAX_TEXELS][BLOCK_MAX_WEIGHTS];
+
+ /**
+ * @brief The bilinear contribution to the Nth texel that uses each weight.
+ * Value is between 0 and 1, stored transposed to improve vectorization.
+ */
+ float texel_contrib_for_weight[BLOCK_MAX_TEXELS][BLOCK_MAX_WEIGHTS];
+};
+
+/**
+ * @brief Metadata for single block mode for a specific block size.
+ */
+struct block_mode
+{
+ /** @brief The block mode index in the ASTC encoded form. */
+ uint16_t mode_index;
+
+ /** @brief The decimation mode index in the compressor reindexed list. */
+ uint8_t decimation_mode;
+
+ /** @brief The weight quantization used by this block mode. */
+ uint8_t quant_mode;
+
+ /** @brief The weight quantization used by this block mode. */
+ uint8_t weight_bits;
+
+ /** @brief Is a dual weight plane used by this block mode? */
+ uint8_t is_dual_plane : 1;
+
+ /**
+ * @brief Get the weight quantization used by this block mode.
+ *
+ * @return The quantization level.
+ */
+ inline quant_method get_weight_quant_mode() const
+ {
+ return static_cast<quant_method>(this->quant_mode);
+ }
+};
+
+/**
+ * @brief Metadata for single decimation mode for a specific block size.
+ */
+struct decimation_mode
+{
+ /** @brief The max weight precision for 1 plane, or -1 if not supported. */
+ int8_t maxprec_1plane;
+
+ /** @brief The max weight precision for 2 planes, or -1 if not supported. */
+ int8_t maxprec_2planes;
+
+ /**
+ * @brief Bitvector indicating weight quant modes used by active 1 plane block modes.
+ *
+ * Bit 0 = QUANT_2, Bit 1 = QUANT_3, etc.
+ */
+ uint16_t refprec_1_plane;
+
+ /**
+ * @brief Bitvector indicating weight quant methods used by active 2 plane block modes.
+ *
+ * Bit 0 = QUANT_2, Bit 1 = QUANT_3, etc.
+ */
+ uint16_t refprec_2_planes;
+
+ /**
+ * @brief Set a 1 plane weight quant as active.
+ *
+ * @param weight_quant The quant method to set.
+ */
+ void set_ref_1_plane(quant_method weight_quant)
+ {
+ refprec_1_plane |= (1 << weight_quant);
+ }
+
+ /**
+ * @brief Test if this mode is active below a given 1 plane weight quant (inclusive).
+ *
+ * @param max_weight_quant The max quant method to test.
+ */
+ bool is_ref_1_plane(quant_method max_weight_quant) const
+ {
+ uint16_t mask = static_cast<uint16_t>((1 << (max_weight_quant + 1)) - 1);
+ return (refprec_1_plane & mask) != 0;
+ }
+
+ /**
+ * @brief Set a 2 plane weight quant as active.
+ *
+ * @param weight_quant The quant method to set.
+ */
+ void set_ref_2_plane(quant_method weight_quant)
+ {
+ refprec_2_planes |= static_cast<uint16_t>(1 << weight_quant);
+ }
+
+ /**
+ * @brief Test if this mode is active below a given 2 plane weight quant (inclusive).
+ *
+ * @param max_weight_quant The max quant method to test.
+ */
+ bool is_ref_2_plane(quant_method max_weight_quant) const
+ {
+ uint16_t mask = static_cast<uint16_t>((1 << (max_weight_quant + 1)) - 1);
+ return (refprec_2_planes & mask) != 0;
+ }
+};
+
+/**
+ * @brief Data tables for a single block size.
+ *
+ * The decimation tables store the information to apply weight grid dimension reductions. We only
+ * store the decimation modes that are actually needed by the current context; many of the possible
+ * modes will be unused (too many weights for the current block size or disabled by heuristics). The
+ * actual number of weights stored is @c decimation_mode_count, and the @c decimation_modes and
+ * @c decimation_tables arrays store the active modes contiguously at the start of the array. These
+ * entries are not stored in any particular order.
+ *
+ * The block mode tables store the unpacked block mode settings. Block modes are stored in the
+ * compressed block as an 11 bit field, but for any given block size and set of compressor
+ * heuristics, only a subset of the block modes will be used. The actual number of block modes
+ * stored is indicated in @c block_mode_count, and the @c block_modes array store the active modes
+ * contiguously at the start of the array. These entries are stored in incrementing "packed" value
+ * order, which doesn't mean much once unpacked. To allow decompressors to reference the packed data
+ * efficiently the @c block_mode_packed_index array stores the mapping between physical ID and the
+ * actual remapped array index.
+ */
+struct block_size_descriptor
+{
+ /** @brief The block X dimension, in texels. */
+ uint8_t xdim;
+
+ /** @brief The block Y dimension, in texels. */
+ uint8_t ydim;
+
+ /** @brief The block Z dimension, in texels. */
+ uint8_t zdim;
+
+ /** @brief The block total texel count. */
+ uint8_t texel_count;
+
+ /**
+ * @brief The number of stored decimation modes which are "always" modes.
+ *
+ * Always modes are stored at the start of the decimation_modes list.
+ */
+ unsigned int decimation_mode_count_always;
+
+ /** @brief The number of stored decimation modes for selected encodings. */
+ unsigned int decimation_mode_count_selected;
+
+ /** @brief The number of stored decimation modes for any encoding. */
+ unsigned int decimation_mode_count_all;
+
+ /**
+ * @brief The number of stored block modes which are "always" modes.
+ *
+ * Always modes are stored at the start of the block_modes list.
+ */
+ unsigned int block_mode_count_1plane_always;
+
+ /** @brief The number of stored block modes for active 1 plane encodings. */
+ unsigned int block_mode_count_1plane_selected;
+
+ /** @brief The number of stored block modes for active 1 and 2 plane encodings. */
+ unsigned int block_mode_count_1plane_2plane_selected;
+
+ /** @brief The number of stored block modes for any encoding. */
+ unsigned int block_mode_count_all;
+
+ /** @brief The number of selected partitionings for 1/2/3/4 partitionings. */
+ unsigned int partitioning_count_selected[BLOCK_MAX_PARTITIONS];
+
+ /** @brief The number of partitionings for 1/2/3/4 partitionings. */
+ unsigned int partitioning_count_all[BLOCK_MAX_PARTITIONS];
+
+ /** @brief The active decimation modes, stored in low indices. */
+ decimation_mode decimation_modes[WEIGHTS_MAX_DECIMATION_MODES];
+
+ /** @brief The active decimation tables, stored in low indices. */
+ alignas(ASTCENC_VECALIGN) decimation_info decimation_tables[WEIGHTS_MAX_DECIMATION_MODES];
+
+ /** @brief The packed block mode array index, or @c BLOCK_BAD_BLOCK_MODE if not active. */
+ uint16_t block_mode_packed_index[WEIGHTS_MAX_BLOCK_MODES];
+
+ /** @brief The active block modes, stored in low indices. */
+ block_mode block_modes[WEIGHTS_MAX_BLOCK_MODES];
+
+ /** @brief The active partition tables, stored in low indices per-count. */
+ partition_info partitionings[(3 * BLOCK_MAX_PARTITIONINGS) + 1];
+
+ /**
+ * @brief The packed partition table array index, or @c BLOCK_BAD_PARTITIONING if not active.
+ *
+ * Indexed by partition_count - 2, containing 2, 3 and 4 partitions.
+ */
+ uint16_t partitioning_packed_index[3][BLOCK_MAX_PARTITIONINGS];
+
+ /** @brief The active texels for k-means partition selection. */
+ uint8_t kmeans_texels[BLOCK_MAX_KMEANS_TEXELS];
+
+ /**
+ * @brief The canonical 2-partition coverage pattern used during block partition search.
+ *
+ * Indexed by remapped index, not physical index.
+ */
+ uint64_t coverage_bitmaps_2[BLOCK_MAX_PARTITIONINGS][2];
+
+ /**
+ * @brief The canonical 3-partition coverage pattern used during block partition search.
+ *
+ * Indexed by remapped index, not physical index.
+ */
+ uint64_t coverage_bitmaps_3[BLOCK_MAX_PARTITIONINGS][3];
+
+ /**
+ * @brief The canonical 4-partition coverage pattern used during block partition search.
+ *
+ * Indexed by remapped index, not physical index.
+ */
+ uint64_t coverage_bitmaps_4[BLOCK_MAX_PARTITIONINGS][4];
+
+ /**
+ * @brief Get the block mode structure for index @c block_mode.
+ *
+ * This function can only return block modes that are enabled by the current compressor config.
+ * Decompression from an arbitrary source should not use this without first checking that the
+ * packed block mode index is not @c BLOCK_BAD_BLOCK_MODE.
+ *
+ * @param block_mode The packed block mode index.
+ *
+ * @return The block mode structure.
+ */
+ const block_mode& get_block_mode(unsigned int block_mode) const
+ {
+ unsigned int packed_index = this->block_mode_packed_index[block_mode];
+ assert(packed_index != BLOCK_BAD_BLOCK_MODE && packed_index < this->block_mode_count_all);
+ return this->block_modes[packed_index];
+ }
+
+ /**
+ * @brief Get the decimation mode structure for index @c decimation_mode.
+ *
+ * This function can only return decimation modes that are enabled by the current compressor
+ * config. The mode array is stored packed, but this is only ever indexed by the packed index
+ * stored in the @c block_mode and never exists in an unpacked form.
+ *
+ * @param decimation_mode The packed decimation mode index.
+ *
+ * @return The decimation mode structure.
+ */
+ const decimation_mode& get_decimation_mode(unsigned int decimation_mode) const
+ {
+ return this->decimation_modes[decimation_mode];
+ }
+
+ /**
+ * @brief Get the decimation info structure for index @c decimation_mode.
+ *
+ * This function can only return decimation modes that are enabled by the current compressor
+ * config. The mode array is stored packed, but this is only ever indexed by the packed index
+ * stored in the @c block_mode and never exists in an unpacked form.
+ *
+ * @param decimation_mode The packed decimation mode index.
+ *
+ * @return The decimation info structure.
+ */
+ const decimation_info& get_decimation_info(unsigned int decimation_mode) const
+ {
+ return this->decimation_tables[decimation_mode];
+ }
+
+ /**
+ * @brief Get the partition info table for a given partition count.
+ *
+ * @param partition_count The number of partitions we want the table for.
+ *
+ * @return The pointer to the table of 1024 entries (for 2/3/4 parts) or 1 entry (for 1 part).
+ */
+ const partition_info* get_partition_table(unsigned int partition_count) const
+ {
+ if (partition_count == 1)
+ {
+ partition_count = 5;
+ }
+ unsigned int index = (partition_count - 2) * BLOCK_MAX_PARTITIONINGS;
+ return this->partitionings + index;
+ }
+
+ /**
+ * @brief Get the partition info structure for a given partition count and seed.
+ *
+ * @param partition_count The number of partitions we want the info for.
+ * @param index The partition seed (between 0 and 1023).
+ *
+ * @return The partition info structure.
+ */
+ const partition_info& get_partition_info(unsigned int partition_count, unsigned int index) const
+ {
+ unsigned int packed_index = 0;
+ if (partition_count >= 2)
+ {
+ packed_index = this->partitioning_packed_index[partition_count - 2][index];
+ }
+
+ assert(packed_index != BLOCK_BAD_PARTITIONING && packed_index < this->partitioning_count_all[partition_count - 1]);
+ auto& result = get_partition_table(partition_count)[packed_index];
+ assert(index == result.partition_index);
+ return result;
+ }
+
+ /**
+ * @brief Get the partition info structure for a given partition count and seed.
+ *
+ * @param partition_count The number of partitions we want the info for.
+ * @param packed_index The raw array offset.
+ *
+ * @return The partition info structure.
+ */
+ const partition_info& get_raw_partition_info(unsigned int partition_count, unsigned int packed_index) const
+ {
+ assert(packed_index != BLOCK_BAD_PARTITIONING && packed_index < this->partitioning_count_all[partition_count - 1]);
+ auto& result = get_partition_table(partition_count)[packed_index];
+ return result;
+ }
+};
+
+/**
+ * @brief The image data for a single block.
+ *
+ * The @c data_[rgba] fields store the image data in an encoded SoA float form designed for easy
+ * vectorization. Input data is converted to float and stored as values between 0 and 65535. LDR
+ * data is stored as direct UNORM data, HDR data is stored as LNS data.
+ *
+ * The @c rgb_lns and @c alpha_lns fields that assigned a per-texel use of HDR are only used during
+ * decompression. The current compressor will always use HDR endpoint formats when in HDR mode.
+ */
+struct image_block
+{
+ /** @brief The input (compress) or output (decompress) data for the red color component. */
+ alignas(ASTCENC_VECALIGN) float data_r[BLOCK_MAX_TEXELS];
+
+ /** @brief The input (compress) or output (decompress) data for the green color component. */
+ alignas(ASTCENC_VECALIGN) float data_g[BLOCK_MAX_TEXELS];
+
+ /** @brief The input (compress) or output (decompress) data for the blue color component. */
+ alignas(ASTCENC_VECALIGN) float data_b[BLOCK_MAX_TEXELS];
+
+ /** @brief The input (compress) or output (decompress) data for the alpha color component. */
+ alignas(ASTCENC_VECALIGN) float data_a[BLOCK_MAX_TEXELS];
+
+ /** @brief The number of texels in the block. */
+ uint8_t texel_count;
+
+ /** @brief The original data for texel 0 for constant color block encoding. */
+ vfloat4 origin_texel;
+
+ /** @brief The min component value of all texels in the block. */
+ vfloat4 data_min;
+
+ /** @brief The mean component value of all texels in the block. */
+ vfloat4 data_mean;
+
+ /** @brief The max component value of all texels in the block. */
+ vfloat4 data_max;
+
+ /** @brief The relative error significance of the color channels. */
+ vfloat4 channel_weight;
+
+ /** @brief Is this grayscale block where R == G == B for all texels? */
+ bool grayscale;
+
+ /** @brief Set to 1 if a texel is using HDR RGB endpoints (decompression only). */
+ uint8_t rgb_lns[BLOCK_MAX_TEXELS];
+
+ /** @brief Set to 1 if a texel is using HDR alpha endpoints (decompression only). */
+ uint8_t alpha_lns[BLOCK_MAX_TEXELS];
+
+ /** @brief The X position of this block in the input or output image. */
+ unsigned int xpos;
+
+ /** @brief The Y position of this block in the input or output image. */
+ unsigned int ypos;
+
+ /** @brief The Z position of this block in the input or output image. */
+ unsigned int zpos;
+
+ /**
+ * @brief Get an RGBA texel value from the data.
+ *
+ * @param index The texel index.
+ *
+ * @return The texel in RGBA component ordering.
+ */
+ inline vfloat4 texel(unsigned int index) const
+ {
+ return vfloat4(data_r[index],
+ data_g[index],
+ data_b[index],
+ data_a[index]);
+ }
+
+ /**
+ * @brief Get an RGB texel value from the data.
+ *
+ * @param index The texel index.
+ *
+ * @return The texel in RGB0 component ordering.
+ */
+ inline vfloat4 texel3(unsigned int index) const
+ {
+ return vfloat3(data_r[index],
+ data_g[index],
+ data_b[index]);
+ }
+
+ /**
+ * @brief Get the default alpha value for endpoints that don't store it.
+ *
+ * The default depends on whether the alpha endpoint is LDR or HDR.
+ *
+ * @return The alpha value in the scaled range used by the compressor.
+ */
+ inline float get_default_alpha() const
+ {
+ return this->alpha_lns[0] ? static_cast<float>(0x7800) : static_cast<float>(0xFFFF);
+ }
+
+ /**
+ * @brief Test if a single color channel is constant across the block.
+ *
+ * Constant color channels are easier to compress as interpolating between two identical colors
+ * always returns the same value, irrespective of the weight used. They therefore can be ignored
+ * for the purposes of weight selection and use of a second weight plane.
+ *
+ * @return @c true if the channel is constant across the block, @c false otherwise.
+ */
+ inline bool is_constant_channel(int channel) const
+ {
+ vmask4 lane_mask = vint4::lane_id() == vint4(channel);
+ vmask4 color_mask = this->data_min == this->data_max;
+ return any(lane_mask & color_mask);
+ }
+
+ /**
+ * @brief Test if this block is a luminance block with constant 1.0 alpha.
+ *
+ * @return @c true if the block is a luminance block , @c false otherwise.
+ */
+ inline bool is_luminance() const
+ {
+ float default_alpha = this->get_default_alpha();
+ bool alpha1 = (this->data_min.lane<3>() == default_alpha) &&
+ (this->data_max.lane<3>() == default_alpha);
+ return this->grayscale && alpha1;
+ }
+
+ /**
+ * @brief Test if this block is a luminance block with variable alpha.
+ *
+ * @return @c true if the block is a luminance + alpha block , @c false otherwise.
+ */
+ inline bool is_luminancealpha() const
+ {
+ float default_alpha = this->get_default_alpha();
+ bool alpha1 = (this->data_min.lane<3>() == default_alpha) &&
+ (this->data_max.lane<3>() == default_alpha);
+ return this->grayscale && !alpha1;
+ }
+};
+
+/**
+ * @brief Data structure storing the color endpoints for a block.
+ */
+struct endpoints
+{
+ /** @brief The number of partition endpoints stored. */
+ unsigned int partition_count;
+
+ /** @brief The colors for endpoint 0. */
+ vfloat4 endpt0[BLOCK_MAX_PARTITIONS];
+
+ /** @brief The colors for endpoint 1. */
+ vfloat4 endpt1[BLOCK_MAX_PARTITIONS];
+};
+
+/**
+ * @brief Data structure storing the color endpoints and weights.
+ */
+struct endpoints_and_weights
+{
+ /** @brief True if all active values in weight_error_scale are the same. */
+ bool is_constant_weight_error_scale;
+
+ /** @brief The color endpoints. */
+ endpoints ep;
+
+ /** @brief The ideal weight for each texel; may be undecimated or decimated. */
+ alignas(ASTCENC_VECALIGN) float weights[BLOCK_MAX_TEXELS];
+
+ /** @brief The ideal weight error scaling for each texel; may be undecimated or decimated. */
+ alignas(ASTCENC_VECALIGN) float weight_error_scale[BLOCK_MAX_TEXELS];
+};
+
+/**
+ * @brief Utility storing estimated errors from choosing particular endpoint encodings.
+ */
+struct encoding_choice_errors
+{
+ /** @brief Error of using LDR RGB-scale instead of complete endpoints. */
+ float rgb_scale_error;
+
+ /** @brief Error of using HDR RGB-scale instead of complete endpoints. */
+ float rgb_luma_error;
+
+ /** @brief Error of using luminance instead of RGB. */
+ float luminance_error;
+
+ /** @brief Error of discarding alpha and using a constant 1.0 alpha. */
+ float alpha_drop_error;
+
+ /** @brief Can we use delta offset encoding? */
+ bool can_offset_encode;
+
+ /** @brief Can we use blue contraction encoding? */
+ bool can_blue_contract;
+};
+
+/**
+ * @brief Preallocated working buffers, allocated per thread during context creation.
+ */
+struct alignas(ASTCENC_VECALIGN) compression_working_buffers
+{
+ /** @brief Ideal endpoints and weights for plane 1. */
+ endpoints_and_weights ei1;
+
+ /** @brief Ideal endpoints and weights for plane 2. */
+ endpoints_and_weights ei2;
+
+ /**
+ * @brief Decimated ideal weight values in the ~0-1 range.
+ *
+ * Note that values can be slightly below zero or higher than one due to
+ * endpoint extents being inside the ideal color representation.
+ *
+ * For two planes, second plane starts at @c WEIGHTS_PLANE2_OFFSET offsets.
+ */
+ alignas(ASTCENC_VECALIGN) float dec_weights_ideal[WEIGHTS_MAX_DECIMATION_MODES * BLOCK_MAX_WEIGHTS];
+
+ /**
+ * @brief Decimated quantized weight values in the unquantized 0-64 range.
+ *
+ * For two planes, second plane starts at @c WEIGHTS_PLANE2_OFFSET offsets.
+ */
+ uint8_t dec_weights_uquant[WEIGHTS_MAX_BLOCK_MODES * BLOCK_MAX_WEIGHTS];
+
+ /** @brief Error of the best encoding combination for each block mode. */
+ alignas(ASTCENC_VECALIGN) float errors_of_best_combination[WEIGHTS_MAX_BLOCK_MODES];
+
+ /** @brief The best color quant for each block mode. */
+ uint8_t best_quant_levels[WEIGHTS_MAX_BLOCK_MODES];
+
+ /** @brief The best color quant for each block mode if modes are the same and we have spare bits. */
+ uint8_t best_quant_levels_mod[WEIGHTS_MAX_BLOCK_MODES];
+
+ /** @brief The best endpoint format for each partition. */
+ uint8_t best_ep_formats[WEIGHTS_MAX_BLOCK_MODES][BLOCK_MAX_PARTITIONS];
+
+ /** @brief The total bit storage needed for quantized weights for each block mode. */
+ int8_t qwt_bitcounts[WEIGHTS_MAX_BLOCK_MODES];
+
+ /** @brief The cumulative error for quantized weights for each block mode. */
+ float qwt_errors[WEIGHTS_MAX_BLOCK_MODES];
+
+ /** @brief The low weight value in plane 1 for each block mode. */
+ float weight_low_value1[WEIGHTS_MAX_BLOCK_MODES];
+
+ /** @brief The high weight value in plane 1 for each block mode. */
+ float weight_high_value1[WEIGHTS_MAX_BLOCK_MODES];
+
+ /** @brief The low weight value in plane 1 for each quant level and decimation mode. */
+ float weight_low_values1[WEIGHTS_MAX_DECIMATION_MODES][TUNE_MAX_ANGULAR_QUANT + 1];
+
+ /** @brief The high weight value in plane 1 for each quant level and decimation mode. */
+ float weight_high_values1[WEIGHTS_MAX_DECIMATION_MODES][TUNE_MAX_ANGULAR_QUANT + 1];
+
+ /** @brief The low weight value in plane 2 for each block mode. */
+ float weight_low_value2[WEIGHTS_MAX_BLOCK_MODES];
+
+ /** @brief The high weight value in plane 2 for each block mode. */
+ float weight_high_value2[WEIGHTS_MAX_BLOCK_MODES];
+
+ /** @brief The low weight value in plane 2 for each quant level and decimation mode. */
+ float weight_low_values2[WEIGHTS_MAX_DECIMATION_MODES][TUNE_MAX_ANGULAR_QUANT + 1];
+
+ /** @brief The high weight value in plane 2 for each quant level and decimation mode. */
+ float weight_high_values2[WEIGHTS_MAX_DECIMATION_MODES][TUNE_MAX_ANGULAR_QUANT + 1];
+};
+
+struct dt_init_working_buffers
+{
+ uint8_t weight_count_of_texel[BLOCK_MAX_TEXELS];
+ uint8_t grid_weights_of_texel[BLOCK_MAX_TEXELS][4];
+ uint8_t weights_of_texel[BLOCK_MAX_TEXELS][4];
+
+ uint8_t texel_count_of_weight[BLOCK_MAX_WEIGHTS];
+ uint8_t texels_of_weight[BLOCK_MAX_WEIGHTS][BLOCK_MAX_TEXELS];
+ uint8_t texel_weights_of_weight[BLOCK_MAX_WEIGHTS][BLOCK_MAX_TEXELS];
+};
+
+/**
+ * @brief Weight quantization transfer table.
+ *
+ * ASTC can store texel weights at many quantization levels, so for performance we store essential
+ * information about each level as a precomputed data structure. Unquantized weights are integers
+ * or floats in the range [0, 64].
+ *
+ * This structure provides a table, used to estimate the closest quantized weight for a given
+ * floating-point weight. For each quantized weight, the corresponding unquantized values. For each
+ * quantized weight, a previous-value and a next-value.
+*/
+struct quant_and_transfer_table
+{
+ /** @brief The unscrambled unquantized value. */
+ int8_t quant_to_unquant[32];
+
+ /** @brief The scrambling order: scrambled_quant = map[unscrambled_quant]. */
+ int8_t scramble_map[32];
+
+ /** @brief The unscrambling order: unscrambled_unquant = map[scrambled_quant]. */
+ int8_t unscramble_and_unquant_map[32];
+
+ /**
+ * @brief A table of previous-and-next weights, indexed by the current unquantized value.
+ * * bits 7:0 = previous-index, unquantized
+ * * bits 15:8 = next-index, unquantized
+ */
+ uint16_t prev_next_values[65];
+};
+
+/** @brief The precomputed quant and transfer table. */
+extern const quant_and_transfer_table quant_and_xfer_tables[12];
+
+/** @brief The block is an error block, and will return error color or NaN. */
+static constexpr uint8_t SYM_BTYPE_ERROR { 0 };
+
+/** @brief The block is a constant color block using FP16 colors. */
+static constexpr uint8_t SYM_BTYPE_CONST_F16 { 1 };
+
+/** @brief The block is a constant color block using UNORM16 colors. */
+static constexpr uint8_t SYM_BTYPE_CONST_U16 { 2 };
+
+/** @brief The block is a normal non-constant color block. */
+static constexpr uint8_t SYM_BTYPE_NONCONST { 3 };
+
+/**
+ * @brief A symbolic representation of a compressed block.
+ *
+ * The symbolic representation stores the unpacked content of a single
+ * @c physical_compressed_block, in a form which is much easier to access for
+ * the rest of the compressor code.
+ */
+struct symbolic_compressed_block
+{
+ /** @brief The block type, one of the @c SYM_BTYPE_* constants. */
+ uint8_t block_type;
+
+ /** @brief The number of partitions; valid for @c NONCONST blocks. */
+ uint8_t partition_count;
+
+ /** @brief Non-zero if the color formats matched; valid for @c NONCONST blocks. */
+ uint8_t color_formats_matched;
+
+ /** @brief The plane 2 color component, or -1 if single plane; valid for @c NONCONST blocks. */
+ int8_t plane2_component;
+
+ /** @brief The block mode; valid for @c NONCONST blocks. */
+ uint16_t block_mode;
+
+ /** @brief The partition index; valid for @c NONCONST blocks if 2 or more partitions. */
+ uint16_t partition_index;
+
+ /** @brief The endpoint color formats for each partition; valid for @c NONCONST blocks. */
+ uint8_t color_formats[BLOCK_MAX_PARTITIONS];
+
+ /** @brief The endpoint color quant mode; valid for @c NONCONST blocks. */
+ quant_method quant_mode;
+
+ /** @brief The error of the current encoding; valid for @c NONCONST blocks. */
+ float errorval;
+
+ // We can't have both of these at the same time
+ union {
+ /** @brief The constant color; valid for @c CONST blocks. */
+ int constant_color[BLOCK_MAX_COMPONENTS];
+
+ /** @brief The quantized endpoint color pairs; valid for @c NONCONST blocks. */
+ uint8_t color_values[BLOCK_MAX_PARTITIONS][8];
+ };
+
+ /** @brief The quantized and decimated weights.
+ *
+ * Weights are stored in the 0-64 unpacked range allowing them to be used
+ * directly in encoding passes without per-use unpacking. Packing happens
+ * when converting to/from the physical bitstream encoding.
+ *
+ * If dual plane, the second plane starts at @c weights[WEIGHTS_PLANE2_OFFSET].
+ */
+ uint8_t weights[BLOCK_MAX_WEIGHTS];
+
+ /**
+ * @brief Get the weight quantization used by this block mode.
+ *
+ * @return The quantization level.
+ */
+ inline quant_method get_color_quant_mode() const
+ {
+ return this->quant_mode;
+ }
+};
+
+/**
+ * @brief A physical representation of a compressed block.
+ *
+ * The physical representation stores the raw bytes of the format in memory.
+ */
+struct physical_compressed_block
+{
+ /** @brief The ASTC encoded data for a single block. */
+ uint8_t data[16];
+};
+
+
+/**
+ * @brief Parameter structure for @c compute_pixel_region_variance().
+ *
+ * This function takes a structure to avoid spilling arguments to the stack on every function
+ * invocation, as there are a lot of parameters.
+ */
+struct pixel_region_args
+{
+ /** @brief The image to analyze. */
+ const astcenc_image* img;
+
+ /** @brief The component swizzle pattern. */
+ astcenc_swizzle swz;
+
+ /** @brief Should the algorithm bother with Z axis processing? */
+ bool have_z;
+
+ /** @brief The kernel radius for alpha processing. */
+ unsigned int alpha_kernel_radius;
+
+ /** @brief The X dimension of the working data to process. */
+ unsigned int size_x;
+
+ /** @brief The Y dimension of the working data to process. */
+ unsigned int size_y;
+
+ /** @brief The Z dimension of the working data to process. */
+ unsigned int size_z;
+
+ /** @brief The X position of first src and dst data in the data set. */
+ unsigned int offset_x;
+
+ /** @brief The Y position of first src and dst data in the data set. */
+ unsigned int offset_y;
+
+ /** @brief The Z position of first src and dst data in the data set. */
+ unsigned int offset_z;
+
+ /** @brief The working memory buffer. */
+ vfloat4 *work_memory;
+};
+
+/**
+ * @brief Parameter structure for @c compute_averages_proc().
+ */
+struct avg_args
+{
+ /** @brief The arguments for the nested variance computation. */
+ pixel_region_args arg;
+
+ /** @brief The image X dimensions. */
+ unsigned int img_size_x;
+
+ /** @brief The image Y dimensions. */
+ unsigned int img_size_y;
+
+ /** @brief The image Z dimensions. */
+ unsigned int img_size_z;
+
+ /** @brief The maximum working block dimensions in X and Y dimensions. */
+ unsigned int blk_size_xy;
+
+ /** @brief The maximum working block dimensions in Z dimensions. */
+ unsigned int blk_size_z;
+
+ /** @brief The working block memory size. */
+ unsigned int work_memory_size;
+};
+
+#if defined(ASTCENC_DIAGNOSTICS)
+/* See astcenc_diagnostic_trace header for details. */
+class TraceLog;
+#endif
+
+/**
+ * @brief The astcenc compression context.
+ */
+struct astcenc_contexti
+{
+ /** @brief The configuration this context was created with. */
+ astcenc_config config;
+
+ /** @brief The thread count supported by this context. */
+ unsigned int thread_count;
+
+ /** @brief The block size descriptor this context was created with. */
+ block_size_descriptor* bsd;
+
+ /*
+ * Fields below here are not needed in a decompress-only build, but some remain as they are
+ * small and it avoids littering the code with #ifdefs. The most significant contributors to
+ * large structure size are omitted.
+ */
+
+ /** @brief The input image alpha channel averages table, may be @c nullptr if not needed. */
+ float* input_alpha_averages;
+
+ /** @brief The scratch working buffers, one per thread (see @c thread_count). */
+ compression_working_buffers* working_buffers;
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+ /** @brief The pixel region and variance worker arguments. */
+ avg_args avg_preprocess_args;
+#endif
+
+#if defined(ASTCENC_DIAGNOSTICS)
+ /**
+ * @brief The diagnostic trace logger.
+ *
+ * Note that this is a singleton, so can only be used in single threaded mode. It only exists
+ * here so we have a reference to close the file at the end of the capture.
+ */
+ TraceLog* trace_log;
+#endif
+};
+
+/* ============================================================================
+ Functionality for managing block sizes and partition tables.
+============================================================================ */
+
+/**
+ * @brief Populate the block size descriptor for the target block size.
+ *
+ * This will also initialize the partition table metadata, which is stored as part of the BSD
+ * structure.
+ *
+ * @param x_texels The number of texels in the block X dimension.
+ * @param y_texels The number of texels in the block Y dimension.
+ * @param z_texels The number of texels in the block Z dimension.
+ * @param can_omit_modes Can we discard modes and partitionings that astcenc won't use?
+ * @param partition_count_cutoff The partition count cutoff to use, if we can omit partitionings.
+ * @param mode_cutoff The block mode percentile cutoff [0-1].
+ * @param[out] bsd The descriptor to initialize.
+ */
+void init_block_size_descriptor(
+ unsigned int x_texels,
+ unsigned int y_texels,
+ unsigned int z_texels,
+ bool can_omit_modes,
+ unsigned int partition_count_cutoff,
+ float mode_cutoff,
+ block_size_descriptor& bsd);
+
+/**
+ * @brief Populate the partition tables for the target block size.
+ *
+ * Note the @c bsd descriptor must be initialized by calling @c init_block_size_descriptor() before
+ * calling this function.
+ *
+ * @param[out] bsd The block size information structure to populate.
+ * @param can_omit_partitionings True if we can we drop partitionings that astcenc won't use.
+ * @param partition_count_cutoff The partition count cutoff to use, if we can omit partitionings.
+ */
+void init_partition_tables(
+ block_size_descriptor& bsd,
+ bool can_omit_partitionings,
+ unsigned int partition_count_cutoff);
+
+/**
+ * @brief Get the percentile table for 2D block modes.
+ *
+ * This is an empirically determined prioritization of which block modes to use in the search in
+ * terms of their centile (lower centiles = more useful).
+ *
+ * Returns a dynamically allocated array; caller must free with delete[].
+ *
+ * @param xdim The block x size.
+ * @param ydim The block y size.
+ *
+ * @return The unpacked table.
+ */
+const float* get_2d_percentile_table(
+ unsigned int xdim,
+ unsigned int ydim);
+
+/**
+ * @brief Query if a 2D block size is legal.
+ *
+ * @return True if legal, false otherwise.
+ */
+bool is_legal_2d_block_size(
+ unsigned int xdim,
+ unsigned int ydim);
+
+/**
+ * @brief Query if a 3D block size is legal.
+ *
+ * @return True if legal, false otherwise.
+ */
+bool is_legal_3d_block_size(
+ unsigned int xdim,
+ unsigned int ydim,
+ unsigned int zdim);
+
+/* ============================================================================
+ Functionality for managing BISE quantization and unquantization.
+============================================================================ */
+
+/**
+ * @brief The precomputed table for quantizing color values.
+ *
+ * Converts unquant value in 0-255 range into quant value in 0-255 range.
+ * No BISE scrambling is applied at this stage.
+ *
+ * Indexed by [quant_mode - 4][data_value].
+ */
+extern const uint8_t color_unquant_to_uquant_tables[17][256];
+
+/**
+ * @brief The precomputed table for packing quantized color values.
+ *
+ * Converts quant value in 0-255 range into packed quant value in 0-N range,
+ * with BISE scrambling applied.
+ *
+ * Indexed by [quant_mode - 4][data_value].
+ */
+extern const uint8_t color_uquant_to_scrambled_pquant_tables[17][256];
+
+/**
+ * @brief The precomputed table for unpacking color values.
+ *
+ * Converts quant value in 0-N range into unpacked value in 0-255 range,
+ * with BISE unscrambling applied.
+ *
+ * Indexed by [quant_mode - 4][data_value].
+ */
+extern const uint8_t* color_scrambled_pquant_to_uquant_tables[17];
+
+/**
+ * @brief The precomputed quant mode storage table.
+ *
+ * Indexing by [integer_count/2][bits] gives us the quantization level for a given integer count and
+ * number of compressed storage bits. Returns -1 for cases where the requested integer count cannot
+ * ever fit in the supplied storage size.
+ */
+extern const int8_t quant_mode_table[10][128];
+
+/**
+ * @brief Encode a packed string using BISE.
+ *
+ * Note that BISE can return strings that are not a whole number of bytes in length, and ASTC can
+ * start storing strings in a block at arbitrary bit offsets in the encoded data.
+ *
+ * @param quant_level The BISE alphabet size.
+ * @param character_count The number of characters in the string.
+ * @param input_data The unpacked string, one byte per character.
+ * @param[in,out] output_data The output packed string.
+ * @param bit_offset The starting offset in the output storage.
+ */
+void encode_ise(
+ quant_method quant_level,
+ unsigned int character_count,
+ const uint8_t* input_data,
+ uint8_t* output_data,
+ unsigned int bit_offset);
+
+/**
+ * @brief Decode a packed string using BISE.
+ *
+ * Note that BISE input strings are not a whole number of bytes in length, and ASTC can start
+ * strings at arbitrary bit offsets in the encoded data.
+ *
+ * @param quant_level The BISE alphabet size.
+ * @param character_count The number of characters in the string.
+ * @param input_data The packed string.
+ * @param[in,out] output_data The output storage, one byte per character.
+ * @param bit_offset The starting offset in the output storage.
+ */
+void decode_ise(
+ quant_method quant_level,
+ unsigned int character_count,
+ const uint8_t* input_data,
+ uint8_t* output_data,
+ unsigned int bit_offset);
+
+/**
+ * @brief Return the number of bits needed to encode an ISE sequence.
+ *
+ * This implementation assumes that the @c quant level is untrusted, given it may come from random
+ * data being decompressed, so we return an arbitrary unencodable size if that is the case.
+ *
+ * @param character_count The number of items in the sequence.
+ * @param quant_level The desired quantization level.
+ *
+ * @return The number of bits needed to encode the BISE string.
+ */
+unsigned int get_ise_sequence_bitcount(
+ unsigned int character_count,
+ quant_method quant_level);
+
+/* ============================================================================
+ Functionality for managing color partitioning.
+============================================================================ */
+
+/**
+ * @brief Compute averages and dominant directions for each partition in a 2 component texture.
+ *
+ * @param pi The partition info for the current trial.
+ * @param blk The image block color data to be compressed.
+ * @param component1 The first component included in the analysis.
+ * @param component2 The second component included in the analysis.
+ * @param[out] pm The output partition metrics.
+ * - Only pi.partition_count array entries actually get initialized.
+ * - Direction vectors @c pm.dir are not normalized.
+ */
+void compute_avgs_and_dirs_2_comp(
+ const partition_info& pi,
+ const image_block& blk,
+ unsigned int component1,
+ unsigned int component2,
+ partition_metrics pm[BLOCK_MAX_PARTITIONS]);
+
+/**
+ * @brief Compute averages and dominant directions for each partition in a 3 component texture.
+ *
+ * @param pi The partition info for the current trial.
+ * @param blk The image block color data to be compressed.
+ * @param omitted_component The component excluded from the analysis.
+ * @param[out] pm The output partition metrics.
+ * - Only pi.partition_count array entries actually get initialized.
+ * - Direction vectors @c pm.dir are not normalized.
+ */
+void compute_avgs_and_dirs_3_comp(
+ const partition_info& pi,
+ const image_block& blk,
+ unsigned int omitted_component,
+ partition_metrics pm[BLOCK_MAX_PARTITIONS]);
+
+/**
+ * @brief Compute averages and dominant directions for each partition in a 3 component texture.
+ *
+ * This is a specialization of @c compute_avgs_and_dirs_3_comp where the omitted component is
+ * always alpha, a common case during partition search.
+ *
+ * @param pi The partition info for the current trial.
+ * @param blk The image block color data to be compressed.
+ * @param[out] pm The output partition metrics.
+ * - Only pi.partition_count array entries actually get initialized.
+ * - Direction vectors @c pm.dir are not normalized.
+ */
+void compute_avgs_and_dirs_3_comp_rgb(
+ const partition_info& pi,
+ const image_block& blk,
+ partition_metrics pm[BLOCK_MAX_PARTITIONS]);
+
+/**
+ * @brief Compute averages and dominant directions for each partition in a 4 component texture.
+ *
+ * @param pi The partition info for the current trial.
+ * @param blk The image block color data to be compressed.
+ * @param[out] pm The output partition metrics.
+ * - Only pi.partition_count array entries actually get initialized.
+ * - Direction vectors @c pm.dir are not normalized.
+ */
+void compute_avgs_and_dirs_4_comp(
+ const partition_info& pi,
+ const image_block& blk,
+ partition_metrics pm[BLOCK_MAX_PARTITIONS]);
+
+/**
+ * @brief Compute the RGB error for uncorrelated and same chroma projections.
+ *
+ * The output of compute averages and dirs is post processed to define two lines, both of which go
+ * through the mean-color-value. One line has a direction defined by the dominant direction; this
+ * is used to assess the error from using an uncorrelated color representation. The other line goes
+ * through (0,0,0) and is used to assess the error from using an RGBS color representation.
+ *
+ * This function computes the squared error when using these two representations.
+ *
+ * @param pi The partition info for the current trial.
+ * @param blk The image block color data to be compressed.
+ * @param[in,out] plines Processed line inputs, and line length outputs.
+ * @param[out] uncor_error The cumulative error for using the uncorrelated line.
+ * @param[out] samec_error The cumulative error for using the same chroma line.
+ */
+void compute_error_squared_rgb(
+ const partition_info& pi,
+ const image_block& blk,
+ partition_lines3 plines[BLOCK_MAX_PARTITIONS],
+ float& uncor_error,
+ float& samec_error);
+
+/**
+ * @brief Compute the RGBA error for uncorrelated and same chroma projections.
+ *
+ * The output of compute averages and dirs is post processed to define two lines, both of which go
+ * through the mean-color-value. One line has a direction defined by the dominant direction; this
+ * is used to assess the error from using an uncorrelated color representation. The other line goes
+ * through (0,0,0,1) and is used to assess the error from using an RGBS color representation.
+ *
+ * This function computes the squared error when using these two representations.
+ *
+ * @param pi The partition info for the current trial.
+ * @param blk The image block color data to be compressed.
+ * @param uncor_plines Processed uncorrelated partition lines for each partition.
+ * @param samec_plines Processed same chroma partition lines for each partition.
+ * @param[out] uncor_lengths The length of each components deviation from the line.
+ * @param[out] samec_lengths The length of each components deviation from the line.
+ * @param[out] uncor_error The cumulative error for using the uncorrelated line.
+ * @param[out] samec_error The cumulative error for using the same chroma line.
+ */
+void compute_error_squared_rgba(
+ const partition_info& pi,
+ const image_block& blk,
+ const processed_line4 uncor_plines[BLOCK_MAX_PARTITIONS],
+ const processed_line4 samec_plines[BLOCK_MAX_PARTITIONS],
+ float uncor_lengths[BLOCK_MAX_PARTITIONS],
+ float samec_lengths[BLOCK_MAX_PARTITIONS],
+ float& uncor_error,
+ float& samec_error);
+
+/**
+ * @brief Find the best set of partitions to trial for a given block.
+ *
+ * On return the @c best_partitions list will contain the two best partition
+ * candidates; one assuming data has uncorrelated chroma and one assuming the
+ * data has correlated chroma. The best candidate is returned first in the list.
+ *
+ * @param bsd The block size information.
+ * @param blk The image block color data to compress.
+ * @param partition_count The number of partitions in the block.
+ * @param partition_search_limit The number of candidate partition encodings to trial.
+ * @param[out] best_partitions The best partition candidates.
+ * @param requested_candidates The number of requested partitionings. May return fewer if
+ * candidates are not available.
+ *
+ * @return The actual number of candidates returned.
+ */
+unsigned int find_best_partition_candidates(
+ const block_size_descriptor& bsd,
+ const image_block& blk,
+ unsigned int partition_count,
+ unsigned int partition_search_limit,
+ unsigned int best_partitions[TUNE_MAX_PARTITIONING_CANDIDATES],
+ unsigned int requested_candidates);
+
+/* ============================================================================
+ Functionality for managing images and image related data.
+============================================================================ */
+
+/**
+ * @brief Setup computation of regional averages in an image.
+ *
+ * This must be done by only a single thread per image, before any thread calls
+ * @c compute_averages().
+ *
+ * Results are written back into @c img->input_alpha_averages.
+ *
+ * @param img The input image data, also holds output data.
+ * @param alpha_kernel_radius The kernel radius (in pixels) for alpha mods.
+ * @param swz Input data component swizzle.
+ * @param[out] ag The average variance arguments to init.
+ *
+ * @return The number of tasks in the processing stage.
+ */
+unsigned int init_compute_averages(
+ const astcenc_image& img,
+ unsigned int alpha_kernel_radius,
+ const astcenc_swizzle& swz,
+ avg_args& ag);
+
+/**
+ * @brief Compute averages for a pixel region.
+ *
+ * The routine computes both in a single pass, using a summed-area table to decouple the running
+ * time from the averaging/variance kernel size.
+ *
+ * @param[out] ctx The compressor context storing the output data.
+ * @param arg The input parameter structure.
+ */
+void compute_pixel_region_variance(
+ astcenc_contexti& ctx,
+ const pixel_region_args& arg);
+/**
+ * @brief Load a single image block from the input image.
+ *
+ * @param decode_mode The compression color profile.
+ * @param img The input image data.
+ * @param[out] blk The image block to populate.
+ * @param bsd The block size information.
+ * @param xpos The block X coordinate in the input image.
+ * @param ypos The block Y coordinate in the input image.
+ * @param zpos The block Z coordinate in the input image.
+ * @param swz The swizzle to apply on load.
+ */
+void load_image_block(
+ astcenc_profile decode_mode,
+ const astcenc_image& img,
+ image_block& blk,
+ const block_size_descriptor& bsd,
+ unsigned int xpos,
+ unsigned int ypos,
+ unsigned int zpos,
+ const astcenc_swizzle& swz);
+
+/**
+ * @brief Load a single image block from the input image.
+ *
+ * This specialized variant can be used only if the block is 2D LDR U8 data,
+ * with no swizzle.
+ *
+ * @param decode_mode The compression color profile.
+ * @param img The input image data.
+ * @param[out] blk The image block to populate.
+ * @param bsd The block size information.
+ * @param xpos The block X coordinate in the input image.
+ * @param ypos The block Y coordinate in the input image.
+ * @param zpos The block Z coordinate in the input image.
+ * @param swz The swizzle to apply on load.
+ */
+void load_image_block_fast_ldr(
+ astcenc_profile decode_mode,
+ const astcenc_image& img,
+ image_block& blk,
+ const block_size_descriptor& bsd,
+ unsigned int xpos,
+ unsigned int ypos,
+ unsigned int zpos,
+ const astcenc_swizzle& swz);
+
+/**
+ * @brief Store a single image block to the output image.
+ *
+ * @param[out] img The output image data.
+ * @param blk The image block to export.
+ * @param bsd The block size information.
+ * @param xpos The block X coordinate in the input image.
+ * @param ypos The block Y coordinate in the input image.
+ * @param zpos The block Z coordinate in the input image.
+ * @param swz The swizzle to apply on store.
+ */
+void store_image_block(
+ astcenc_image& img,
+ const image_block& blk,
+ const block_size_descriptor& bsd,
+ unsigned int xpos,
+ unsigned int ypos,
+ unsigned int zpos,
+ const astcenc_swizzle& swz);
+
+/* ============================================================================
+ Functionality for computing endpoint colors and weights for a block.
+============================================================================ */
+
+/**
+ * @brief Compute ideal endpoint colors and weights for 1 plane of weights.
+ *
+ * The ideal endpoints define a color line for the partition. For each texel the ideal weight
+ * defines an exact position on the partition color line. We can then use these to assess the error
+ * introduced by removing and quantizing the weight grid.
+ *
+ * @param blk The image block color data to compress.
+ * @param pi The partition info for the current trial.
+ * @param[out] ei The endpoint and weight values.
+ */
+void compute_ideal_colors_and_weights_1plane(
+ const image_block& blk,
+ const partition_info& pi,
+ endpoints_and_weights& ei);
+
+/**
+ * @brief Compute ideal endpoint colors and weights for 2 planes of weights.
+ *
+ * The ideal endpoints define a color line for the partition. For each texel the ideal weight
+ * defines an exact position on the partition color line. We can then use these to assess the error
+ * introduced by removing and quantizing the weight grid.
+ *
+ * @param bsd The block size information.
+ * @param blk The image block color data to compress.
+ * @param plane2_component The component assigned to plane 2.
+ * @param[out] ei1 The endpoint and weight values for plane 1.
+ * @param[out] ei2 The endpoint and weight values for plane 2.
+ */
+void compute_ideal_colors_and_weights_2planes(
+ const block_size_descriptor& bsd,
+ const image_block& blk,
+ unsigned int plane2_component,
+ endpoints_and_weights& ei1,
+ endpoints_and_weights& ei2);
+
+/**
+ * @brief Compute the optimal unquantized weights for a decimation table.
+ *
+ * After computing ideal weights for the case for a complete weight grid, we we want to compute the
+ * ideal weights for the case where weights exist only for some texels. We do this with a
+ * steepest-descent grid solver which works as follows:
+ *
+ * First, for each actual weight, perform a weighted averaging of the texels affected by the weight.
+ * Then, set step size to <some initial value> and attempt one step towards the original ideal
+ * weight if it helps to reduce error.
+ *
+ * @param ei The non-decimated endpoints and weights.
+ * @param di The selected weight decimation.
+ * @param[out] dec_weight_ideal_value The ideal values for the decimated weight set.
+ */
+void compute_ideal_weights_for_decimation(
+ const endpoints_and_weights& ei,
+ const decimation_info& di,
+ float* dec_weight_ideal_value);
+
+/**
+ * @brief Compute the optimal quantized weights for a decimation table.
+ *
+ * We test the two closest weight indices in the allowed quantization range and keep the weight that
+ * is the closest match.
+ *
+ * @param di The selected weight decimation.
+ * @param low_bound The lowest weight allowed.
+ * @param high_bound The highest weight allowed.
+ * @param dec_weight_ideal_value The ideal weight set.
+ * @param[out] dec_weight_quant_uvalue The output quantized weight as a float.
+ * @param[out] dec_weight_uquant The output quantized weight as encoded int.
+ * @param quant_level The desired weight quant level.
+ */
+void compute_quantized_weights_for_decimation(
+ const decimation_info& di,
+ float low_bound,
+ float high_bound,
+ const float* dec_weight_ideal_value,
+ float* dec_weight_quant_uvalue,
+ uint8_t* dec_weight_uquant,
+ quant_method quant_level);
+
+/**
+ * @brief Compute the error of a decimated weight set for 1 plane.
+ *
+ * After computing ideal weights for the case with one weight per texel, we want to compute the
+ * error for decimated weight grids where weights are stored at a lower resolution. This function
+ * computes the error of the reduced grid, compared to the full grid.
+ *
+ * @param eai The ideal weights for the full grid.
+ * @param di The selected weight decimation.
+ * @param dec_weight_quant_uvalue The quantized weights for the decimated grid.
+ *
+ * @return The accumulated error.
+ */
+float compute_error_of_weight_set_1plane(
+ const endpoints_and_weights& eai,
+ const decimation_info& di,
+ const float* dec_weight_quant_uvalue);
+
+/**
+ * @brief Compute the error of a decimated weight set for 2 planes.
+ *
+ * After computing ideal weights for the case with one weight per texel, we want to compute the
+ * error for decimated weight grids where weights are stored at a lower resolution. This function
+ * computes the error of the reduced grid, compared to the full grid.
+ *
+ * @param eai1 The ideal weights for the full grid and plane 1.
+ * @param eai2 The ideal weights for the full grid and plane 2.
+ * @param di The selected weight decimation.
+ * @param dec_weight_quant_uvalue_plane1 The quantized weights for the decimated grid plane 1.
+ * @param dec_weight_quant_uvalue_plane2 The quantized weights for the decimated grid plane 2.
+ *
+ * @return The accumulated error.
+ */
+float compute_error_of_weight_set_2planes(
+ const endpoints_and_weights& eai1,
+ const endpoints_and_weights& eai2,
+ const decimation_info& di,
+ const float* dec_weight_quant_uvalue_plane1,
+ const float* dec_weight_quant_uvalue_plane2);
+
+/**
+ * @brief Pack a single pair of color endpoints as effectively as possible.
+ *
+ * The user requests a base color endpoint mode in @c format, but the quantizer may choose a
+ * delta-based representation. It will report back the format variant it actually used.
+ *
+ * @param color0 The input unquantized color0 endpoint for absolute endpoint pairs.
+ * @param color1 The input unquantized color1 endpoint for absolute endpoint pairs.
+ * @param rgbs_color The input unquantized RGBS variant endpoint for same chroma endpoints.
+ * @param rgbo_color The input unquantized RGBS variant endpoint for HDR endpoints.
+ * @param format The desired base format.
+ * @param[out] output The output storage for the quantized colors/
+ * @param quant_level The quantization level requested.
+ *
+ * @return The actual endpoint mode used.
+ */
+uint8_t pack_color_endpoints(
+ vfloat4 color0,
+ vfloat4 color1,
+ vfloat4 rgbs_color,
+ vfloat4 rgbo_color,
+ int format,
+ uint8_t* output,
+ quant_method quant_level);
+
+/**
+ * @brief Unpack a single pair of encoded endpoints.
+ *
+ * Endpoints must be unscrambled and converted into the 0-255 range before calling this functions.
+ *
+ * @param decode_mode The decode mode (LDR, HDR).
+ * @param format The color endpoint mode used.
+ * @param input The raw array of encoded input integers. The length of this array
+ * depends on @c format; it can be safely assumed to be large enough.
+ * @param[out] rgb_hdr Is the endpoint using HDR for the RGB channels?
+ * @param[out] alpha_hdr Is the endpoint using HDR for the A channel?
+ * @param[out] output0 The output color for endpoint 0.
+ * @param[out] output1 The output color for endpoint 1.
+ */
+void unpack_color_endpoints(
+ astcenc_profile decode_mode,
+ int format,
+ const uint8_t* input,
+ bool& rgb_hdr,
+ bool& alpha_hdr,
+ vint4& output0,
+ vint4& output1);
+
+/**
+ * @brief Unpack a set of quantized and decimated weights.
+ *
+ * TODO: Can we skip this for non-decimated weights now that the @c scb is
+ * already storing unquantized weights?
+ *
+ * @param bsd The block size information.
+ * @param scb The symbolic compressed encoding.
+ * @param di The weight grid decimation table.
+ * @param is_dual_plane @c true if this is a dual plane block, @c false otherwise.
+ * @param[out] weights_plane1 The output array for storing the plane 1 weights.
+ * @param[out] weights_plane2 The output array for storing the plane 2 weights.
+ */
+void unpack_weights(
+ const block_size_descriptor& bsd,
+ const symbolic_compressed_block& scb,
+ const decimation_info& di,
+ bool is_dual_plane,
+ int weights_plane1[BLOCK_MAX_TEXELS],
+ int weights_plane2[BLOCK_MAX_TEXELS]);
+
+/**
+ * @brief Identify, for each mode, which set of color endpoint produces the best result.
+ *
+ * Returns the best @c tune_candidate_limit best looking modes, along with the ideal color encoding
+ * combination for each. The modified quantization level can be used when all formats are the same,
+ * as this frees up two additional bits of storage.
+ *
+ * @param pi The partition info for the current trial.
+ * @param blk The image block color data to compress.
+ * @param ep The ideal endpoints.
+ * @param qwt_bitcounts Bit counts for different quantization methods.
+ * @param qwt_errors Errors for different quantization methods.
+ * @param tune_candidate_limit The max number of candidates to return, may be less.
+ * @param start_block_mode The first block mode to inspect.
+ * @param end_block_mode The last block mode to inspect.
+ * @param[out] partition_format_specifiers The best formats per partition.
+ * @param[out] block_mode The best packed block mode indexes.
+ * @param[out] quant_level The best color quant level.
+ * @param[out] quant_level_mod The best color quant level if endpoints are the same.
+ * @param[out] tmpbuf Preallocated scratch buffers for the compressor.
+ *
+ * @return The actual number of candidate matches returned.
+ */
+unsigned int compute_ideal_endpoint_formats(
+ const partition_info& pi,
+ const image_block& blk,
+ const endpoints& ep,
+ const int8_t* qwt_bitcounts,
+ const float* qwt_errors,
+ unsigned int tune_candidate_limit,
+ unsigned int start_block_mode,
+ unsigned int end_block_mode,
+ uint8_t partition_format_specifiers[TUNE_MAX_TRIAL_CANDIDATES][BLOCK_MAX_PARTITIONS],
+ int block_mode[TUNE_MAX_TRIAL_CANDIDATES],
+ quant_method quant_level[TUNE_MAX_TRIAL_CANDIDATES],
+ quant_method quant_level_mod[TUNE_MAX_TRIAL_CANDIDATES],
+ compression_working_buffers& tmpbuf);
+
+/**
+ * @brief For a given 1 plane weight set recompute the endpoint colors.
+ *
+ * As we quantize and decimate weights the optimal endpoint colors may change slightly, so we must
+ * recompute the ideal colors for a specific weight set.
+ *
+ * @param blk The image block color data to compress.
+ * @param pi The partition info for the current trial.
+ * @param di The weight grid decimation table.
+ * @param dec_weights_uquant The quantized weight set.
+ * @param[in,out] ep The color endpoints (modifed in place).
+ * @param[out] rgbs_vectors The RGB+scale vectors for LDR blocks.
+ * @param[out] rgbo_vectors The RGB+offset vectors for HDR blocks.
+ */
+void recompute_ideal_colors_1plane(
+ const image_block& blk,
+ const partition_info& pi,
+ const decimation_info& di,
+ const uint8_t* dec_weights_uquant,
+ endpoints& ep,
+ vfloat4 rgbs_vectors[BLOCK_MAX_PARTITIONS],
+ vfloat4 rgbo_vectors[BLOCK_MAX_PARTITIONS]);
+
+/**
+ * @brief For a given 2 plane weight set recompute the endpoint colors.
+ *
+ * As we quantize and decimate weights the optimal endpoint colors may change slightly, so we must
+ * recompute the ideal colors for a specific weight set.
+ *
+ * @param blk The image block color data to compress.
+ * @param bsd The block_size descriptor.
+ * @param di The weight grid decimation table.
+ * @param dec_weights_uquant_plane1 The quantized weight set for plane 1.
+ * @param dec_weights_uquant_plane2 The quantized weight set for plane 2.
+ * @param[in,out] ep The color endpoints (modifed in place).
+ * @param[out] rgbs_vector The RGB+scale color for LDR blocks.
+ * @param[out] rgbo_vector The RGB+offset color for HDR blocks.
+ * @param plane2_component The component assigned to plane 2.
+ */
+void recompute_ideal_colors_2planes(
+ const image_block& blk,
+ const block_size_descriptor& bsd,
+ const decimation_info& di,
+ const uint8_t* dec_weights_uquant_plane1,
+ const uint8_t* dec_weights_uquant_plane2,
+ endpoints& ep,
+ vfloat4& rgbs_vector,
+ vfloat4& rgbo_vector,
+ int plane2_component);
+
+/**
+ * @brief Expand the angular tables needed for the alternative to PCA that we use.
+ */
+void prepare_angular_tables();
+
+/**
+ * @brief Compute the angular endpoints for one plane for each block mode.
+ *
+ * @param only_always Only consider block modes that are always enabled.
+ * @param bsd The block size descriptor for the current trial.
+ * @param dec_weight_ideal_value The ideal decimated unquantized weight values.
+ * @param max_weight_quant The maximum block mode weight quantization allowed.
+ * @param[out] tmpbuf Preallocated scratch buffers for the compressor.
+ */
+void compute_angular_endpoints_1plane(
+ bool only_always,
+ const block_size_descriptor& bsd,
+ const float* dec_weight_ideal_value,
+ unsigned int max_weight_quant,
+ compression_working_buffers& tmpbuf);
+
+/**
+ * @brief Compute the angular endpoints for two planes for each block mode.
+ *
+ * @param bsd The block size descriptor for the current trial.
+ * @param dec_weight_ideal_value The ideal decimated unquantized weight values.
+ * @param max_weight_quant The maximum block mode weight quantization allowed.
+ * @param[out] tmpbuf Preallocated scratch buffers for the compressor.
+ */
+void compute_angular_endpoints_2planes(
+ const block_size_descriptor& bsd,
+ const float* dec_weight_ideal_value,
+ unsigned int max_weight_quant,
+ compression_working_buffers& tmpbuf);
+
+/* ============================================================================
+ Functionality for high level compression and decompression access.
+============================================================================ */
+
+/**
+ * @brief Compress an image block into a physical block.
+ *
+ * @param ctx The compressor context and configuration.
+ * @param blk The image block color data to compress.
+ * @param[out] pcb The physical compressed block output.
+ * @param[out] tmpbuf Preallocated scratch buffers for the compressor.
+ */
+void compress_block(
+ const astcenc_contexti& ctx,
+ const image_block& blk,
+ physical_compressed_block& pcb,
+ compression_working_buffers& tmpbuf);
+
+/**
+ * @brief Decompress a symbolic block in to an image block.
+ *
+ * @param decode_mode The decode mode (LDR, HDR, etc).
+ * @param bsd The block size information.
+ * @param xpos The X coordinate of the block in the overall image.
+ * @param ypos The Y coordinate of the block in the overall image.
+ * @param zpos The Z coordinate of the block in the overall image.
+ * @param[out] blk The decompressed image block color data.
+ */
+void decompress_symbolic_block(
+ astcenc_profile decode_mode,
+ const block_size_descriptor& bsd,
+ int xpos,
+ int ypos,
+ int zpos,
+ const symbolic_compressed_block& scb,
+ image_block& blk);
+
+/**
+ * @brief Compute the error between a symbolic block and the original input data.
+ *
+ * This function is specialized for 2 plane and 1 partition search.
+ *
+ * In RGBM mode this will reject blocks that attempt to encode a zero M value.
+ *
+ * @param config The compressor config.
+ * @param bsd The block size information.
+ * @param scb The symbolic compressed encoding.
+ * @param blk The original image block color data.
+ *
+ * @return Returns the computed error, or a negative value if the encoding
+ * should be rejected for any reason.
+ */
+float compute_symbolic_block_difference_2plane(
+ const astcenc_config& config,
+ const block_size_descriptor& bsd,
+ const symbolic_compressed_block& scb,
+ const image_block& blk);
+
+/**
+ * @brief Compute the error between a symbolic block and the original input data.
+ *
+ * This function is specialized for 1 plane and N partition search.
+ *
+ * In RGBM mode this will reject blocks that attempt to encode a zero M value.
+ *
+ * @param config The compressor config.
+ * @param bsd The block size information.
+ * @param scb The symbolic compressed encoding.
+ * @param blk The original image block color data.
+ *
+ * @return Returns the computed error, or a negative value if the encoding
+ * should be rejected for any reason.
+ */
+float compute_symbolic_block_difference_1plane(
+ const astcenc_config& config,
+ const block_size_descriptor& bsd,
+ const symbolic_compressed_block& scb,
+ const image_block& blk);
+
+/**
+ * @brief Compute the error between a symbolic block and the original input data.
+ *
+ * This function is specialized for 1 plane and 1 partition search.
+ *
+ * In RGBM mode this will reject blocks that attempt to encode a zero M value.
+ *
+ * @param config The compressor config.
+ * @param bsd The block size information.
+ * @param scb The symbolic compressed encoding.
+ * @param blk The original image block color data.
+ *
+ * @return Returns the computed error, or a negative value if the encoding
+ * should be rejected for any reason.
+ */
+float compute_symbolic_block_difference_1plane_1partition(
+ const astcenc_config& config,
+ const block_size_descriptor& bsd,
+ const symbolic_compressed_block& scb,
+ const image_block& blk);
+
+/**
+ * @brief Convert a symbolic representation into a binary physical encoding.
+ *
+ * It is assumed that the symbolic encoding is valid and encodable, or
+ * previously flagged as an error block if an error color it to be encoded.
+ *
+ * @param bsd The block size information.
+ * @param scb The symbolic representation.
+ * @param[out] pcb The binary encoded data.
+ */
+void symbolic_to_physical(
+ const block_size_descriptor& bsd,
+ const symbolic_compressed_block& scb,
+ physical_compressed_block& pcb);
+
+/**
+ * @brief Convert a binary physical encoding into a symbolic representation.
+ *
+ * This function can cope with arbitrary input data; output blocks will be
+ * flagged as an error block if the encoding is invalid.
+ *
+ * @param bsd The block size information.
+ * @param pcb The binary encoded data.
+ * @param[out] scb The output symbolic representation.
+ */
+void physical_to_symbolic(
+ const block_size_descriptor& bsd,
+ const physical_compressed_block& pcb,
+ symbolic_compressed_block& scb);
+
+/* ============================================================================
+Platform-specific functions.
+============================================================================ */
+/**
+ * @brief Run-time detection if the host CPU supports the POPCNT extension.
+ *
+ * @return @c true if supported, @c false if not.
+ */
+bool cpu_supports_popcnt();
+
+/**
+ * @brief Run-time detection if the host CPU supports F16C extension.
+ *
+ * @return @c true if supported, @c false if not.
+ */
+bool cpu_supports_f16c();
+
+/**
+ * @brief Run-time detection if the host CPU supports SSE 4.1 extension.
+ *
+ * @return @c true if supported, @c false if not.
+ */
+bool cpu_supports_sse41();
+
+/**
+ * @brief Run-time detection if the host CPU supports AVX 2 extension.
+ *
+ * @return @c true if supported, @c false if not.
+ */
+bool cpu_supports_avx2();
+
+/**
+ * @brief Allocate an aligned memory buffer.
+ *
+ * Allocated memory must be freed by aligned_free;
+ *
+ * @param size The desired buffer size.
+ * @param align The desired buffer alignment; must be 2^N.
+ *
+ * @return The memory buffer pointer or nullptr on allocation failure.
+ */
+template<typename T>
+T* aligned_malloc(size_t size, size_t align)
+{
+ void* ptr;
+ int error = 0;
+
+#if defined(_WIN32)
+ ptr = _aligned_malloc(size, align);
+#else
+ error = posix_memalign(&ptr, align, size);
+#endif
+
+ if (error || (!ptr))
+ {
+ return nullptr;
+ }
+
+ return static_cast<T*>(ptr);
+}
+
+/**
+ * @brief Free an aligned memory buffer.
+ *
+ * @param ptr The buffer to free.
+ */
+template<typename T>
+void aligned_free(T* ptr)
+{
+#if defined(_WIN32)
+ _aligned_free(reinterpret_cast<void*>(ptr));
+#else
+ free(reinterpret_cast<void*>(ptr));
+#endif
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_internal_entry.h b/thirdparty/astcenc/astcenc_internal_entry.h
new file mode 100644
index 0000000000..4e8794547a
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_internal_entry.h
@@ -0,0 +1,273 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Functions and data declarations for the outer context.
+ *
+ * The outer context includes thread-pool management, which is slower to
+ * compile due to increased use of C++ stdlib. The inner context used in the
+ * majority of the codec library does not include this.
+ */
+
+#ifndef ASTCENC_INTERNAL_ENTRY_INCLUDED
+#define ASTCENC_INTERNAL_ENTRY_INCLUDED
+
+#include <atomic>
+#include <condition_variable>
+#include <functional>
+#include <mutex>
+
+#include "astcenc_internal.h"
+
+/* ============================================================================
+ Parallel execution control
+============================================================================ */
+
+/**
+ * @brief A simple counter-based manager for parallel task execution.
+ *
+ * The task processing execution consists of:
+ *
+ * * A single-threaded init stage.
+ * * A multi-threaded processing stage.
+ * * A condition variable so threads can wait for processing completion.
+ *
+ * The init stage will be executed by the first thread to arrive in the critical section, there is
+ * no main thread in the thread pool.
+ *
+ * The processing stage uses dynamic dispatch to assign task tickets to threads on an on-demand
+ * basis. Threads may each therefore executed different numbers of tasks, depending on their
+ * processing complexity. The task queue and the task tickets are just counters; the caller must map
+ * these integers to an actual processing partition in a specific problem domain.
+ *
+ * The exit wait condition is needed to ensure processing has finished before a worker thread can
+ * progress to the next stage of the pipeline. Specifically a worker may exit the processing stage
+ * because there are no new tasks to assign to it while other worker threads are still processing.
+ * Calling @c wait() will ensure that all other worker have finished before the thread can proceed.
+ *
+ * The basic usage model:
+ *
+ * // --------- From single-threaded code ---------
+ *
+ * // Reset the tracker state
+ * manager->reset()
+ *
+ * // --------- From multi-threaded code ---------
+ *
+ * // Run the stage init; only first thread actually runs the lambda
+ * manager->init(<lambda>)
+ *
+ * do
+ * {
+ * // Request a task assignment
+ * uint task_count;
+ * uint base_index = manager->get_tasks(<granule>, task_count);
+ *
+ * // Process any tasks we were given (task_count <= granule size)
+ * if (task_count)
+ * {
+ * // Run the user task processing code for N tasks here
+ * ...
+ *
+ * // Flag these tasks as complete
+ * manager->complete_tasks(task_count);
+ * }
+ * } while (task_count);
+ *
+ * // Wait for all threads to complete tasks before progressing
+ * manager->wait()
+ *
+ * // Run the stage term; only first thread actually runs the lambda
+ * manager->term(<lambda>)
+ */
+class ParallelManager
+{
+private:
+ /** @brief Lock used for critical section and condition synchronization. */
+ std::mutex m_lock;
+
+ /** @brief True if the stage init() step has been executed. */
+ bool m_init_done;
+
+ /** @brief True if the stage term() step has been executed. */
+ bool m_term_done;
+
+ /** @brief Condition variable for tracking stage processing completion. */
+ std::condition_variable m_complete;
+
+ /** @brief Number of tasks started, but not necessarily finished. */
+ std::atomic<unsigned int> m_start_count;
+
+ /** @brief Number of tasks finished. */
+ unsigned int m_done_count;
+
+ /** @brief Number of tasks that need to be processed. */
+ unsigned int m_task_count;
+
+public:
+ /** @brief Create a new ParallelManager. */
+ ParallelManager()
+ {
+ reset();
+ }
+
+ /**
+ * @brief Reset the tracker for a new processing batch.
+ *
+ * This must be called from single-threaded code before starting the multi-threaded processing
+ * operations.
+ */
+ void reset()
+ {
+ m_init_done = false;
+ m_term_done = false;
+ m_start_count = 0;
+ m_done_count = 0;
+ m_task_count = 0;
+ }
+
+ /**
+ * @brief Trigger the pipeline stage init step.
+ *
+ * This can be called from multi-threaded code. The first thread to hit this will process the
+ * initialization. Other threads will block and wait for it to complete.
+ *
+ * @param init_func Callable which executes the stage initialization. It must return the
+ * total number of tasks in the stage.
+ */
+ void init(std::function<unsigned int(void)> init_func)
+ {
+ std::lock_guard<std::mutex> lck(m_lock);
+ if (!m_init_done)
+ {
+ m_task_count = init_func();
+ m_init_done = true;
+ }
+ }
+
+ /**
+ * @brief Trigger the pipeline stage init step.
+ *
+ * This can be called from multi-threaded code. The first thread to hit this will process the
+ * initialization. Other threads will block and wait for it to complete.
+ *
+ * @param task_count Total number of tasks needing processing.
+ */
+ void init(unsigned int task_count)
+ {
+ std::lock_guard<std::mutex> lck(m_lock);
+ if (!m_init_done)
+ {
+ m_task_count = task_count;
+ m_init_done = true;
+ }
+ }
+
+ /**
+ * @brief Request a task assignment.
+ *
+ * Assign up to @c granule tasks to the caller for processing.
+ *
+ * @param granule Maximum number of tasks that can be assigned.
+ * @param[out] count Actual number of tasks assigned, or zero if no tasks were assigned.
+ *
+ * @return Task index of the first assigned task; assigned tasks increment from this.
+ */
+ unsigned int get_task_assignment(unsigned int granule, unsigned int& count)
+ {
+ unsigned int base = m_start_count.fetch_add(granule, std::memory_order_relaxed);
+ if (base >= m_task_count)
+ {
+ count = 0;
+ return 0;
+ }
+
+ count = astc::min(m_task_count - base, granule);
+ return base;
+ }
+
+ /**
+ * @brief Complete a task assignment.
+ *
+ * Mark @c count tasks as complete. This will notify all threads blocked on @c wait() if this
+ * completes the processing of the stage.
+ *
+ * @param count The number of completed tasks.
+ */
+ void complete_task_assignment(unsigned int count)
+ {
+ // Note: m_done_count cannot use an atomic without the mutex; this has a race between the
+ // update here and the wait() for other threads
+ std::unique_lock<std::mutex> lck(m_lock);
+ this->m_done_count += count;
+ if (m_done_count == m_task_count)
+ {
+ lck.unlock();
+ m_complete.notify_all();
+ }
+ }
+
+ /**
+ * @brief Wait for stage processing to complete.
+ */
+ void wait()
+ {
+ std::unique_lock<std::mutex> lck(m_lock);
+ m_complete.wait(lck, [this]{ return m_done_count == m_task_count; });
+ }
+
+ /**
+ * @brief Trigger the pipeline stage term step.
+ *
+ * This can be called from multi-threaded code. The first thread to hit this will process the
+ * work pool termination. Caller must have called @c wait() prior to calling this function to
+ * ensure that processing is complete.
+ *
+ * @param term_func Callable which executes the stage termination.
+ */
+ void term(std::function<void(void)> term_func)
+ {
+ std::lock_guard<std::mutex> lck(m_lock);
+ if (!m_term_done)
+ {
+ term_func();
+ m_term_done = true;
+ }
+ }
+};
+
+/**
+ * @brief The astcenc compression context.
+ */
+struct astcenc_context
+{
+ /** @brief The context internal state. */
+ astcenc_contexti context;
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+ /** @brief The parallel manager for averages computation. */
+ ParallelManager manage_avg;
+
+ /** @brief The parallel manager for compression. */
+ ParallelManager manage_compress;
+#endif
+
+ /** @brief The parallel manager for decompression. */
+ ParallelManager manage_decompress;
+};
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_mathlib.cpp b/thirdparty/astcenc/astcenc_mathlib.cpp
new file mode 100644
index 0000000000..f276ac7e3d
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_mathlib.cpp
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2021 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+#include "astcenc_mathlib.h"
+
+/**
+ * @brief 64-bit rotate left.
+ *
+ * @param val The value to rotate.
+ * @param count The rotation, in bits.
+ */
+static inline uint64_t rotl(uint64_t val, int count)
+{
+ return (val << count) | (val >> (64 - count));
+}
+
+/* See header for documentation. */
+void astc::rand_init(uint64_t state[2])
+{
+ state[0] = 0xfaf9e171cea1ec6bULL;
+ state[1] = 0xf1b318cc06af5d71ULL;
+}
+
+/* See header for documentation. */
+uint64_t astc::rand(uint64_t state[2])
+{
+ uint64_t s0 = state[0];
+ uint64_t s1 = state[1];
+ uint64_t res = s0 + s1;
+ s1 ^= s0;
+ state[0] = rotl(s0, 24) ^ s1 ^ (s1 << 16);
+ state[1] = rotl(s1, 37);
+ return res;
+}
diff --git a/thirdparty/astcenc/astcenc_mathlib.h b/thirdparty/astcenc/astcenc_mathlib.h
new file mode 100644
index 0000000000..67e989e7f5
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_mathlib.h
@@ -0,0 +1,478 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2021 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/*
+ * This module implements a variety of mathematical data types and library
+ * functions used by the codec.
+ */
+
+#ifndef ASTC_MATHLIB_H_INCLUDED
+#define ASTC_MATHLIB_H_INCLUDED
+
+#include <cassert>
+#include <cstdint>
+#include <cmath>
+
+#ifndef ASTCENC_POPCNT
+ #if defined(__POPCNT__)
+ #define ASTCENC_POPCNT 1
+ #else
+ #define ASTCENC_POPCNT 0
+ #endif
+#endif
+
+#ifndef ASTCENC_F16C
+ #if defined(__F16C__)
+ #define ASTCENC_F16C 1
+ #else
+ #define ASTCENC_F16C 0
+ #endif
+#endif
+
+#ifndef ASTCENC_SSE
+ #if defined(__SSE4_2__)
+ #define ASTCENC_SSE 42
+ #elif defined(__SSE4_1__)
+ #define ASTCENC_SSE 41
+ #elif defined(__SSE3__)
+ #define ASTCENC_SSE 30
+ #elif defined(__SSE2__)
+ #define ASTCENC_SSE 20
+ #else
+ #define ASTCENC_SSE 0
+ #endif
+#endif
+
+#ifndef ASTCENC_AVX
+ #if defined(__AVX2__)
+ #define ASTCENC_AVX 2
+ #elif defined(__AVX__)
+ #define ASTCENC_AVX 1
+ #else
+ #define ASTCENC_AVX 0
+ #endif
+#endif
+
+#ifndef ASTCENC_NEON
+ #if defined(__aarch64__)
+ #define ASTCENC_NEON 1
+ #else
+ #define ASTCENC_NEON 0
+ #endif
+#endif
+
+#if ASTCENC_AVX
+ #define ASTCENC_VECALIGN 32
+#else
+ #define ASTCENC_VECALIGN 16
+#endif
+
+#if ASTCENC_SSE != 0 || ASTCENC_AVX != 0 || ASTCENC_POPCNT != 0
+ #include <immintrin.h>
+#endif
+
+/* ============================================================================
+ Fast math library; note that many of the higher-order functions in this set
+ use approximations which are less accurate, but faster, than <cmath> standard
+ library equivalents.
+
+ Note: Many of these are not necessarily faster than simple C versions when
+ used on a single scalar value, but are included for testing purposes as most
+ have an option based on SSE intrinsics and therefore provide an obvious route
+ to future vectorization.
+============================================================================ */
+
+// Union for manipulation of float bit patterns
+typedef union
+{
+ uint32_t u;
+ int32_t s;
+ float f;
+} if32;
+
+// These are namespaced to avoid colliding with C standard library functions.
+namespace astc
+{
+
+static const float PI = 3.14159265358979323846f;
+static const float PI_OVER_TWO = 1.57079632679489661923f;
+
+/**
+ * @brief SP float absolute value.
+ *
+ * @param v The value to make absolute.
+ *
+ * @return The absolute value.
+ */
+static inline float fabs(float v)
+{
+ return std::fabs(v);
+}
+
+/**
+ * @brief Test if a float value is a nan.
+ *
+ * @param v The value test.
+ *
+ * @return Zero is not a NaN, non-zero otherwise.
+ */
+static inline bool isnan(float v)
+{
+ return v != v;
+}
+
+/**
+ * @brief Return the minimum of two values.
+ *
+ * For floats, NaNs are turned into @c q.
+ *
+ * @param p The first value to compare.
+ * @param q The second value to compare.
+ *
+ * @return The smallest value.
+ */
+template<typename T>
+static inline T min(T p, T q)
+{
+ return p < q ? p : q;
+}
+
+/**
+ * @brief Return the minimum of three values.
+ *
+ * For floats, NaNs are turned into @c r.
+ *
+ * @param p The first value to compare.
+ * @param q The second value to compare.
+ * @param r The third value to compare.
+ *
+ * @return The smallest value.
+ */
+template<typename T>
+static inline T min(T p, T q, T r)
+{
+ return min(min(p, q), r);
+}
+
+/**
+ * @brief Return the minimum of four values.
+ *
+ * For floats, NaNs are turned into @c s.
+ *
+ * @param p The first value to compare.
+ * @param q The second value to compare.
+ * @param r The third value to compare.
+ * @param s The fourth value to compare.
+ *
+ * @return The smallest value.
+ */
+template<typename T>
+static inline T min(T p, T q, T r, T s)
+{
+ return min(min(p, q), min(r, s));
+}
+
+/**
+ * @brief Return the maximum of two values.
+ *
+ * For floats, NaNs are turned into @c q.
+ *
+ * @param p The first value to compare.
+ * @param q The second value to compare.
+ *
+ * @return The largest value.
+ */
+template<typename T>
+static inline T max(T p, T q)
+{
+ return p > q ? p : q;
+}
+
+/**
+ * @brief Return the maximum of three values.
+ *
+ * For floats, NaNs are turned into @c r.
+ *
+ * @param p The first value to compare.
+ * @param q The second value to compare.
+ * @param r The third value to compare.
+ *
+ * @return The largest value.
+ */
+template<typename T>
+static inline T max(T p, T q, T r)
+{
+ return max(max(p, q), r);
+}
+
+/**
+ * @brief Return the maximum of four values.
+ *
+ * For floats, NaNs are turned into @c s.
+ *
+ * @param p The first value to compare.
+ * @param q The second value to compare.
+ * @param r The third value to compare.
+ * @param s The fourth value to compare.
+ *
+ * @return The largest value.
+ */
+template<typename T>
+static inline T max(T p, T q, T r, T s)
+{
+ return max(max(p, q), max(r, s));
+}
+
+/**
+ * @brief Clamp a value value between @c mn and @c mx.
+ *
+ * For floats, NaNs are turned into @c mn.
+ *
+ * @param v The value to clamp.
+ * @param mn The min value (inclusive).
+ * @param mx The max value (inclusive).
+ *
+ * @return The clamped value.
+ */
+template<typename T>
+inline T clamp(T v, T mn, T mx)
+{
+ // Do not reorder; correct NaN handling relies on the fact that comparison
+ // with NaN returns false and will fall-though to the "min" value.
+ if (v > mx) return mx;
+ if (v > mn) return v;
+ return mn;
+}
+
+/**
+ * @brief Clamp a float value between 0.0f and 1.0f.
+ *
+ * NaNs are turned into 0.0f.
+ *
+ * @param v The value to clamp.
+ *
+ * @return The clamped value.
+ */
+static inline float clamp1f(float v)
+{
+ return astc::clamp(v, 0.0f, 1.0f);
+}
+
+/**
+ * @brief Clamp a float value between 0.0f and 255.0f.
+ *
+ * NaNs are turned into 0.0f.
+ *
+ * @param v The value to clamp.
+ *
+ * @return The clamped value.
+ */
+static inline float clamp255f(float v)
+{
+ return astc::clamp(v, 0.0f, 255.0f);
+}
+
+/**
+ * @brief SP float round-down.
+ *
+ * @param v The value to round.
+ *
+ * @return The rounded value.
+ */
+static inline float flt_rd(float v)
+{
+ return std::floor(v);
+}
+
+/**
+ * @brief SP float round-to-nearest and convert to integer.
+ *
+ * @param v The value to round.
+ *
+ * @return The rounded value.
+ */
+static inline int flt2int_rtn(float v)
+{
+
+ return static_cast<int>(v + 0.5f);
+}
+
+/**
+ * @brief SP float round down and convert to integer.
+ *
+ * @param v The value to round.
+ *
+ * @return The rounded value.
+ */
+static inline int flt2int_rd(float v)
+{
+ return static_cast<int>(v);
+}
+
+/**
+ * @brief SP float bit-interpreted as an integer.
+ *
+ * @param v The value to bitcast.
+ *
+ * @return The converted value.
+ */
+static inline int float_as_int(float v)
+{
+ union { int a; float b; } u;
+ u.b = v;
+ return u.a;
+}
+
+/**
+ * @brief Integer bit-interpreted as an SP float.
+ *
+ * @param v The value to bitcast.
+ *
+ * @return The converted value.
+ */
+static inline float int_as_float(int v)
+{
+ union { int a; float b; } u;
+ u.a = v;
+ return u.b;
+}
+
+/**
+ * @brief Fast approximation of 1.0 / sqrt(val).
+ *
+ * @param v The input value.
+ *
+ * @return The approximated result.
+ */
+static inline float rsqrt(float v)
+{
+ return 1.0f / std::sqrt(v);
+}
+
+/**
+ * @brief Fast approximation of sqrt(val).
+ *
+ * @param v The input value.
+ *
+ * @return The approximated result.
+ */
+static inline float sqrt(float v)
+{
+ return std::sqrt(v);
+}
+
+/**
+ * @brief Extract mantissa and exponent of a float value.
+ *
+ * @param v The input value.
+ * @param[out] expo The output exponent.
+ *
+ * @return The mantissa.
+ */
+static inline float frexp(float v, int* expo)
+{
+ if32 p;
+ p.f = v;
+ *expo = ((p.u >> 23) & 0xFF) - 126;
+ p.u = (p.u & 0x807fffff) | 0x3f000000;
+ return p.f;
+}
+
+/**
+ * @brief Initialize the seed structure for a random number generator.
+ *
+ * Important note: For the purposes of ASTC we want sets of random numbers to
+ * use the codec, but we want the same seed value across instances and threads
+ * to ensure that image output is stable across compressor runs and across
+ * platforms. Every PRNG created by this call will therefore return the same
+ * sequence of values ...
+ *
+ * @param state The state structure to initialize.
+ */
+void rand_init(uint64_t state[2]);
+
+/**
+ * @brief Return the next random number from the generator.
+ *
+ * This RNG is an implementation of the "xoroshoro-128+ 1.0" PRNG, based on the
+ * public-domain implementation given by David Blackman & Sebastiano Vigna at
+ * http://vigna.di.unimi.it/xorshift/xoroshiro128plus.c
+ *
+ * @param state The state structure to use/update.
+ */
+uint64_t rand(uint64_t state[2]);
+
+}
+
+/* ============================================================================
+ Softfloat library with fp32 and fp16 conversion functionality.
+============================================================================ */
+#if (ASTCENC_F16C == 0) && (ASTCENC_NEON == 0)
+ /* narrowing float->float conversions */
+ uint16_t float_to_sf16(float val);
+ float sf16_to_float(uint16_t val);
+#endif
+
+/*********************************
+ Vector library
+*********************************/
+#include "astcenc_vecmathlib.h"
+
+/*********************************
+ Declaration of line types
+*********************************/
+// parametric line, 2D: The line is given by line = a + b * t.
+
+struct line2
+{
+ vfloat4 a;
+ vfloat4 b;
+};
+
+// parametric line, 3D
+struct line3
+{
+ vfloat4 a;
+ vfloat4 b;
+};
+
+struct line4
+{
+ vfloat4 a;
+ vfloat4 b;
+};
+
+
+struct processed_line2
+{
+ vfloat4 amod;
+ vfloat4 bs;
+};
+
+struct processed_line3
+{
+ vfloat4 amod;
+ vfloat4 bs;
+};
+
+struct processed_line4
+{
+ vfloat4 amod;
+ vfloat4 bs;
+};
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_mathlib_softfloat.cpp b/thirdparty/astcenc/astcenc_mathlib_softfloat.cpp
new file mode 100644
index 0000000000..42db764549
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_mathlib_softfloat.cpp
@@ -0,0 +1,411 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2021 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Soft-float library for IEEE-754.
+ */
+#if (ASTCENC_F16C == 0) && (ASTCENC_NEON == 0)
+
+#include "astcenc_mathlib.h"
+
+/* sized soft-float types. These are mapped to the sized integer
+ types of C99, instead of C's floating-point types; this is because
+ the library needs to maintain exact, bit-level control on all
+ operations on these data types. */
+typedef uint16_t sf16;
+typedef uint32_t sf32;
+
+/******************************************
+ helper functions and their lookup tables
+ ******************************************/
+/* count leading zeros functions. Only used when the input is nonzero. */
+
+#if defined(__GNUC__) && (defined(__i386) || defined(__amd64))
+#elif defined(__arm__) && defined(__ARMCC_VERSION)
+#elif defined(__arm__) && defined(__GNUC__)
+#else
+ /* table used for the slow default versions. */
+ static const uint8_t clz_table[256] =
+ {
+ 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+#endif
+
+/*
+ 32-bit count-leading-zeros function: use the Assembly instruction whenever possible. */
+static uint32_t clz32(uint32_t inp)
+{
+ #if defined(__GNUC__) && (defined(__i386) || defined(__amd64))
+ uint32_t bsr;
+ __asm__("bsrl %1, %0": "=r"(bsr):"r"(inp | 1));
+ return 31 - bsr;
+ #else
+ #if defined(__arm__) && defined(__ARMCC_VERSION)
+ return __clz(inp); /* armcc builtin */
+ #else
+ #if defined(__arm__) && defined(__GNUC__)
+ uint32_t lz;
+ __asm__("clz %0, %1": "=r"(lz):"r"(inp));
+ return lz;
+ #else
+ /* slow default version */
+ uint32_t summa = 24;
+ if (inp >= UINT32_C(0x10000))
+ {
+ inp >>= 16;
+ summa -= 16;
+ }
+ if (inp >= UINT32_C(0x100))
+ {
+ inp >>= 8;
+ summa -= 8;
+ }
+ return summa + clz_table[inp];
+ #endif
+ #endif
+ #endif
+}
+
+/* the five rounding modes that IEEE-754r defines */
+typedef enum
+{
+ SF_UP = 0, /* round towards positive infinity */
+ SF_DOWN = 1, /* round towards negative infinity */
+ SF_TOZERO = 2, /* round towards zero */
+ SF_NEARESTEVEN = 3, /* round toward nearest value; if mid-between, round to even value */
+ SF_NEARESTAWAY = 4 /* round toward nearest value; if mid-between, round away from zero */
+} roundmode;
+
+
+static uint32_t rtne_shift32(uint32_t inp, uint32_t shamt)
+{
+ uint32_t vl1 = UINT32_C(1) << shamt;
+ uint32_t inp2 = inp + (vl1 >> 1); /* added 0.5 ULP */
+ uint32_t msk = (inp | UINT32_C(1)) & vl1; /* nonzero if odd. '| 1' forces it to 1 if the shamt is 0. */
+ msk--; /* negative if even, nonnegative if odd. */
+ inp2 -= (msk >> 31); /* subtract epsilon before shift if even. */
+ inp2 >>= shamt;
+ return inp2;
+}
+
+static uint32_t rtna_shift32(uint32_t inp, uint32_t shamt)
+{
+ uint32_t vl1 = (UINT32_C(1) << shamt) >> 1;
+ inp += vl1;
+ inp >>= shamt;
+ return inp;
+}
+
+static uint32_t rtup_shift32(uint32_t inp, uint32_t shamt)
+{
+ uint32_t vl1 = UINT32_C(1) << shamt;
+ inp += vl1;
+ inp--;
+ inp >>= shamt;
+ return inp;
+}
+
+/* convert from FP16 to FP32. */
+static sf32 sf16_to_sf32(sf16 inp)
+{
+ uint32_t inpx = inp;
+
+ /*
+ This table contains, for every FP16 sign/exponent value combination,
+ the difference between the input FP16 value and the value obtained
+ by shifting the correct FP32 result right by 13 bits.
+ This table allows us to handle every case except denormals and NaN
+ with just 1 table lookup, 2 shifts and 1 add.
+ */
+
+ #define WITH_MSB(a) (UINT32_C(a) | (1u << 31))
+ static const uint32_t tbl[64] =
+ {
+ WITH_MSB(0x00000), 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000,
+ 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000,
+ 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000,
+ 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000, 0x1C000, WITH_MSB(0x38000),
+ WITH_MSB(0x38000), 0x54000, 0x54000, 0x54000, 0x54000, 0x54000, 0x54000, 0x54000,
+ 0x54000, 0x54000, 0x54000, 0x54000, 0x54000, 0x54000, 0x54000, 0x54000,
+ 0x54000, 0x54000, 0x54000, 0x54000, 0x54000, 0x54000, 0x54000, 0x54000,
+ 0x54000, 0x54000, 0x54000, 0x54000, 0x54000, 0x54000, 0x54000, WITH_MSB(0x70000)
+ };
+
+ uint32_t res = tbl[inpx >> 10];
+ res += inpx;
+
+ /* Normal cases: MSB of 'res' not set. */
+ if ((res & WITH_MSB(0)) == 0)
+ {
+ return res << 13;
+ }
+
+ /* Infinity and Zero: 10 LSB of 'res' not set. */
+ if ((res & 0x3FF) == 0)
+ {
+ return res << 13;
+ }
+
+ /* NaN: the exponent field of 'inp' is non-zero. */
+ if ((inpx & 0x7C00) != 0)
+ {
+ /* All NaNs are quietened. */
+ return (res << 13) | 0x400000;
+ }
+
+ /* Denormal cases */
+ uint32_t sign = (inpx & 0x8000) << 16;
+ uint32_t mskval = inpx & 0x7FFF;
+ uint32_t leadingzeroes = clz32(mskval);
+ mskval <<= leadingzeroes;
+ return (mskval >> 8) + ((0x85 - leadingzeroes) << 23) + sign;
+}
+
+/* Conversion routine that converts from FP32 to FP16. It supports denormals and all rounding modes. If a NaN is given as input, it is quietened. */
+static sf16 sf32_to_sf16(sf32 inp, roundmode rmode)
+{
+ /* for each possible sign/exponent combination, store a case index. This gives a 512-byte table */
+ static const uint8_t tab[512] {
+ 0, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30,
+ 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 50,
+
+ 5, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+ 25, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35,
+ 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45,
+ 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 45, 55,
+ };
+
+ /* many of the cases below use a case-dependent magic constant. So we look up a magic constant before actually performing the switch. This table allows us to group cases, thereby minimizing code
+ size. */
+ static const uint32_t tabx[60] {
+ UINT32_C(0), UINT32_C(0), UINT32_C(0), UINT32_C(0), UINT32_C(0), UINT32_C(0x8000), UINT32_C(0x80000000), UINT32_C(0x8000), UINT32_C(0x8000), UINT32_C(0x8000),
+ UINT32_C(1), UINT32_C(0), UINT32_C(0), UINT32_C(0), UINT32_C(0), UINT32_C(0x8000), UINT32_C(0x8001), UINT32_C(0x8000), UINT32_C(0x8000), UINT32_C(0x8000),
+ UINT32_C(0), UINT32_C(0), UINT32_C(0), UINT32_C(0), UINT32_C(0), UINT32_C(0x8000), UINT32_C(0x8000), UINT32_C(0x8000), UINT32_C(0x8000), UINT32_C(0x8000),
+ UINT32_C(0xC8001FFF), UINT32_C(0xC8000000), UINT32_C(0xC8000000), UINT32_C(0xC8000FFF), UINT32_C(0xC8001000),
+ UINT32_C(0x58000000), UINT32_C(0x38001FFF), UINT32_C(0x58000000), UINT32_C(0x58000FFF), UINT32_C(0x58001000),
+ UINT32_C(0x7C00), UINT32_C(0x7BFF), UINT32_C(0x7BFF), UINT32_C(0x7C00), UINT32_C(0x7C00),
+ UINT32_C(0xFBFF), UINT32_C(0xFC00), UINT32_C(0xFBFF), UINT32_C(0xFC00), UINT32_C(0xFC00),
+ UINT32_C(0x90000000), UINT32_C(0x90000000), UINT32_C(0x90000000), UINT32_C(0x90000000), UINT32_C(0x90000000),
+ UINT32_C(0x20000000), UINT32_C(0x20000000), UINT32_C(0x20000000), UINT32_C(0x20000000), UINT32_C(0x20000000)
+ };
+
+ uint32_t p;
+ uint32_t idx = rmode + tab[inp >> 23];
+ uint32_t vlx = tabx[idx];
+ switch (idx)
+ {
+ /*
+ Positive number which may be Infinity or NaN.
+ We need to check whether it is NaN; if it is, quieten it by setting the top bit of the mantissa.
+ (If we don't do this quieting, then a NaN that is distinguished only by having
+ its low-order bits set, would be turned into an INF. */
+ case 50:
+ case 51:
+ case 52:
+ case 53:
+ case 54:
+ case 55:
+ case 56:
+ case 57:
+ case 58:
+ case 59:
+ /*
+ the input value is 0x7F800000 or 0xFF800000 if it is INF.
+ By subtracting 1, we get 7F7FFFFF or FF7FFFFF, that is, bit 23 becomes zero.
+ For NaNs, however, this operation will keep bit 23 with the value 1.
+ We can then extract bit 23, and logical-OR bit 9 of the result with this
+ bit in order to quieten the NaN (a Quiet NaN is a NaN where the top bit
+ of the mantissa is set.)
+ */
+ p = (inp - 1) & UINT32_C(0x800000); /* zero if INF, nonzero if NaN. */
+ return static_cast<sf16>(((inp + vlx) >> 13) | (p >> 14));
+ /*
+ positive, exponent = 0, round-mode == UP; need to check whether number actually is 0.
+ If it is, then return 0, else return 1 (the smallest representable nonzero number)
+ */
+ case 0:
+ /*
+ -inp will set the MSB if the input number is nonzero.
+ Thus (-inp) >> 31 will turn into 0 if the input number is 0 and 1 otherwise.
+ */
+ return static_cast<sf16>(static_cast<uint32_t>((-static_cast<int32_t>(inp))) >> 31);
+
+ /*
+ negative, exponent = , round-mode == DOWN, need to check whether number is
+ actually 0. If it is, return 0x8000 ( float -0.0 )
+ Else return the smallest negative number ( 0x8001 ) */
+ case 6:
+ /*
+ in this case 'vlx' is 0x80000000. By subtracting the input value from it,
+ we obtain a value that is 0 if the input value is in fact zero and has
+ the MSB set if it isn't. We then right-shift the value by 31 places to
+ get a value that is 0 if the input is -0.0 and 1 otherwise.
+ */
+ return static_cast<sf16>(((vlx - inp) >> 31) + UINT32_C(0x8000));
+
+ /*
+ for all other cases involving underflow/overflow, we don't need to
+ do actual tests; we just return 'vlx'.
+ */
+ case 1:
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ case 16:
+ case 17:
+ case 18:
+ case 19:
+ case 40:
+ case 41:
+ case 42:
+ case 43:
+ case 44:
+ case 45:
+ case 46:
+ case 47:
+ case 48:
+ case 49:
+ return static_cast<sf16>(vlx);
+
+ /*
+ for normal numbers, 'vlx' is the difference between the FP32 value of a number and the
+ FP16 representation of the same number left-shifted by 13 places. In addition, a rounding constant is
+ baked into 'vlx': for rounding-away-from zero, the constant is 2^13 - 1, causing roundoff away
+ from zero. for round-to-nearest away, the constant is 2^12, causing roundoff away from zero.
+ for round-to-nearest-even, the constant is 2^12 - 1. This causes correct round-to-nearest-even
+ except for odd input numbers. For odd input numbers, we need to add 1 to the constant. */
+
+ /* normal number, all rounding modes except round-to-nearest-even: */
+ case 30:
+ case 31:
+ case 32:
+ case 34:
+ case 35:
+ case 36:
+ case 37:
+ case 39:
+ return static_cast<sf16>((inp + vlx) >> 13);
+
+ /* normal number, round-to-nearest-even. */
+ case 33:
+ case 38:
+ p = inp + vlx;
+ p += (inp >> 13) & 1;
+ return static_cast<sf16>(p >> 13);
+
+ /*
+ the various denormal cases. These are not expected to be common, so their performance is a bit
+ less important. For each of these cases, we need to extract an exponent and a mantissa
+ (including the implicit '1'!), and then right-shift the mantissa by a shift-amount that
+ depends on the exponent. The shift must apply the correct rounding mode. 'vlx' is used to supply the
+ sign of the resulting denormal number.
+ */
+ case 21:
+ case 22:
+ case 25:
+ case 27:
+ /* denormal, round towards zero. */
+ p = 126 - ((inp >> 23) & 0xFF);
+ return static_cast<sf16>((((inp & UINT32_C(0x7FFFFF)) + UINT32_C(0x800000)) >> p) | vlx);
+ case 20:
+ case 26:
+ /* denormal, round away from zero. */
+ p = 126 - ((inp >> 23) & 0xFF);
+ return static_cast<sf16>(rtup_shift32((inp & UINT32_C(0x7FFFFF)) + UINT32_C(0x800000), p) | vlx);
+ case 24:
+ case 29:
+ /* denormal, round to nearest-away */
+ p = 126 - ((inp >> 23) & 0xFF);
+ return static_cast<sf16>(rtna_shift32((inp & UINT32_C(0x7FFFFF)) + UINT32_C(0x800000), p) | vlx);
+ case 23:
+ case 28:
+ /* denormal, round to nearest-even. */
+ p = 126 - ((inp >> 23) & 0xFF);
+ return static_cast<sf16>(rtne_shift32((inp & UINT32_C(0x7FFFFF)) + UINT32_C(0x800000), p) | vlx);
+ }
+
+ return 0;
+}
+
+/* convert from soft-float to native-float */
+float sf16_to_float(uint16_t p)
+{
+ if32 i;
+ i.u = sf16_to_sf32(p);
+ return i.f;
+}
+
+/* convert from native-float to soft-float */
+uint16_t float_to_sf16(float p)
+{
+ if32 i;
+ i.f = p;
+ return sf32_to_sf16(i.u, SF_NEARESTEVEN);
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_partition_tables.cpp b/thirdparty/astcenc/astcenc_partition_tables.cpp
new file mode 100644
index 0000000000..cad42384d7
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_partition_tables.cpp
@@ -0,0 +1,481 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2023 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Functions for generating partition tables on demand.
+ */
+
+#include "astcenc_internal.h"
+
+/** @brief The number of 64-bit words needed to represent a canonical partition bit pattern. */
+#define BIT_PATTERN_WORDS (((ASTCENC_BLOCK_MAX_TEXELS * 2) + 63) / 64)
+
+/**
+ * @brief Generate a canonical representation of a partition pattern.
+ *
+ * The returned value stores two bits per texel, for up to 6x6x6 texels, where the two bits store
+ * the remapped texel index. Remapping ensures that we only match on the partition pattern,
+ * independent of the partition order generated by the hash.
+ *
+ * @param texel_count The number of texels in the block.
+ * @param partition_of_texel The partition assignments, in hash order.
+ * @param[out] bit_pattern The output bit pattern representation.
+ */
+static void generate_canonical_partitioning(
+ unsigned int texel_count,
+ const uint8_t* partition_of_texel,
+ uint64_t bit_pattern[BIT_PATTERN_WORDS]
+) {
+ // Clear the pattern
+ for (unsigned int i = 0; i < BIT_PATTERN_WORDS; i++)
+ {
+ bit_pattern[i] = 0;
+ }
+
+ // Store a mapping to reorder the raw partitions so that the partitions are ordered such
+ // that the lowest texel index in partition N is smaller than the lowest texel index in
+ // partition N + 1.
+ int mapped_index[BLOCK_MAX_PARTITIONS];
+ int map_weight_count = 0;
+
+ for (unsigned int i = 0; i < BLOCK_MAX_PARTITIONS; i++)
+ {
+ mapped_index[i] = -1;
+ }
+
+ for (unsigned int i = 0; i < texel_count; i++)
+ {
+ int index = partition_of_texel[i];
+ if (mapped_index[index] < 0)
+ {
+ mapped_index[index] = map_weight_count++;
+ }
+
+ uint64_t xlat_index = mapped_index[index];
+ bit_pattern[i >> 5] |= xlat_index << (2 * (i & 0x1F));
+ }
+}
+
+/**
+ * @brief Compare two canonical patterns to see if they are the same.
+ *
+ * @param part1 The first canonical bit pattern to check.
+ * @param part2 The second canonical bit pattern to check.
+ *
+ * @return @c true if the patterns are the same, @c false otherwise.
+ */
+static bool compare_canonical_partitionings(
+ const uint64_t part1[BIT_PATTERN_WORDS],
+ const uint64_t part2[BIT_PATTERN_WORDS]
+) {
+ return (part1[0] == part2[0])
+#if BIT_PATTERN_WORDS > 1
+ && (part1[1] == part2[1])
+#endif
+#if BIT_PATTERN_WORDS > 2
+ && (part1[2] == part2[2])
+#endif
+#if BIT_PATTERN_WORDS > 3
+ && (part1[3] == part2[3])
+#endif
+#if BIT_PATTERN_WORDS > 4
+ && (part1[4] == part2[4])
+#endif
+#if BIT_PATTERN_WORDS > 5
+ && (part1[5] == part2[5])
+#endif
+#if BIT_PATTERN_WORDS > 6
+ && (part1[6] == part2[6])
+#endif
+ ;
+}
+
+/**
+ * @brief Hash function used for procedural partition assignment.
+ *
+ * @param inp The hash seed.
+ *
+ * @return The hashed value.
+ */
+static uint32_t hash52(
+ uint32_t inp
+) {
+ inp ^= inp >> 15;
+
+ // (2^4 + 1) * (2^7 + 1) * (2^17 - 1)
+ inp *= 0xEEDE0891;
+ inp ^= inp >> 5;
+ inp += inp << 16;
+ inp ^= inp >> 7;
+ inp ^= inp >> 3;
+ inp ^= inp << 6;
+ inp ^= inp >> 17;
+ return inp;
+}
+
+/**
+ * @brief Select texel assignment for a single coordinate.
+ *
+ * @param seed The seed - the partition index from the block.
+ * @param x The texel X coordinate in the block.
+ * @param y The texel Y coordinate in the block.
+ * @param z The texel Z coordinate in the block.
+ * @param partition_count The total partition count of this encoding.
+ * @param small_block @c true if the block has fewer than 32 texels.
+ *
+ * @return The assigned partition index for this texel.
+ */
+static uint8_t select_partition(
+ int seed,
+ int x,
+ int y,
+ int z,
+ int partition_count,
+ bool small_block
+) {
+ // For small blocks bias the coordinates to get better distribution
+ if (small_block)
+ {
+ x <<= 1;
+ y <<= 1;
+ z <<= 1;
+ }
+
+ seed += (partition_count - 1) * 1024;
+
+ uint32_t rnum = hash52(seed);
+
+ uint8_t seed1 = rnum & 0xF;
+ uint8_t seed2 = (rnum >> 4) & 0xF;
+ uint8_t seed3 = (rnum >> 8) & 0xF;
+ uint8_t seed4 = (rnum >> 12) & 0xF;
+ uint8_t seed5 = (rnum >> 16) & 0xF;
+ uint8_t seed6 = (rnum >> 20) & 0xF;
+ uint8_t seed7 = (rnum >> 24) & 0xF;
+ uint8_t seed8 = (rnum >> 28) & 0xF;
+ uint8_t seed9 = (rnum >> 18) & 0xF;
+ uint8_t seed10 = (rnum >> 22) & 0xF;
+ uint8_t seed11 = (rnum >> 26) & 0xF;
+ uint8_t seed12 = ((rnum >> 30) | (rnum << 2)) & 0xF;
+
+ // Squaring all the seeds in order to bias their distribution towards lower values.
+ seed1 *= seed1;
+ seed2 *= seed2;
+ seed3 *= seed3;
+ seed4 *= seed4;
+ seed5 *= seed5;
+ seed6 *= seed6;
+ seed7 *= seed7;
+ seed8 *= seed8;
+ seed9 *= seed9;
+ seed10 *= seed10;
+ seed11 *= seed11;
+ seed12 *= seed12;
+
+ int sh1, sh2;
+ if (seed & 1)
+ {
+ sh1 = (seed & 2 ? 4 : 5);
+ sh2 = (partition_count == 3 ? 6 : 5);
+ }
+ else
+ {
+ sh1 = (partition_count == 3 ? 6 : 5);
+ sh2 = (seed & 2 ? 4 : 5);
+ }
+
+ int sh3 = (seed & 0x10) ? sh1 : sh2;
+
+ seed1 >>= sh1;
+ seed2 >>= sh2;
+ seed3 >>= sh1;
+ seed4 >>= sh2;
+ seed5 >>= sh1;
+ seed6 >>= sh2;
+ seed7 >>= sh1;
+ seed8 >>= sh2;
+
+ seed9 >>= sh3;
+ seed10 >>= sh3;
+ seed11 >>= sh3;
+ seed12 >>= sh3;
+
+ int a = seed1 * x + seed2 * y + seed11 * z + (rnum >> 14);
+ int b = seed3 * x + seed4 * y + seed12 * z + (rnum >> 10);
+ int c = seed5 * x + seed6 * y + seed9 * z + (rnum >> 6);
+ int d = seed7 * x + seed8 * y + seed10 * z + (rnum >> 2);
+
+ // Apply the saw
+ a &= 0x3F;
+ b &= 0x3F;
+ c &= 0x3F;
+ d &= 0x3F;
+
+ // Remove some of the components if we are to output < 4 partitions.
+ if (partition_count <= 3)
+ {
+ d = 0;
+ }
+
+ if (partition_count <= 2)
+ {
+ c = 0;
+ }
+
+ if (partition_count <= 1)
+ {
+ b = 0;
+ }
+
+ uint8_t partition;
+ if (a >= b && a >= c && a >= d)
+ {
+ partition = 0;
+ }
+ else if (b >= c && b >= d)
+ {
+ partition = 1;
+ }
+ else if (c >= d)
+ {
+ partition = 2;
+ }
+ else
+ {
+ partition = 3;
+ }
+
+ return partition;
+}
+
+/**
+ * @brief Generate a single partition info structure.
+ *
+ * @param[out] bsd The block size information.
+ * @param partition_count The partition count of this partitioning.
+ * @param partition_index The partition index / seed of this partitioning.
+ * @param partition_remap_index The remapped partition index of this partitioning.
+ * @param[out] pi The partition info structure to populate.
+ *
+ * @return True if this is a useful partition index, False if we can skip it.
+ */
+static bool generate_one_partition_info_entry(
+ block_size_descriptor& bsd,
+ unsigned int partition_count,
+ unsigned int partition_index,
+ unsigned int partition_remap_index,
+ partition_info& pi
+) {
+ int texels_per_block = bsd.texel_count;
+ bool small_block = texels_per_block < 32;
+
+ uint8_t *partition_of_texel = pi.partition_of_texel;
+
+ // Assign texels to partitions
+ int texel_idx = 0;
+ int counts[BLOCK_MAX_PARTITIONS] { 0 };
+ for (unsigned int z = 0; z < bsd.zdim; z++)
+ {
+ for (unsigned int y = 0; y < bsd.ydim; y++)
+ {
+ for (unsigned int x = 0; x < bsd.xdim; x++)
+ {
+ uint8_t part = select_partition(partition_index, x, y, z, partition_count, small_block);
+ pi.texels_of_partition[part][counts[part]++] = static_cast<uint8_t>(texel_idx++);
+ *partition_of_texel++ = part;
+ }
+ }
+ }
+
+ // Fill loop tail so we can overfetch later
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ int ptex_count = counts[i];
+ int ptex_count_simd = round_up_to_simd_multiple_vla(ptex_count);
+ for (int j = ptex_count; j < ptex_count_simd; j++)
+ {
+ pi.texels_of_partition[i][j] = pi.texels_of_partition[i][ptex_count - 1];
+ }
+ }
+
+ // Populate the actual procedural partition count
+ if (counts[0] == 0)
+ {
+ pi.partition_count = 0;
+ }
+ else if (counts[1] == 0)
+ {
+ pi.partition_count = 1;
+ }
+ else if (counts[2] == 0)
+ {
+ pi.partition_count = 2;
+ }
+ else if (counts[3] == 0)
+ {
+ pi.partition_count = 3;
+ }
+ else
+ {
+ pi.partition_count = 4;
+ }
+
+ // Populate the partition index
+ pi.partition_index = static_cast<uint16_t>(partition_index);
+
+ // Populate the coverage bitmaps for 2/3/4 partitions
+ uint64_t* bitmaps { nullptr };
+ if (partition_count == 2)
+ {
+ bitmaps = bsd.coverage_bitmaps_2[partition_remap_index];
+ }
+ else if (partition_count == 3)
+ {
+ bitmaps = bsd.coverage_bitmaps_3[partition_remap_index];
+ }
+ else if (partition_count == 4)
+ {
+ bitmaps = bsd.coverage_bitmaps_4[partition_remap_index];
+ }
+
+ for (unsigned int i = 0; i < BLOCK_MAX_PARTITIONS; i++)
+ {
+ pi.partition_texel_count[i] = static_cast<uint8_t>(counts[i]);
+ }
+
+ // Valid partitionings have texels in all of the requested partitions
+ bool valid = pi.partition_count == partition_count;
+
+ if (bitmaps)
+ {
+ // Populate the partition coverage bitmap
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ bitmaps[i] = 0ULL;
+ }
+
+ unsigned int texels_to_process = astc::min(bsd.texel_count, BLOCK_MAX_KMEANS_TEXELS);
+ for (unsigned int i = 0; i < texels_to_process; i++)
+ {
+ unsigned int idx = bsd.kmeans_texels[i];
+ bitmaps[pi.partition_of_texel[idx]] |= 1ULL << i;
+ }
+ }
+
+ return valid;
+}
+
+static void build_partition_table_for_one_partition_count(
+ block_size_descriptor& bsd,
+ bool can_omit_partitionings,
+ unsigned int partition_count_cutoff,
+ unsigned int partition_count,
+ partition_info* ptab,
+ uint64_t* canonical_patterns
+) {
+ unsigned int next_index = 0;
+ bsd.partitioning_count_selected[partition_count - 1] = 0;
+ bsd.partitioning_count_all[partition_count - 1] = 0;
+
+ // Skip tables larger than config max partition count if we can omit modes
+ if (can_omit_partitionings && (partition_count > partition_count_cutoff))
+ {
+ return;
+ }
+
+ // Iterate through twice
+ // - Pass 0: Keep selected partitionings
+ // - Pass 1: Keep non-selected partitionings (skip if in omit mode)
+ unsigned int max_iter = can_omit_partitionings ? 1 : 2;
+
+ // Tracker for things we built in the first iteration
+ uint8_t build[BLOCK_MAX_PARTITIONINGS] { 0 };
+ for (unsigned int x = 0; x < max_iter; x++)
+ {
+ for (unsigned int i = 0; i < BLOCK_MAX_PARTITIONINGS; i++)
+ {
+ // Don't include things we built in the first pass
+ if ((x == 1) && build[i])
+ {
+ continue;
+ }
+
+ bool keep_useful = generate_one_partition_info_entry(bsd, partition_count, i, next_index, ptab[next_index]);
+ if ((x == 0) && !keep_useful)
+ {
+ continue;
+ }
+
+ generate_canonical_partitioning(bsd.texel_count, ptab[next_index].partition_of_texel, canonical_patterns + next_index * BIT_PATTERN_WORDS);
+ bool keep_canonical = true;
+ for (unsigned int j = 0; j < next_index; j++)
+ {
+ bool match = compare_canonical_partitionings(canonical_patterns + next_index * BIT_PATTERN_WORDS, canonical_patterns + j * BIT_PATTERN_WORDS);
+ if (match)
+ {
+ keep_canonical = false;
+ break;
+ }
+ }
+
+ if (keep_useful && keep_canonical)
+ {
+ if (x == 0)
+ {
+ bsd.partitioning_packed_index[partition_count - 2][i] = static_cast<uint16_t>(next_index);
+ bsd.partitioning_count_selected[partition_count - 1]++;
+ bsd.partitioning_count_all[partition_count - 1]++;
+ build[i] = 1;
+ next_index++;
+ }
+ }
+ else
+ {
+ if (x == 1)
+ {
+ bsd.partitioning_packed_index[partition_count - 2][i] = static_cast<uint16_t>(next_index);
+ bsd.partitioning_count_all[partition_count - 1]++;
+ next_index++;
+ }
+ }
+ }
+ }
+}
+
+/* See header for documentation. */
+void init_partition_tables(
+ block_size_descriptor& bsd,
+ bool can_omit_partitionings,
+ unsigned int partition_count_cutoff
+) {
+ partition_info* par_tab2 = bsd.partitionings;
+ partition_info* par_tab3 = par_tab2 + BLOCK_MAX_PARTITIONINGS;
+ partition_info* par_tab4 = par_tab3 + BLOCK_MAX_PARTITIONINGS;
+ partition_info* par_tab1 = par_tab4 + BLOCK_MAX_PARTITIONINGS;
+
+ generate_one_partition_info_entry(bsd, 1, 0, 0, *par_tab1);
+ bsd.partitioning_count_selected[0] = 1;
+ bsd.partitioning_count_all[0] = 1;
+
+ uint64_t* canonical_patterns = new uint64_t[BLOCK_MAX_PARTITIONINGS * BIT_PATTERN_WORDS];
+
+ build_partition_table_for_one_partition_count(bsd, can_omit_partitionings, partition_count_cutoff, 2, par_tab2, canonical_patterns);
+ build_partition_table_for_one_partition_count(bsd, can_omit_partitionings, partition_count_cutoff, 3, par_tab3, canonical_patterns);
+ build_partition_table_for_one_partition_count(bsd, can_omit_partitionings, partition_count_cutoff, 4, par_tab4, canonical_patterns);
+
+ delete[] canonical_patterns;
+}
diff --git a/thirdparty/astcenc/astcenc_percentile_tables.cpp b/thirdparty/astcenc/astcenc_percentile_tables.cpp
new file mode 100644
index 0000000000..448ddcc968
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_percentile_tables.cpp
@@ -0,0 +1,1251 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Percentile data tables for different block encodings.
+ *
+ * To reduce binary size the tables are stored using a packed differential encoding.
+ */
+
+#include "astcenc_internal.h"
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+/**
+ * @brief Structure containing packed percentile metadata.
+ *
+ * Note that percentile tables do not exist for 3D textures, so no zdim is stored.
+ */
+struct packed_percentile_table
+{
+ /** The block X dimension. */
+ uint8_t xdim;
+
+ /** The block Y dimension. */
+ uint8_t ydim;
+
+ /** The number of packed items in the 1 and 2 plane data. */
+ uint16_t item_count[2];
+
+ /** The accumulator divisor for 1 and 2 plane data. */
+ uint16_t difscales[2];
+
+ /** The initial accumulator values for 1 and 2 plane data. */
+ uint16_t initial_percs[2];
+
+ /** The packed data for the 1 and 2 plane data. */
+ const uint16_t *items[2];
+};
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (4 * 4)
+static const uint16_t percentile_arr_4x4_0[61] {
+ 0x0242, 0x7243, 0x6A51, 0x6A52, 0x5A41, 0x4A53, 0x8851, 0x3842,
+ 0x3852, 0x3853, 0x3043, 0xFA33, 0x1BDF, 0x2022, 0x1032, 0x29CE,
+ 0x21DE, 0x2823, 0x0813, 0x0A13, 0x0A31, 0x0A23, 0x09CF, 0x0833,
+ 0x0A32, 0x01DF, 0x0BDD, 0x0BCF, 0x0221, 0x095F, 0x0A01, 0x0BDE,
+ 0x0BCD, 0x0A22, 0x09AF, 0x0B5F, 0x0B4D, 0x0BCE, 0x0BBF, 0x0A11,
+ 0x01BF, 0x0202, 0x0B5D, 0x1203, 0x034E, 0x0B8E, 0x035E, 0x0212,
+ 0x032E, 0x0B4F, 0x03AF, 0x03AD, 0x03BD, 0x0BBE, 0x03AE, 0x039F,
+ 0x039E, 0x033E, 0x033F, 0x038F, 0x032F
+};
+
+static const uint16_t percentile_arr_4x4_1[84] {
+ 0x0452, 0xFFAE, 0x2433, 0x1DDF, 0x17CD, 0x1E21, 0x1C43, 0x1442,
+ 0x3FBE, 0x1FDD, 0x0E31, 0x0F4F, 0x1423, 0x0FBD, 0x1451, 0x0E03,
+ 0x05CF, 0x0C32, 0x0DDE, 0x27AD, 0x274E, 0x0E02, 0x0F5E, 0x07AF,
+ 0x0F5F, 0x0DCE, 0x0C41, 0x0422, 0x0613, 0x0E12, 0x0611, 0x0F3F,
+ 0x0601, 0x0DBF, 0x05DD, 0x075D, 0x0C02, 0x054E, 0x0431, 0x0413,
+ 0x079F, 0x05BE, 0x0F4D, 0x0403, 0x05AF, 0x055F, 0x05AE, 0x054F,
+ 0x0421, 0x05BD, 0x0DCD, 0x0411, 0x0412, 0x055E, 0x055D, 0x073D,
+ 0x058E, 0x072F, 0x072D, 0x079D, 0x0D2E, 0x0453, 0x078D, 0x053E,
+ 0x053F, 0x059E, 0x052F, 0x058F, 0x072E, 0x078F, 0x059F, 0x078E,
+ 0x071F, 0x073E, 0x051F, 0x070D, 0x079E, 0x070E, 0x071D, 0x0622,
+ 0x070F, 0x071E, 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_4x4 {
+ 4, 4,
+ { 61, 84 },
+ { 184, 141 },
+ { 0, 53 },
+ { percentile_arr_4x4_0, percentile_arr_4x4_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (5 * 4)
+static const uint16_t percentile_arr_5x4_0[91] {
+ 0x02C1, 0xFAD1, 0xE8D3, 0xDAC2, 0xA8D2, 0x70D1, 0x50C2, 0x80C3,
+ 0xD2C3, 0x4AA2, 0x2AD2, 0x2242, 0x2251, 0x42A3, 0x1A43, 0x4A52,
+ 0x32B3, 0x2A41, 0x1042, 0x1851, 0x5892, 0x10A2, 0x2253, 0x10B2,
+ 0x10B3, 0x13DF, 0x3083, 0x08B1, 0x1043, 0x12B1, 0x0AB2, 0x1A93,
+ 0x1852, 0x1A33, 0x09CE, 0x08A3, 0x1022, 0x1283, 0x0853, 0x1AA1,
+ 0x1093, 0x11DE, 0x135F, 0x1832, 0x195F, 0x0A81, 0x11CF, 0x0A31,
+ 0x09DF, 0x0B4D, 0x09AF, 0x03CF, 0x0813, 0x03DD, 0x0A92, 0x0A82,
+ 0x03CD, 0x0023, 0x0BDE, 0x0BBF, 0x1232, 0x0221, 0x0291, 0x0A23,
+ 0x0833, 0x035D, 0x0BCE, 0x01BF, 0x0222, 0x134E, 0x0213, 0x0A01,
+ 0x0B4F, 0x0B5E, 0x038E, 0x032E, 0x03AF, 0x0A11, 0x03AD, 0x0203,
+ 0x0202, 0x0BBD, 0x033E, 0x03AE, 0x03BE, 0x0212, 0x033F, 0x039E,
+ 0x039F, 0x032F, 0x038F
+};
+
+static const uint16_t percentile_arr_5x4_1[104] {
+ 0x0433, 0xB621, 0x5452, 0x4443, 0x7FAE, 0xFCA3, 0x7CC2, 0x24B2,
+ 0x45DF, 0x44B3, 0x7631, 0x27CD, 0x1CD1, 0x1E03, 0x4FBE, 0x774F,
+ 0x1C42, 0x7691, 0x24A2, 0x2681, 0x3C23, 0x3C93, 0x0FBD, 0x1C32,
+ 0x1E82, 0x1E12, 0x0F4E, 0x1602, 0x0FAD, 0x0C51, 0x1FDD, 0x0E13,
+ 0x0DCF, 0x175E, 0x0C22, 0x175F, 0x15DE, 0x0CB1, 0x17AF, 0x1CC1,
+ 0x1F3F, 0x1483, 0x0441, 0x0C91, 0x04D2, 0x0DCE, 0x154E, 0x079F,
+ 0x0CA1, 0x0F5D, 0x0431, 0x15DD, 0x05BF, 0x0C92, 0x0611, 0x0C82,
+ 0x0402, 0x074D, 0x0DBD, 0x055E, 0x05BE, 0x0DCD, 0x0421, 0x05AF,
+ 0x0403, 0x0D4F, 0x055F, 0x05AE, 0x0413, 0x0E01, 0x055D, 0x073D,
+ 0x0C12, 0x0692, 0x0411, 0x072D, 0x078D, 0x079D, 0x058E, 0x0D2E,
+ 0x0453, 0x072F, 0x059E, 0x052F, 0x071F, 0x053F, 0x053E, 0x078F,
+ 0x058F, 0x051F, 0x0F2E, 0x059F, 0x078E, 0x073E, 0x071D, 0x070D,
+ 0x070E, 0x079E, 0x0622, 0x0683, 0x070F, 0x071E, 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_5x4 {
+ 5, 4,
+ { 91, 104 },
+ { 322, 464 },
+ { 0, 202 },
+ { percentile_arr_5x4_0, percentile_arr_5x4_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (5 * 5)
+static const uint16_t percentile_arr_5x5_0[129] {
+ 0x00F3, 0xF8F2, 0x70E3, 0x62E1, 0x60E1, 0x4AC1, 0x3261, 0x38D3,
+ 0x3271, 0x5AF1, 0x5873, 0x2AD1, 0x28E2, 0x28F1, 0x2262, 0x9AC2,
+ 0x18D2, 0x1072, 0x1071, 0x22A2, 0x2062, 0x1A51, 0x10C2, 0x0892,
+ 0x08D1, 0x1AA3, 0x23EE, 0x08C3, 0x0BEF, 0x2242, 0x0863, 0x0AB3,
+ 0x0BFF, 0x0A93, 0x08A2, 0x0A41, 0x1083, 0x0842, 0x10B3, 0x21EE,
+ 0x10B2, 0x00B1, 0x1263, 0x12C3, 0x0A83, 0x0851, 0x11FE, 0x0253,
+ 0x09FD, 0x0A72, 0x09FF, 0x1AB2, 0x0BDF, 0x0A33, 0x0243, 0x0B7F,
+ 0x0AB1, 0x12D2, 0x0252, 0x096F, 0x00A3, 0x0893, 0x0822, 0x0843,
+ 0x097E, 0x097F, 0x01EF, 0x09CE, 0x03FE, 0x0A81, 0x036F, 0x0052,
+ 0x13FD, 0x0AA1, 0x1853, 0x036D, 0x0A92, 0x0832, 0x01DE, 0x0A82,
+ 0x0BED, 0x0231, 0x0BBF, 0x03DD, 0x0B6E, 0x01AF, 0x0813, 0x0023,
+ 0x0A91, 0x015F, 0x037E, 0x01CF, 0x0232, 0x0BCD, 0x0221, 0x0BDE,
+ 0x0213, 0x035F, 0x0B7D, 0x0223, 0x01BF, 0x0BCF, 0x01DF, 0x0033,
+ 0x0222, 0x03CE, 0x0A01, 0x03AF, 0x034D, 0x0B8E, 0x032E, 0x0203,
+ 0x0211, 0x0202, 0x0B5D, 0x03AD, 0x034E, 0x03AE, 0x034F, 0x033F,
+ 0x039F, 0x03BD, 0x03BE, 0x035E, 0x0212, 0x033E, 0x039E, 0x032F,
+ 0x038F
+};
+
+static const uint16_t percentile_arr_5x5_1[126] {
+ 0x0443, 0x6452, 0xFE21, 0x27AE, 0x2433, 0x1FCD, 0x25DF, 0x6CC2,
+ 0x2C62, 0x1F4F, 0x4C42, 0x1FBE, 0x0DEF, 0x34A3, 0x0E03, 0x54B2,
+ 0x1F7D, 0x17DD, 0x0DFF, 0x0CD1, 0x0E31, 0x0C71, 0x1CF1, 0x15FE,
+ 0x1691, 0x1681, 0x24B3, 0x174E, 0x0F6E, 0x0493, 0x175E, 0x1C51,
+ 0x17BD, 0x076D, 0x2CA2, 0x05EE, 0x1472, 0x2423, 0x0DCF, 0x0432,
+ 0x15DE, 0x0612, 0x0CD2, 0x0682, 0x0F5F, 0x07AD, 0x0602, 0x0CE1,
+ 0x0C91, 0x0FAF, 0x073F, 0x0E13, 0x0D7F, 0x0DCE, 0x0422, 0x0D7D,
+ 0x0441, 0x05FD, 0x0CB1, 0x0C83, 0x04C1, 0x0461, 0x0F9F, 0x0DDD,
+ 0x056E, 0x0C92, 0x0482, 0x0431, 0x05ED, 0x0D6F, 0x075D, 0x0402,
+ 0x057E, 0x0DBF, 0x04A1, 0x054E, 0x0F4D, 0x0403, 0x05CD, 0x0453,
+ 0x05AE, 0x0421, 0x0F1F, 0x05BE, 0x0601, 0x0611, 0x05BD, 0x05AF,
+ 0x078D, 0x072D, 0x073D, 0x055E, 0x0F9D, 0x0411, 0x0413, 0x0412,
+ 0x055F, 0x077E, 0x055D, 0x052E, 0x054F, 0x053E, 0x058E, 0x078F,
+ 0x059E, 0x071D, 0x0E92, 0x053F, 0x059F, 0x051F, 0x072F, 0x052F,
+ 0x070D, 0x079E, 0x058F, 0x072E, 0x070E, 0x078E, 0x070F, 0x073E,
+ 0x0622, 0x0683, 0x071E, 0x076F, 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_5x5 {
+ 5, 5,
+ { 129, 126 },
+ { 258, 291 },
+ { 0, 116 },
+ { percentile_arr_5x5_0, percentile_arr_5x5_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (6 * 5)
+static const uint16_t percentile_arr_6x5_0[165] {
+ 0x0163, 0xF8F3, 0x9962, 0x8972, 0x7961, 0x7173, 0x6953, 0x5943,
+ 0x4B41, 0x3AE1, 0x38E3, 0x6971, 0x32C1, 0x28D3, 0x2A61, 0xC8F2,
+ 0x2271, 0x4873, 0x5B21, 0x3AD1, 0x1B13, 0x1952, 0x1B51, 0x12F1,
+ 0x1A62, 0x1322, 0x1951, 0x10E2, 0x1B31, 0x20F1, 0x2102, 0x2072,
+ 0x10D2, 0x1142, 0x2912, 0x3871, 0x2BEE, 0x0862, 0x1123, 0x0AC2,
+ 0x12A2, 0x0A51, 0x1922, 0x0941, 0x1BEF, 0x0B42, 0x08D1, 0x13FF,
+ 0x1933, 0x08C3, 0x08C2, 0x1131, 0x08E1, 0x2903, 0x0863, 0x0B32,
+ 0x1132, 0x1AC3, 0x0A42, 0x1A41, 0x0042, 0x21EE, 0x09FF, 0x03DF,
+ 0x0AA3, 0x11FE, 0x02B3, 0x0B11, 0x10B3, 0x0B03, 0x11FD, 0x0913,
+ 0x0A53, 0x037F, 0x1263, 0x0051, 0x0A33, 0x0B01, 0x016F, 0x0A72,
+ 0x1312, 0x08A2, 0x10B1, 0x0BFE, 0x11EF, 0x0B02, 0x0A52, 0x0043,
+ 0x0822, 0x01CE, 0x0A43, 0x097F, 0x036F, 0x08B2, 0x03FD, 0x0A83,
+ 0x0B33, 0x0AB1, 0x017E, 0x0B23, 0x0852, 0x02D2, 0x0BBF, 0x0BDD,
+ 0x03ED, 0x0AB2, 0x02A1, 0x0853, 0x036D, 0x0892, 0x0032, 0x0A31,
+ 0x0083, 0x09DE, 0x0A93, 0x08A3, 0x1213, 0x0BDE, 0x03CD, 0x036E,
+ 0x037E, 0x0A21, 0x0023, 0x0BCF, 0x01CF, 0x0013, 0x01AF, 0x0A92,
+ 0x0232, 0x035F, 0x0093, 0x0B7D, 0x015F, 0x0282, 0x01BF, 0x09DF,
+ 0x03CE, 0x0223, 0x0833, 0x0222, 0x03AF, 0x0A01, 0x0291, 0x0B4D,
+ 0x032E, 0x038E, 0x0203, 0x0281, 0x035D, 0x03AD, 0x0B9F, 0x0202,
+ 0x034F, 0x03BE, 0x0211, 0x03AE, 0x03BD, 0x0212, 0x034E, 0x033F,
+ 0x033E, 0x035E, 0x039E, 0x032F, 0x038F
+};
+
+static const uint16_t percentile_arr_6x5_1[145] {
+ 0x0443, 0xEFAE, 0x2CC2, 0x2E21, 0x2C52, 0x7C33, 0x47CD, 0x25DF,
+ 0x3CA3, 0xFFBE, 0x2551, 0x24B3, 0x474F, 0x1513, 0x2691, 0x1603,
+ 0x1462, 0x1D32, 0x14B2, 0x5442, 0x2CD2, 0x35EF, 0x0CD1, 0x3D22,
+ 0x17BD, 0x0FDD, 0x0DFF, 0x2631, 0x177D, 0x0CF1, 0x1E81, 0x0E82,
+ 0x1DFE, 0x0F5E, 0x0701, 0x2CA2, 0x1D03, 0x0F4E, 0x1471, 0x0C51,
+ 0x1F6E, 0x2FAF, 0x0561, 0x0C72, 0x176D, 0x0FAD, 0x0DEE, 0x05CF,
+ 0x0E13, 0x0F5F, 0x0E12, 0x0C23, 0x1E02, 0x1D12, 0x0CB1, 0x0C32,
+ 0x0C93, 0x15DE, 0x0F9F, 0x0F3F, 0x0D41, 0x0C41, 0x0CC1, 0x0D31,
+ 0x0C22, 0x05FD, 0x057F, 0x0D01, 0x0461, 0x04E1, 0x0D7D, 0x05CE,
+ 0x0502, 0x0C31, 0x05ED, 0x05DD, 0x0511, 0x0F11, 0x0491, 0x0D6F,
+ 0x0521, 0x056E, 0x0C83, 0x0D23, 0x04A1, 0x0C02, 0x075D, 0x05BF,
+ 0x0C21, 0x079D, 0x0482, 0x05BD, 0x0DBE, 0x05CD, 0x054E, 0x057E,
+ 0x0DAE, 0x074D, 0x078D, 0x0542, 0x0492, 0x05AF, 0x0611, 0x0F3D,
+ 0x0601, 0x071F, 0x055E, 0x059E, 0x0571, 0x054F, 0x0412, 0x0453,
+ 0x058E, 0x0413, 0x0D3E, 0x077E, 0x072D, 0x052E, 0x059F, 0x055D,
+ 0x072F, 0x0403, 0x0411, 0x058F, 0x055F, 0x0692, 0x078E, 0x053F,
+ 0x0D2F, 0x078F, 0x070D, 0x071D, 0x051F, 0x072E, 0x079E, 0x070E,
+ 0x070F, 0x073E, 0x0622, 0x0683, 0x0702, 0x071E, 0x076F, 0x07BF,
+ 0x07CE
+};
+
+static const packed_percentile_table block_pcd_6x5 {
+ 6, 5,
+ { 165, 145 },
+ { 388, 405 },
+ { 0, 156 },
+ { percentile_arr_6x5_0, percentile_arr_6x5_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (6 * 6)
+static const uint16_t percentile_arr_6x6_0[206] {
+ 0x006F, 0xF908, 0xF104, 0xE918, 0xE963, 0xD114, 0xB0F3, 0xA07E,
+ 0x7972, 0x705F, 0x687F, 0x6162, 0x5953, 0x586E, 0x610C, 0x524D,
+ 0x5973, 0x9943, 0x98E3, 0x904F, 0x8341, 0x7AC1, 0x3A61, 0x70D3,
+ 0xA073, 0x6AE1, 0x30F2, 0x3313, 0x2B21, 0x9A2E, 0x4322, 0x225D,
+ 0x2331, 0x2271, 0x22D1, 0x1A2D, 0x221F, 0x22F1, 0x1971, 0x6952,
+ 0x1951, 0x187D, 0x18F1, 0x1902, 0x185E, 0x1B51, 0x105D, 0x1A3D,
+ 0x30E2, 0x10D2, 0x1961, 0x12A2, 0x6072, 0x3942, 0x386D, 0x33EE,
+ 0x104E, 0x4923, 0x101E, 0x2122, 0x1251, 0x1141, 0x182F, 0x3133,
+ 0x080E, 0x1262, 0x123E, 0x1B32, 0x102E, 0x1931, 0x10D1, 0x1912,
+ 0x0871, 0x12C2, 0x08C2, 0x1103, 0x0B03, 0x1062, 0x083D, 0x08E1,
+ 0x1132, 0x184D, 0x0863, 0x08C3, 0x303F, 0x083E, 0x10B3, 0x12A3,
+ 0x0BEF, 0x0B11, 0x1A42, 0x2233, 0x13FF, 0x080F, 0x0A41, 0x0AC3,
+ 0x0842, 0x1A63, 0x0BDF, 0x09FF, 0x12B3, 0x124E, 0x0B12, 0x0B42,
+ 0x0A2F, 0x1253, 0x0913, 0x1051, 0x0B01, 0x120F, 0x0B02, 0x08A2,
+ 0x0BBF, 0x00B1, 0x22B1, 0x01EE, 0x1B33, 0x0B23, 0x0283, 0x13FD,
+ 0x0AB2, 0x11FD, 0x09FE, 0x0A43, 0x08B2, 0x0A1D, 0x0A52, 0x023F,
+ 0x101F, 0x01CE, 0x0A31, 0x0BDD, 0x0293, 0x1822, 0x12A1, 0x03FE,
+ 0x121E, 0x0843, 0x0272, 0x0B6F, 0x0052, 0x0A0D, 0x0BED, 0x12D2,
+ 0x1B7F, 0x1053, 0x0032, 0x01DE, 0x08A3, 0x020E, 0x0883, 0x09EF,
+ 0x0892, 0x0A21, 0x03CD, 0x0B5F, 0x0213, 0x0A32, 0x016F, 0x1292,
+ 0x03DE, 0x017E, 0x0BAF, 0x0223, 0x1093, 0x0BCF, 0x037E, 0x01DF,
+ 0x09CF, 0x015F, 0x09AF, 0x0023, 0x01BF, 0x0222, 0x0282, 0x03CE,
+ 0x1013, 0x036E, 0x097F, 0x0033, 0x0A01, 0x0B6D, 0x03BE, 0x037D,
+ 0x0281, 0x0BAE, 0x0203, 0x032E, 0x034D, 0x034F, 0x0291, 0x0211,
+ 0x038E, 0x03BD, 0x039E, 0x0BAD, 0x033E, 0x034E, 0x039F, 0x0202,
+ 0x035D, 0x0212, 0x033F, 0x035E, 0x038F, 0x032F
+};
+
+static const uint16_t percentile_arr_6x6_1[164] {
+ 0x07AE, 0x8443, 0x7E21, 0x77CD, 0x6C62, 0x9433, 0x6452, 0x34C2,
+ 0x5DDF, 0xC7BE, 0x25EF, 0x24A3, 0x3CF1, 0xFDFF, 0x177D, 0x1F4F,
+ 0xC551, 0x5CB3, 0x1532, 0x1513, 0x143E, 0x245D, 0x14B2, 0x2472,
+ 0x14D2, 0x1FBD, 0x1631, 0x2DFE, 0x1691, 0x17DD, 0x2E03, 0x376E,
+ 0x2442, 0x0F6D, 0x3C71, 0x2CD1, 0x2522, 0x6C51, 0x260D, 0x17AF,
+ 0x0DEE, 0x1C1F, 0x2F01, 0x142E, 0x0CA2, 0x0FAD, 0x3D03, 0x275E,
+ 0x1681, 0x274E, 0x1682, 0x1C23, 0x273F, 0x0F5F, 0x05DE, 0x15FD,
+ 0x0DCF, 0x1E02, 0x04B1, 0x144D, 0x0E12, 0x0D12, 0x1CC1, 0x0E13,
+ 0x1C6D, 0x0C32, 0x043D, 0x0C61, 0x0F9F, 0x04E1, 0x0DCE, 0x0D41,
+ 0x1C93, 0x0C22, 0x061D, 0x0D7F, 0x0C41, 0x0561, 0x0531, 0x0D21,
+ 0x0711, 0x0C91, 0x0501, 0x0C1E, 0x040F, 0x15DD, 0x0431, 0x0C2F,
+ 0x057D, 0x0C2D, 0x0DBE, 0x040E, 0x0D02, 0x0D11, 0x054E, 0x040D,
+ 0x0D23, 0x0DBF, 0x04A1, 0x05ED, 0x0C1D, 0x05BD, 0x072D, 0x056E,
+ 0x0483, 0x0F3D, 0x0482, 0x078D, 0x0F5D, 0x0453, 0x0D9E, 0x0C4E,
+ 0x05CD, 0x079D, 0x0402, 0x05AE, 0x0F1F, 0x0542, 0x074D, 0x056F,
+ 0x0421, 0x0D4F, 0x0601, 0x0571, 0x0492, 0x059F, 0x053F, 0x05AF,
+ 0x0611, 0x055E, 0x0D8E, 0x053E, 0x055D, 0x047D, 0x0411, 0x052E,
+ 0x058F, 0x051F, 0x055F, 0x0D7E, 0x072F, 0x052F, 0x0412, 0x078F,
+ 0x0403, 0x077E, 0x070D, 0x070E, 0x078E, 0x0F1D, 0x072E, 0x0413,
+ 0x070F, 0x0692, 0x079E, 0x060E, 0x0622, 0x0683, 0x0702, 0x071E,
+ 0x073E, 0x076F, 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_6x6 {
+ 6, 6,
+ { 206, 164 },
+ { 769, 644 },
+ { 0, 256 },
+ { percentile_arr_6x6_0, percentile_arr_6x6_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (8 * 5)
+static const uint16_t percentile_arr_8x5_0[226] {
+ 0x0066, 0xF865, 0xE963, 0xA856, 0xA1F2, 0x9875, 0x91C3, 0x91E2,
+ 0x80F3, 0x8076, 0x61E3, 0x6153, 0x5172, 0x59D2, 0x51D3, 0x5047,
+ 0xA943, 0x49B3, 0x4846, 0x4962, 0xC037, 0x4173, 0x39F1, 0x7027,
+ 0xA2C1, 0x3AE1, 0x9341, 0x30D3, 0x5225, 0x2A61, 0x33C1, 0x28E3,
+ 0x53A1, 0x49C2, 0x2A06, 0x4055, 0x2006, 0x21D1, 0x2271, 0x4321,
+ 0x3873, 0x18F2, 0x2015, 0x1A15, 0x1857, 0x52D1, 0x3045, 0x4835,
+ 0x1952, 0x29E1, 0x3207, 0x1036, 0x1816, 0x2A16, 0x2971, 0x13B1,
+ 0x2A17, 0x2351, 0x1025, 0x1826, 0x30E2, 0x1262, 0x20F1, 0x1007,
+ 0x1072, 0x1151, 0x10D2, 0x1235, 0x1205, 0x1062, 0x4AF1, 0x1251,
+ 0x0B31, 0x1381, 0x13EE, 0x1B92, 0x13EF, 0x0942, 0x1AA2, 0x13FF,
+ 0x1161, 0x0B93, 0x19A2, 0x11B1, 0x08D1, 0x12C2, 0x0B13, 0x1B22,
+ 0x2123, 0x09A3, 0x2071, 0x1B7F, 0x1817, 0x0A42, 0x10C2, 0x1233,
+ 0x08C3, 0x0A41, 0x0B42, 0x09C1, 0x0933, 0x1AB3, 0x1382, 0x1BDF,
+ 0x2122, 0x0A53, 0x0AC3, 0x20E1, 0x0941, 0x0931, 0x0042, 0x0BA2,
+ 0x0AA3, 0x0992, 0x0863, 0x08B3, 0x11B2, 0x0902, 0x1283, 0x09FF,
+ 0x0B83, 0x0982, 0x0932, 0x0BFE, 0x0B32, 0x0BBF, 0x11FE, 0x036F,
+ 0x0851, 0x08B1, 0x18A2, 0x11EE, 0x0A52, 0x0BB2, 0x01FD, 0x0A43,
+ 0x1A63, 0x1193, 0x0B91, 0x0043, 0x1231, 0x0A26, 0x0AB1, 0x03FD,
+ 0x096F, 0x00B2, 0x0983, 0x0A72, 0x01CE, 0x0BDD, 0x0022, 0x0B11,
+ 0x1213, 0x0B6D, 0x017E, 0x1333, 0x0112, 0x0852, 0x02D2, 0x097F,
+ 0x01EF, 0x0AB2, 0x0293, 0x0853, 0x0BED, 0x0B12, 0x1303, 0x02A1,
+ 0x0892, 0x0032, 0x0883, 0x0B6E, 0x0292, 0x0A32, 0x037E, 0x0B23,
+ 0x0103, 0x0A21, 0x0B01, 0x0302, 0x0BCD, 0x00A3, 0x0BCF, 0x0BDE,
+ 0x0113, 0x01DE, 0x0B5F, 0x0013, 0x0BAF, 0x0223, 0x0222, 0x0A82,
+ 0x0833, 0x0023, 0x09CF, 0x037D, 0x01AF, 0x095F, 0x03CE, 0x09DF,
+ 0x01BF, 0x0893, 0x0203, 0x0201, 0x0B4D, 0x03BE, 0x032E, 0x03AE,
+ 0x0291, 0x0A02, 0x0211, 0x039F, 0x0281, 0x038E, 0x03AD, 0x033F,
+ 0x035D, 0x033E, 0x034E, 0x034F, 0x0212, 0x03BD, 0x032F, 0x035E,
+ 0x038F, 0x039E
+};
+
+static const uint16_t percentile_arr_8x5_1[167] {
+ 0x0621, 0xFCC2, 0x3443, 0xA433, 0x5532, 0x2551, 0x6CA3, 0x27AE,
+ 0x6452, 0x8E03, 0x3CB3, 0x4DA2, 0x6DDF, 0x37CD, 0x6F01, 0x1691,
+ 0x2E82, 0x27BE, 0x1513, 0x34D2, 0x1D22, 0x3E31, 0x2593, 0x2CB2,
+ 0x1C16, 0x374F, 0x0DD1, 0x2583, 0x6613, 0x0CD1, 0x0C35, 0x1462,
+ 0x3E81, 0x2612, 0x2C42, 0x3407, 0x14A2, 0x0E02, 0x1CF1, 0x0C06,
+ 0x17BD, 0x0F7D, 0x1D23, 0x35B1, 0x179F, 0x0D92, 0x0F5E, 0x1451,
+ 0x04B1, 0x1F6E, 0x0DEF, 0x0D31, 0x374E, 0x15C1, 0x0541, 0x2405,
+ 0x17AD, 0x0471, 0x1472, 0x0DFE, 0x0711, 0x0FDD, 0x0DFF, 0x0432,
+ 0x1D82, 0x0423, 0x0F6D, 0x07AF, 0x0F5F, 0x04C1, 0x1542, 0x0561,
+ 0x0DCF, 0x1D03, 0x1493, 0x0422, 0x0445, 0x0D12, 0x0C25, 0x0415,
+ 0x0DA1, 0x1591, 0x0DEE, 0x05DE, 0x0C31, 0x0491, 0x0441, 0x0D21,
+ 0x078D, 0x057D, 0x0C61, 0x0F3F, 0x0581, 0x0D6E, 0x0501, 0x0CA1,
+ 0x04E1, 0x0DFD, 0x057F, 0x0502, 0x0511, 0x0C82, 0x0483, 0x0C03,
+ 0x079D, 0x0402, 0x0DDD, 0x0611, 0x05AE, 0x0DCE, 0x056F, 0x0421,
+ 0x057E, 0x071F, 0x0DBF, 0x05BE, 0x0412, 0x059F, 0x054E, 0x077E,
+ 0x0C26, 0x05ED, 0x073D, 0x0601, 0x0492, 0x0453, 0x075D, 0x058E,
+ 0x0F2D, 0x05CD, 0x0571, 0x053E, 0x0692, 0x05BD, 0x054F, 0x055E,
+ 0x0411, 0x0F1D, 0x074D, 0x059E, 0x05AF, 0x070D, 0x053F, 0x058F,
+ 0x0413, 0x070F, 0x055D, 0x070E, 0x078F, 0x052E, 0x072F, 0x055F,
+ 0x078E, 0x0F2E, 0x052F, 0x051F, 0x0417, 0x071E, 0x0781, 0x0622,
+ 0x0683, 0x0702, 0x073E, 0x076F, 0x079E, 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_8x5 {
+ 8, 5,
+ { 226, 167 },
+ { 763, 517 },
+ { 0, 178 },
+ { percentile_arr_8x5_0, percentile_arr_8x5_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (8 * 6)
+static const uint16_t percentile_arr_8x6_0[273] {
+ 0x0154, 0xF944, 0xE066, 0xA128, 0x9963, 0x8118, 0x806F, 0x79F2,
+ 0x79E2, 0x7108, 0xD934, 0x6056, 0x69C3, 0x60F3, 0x5972, 0x59E3,
+ 0x5075, 0x91B3, 0xC9D2, 0x807E, 0x385F, 0x4153, 0x3943, 0x4162,
+ 0x3837, 0x3847, 0x7173, 0x31D3, 0x6948, 0x3046, 0x307F, 0x5827,
+ 0x3114, 0x32C1, 0x3076, 0x2A4D, 0x58E3, 0x306E, 0x2924, 0x2A61,
+ 0x29F1, 0x50D3, 0x704F, 0x210C, 0x2BA1, 0x2225, 0x2873, 0x4865,
+ 0x2206, 0x8341, 0x2006, 0x3B21, 0x18F2, 0x21C2, 0x1A1F, 0x23C1,
+ 0x3AE1, 0x1855, 0x19D1, 0x1A15, 0x3815, 0x1207, 0x1835, 0x2A2E,
+ 0x1A16, 0x1836, 0x2271, 0x2845, 0x1A2D, 0x11E1, 0x1816, 0x1171,
+ 0x2217, 0x1952, 0x12D1, 0x3904, 0x125D, 0x4BB1, 0x207D, 0x10E2,
+ 0x1026, 0x2025, 0x12F1, 0x28F1, 0x105D, 0x1235, 0x12A2, 0x1007,
+ 0x123D, 0x1A05, 0x1072, 0x1331, 0x101E, 0x0951, 0x10D2, 0x1057,
+ 0x1B92, 0x185E, 0x1251, 0x19A2, 0x186D, 0x0B81, 0x2BEE, 0x080E,
+ 0x1A33, 0x1942, 0x0B13, 0x0B51, 0x11A3, 0x0923, 0x2322, 0x09B1,
+ 0x184E, 0x1161, 0x18D1, 0x0933, 0x0B93, 0x4A62, 0x1017, 0x082F,
+ 0x0A42, 0x0B82, 0x0AA3, 0x0A41, 0x08C2, 0x08B3, 0x0A3E, 0x22B3,
+ 0x0871, 0x1BBF, 0x09C1, 0x0AC2, 0x09B2, 0x0BEF, 0x082E, 0x1062,
+ 0x0922, 0x08C3, 0x1063, 0x0A53, 0x0BDF, 0x080F, 0x0B42, 0x0A83,
+ 0x084D, 0x103F, 0x0931, 0x08E1, 0x0A0F, 0x1BA2, 0x09FF, 0x1332,
+ 0x03FF, 0x0941, 0x12C3, 0x0A63, 0x003D, 0x0842, 0x083E, 0x0B83,
+ 0x0BB2, 0x0A31, 0x0932, 0x1102, 0x0992, 0x0982, 0x1051, 0x08B1,
+ 0x0A2F, 0x121E, 0x02B1, 0x0A4E, 0x11EE, 0x00A2, 0x1022, 0x0043,
+ 0x0A52, 0x0A1D, 0x0226, 0x1193, 0x03DD, 0x08B2, 0x0BFD, 0x0A43,
+ 0x0A13, 0x0AB2, 0x01FD, 0x09FE, 0x020D, 0x081F, 0x0B33, 0x0053,
+ 0x0B91, 0x0293, 0x0B11, 0x0B7F, 0x0AA1, 0x0B03, 0x0A0E, 0x03FE,
+ 0x01CE, 0x0B6F, 0x0183, 0x0912, 0x023F, 0x0852, 0x0A21, 0x0323,
+ 0x03ED, 0x0A32, 0x13AF, 0x0272, 0x08A3, 0x0B12, 0x0083, 0x0832,
+ 0x13CD, 0x0223, 0x0A92, 0x0092, 0x0AD2, 0x0301, 0x0302, 0x0BDE,
+ 0x0A22, 0x01EF, 0x0B5F, 0x0103, 0x0BCF, 0x096F, 0x017E, 0x0113,
+ 0x01DE, 0x0823, 0x0282, 0x0B6E, 0x015F, 0x0813, 0x01AF, 0x01CF,
+ 0x0B7E, 0x0033, 0x01DF, 0x0BCE, 0x01BF, 0x036D, 0x0A03, 0x017F,
+ 0x03BE, 0x0201, 0x0893, 0x038E, 0x034D, 0x03AE, 0x0202, 0x039F,
+ 0x0291, 0x0A11, 0x032E, 0x033F, 0x034F, 0x0281, 0x037D, 0x03BD,
+ 0x0212, 0x033E, 0x035E, 0x034E, 0x035D, 0x03AD, 0x032F, 0x038F,
+ 0x039E
+};
+
+static const uint16_t percentile_arr_8x6_1[186] {
+ 0x0621, 0xFC33, 0x37AE, 0x1CC2, 0x2C43, 0xAD32, 0x34A3, 0x4551,
+ 0x6452, 0x5C62, 0x1FCD, 0x14F1, 0x4CB3, 0x24D2, 0x15DF, 0x0FBE,
+ 0x2603, 0x3DA2, 0x2E31, 0x25D1, 0x25EF, 0x0D22, 0x2E91, 0x1E82,
+ 0x0FBD, 0x1513, 0x0CB2, 0x0CD1, 0x0F4F, 0x1F7D, 0x1701, 0x0C16,
+ 0x2593, 0x2C42, 0x0C72, 0x14A2, 0x0F6E, 0x0C35, 0x0C71, 0x0D83,
+ 0x0C07, 0x1DFF, 0x043E, 0x1613, 0x07DD, 0x0FAD, 0x1451, 0x076D,
+ 0x0E81, 0x05FE, 0x0406, 0x0E0D, 0x045D, 0x2612, 0x0E02, 0x07AF,
+ 0x0DB1, 0x0F5E, 0x15C1, 0x0C23, 0x1523, 0x0C1F, 0x0D92, 0x04B1,
+ 0x0D31, 0x0432, 0x0D61, 0x0F4E, 0x0D41, 0x0DEE, 0x0D42, 0x04C1,
+ 0x0CE1, 0x079F, 0x0C2E, 0x0405, 0x0C22, 0x0461, 0x0E1D, 0x0582,
+ 0x073F, 0x0571, 0x0C4D, 0x0DFD, 0x05CE, 0x0C6D, 0x05DE, 0x0415,
+ 0x0C45, 0x075F, 0x0C41, 0x0D03, 0x05A1, 0x0711, 0x05CF, 0x0425,
+ 0x0C93, 0x0D21, 0x0591, 0x043D, 0x0D12, 0x0501, 0x040F, 0x0511,
+ 0x0431, 0x0C03, 0x04A1, 0x078D, 0x0581, 0x041E, 0x040D, 0x0C02,
+ 0x040E, 0x05DD, 0x057F, 0x079D, 0x042D, 0x0D9F, 0x0502, 0x056E,
+ 0x0412, 0x071F, 0x044E, 0x05BF, 0x0C1D, 0x0482, 0x05AE, 0x042F,
+ 0x057D, 0x0491, 0x054E, 0x047D, 0x0DBE, 0x0611, 0x0492, 0x0601,
+ 0x05BD, 0x05CD, 0x0426, 0x05ED, 0x072D, 0x073D, 0x0483, 0x0F5D,
+ 0x0421, 0x056F, 0x053F, 0x058E, 0x054F, 0x078F, 0x053E, 0x059E,
+ 0x057E, 0x051F, 0x055D, 0x0413, 0x070D, 0x05AF, 0x0411, 0x0453,
+ 0x0D5E, 0x077E, 0x052F, 0x070F, 0x074D, 0x0692, 0x070E, 0x072F,
+ 0x072E, 0x058F, 0x071D, 0x052E, 0x0417, 0x073E, 0x0781, 0x078E,
+ 0x055F, 0x060E, 0x0622, 0x0683, 0x0702, 0x071E, 0x076F, 0x079E,
+ 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_8x6 {
+ 8, 6,
+ { 273, 186 },
+ { 880, 300 },
+ { 0, 64 },
+ { percentile_arr_8x6_0, percentile_arr_8x6_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (8 * 8)
+static const uint16_t percentile_arr_8x8_0[347] {
+ 0x0334, 0xFD44, 0xDD14, 0x9154, 0x9B08, 0x906A, 0x8928, 0x8108,
+ 0xE866, 0xC918, 0x606F, 0xC0FE, 0x5963, 0x58EE, 0x6534, 0x505A,
+ 0x51E2, 0xA8CF, 0x5354, 0x5314, 0x5134, 0x5524, 0x48F3, 0x504B,
+ 0x487E, 0x5344, 0x49C3, 0x4972, 0x49F2, 0x4856, 0xD0EF, 0x81D2,
+ 0x78DE, 0x4261, 0x3AC1, 0x71E3, 0x6879, 0x390C, 0x3143, 0x31B3,
+ 0x385F, 0x3153, 0x306E, 0x3037, 0x30DF, 0x3162, 0x304F, 0x3075,
+ 0xB03B, 0x2847, 0x28E3, 0x2914, 0x507F, 0x28BF, 0x5173, 0x5073,
+ 0x20D3, 0x2A06, 0x2827, 0x2508, 0x2229, 0x29D3, 0x204A, 0x207A,
+ 0x2046, 0x4148, 0x20FD, 0x4225, 0x23A1, 0x3944, 0x2065, 0x1924,
+ 0x2324, 0x1806, 0x19F1, 0x2215, 0x1876, 0x22AD, 0x502B, 0x1B04,
+ 0x18F2, 0x3A4D, 0x3216, 0x3504, 0x18DD, 0x1B21, 0x10CE, 0x1869,
+ 0x1B41, 0x1855, 0x1207, 0x1AE1, 0x2845, 0x19D1, 0x2A0A, 0x1A2D,
+ 0x2A1A, 0x11C2, 0x1A0B, 0x1217, 0x2816, 0x121B, 0x1271, 0x2AD1,
+ 0x1035, 0x1015, 0x287D, 0x12F1, 0x43C1, 0x1171, 0x1A05, 0x08E2,
+ 0x11E1, 0x3251, 0x2049, 0x20F1, 0x12CD, 0x0A39, 0x1219, 0x1059,
+ 0x1104, 0x1036, 0x1872, 0x3007, 0x08ED, 0x205E, 0x1026, 0x0952,
+ 0x1392, 0x1019, 0x0951, 0x100A, 0x13EE, 0x08D2, 0x1242, 0x0ABD,
+ 0x22A2, 0x0BDF, 0x2B81, 0x0A35, 0x13B1, 0x0839, 0x13BF, 0x0A33,
+ 0x1B31, 0x205D, 0x1241, 0x183A, 0x2025, 0x0B93, 0x0A3D, 0x1017,
+ 0x1313, 0x1253, 0x082A, 0x204E, 0x09A2, 0x080B, 0x0A1F, 0x125D,
+ 0x0A2E, 0x081A, 0x08D1, 0x082F, 0x086D, 0x1B82, 0x0A09, 0x0B22,
+ 0x1062, 0x11A3, 0x2161, 0x0923, 0x129F, 0x1A62, 0x0871, 0x0942,
+ 0x081B, 0x1133, 0x18AE, 0x0A9E, 0x0863, 0x09FF, 0x18C2, 0x0B51,
+ 0x08BD, 0x0AA3, 0x09B1, 0x1AC2, 0x08B3, 0x0829, 0x0BEF, 0x0B83,
+ 0x0AAE, 0x0A8D, 0x1857, 0x185B, 0x08AF, 0x103F, 0x08C3, 0x09B2,
+ 0x0A4E, 0x11C1, 0x0A31, 0x0B42, 0x0A83, 0x0BFF, 0x13DD, 0x00CD,
+ 0x0AB3, 0x0842, 0x08BE, 0x0922, 0x1A8E, 0x08E1, 0x002E, 0x0BA2,
+ 0x0A8F, 0x2263, 0x0252, 0x0B32, 0x0AC3, 0x0941, 0x0A43, 0x083D,
+ 0x083E, 0x0A3E, 0x084D, 0x1131, 0x136F, 0x0AB1, 0x0193, 0x0BFD,
+ 0x0391, 0x0851, 0x13AF, 0x0843, 0x0213, 0x1226, 0x0932, 0x03B2,
+ 0x0902, 0x0BCD, 0x0221, 0x089E, 0x00B1, 0x0BDE, 0x03FE, 0x02A1,
+ 0x0982, 0x009F, 0x080E, 0x0B5F, 0x02BE, 0x0A32, 0x0A2A, 0x01EE,
+ 0x0053, 0x0AB2, 0x0192, 0x09FD, 0x0052, 0x0B03, 0x0293, 0x00A2,
+ 0x0B7F, 0x0BED, 0x0311, 0x08B2, 0x0A72, 0x088E, 0x0333, 0x0B12,
+ 0x0A23, 0x0822, 0x0083, 0x11CE, 0x021D, 0x08A3, 0x088F, 0x029D,
+ 0x0A22, 0x0A3F, 0x01FE, 0x020F, 0x0983, 0x02D2, 0x0292, 0x0B23,
+ 0x001E, 0x0BCF, 0x03CE, 0x09AF, 0x0B02, 0x0301, 0x022F, 0x137E,
+ 0x021E, 0x09EF, 0x016F, 0x0112, 0x097E, 0x080F, 0x020D, 0x0092,
+ 0x01DE, 0x09DF, 0x0032, 0x0033, 0x0A82, 0x03BE, 0x0B6E, 0x001F,
+ 0x020E, 0x0023, 0x09CF, 0x0113, 0x0103, 0x0013, 0x0BAE, 0x0203,
+ 0x0BAD, 0x01BF, 0x034F, 0x095F, 0x036D, 0x0202, 0x017F, 0x0093,
+ 0x0201, 0x034D, 0x0212, 0x035D, 0x03BD, 0x0B3F, 0x035E, 0x0211,
+ 0x0281, 0x0291, 0x032E, 0x037D, 0x034E, 0x038E, 0x039F, 0x032F,
+ 0x033E, 0x038F, 0x039E
+};
+
+static const uint16_t percentile_arr_8x8_1[208] {
+ 0x0621, 0x3443, 0x47CD, 0x97AE, 0xFC62, 0x14F1, 0x24C2, 0x25DF,
+ 0x3C33, 0x1C52, 0x9C72, 0x0FBE, 0x0C5D, 0x343E, 0x24A3, 0x1551,
+ 0x5D32, 0x1CD2, 0x15EF, 0x4E31, 0x04DD, 0x1FDD, 0x174F, 0x0DD1,
+ 0x3E0D, 0x15FF, 0x0DA2, 0x1E03, 0x17BD, 0x177D, 0x14B3, 0x0471,
+ 0x0CAE, 0x1C1F, 0x04D1, 0x0F6E, 0x0DFE, 0x1C42, 0x0C16, 0x0D22,
+ 0x0C9F, 0x2C2E, 0x0FAD, 0x0571, 0x147D, 0x0C07, 0x04B2, 0x0F6D,
+ 0x0F5E, 0x07AF, 0x146D, 0x0C51, 0x0593, 0x2583, 0x0C4E, 0x040B,
+ 0x0C35, 0x0513, 0x0E91, 0x0406, 0x073F, 0x144D, 0x0561, 0x048F,
+ 0x0F01, 0x0F4E, 0x0CA2, 0x075F, 0x1682, 0x04E1, 0x0C1A, 0x04BD,
+ 0x0542, 0x0D41, 0x0DEE, 0x04CD, 0x0DCF, 0x04B1, 0x0C15, 0x0C3D,
+ 0x0423, 0x0592, 0x0DDE, 0x0422, 0x0432, 0x05FD, 0x0DC1, 0x05B1,
+ 0x0DCE, 0x0612, 0x0C2F, 0x0445, 0x0602, 0x0531, 0x0439, 0x0E81,
+ 0x0582, 0x0C61, 0x061D, 0x049E, 0x0405, 0x0409, 0x0DBE, 0x079F,
+ 0x0D21, 0x04C1, 0x0C0A, 0x0E13, 0x04AD, 0x040E, 0x0581, 0x0419,
+ 0x05DD, 0x0D03, 0x049D, 0x0449, 0x0429, 0x048E, 0x0DA1, 0x0425,
+ 0x0512, 0x0501, 0x0431, 0x0523, 0x0441, 0x042D, 0x040F, 0x0D7D,
+ 0x0511, 0x0502, 0x05BF, 0x04A1, 0x0C03, 0x0402, 0x079D, 0x05AE,
+ 0x075D, 0x057F, 0x041D, 0x048D, 0x042A, 0x0453, 0x05AF, 0x078D,
+ 0x0C0D, 0x073D, 0x0491, 0x0591, 0x05BD, 0x072D, 0x057E, 0x051F,
+ 0x0482, 0x0492, 0x041E, 0x0412, 0x0D9F, 0x0421, 0x0493, 0x0711,
+ 0x056E, 0x059E, 0x054E, 0x0611, 0x05ED, 0x074D, 0x070F, 0x056F,
+ 0x052F, 0x053F, 0x071F, 0x054F, 0x05CD, 0x0483, 0x055E, 0x072F,
+ 0x0E01, 0x0426, 0x058F, 0x0413, 0x078F, 0x071D, 0x055F, 0x058E,
+ 0x0411, 0x053E, 0x071E, 0x055D, 0x077E, 0x052E, 0x0692, 0x0417,
+ 0x070D, 0x078E, 0x070E, 0x072E, 0x041B, 0x060E, 0x0622, 0x0683,
+ 0x068D, 0x0702, 0x073E, 0x076F, 0x0781, 0x079E, 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_8x8 {
+ 8, 8,
+ { 347, 208 },
+ { 1144, 267 },
+ { 0, 38 },
+ { percentile_arr_8x8_0, percentile_arr_8x8_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (10 * 5)
+static const uint16_t percentile_arr_10x5_0[274] {
+ 0x0165, 0xF975, 0xD866, 0xC056, 0xA946, 0x90C6, 0x90F5, 0x8963,
+ 0x80D6, 0x80E6, 0x60F3, 0x61C3, 0x59F2, 0xA927, 0x5075, 0x4847,
+ 0x5153, 0x4955, 0x49E2, 0x48B6, 0x41D2, 0x4943, 0x8305, 0x8172,
+ 0x4046, 0x4037, 0x40A7, 0x70B7, 0x7AC1, 0x31E3, 0x7027, 0x30E5,
+ 0x69D3, 0x99B3, 0x3315, 0x6115, 0x3136, 0x3076, 0x3173, 0x30D5,
+ 0x3106, 0x8962, 0x2916, 0x30C7, 0x5126, 0x30D3, 0x2956, 0x5117,
+ 0x2B41, 0x2AE1, 0x2A61, 0x29F1, 0x2306, 0x2145, 0x4A85, 0x2057,
+ 0x40E3, 0x4137, 0x3B21, 0x23C1, 0x2065, 0x1925, 0x51C2, 0x5225,
+ 0x4935, 0x1AD1, 0x23A1, 0x19D1, 0x1A71, 0x4055, 0x1873, 0x1A86,
+ 0x1295, 0x18F2, 0x28A6, 0x1952, 0x4AA5, 0x20B5, 0x10C5, 0x2AA2,
+ 0x11E1, 0x1107, 0x10D2, 0x2171, 0x1351, 0x3036, 0x1331, 0x1BEE,
+ 0x2035, 0x1045, 0x1313, 0x0A15, 0x1087, 0x1296, 0x13EF, 0x18E2,
+ 0x1151, 0x1086, 0x10F1, 0x08A5, 0x12C2, 0x1BFF, 0x1095, 0x1A62,
+ 0x1322, 0x0942, 0x1026, 0x1872, 0x1062, 0x0897, 0x1123, 0x08D1,
+ 0x1A06, 0x0806, 0x137F, 0x13B1, 0x13DF, 0x1A51, 0x09B1, 0x0A83,
+ 0x1015, 0x22F1, 0x0961, 0x0B81, 0x12B3, 0x0A35, 0x0AA3, 0x20B3,
+ 0x08C3, 0x2342, 0x0933, 0x0A33, 0x09A2, 0x10C2, 0x0896, 0x2205,
+ 0x0825, 0x20E1, 0x0922, 0x1242, 0x0B16, 0x0B32, 0x09A3, 0x0AC3,
+ 0x0BBF, 0x0B93, 0x0071, 0x0931, 0x0A41, 0x2392, 0x13FE, 0x09C1,
+ 0x0B07, 0x0016, 0x1182, 0x09B2, 0x0A26, 0x0132, 0x0941, 0x0A93,
+ 0x0992, 0x1063, 0x1217, 0x01FF, 0x11EE, 0x1216, 0x0B23, 0x0B82,
+ 0x0042, 0x1102, 0x0213, 0x0B6F, 0x09FE, 0x1207, 0x0807, 0x18B1,
+ 0x0253, 0x0AB1, 0x08A2, 0x13FD, 0x01FD, 0x1983, 0x0AB2, 0x0A31,
+ 0x016F, 0x0B11, 0x00B2, 0x0851, 0x0AD2, 0x0993, 0x0BDD, 0x12A1,
+ 0x017F, 0x0A97, 0x1022, 0x0383, 0x0843, 0x0A52, 0x03A2, 0x097E,
+ 0x0817, 0x03B2, 0x0A43, 0x09EF, 0x0A63, 0x0B33, 0x0B03, 0x0292,
+ 0x0272, 0x09CE, 0x0287, 0x136D, 0x0053, 0x0B12, 0x0083, 0x0892,
+ 0x0112, 0x1282, 0x03ED, 0x0852, 0x0301, 0x1391, 0x0232, 0x0B7E,
+ 0x0221, 0x08A3, 0x0BCD, 0x0BCF, 0x036E, 0x09DE, 0x0103, 0x03DE,
+ 0x0832, 0x0BAF, 0x0302, 0x13CE, 0x035F, 0x0093, 0x0A23, 0x01DF,
+ 0x0013, 0x0A22, 0x0023, 0x0113, 0x09AF, 0x01BF, 0x0033, 0x095F,
+ 0x0203, 0x0281, 0x09CF, 0x037D, 0x0201, 0x0B4D, 0x03AE, 0x03BE,
+ 0x0291, 0x035E, 0x038E, 0x0B9F, 0x03AD, 0x0202, 0x034F, 0x0211,
+ 0x035D, 0x0212, 0x032E, 0x039E, 0x033F, 0x034E, 0x03BD, 0x032F,
+ 0x033E, 0x038F
+};
+
+static const uint16_t percentile_arr_10x5_1[180] {
+ 0x0532, 0xFCA3, 0x3621, 0x6E82, 0x2CC2, 0x3D51, 0x3F01, 0x2691,
+ 0x17AE, 0x35A2, 0x74B3, 0x1603, 0x4433, 0x3C43, 0x6C35, 0x25D1,
+ 0x1D13, 0x15DF, 0x37CD, 0x0D93, 0x1D22, 0x0E81, 0x1452, 0x0CD2,
+ 0x37BE, 0x0CB2, 0x3407, 0x1523, 0x0C16, 0x0CB5, 0x0C96, 0x1486,
+ 0x2631, 0x1506, 0x0F4F, 0x1583, 0x0CD1, 0x2CA2, 0x2612, 0x1613,
+ 0x1602, 0x1F11, 0x179F, 0x17BD, 0x15B1, 0x0406, 0x1D41, 0x0CF1,
+ 0x0D31, 0x0442, 0x1C62, 0x0F6E, 0x077D, 0x0C51, 0x0445, 0x0D15,
+ 0x2592, 0x0CB1, 0x05EF, 0x0542, 0x17AF, 0x1425, 0x075E, 0x0FAD,
+ 0x0CC1, 0x0503, 0x0512, 0x15C1, 0x0C95, 0x0415, 0x0505, 0x0F4E,
+ 0x04A5, 0x0493, 0x0C32, 0x0F5F, 0x04E1, 0x0521, 0x0C85, 0x07DD,
+ 0x0582, 0x15FF, 0x05CF, 0x0405, 0x0D91, 0x05A1, 0x05FE, 0x0C23,
+ 0x0561, 0x0472, 0x0471, 0x0C22, 0x0DEE, 0x076D, 0x0502, 0x0426,
+ 0x0C61, 0x0D7D, 0x0525, 0x05DE, 0x0DCE, 0x079D, 0x0692, 0x0441,
+ 0x0C91, 0x05DD, 0x0511, 0x057F, 0x0611, 0x0DFD, 0x078D, 0x056E,
+ 0x0492, 0x04A1, 0x073F, 0x0C31, 0x05BE, 0x0483, 0x0571, 0x056F,
+ 0x0D9F, 0x0581, 0x0501, 0x057E, 0x05BF, 0x078F, 0x0516, 0x05ED,
+ 0x0402, 0x0F7E, 0x0482, 0x054E, 0x075D, 0x071F, 0x05CD, 0x0535,
+ 0x05AE, 0x0C11, 0x058F, 0x05AF, 0x0421, 0x0413, 0x0601, 0x054F,
+ 0x073D, 0x059E, 0x0487, 0x070F, 0x078E, 0x0781, 0x053E, 0x0403,
+ 0x072D, 0x055D, 0x05BD, 0x079E, 0x0D8E, 0x0412, 0x052E, 0x074D,
+ 0x053F, 0x051F, 0x070E, 0x055F, 0x072F, 0x052F, 0x070D, 0x055E,
+ 0x0417, 0x0453, 0x072E, 0x0622, 0x0683, 0x0702, 0x071D, 0x071E,
+ 0x073E, 0x076F, 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_10x5 {
+ 10, 5,
+ { 274, 180 },
+ { 954, 324 },
+ { 0, 79 },
+ { percentile_arr_10x5_0, percentile_arr_10x5_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (10 * 6)
+static const uint16_t percentile_arr_10x6_0[325] {
+ 0x01A4, 0xF954, 0xA066, 0x9975, 0x80F5, 0x7056, 0x6918, 0x6963,
+ 0x58C6, 0x5946, 0x5928, 0x5174, 0x586F, 0xA0E6, 0x5108, 0x48D6,
+ 0x49E2, 0x40F3, 0x9172, 0x41F2, 0xB875, 0x3927, 0x39C3, 0xA953,
+ 0x3934, 0x3305, 0x30B6, 0x6943, 0x31D2, 0x3876, 0x3037, 0x2955,
+ 0x30A7, 0x32C1, 0x29B3, 0x3027, 0x287E, 0x30B7, 0x29E3, 0x5846,
+ 0x2B15, 0x2847, 0x3162, 0x5173, 0x4936, 0x285F, 0x48D3, 0x2164,
+ 0x4906, 0x20E5, 0x2915, 0x2116, 0x407F, 0x20D5, 0x2A61, 0x4117,
+ 0x20E3, 0x2126, 0x4148, 0x206E, 0x39D3, 0x2145, 0x41B4, 0x1B06,
+ 0x2114, 0x2165, 0x5321, 0x5A85, 0x1A4D, 0x1A1F, 0x19F1, 0x3341,
+ 0x184F, 0x1956, 0x3125, 0x30C7, 0x28F2, 0x1937, 0x1AE1, 0x1073,
+ 0x1BA1, 0x1935, 0x110C, 0x1BC1, 0x3A25, 0x19C2, 0x1295, 0x122E,
+ 0x1944, 0x11D1, 0x1124, 0x1857, 0x22D1, 0x2286, 0x1A2D, 0x12A2,
+ 0x2107, 0x1055, 0x2065, 0x0A71, 0x2152, 0x10C5, 0x10D2, 0x1331,
+ 0x08B5, 0x1171, 0x2836, 0x10A6, 0x0904, 0x123D, 0x20F1, 0x12A5,
+ 0x10E2, 0x107D, 0x1AF1, 0x1313, 0x0951, 0x11E1, 0x1B22, 0x1B51,
+ 0x0835, 0x101E, 0x0A5D, 0x0A15, 0x3045, 0x0A96, 0x08A5, 0x1142,
+ 0x12A3, 0x1872, 0x085D, 0x09B1, 0x100E, 0x0887, 0x0886, 0x086D,
+ 0x0933, 0x12B3, 0x0897, 0x08B3, 0x0A33, 0x0923, 0x1095, 0x0BEE,
+ 0x2BB1, 0x085E, 0x1283, 0x0A51, 0x1026, 0x0A06, 0x12C2, 0x08D1,
+ 0x11A2, 0x13BF, 0x08C3, 0x10C2, 0x0A3E, 0x0BDF, 0x0B81, 0x13EF,
+ 0x0A35, 0x0B16, 0x082F, 0x2161, 0x1B32, 0x0806, 0x084E, 0x11A3,
+ 0x1015, 0x1122, 0x2931, 0x0342, 0x0825, 0x0A0F, 0x0896, 0x0A05,
+ 0x0241, 0x09C1, 0x083F, 0x0A42, 0x0071, 0x0B07, 0x082E, 0x0393,
+ 0x12B1, 0x0A62, 0x0226, 0x0A2F, 0x0B92, 0x0063, 0x0932, 0x0862,
+ 0x09FF, 0x0A31, 0x00E1, 0x12B2, 0x09B2, 0x0AC3, 0x0941, 0x0293,
+ 0x1323, 0x104D, 0x003E, 0x083D, 0x0992, 0x1382, 0x03FF, 0x0A13,
+ 0x1016, 0x0A53, 0x0182, 0x1007, 0x0AA1, 0x080F, 0x0A16, 0x0A1E,
+ 0x0042, 0x0902, 0x13DD, 0x0BB2, 0x0A63, 0x00A2, 0x08B1, 0x03FE,
+ 0x1207, 0x08B2, 0x0B83, 0x09EE, 0x0311, 0x0A87, 0x0BAF, 0x03A2,
+ 0x09FD, 0x0051, 0x0B33, 0x020D, 0x09CE, 0x0217, 0x021D, 0x0817,
+ 0x020E, 0x0A4E, 0x001F, 0x0BFD, 0x0297, 0x0983, 0x0A92, 0x0252,
+ 0x0243, 0x0B03, 0x0193, 0x036F, 0x0B12, 0x0043, 0x0822, 0x0A21,
+ 0x01FE, 0x0853, 0x037F, 0x023F, 0x0BED, 0x02D2, 0x0B91, 0x0232,
+ 0x0282, 0x0912, 0x08A3, 0x0852, 0x0223, 0x0BCD, 0x0083, 0x0301,
+ 0x0832, 0x01EF, 0x0892, 0x0302, 0x0A72, 0x03DE, 0x0893, 0x0BCF,
+ 0x09DE, 0x03CE, 0x035F, 0x0833, 0x0023, 0x0103, 0x017E, 0x0813,
+ 0x01CF, 0x01BF, 0x016F, 0x0A22, 0x037E, 0x0113, 0x01AF, 0x0B6E,
+ 0x03BE, 0x0201, 0x0A03, 0x01DF, 0x036D, 0x03AE, 0x015F, 0x0281,
+ 0x033E, 0x0A02, 0x038E, 0x017F, 0x0291, 0x034D, 0x03BD, 0x0B7D,
+ 0x03AD, 0x0211, 0x0212, 0x034F, 0x032E, 0x039F, 0x034E, 0x035D,
+ 0x035E, 0x033F, 0x039E, 0x032F, 0x038F
+};
+
+static const uint16_t percentile_arr_10x6_1[199] {
+ 0x0621, 0xBD32, 0x5CA3, 0x1FAE, 0x64C2, 0x1D51, 0x6C33, 0xFC43,
+ 0x5CB3, 0x25A2, 0x2E82, 0x35D1, 0x4F01, 0x3FBE, 0x3691, 0x2DDF,
+ 0x2E03, 0x3FCD, 0x14D2, 0x1CF1, 0x0C52, 0x3C35, 0x2D22, 0x1513,
+ 0x1462, 0x54B2, 0x0E31, 0x4E81, 0x1593, 0x1D23, 0x1CD1, 0x14B5,
+ 0x2FBD, 0x0C07, 0x1D06, 0x0DEF, 0x14A2, 0x1612, 0x1F4F, 0x0C16,
+ 0x1F7D, 0x0C96, 0x0486, 0x1F9F, 0x0D42, 0x4583, 0x0E02, 0x0472,
+ 0x0DB1, 0x1613, 0x0FAD, 0x0D41, 0x0F11, 0x0E0D, 0x1C42, 0x143E,
+ 0x076E, 0x04B1, 0x0FAF, 0x0D61, 0x0531, 0x0C71, 0x0DFF, 0x0DFE,
+ 0x0406, 0x0C45, 0x0451, 0x0D15, 0x05C1, 0x2CC1, 0x141F, 0x0CE1,
+ 0x0FDD, 0x0C22, 0x0582, 0x0D92, 0x0571, 0x0F6D, 0x0C93, 0x045D,
+ 0x0F5E, 0x044D, 0x0423, 0x0D05, 0x0425, 0x0C95, 0x04A5, 0x0DCE,
+ 0x075F, 0x0E1D, 0x0503, 0x042E, 0x0D91, 0x0512, 0x0DDE, 0x05A1,
+ 0x074E, 0x0C32, 0x0431, 0x0415, 0x0D21, 0x05EE, 0x040E, 0x0DDD,
+ 0x0485, 0x1525, 0x0491, 0x0C26, 0x046D, 0x0C05, 0x05CF, 0x05FD,
+ 0x0E92, 0x073F, 0x0C0D, 0x043D, 0x0502, 0x0C1E, 0x041D, 0x0461,
+ 0x04A1, 0x0511, 0x0581, 0x05BD, 0x0C41, 0x059F, 0x05BF, 0x040F,
+ 0x0C7D, 0x0402, 0x054E, 0x057D, 0x0403, 0x078D, 0x05AE, 0x042D,
+ 0x0483, 0x079D, 0x0D7F, 0x0482, 0x0611, 0x056E, 0x0516, 0x05BE,
+ 0x0535, 0x044E, 0x05AF, 0x0DED, 0x042F, 0x0492, 0x058E, 0x078F,
+ 0x0412, 0x057E, 0x053E, 0x0F1F, 0x073D, 0x0601, 0x0501, 0x075D,
+ 0x059E, 0x05CD, 0x053F, 0x054F, 0x055E, 0x055D, 0x0421, 0x074D,
+ 0x051F, 0x072F, 0x0781, 0x0411, 0x0D6F, 0x077E, 0x0487, 0x070E,
+ 0x070F, 0x072D, 0x058F, 0x078E, 0x079E, 0x052E, 0x0413, 0x072E,
+ 0x071D, 0x052F, 0x055F, 0x073E, 0x0417, 0x0453, 0x060E, 0x0622,
+ 0x0683, 0x0702, 0x070D, 0x071E, 0x076F, 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_10x6 {
+ 10, 6,
+ { 325, 199 },
+ { 922, 381 },
+ { 0, 78 },
+ { percentile_arr_10x6_0, percentile_arr_10x6_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (10 * 8)
+static const uint16_t percentile_arr_10x8_0[400] {
+ 0x0154, 0xAB34, 0xAD44, 0x8308, 0x7866, 0x7B64, 0x79A4, 0x7975,
+ 0x686A, 0x6908, 0xC514, 0x6174, 0x6128, 0x6118, 0x5B54, 0x5163,
+ 0xF856, 0x50F5, 0x986F, 0xDD34, 0x48FE, 0x4972, 0x48E6, 0x4146,
+ 0x48EE, 0x40F3, 0x4AC1, 0x38C6, 0x41E2, 0xBB05, 0x707E, 0x38D6,
+ 0x3927, 0x6B14, 0x384B, 0x3948, 0x3153, 0x385A, 0x3134, 0x6B15,
+ 0x39F2, 0x30CF, 0x3143, 0x91D2, 0x31C3, 0x60EF, 0x5973, 0x3076,
+ 0x28D3, 0x3261, 0x2875, 0x28DE, 0x290C, 0x51E3, 0x28A7, 0x20E3,
+ 0x2962, 0x2B06, 0x2917, 0x483B, 0x20B6, 0x2D24, 0x206E, 0x285F,
+ 0x20B7, 0x2936, 0x4047, 0x2037, 0x20DF, 0x28BF, 0x21B4, 0x21B3,
+ 0x1D08, 0x2027, 0x404F, 0x3846, 0x2116, 0x187F, 0x1879, 0x2285,
+ 0x1A29, 0x3915, 0x4873, 0x1955, 0x3114, 0x1B44, 0x2165, 0x107A,
+ 0x1956, 0x6137, 0x1106, 0x3145, 0x1B21, 0x19D3, 0x12AD, 0x1B41,
+ 0x1AD1, 0x1126, 0x18F2, 0x282B, 0x40E5, 0x20D5, 0x2A0A, 0x284A,
+ 0x1286, 0x1295, 0x121A, 0x2A0B, 0x321B, 0x122D, 0x10FD, 0x13A1,
+ 0x32A2, 0x12E1, 0x1164, 0x13C1, 0x124D, 0x1239, 0x4504, 0x10C7,
+ 0x22F1, 0x11F1, 0x0AC2, 0x2125, 0x1225, 0x0B04, 0x1107, 0x1069,
+ 0x1A19, 0x13BF, 0x2A96, 0x08D2, 0x1271, 0x0952, 0x2BDF, 0x0B31,
+ 0x1251, 0x2124, 0x0B13, 0x12BD, 0x1233, 0x13EE, 0x2144, 0x0B16,
+ 0x0A15, 0x18E2, 0x08DD, 0x1097, 0x0857, 0x0B24, 0x0AA5, 0x12A3,
+ 0x11C2, 0x11D1, 0x10CE, 0x0865, 0x123D, 0x08B3, 0x0B51, 0x1971,
+ 0x0A41, 0x0A06, 0x1039, 0x080A, 0x0B22, 0x0923, 0x0836, 0x08C3,
+ 0x0A1F, 0x1072, 0x080B, 0x0935, 0x0855, 0x18A6, 0x0A42, 0x1133,
+ 0x0A83, 0x0A09, 0x0ACD, 0x0A2E, 0x0887, 0x083A, 0x10C5, 0x085E,
+ 0x13B1, 0x087D, 0x0819, 0x0A9F, 0x0049, 0x08F1, 0x0BEF, 0x1161,
+ 0x0B42, 0x09E1, 0x0A05, 0x0904, 0x12AE, 0x029E, 0x0A31, 0x09FF,
+ 0x0951, 0x0859, 0x001A, 0x082F, 0x0B81, 0x08B5, 0x0A35, 0x082A,
+ 0x08ED, 0x1142, 0x1262, 0x0B32, 0x08A5, 0x12D2, 0x03DD, 0x0B07,
+ 0x18AE, 0x083F, 0x00AF, 0x0AB3, 0x086D, 0x0287, 0x0A93, 0x025D,
+ 0x0816, 0x13FF, 0x0A8D, 0x005D, 0x08D1, 0x0392, 0x0845, 0x0AC3,
+ 0x08C2, 0x01A3, 0x0AB1, 0x09A2, 0x005B, 0x0B93, 0x02B2, 0x1086,
+ 0x001B, 0x0863, 0x0216, 0x0AA1, 0x0896, 0x0A8F, 0x084E, 0x0A8E,
+ 0x0A53, 0x0026, 0x0A26, 0x0382, 0x0807, 0x0862, 0x0029, 0x0871,
+ 0x00BD, 0x0835, 0x024E, 0x0806, 0x0941, 0x0895, 0x03AF, 0x0A13,
+ 0x0932, 0x03ED, 0x0BFD, 0x0207, 0x0B83, 0x0993, 0x09B1, 0x03CD,
+ 0x0A3E, 0x03FE, 0x0A21, 0x0015, 0x0B11, 0x0A43, 0x00E1, 0x136F,
+ 0x00BE, 0x00A2, 0x0842, 0x0043, 0x0825, 0x082E, 0x0A2A, 0x03DE,
+ 0x0BA2, 0x0122, 0x0BCF, 0x004D, 0x0323, 0x09C1, 0x0292, 0x083E,
+ 0x0252, 0x0017, 0x0A72, 0x00CD, 0x0182, 0x0A63, 0x0131, 0x09B2,
+ 0x0303, 0x0902, 0x0053, 0x035F, 0x0A32, 0x003D, 0x0992, 0x0A2F,
+ 0x03B2, 0x0ABE, 0x009F, 0x0183, 0x0312, 0x08B1, 0x0B02, 0x0A17,
+ 0x0B7F, 0x0333, 0x0297, 0x0A23, 0x020F, 0x0282, 0x0851, 0x0822,
+ 0x03CE, 0x01EE, 0x000E, 0x08B2, 0x0083, 0x0A1D, 0x00A3, 0x0222,
+ 0x088F, 0x0112, 0x029D, 0x0092, 0x0A3F, 0x0391, 0x089E, 0x0301,
+ 0x01FD, 0x09BF, 0x01CE, 0x0852, 0x01FE, 0x0013, 0x0903, 0x088E,
+ 0x037E, 0x021E, 0x01EF, 0x095F, 0x016F, 0x09DE, 0x03BE, 0x020E,
+ 0x0113, 0x01DF, 0x080F, 0x020D, 0x0833, 0x03AE, 0x0032, 0x03BD,
+ 0x0823, 0x001E, 0x01AF, 0x0203, 0x034F, 0x0093, 0x0A81, 0x036E,
+ 0x0291, 0x038E, 0x0A01, 0x001F, 0x017F, 0x01CF, 0x017E, 0x0202,
+ 0x0BAD, 0x0211, 0x035D, 0x035E, 0x039F, 0x0212, 0x032E, 0x033F,
+ 0x034D, 0x034E, 0x036D, 0x032F, 0x033E, 0x037D, 0x038F, 0x039E
+};
+
+static const uint16_t percentile_arr_10x8_1[221] {
+ 0x0621, 0xDFAE, 0x2443, 0x54C2, 0x37CD, 0x1CF1, 0xFCA3, 0x14D2,
+ 0x2D32, 0x5551, 0x7DDF, 0x5C33, 0x15D1, 0x3462, 0x24B3, 0x7452,
+ 0x5FBE, 0x6472, 0x65A2, 0x1D06, 0x445D, 0x15EF, 0x0E31, 0x1D71,
+ 0x343E, 0x0D42, 0x0CDD, 0x1F01, 0x4691, 0x1435, 0x0E82, 0x0DFF,
+ 0x17DD, 0x0D22, 0x24B2, 0x1603, 0x04B5, 0x24AE, 0x060D, 0x2D13,
+ 0x0C7D, 0x0496, 0x17BD, 0x1F4F, 0x1F7D, 0x1486, 0x0593, 0x1C16,
+ 0x0C07, 0x15FE, 0x041F, 0x14D1, 0x0C9F, 0x0E81, 0x0D15, 0x27AF,
+ 0x0C2E, 0x0D23, 0x176E, 0x0FAD, 0x1C06, 0x1561, 0x0DB1, 0x040B,
+ 0x1C4E, 0x0D83, 0x1711, 0x0C42, 0x0C71, 0x1C1A, 0x0D25, 0x04A2,
+ 0x0C45, 0x076D, 0x0F9F, 0x075F, 0x0E12, 0x046D, 0x048F, 0x1D92,
+ 0x0602, 0x0C39, 0x174E, 0x0C51, 0x0CA1, 0x075E, 0x05C1, 0x14BD,
+ 0x0D31, 0x0423, 0x0F3F, 0x0495, 0x0C93, 0x049E, 0x0D05, 0x04E1,
+ 0x0DEE, 0x0415, 0x04B1, 0x0503, 0x0CCD, 0x042F, 0x0DCF, 0x044D,
+ 0x0541, 0x1582, 0x05DE, 0x0D01, 0x0487, 0x040A, 0x0516, 0x0CA5,
+ 0x05FD, 0x05BF, 0x057D, 0x0DA1, 0x0426, 0x040F, 0x071F, 0x0613,
+ 0x0432, 0x0D12, 0x043D, 0x0425, 0x0461, 0x061D, 0x0D21, 0x0591,
+ 0x079D, 0x048D, 0x0429, 0x0C49, 0x04C1, 0x042A, 0x040E, 0x0485,
+ 0x0511, 0x0405, 0x0502, 0x0441, 0x0C19, 0x0692, 0x0535, 0x058F,
+ 0x041D, 0x059F, 0x072D, 0x04AD, 0x049D, 0x05CE, 0x048E, 0x0C31,
+ 0x057F, 0x078D, 0x0409, 0x041E, 0x05AE, 0x0611, 0x058E, 0x05DD,
+ 0x05CD, 0x056E, 0x0483, 0x073D, 0x054E, 0x0D9E, 0x0402, 0x0491,
+ 0x040D, 0x056F, 0x042D, 0x0581, 0x0421, 0x057E, 0x0781, 0x053E,
+ 0x0482, 0x078F, 0x0413, 0x052E, 0x0601, 0x0422, 0x0492, 0x055E,
+ 0x05BE, 0x0F9E, 0x072F, 0x074D, 0x0412, 0x070F, 0x075D, 0x05BD,
+ 0x051F, 0x071D, 0x073E, 0x077E, 0x0403, 0x0411, 0x078E, 0x055D,
+ 0x05AF, 0x05ED, 0x052F, 0x053F, 0x070D, 0x070E, 0x072E, 0x054F,
+ 0x0417, 0x041B, 0x0453, 0x055F, 0x060E, 0x0622, 0x0683, 0x068D,
+ 0x0702, 0x071E, 0x076F, 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_10x8 =
+{
+ 10, 8,
+ { 400, 221 },
+ { 1119, 376 },
+ { 0, 52 },
+ { percentile_arr_10x8_0, percentile_arr_10x8_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (10 * 10)
+static const uint16_t percentile_arr_10x10_0[453] {
+ 0x0334, 0x9514, 0x8954, 0x806A, 0x6F14, 0x6724, 0x6108, 0x6364,
+ 0x5175, 0x5D44, 0x5866, 0x5118, 0x5308, 0xA179, 0x5128, 0xF534,
+ 0x49A4, 0x5354, 0x9174, 0x486F, 0x48EA, 0x40F3, 0x4963, 0x414A,
+ 0xF8F9, 0x3984, 0x4172, 0x387E, 0x405A, 0x38DA, 0x38F5, 0x9B05,
+ 0x30EE, 0x32C1, 0x3261, 0x3D08, 0x31E2, 0x3056, 0x292B, 0x3146,
+ 0x3127, 0x3315, 0x58CA, 0x58E6, 0x290C, 0x3314, 0x8134, 0x28E3,
+ 0x28FE, 0x2948, 0x28C6, 0x78DE, 0x28BB, 0x68D6, 0x286E, 0x2173,
+ 0x2962, 0x21D2, 0x205F, 0x49F2, 0x2917, 0x2306, 0x207F, 0x404F,
+ 0x2153, 0x2943, 0x20CF, 0x21C3, 0x2073, 0x20D3, 0x2136, 0x183B,
+ 0x430A, 0x40A7, 0x18B6, 0x2079, 0x2309, 0x2075, 0x184B, 0x20EF,
+ 0x187A, 0x7837, 0x1B19, 0x20AB, 0x18BA, 0x20B7, 0x1994, 0x19E3,
+ 0x21B4, 0x49B3, 0x38BF, 0x193B, 0x1876, 0x182B, 0x30F2, 0x193A,
+ 0x1827, 0x1965, 0x1914, 0x184A, 0x4047, 0x1916, 0x1285, 0x1937,
+ 0x122D, 0x1915, 0x1321, 0x1955, 0x1046, 0x191B, 0x2106, 0x2919,
+ 0x1344, 0x1524, 0x12E1, 0x3926, 0x10E5, 0x2295, 0x1159, 0x1145,
+ 0x10DF, 0x124D, 0x1271, 0x092A, 0x2169, 0x1704, 0x22A2, 0x1164,
+ 0x13EE, 0x12F1, 0x0AD1, 0x128A, 0x110A, 0x11D3, 0x1286, 0x115A,
+ 0x2BA1, 0x0BBF, 0x3956, 0x2A89, 0x12AD, 0x10E9, 0x0B41, 0x1A29,
+ 0x2225, 0x08FD, 0x1107, 0x08D5, 0x191A, 0x1125, 0x1A96, 0x0B04,
+ 0x18D9, 0x2B16, 0x11F1, 0x0A33, 0x0924, 0x131A, 0x1149, 0x1324,
+ 0x0BEF, 0x0A99, 0x08CB, 0x123D, 0x1331, 0x0BDF, 0x0872, 0x22A3,
+ 0x0AC2, 0x1144, 0x0D04, 0x08D2, 0x08CE, 0x0AA9, 0x0A9A, 0x0B13,
+ 0x1251, 0x0865, 0x1069, 0x0897, 0x1215, 0x18B3, 0x1A62, 0x08C7,
+ 0x185E, 0x10E2, 0x0AA5, 0x21FF, 0x090B, 0x0952, 0x09E1, 0x0A42,
+ 0x08F1, 0x0A06, 0x0B22, 0x087D, 0x1139, 0x021F, 0x122E, 0x082F,
+ 0x09C2, 0x0887, 0x0A0A, 0x03C1, 0x0929, 0x0A5D, 0x0A83, 0x0BFF,
+ 0x0935, 0x085B, 0x0104, 0x08DD, 0x0923, 0x083F, 0x0241, 0x09D1,
+ 0x0A39, 0x0863, 0x0A8B, 0x08A6, 0x008B, 0x1133, 0x13B1, 0x089B,
+ 0x0AB3, 0x0036, 0x0BDD, 0x08ED, 0x0857, 0x0971, 0x0219, 0x1235,
+ 0x0AB1, 0x0ACD, 0x036F, 0x0A31, 0x08AA, 0x003A, 0x08C3, 0x0A05,
+ 0x02BD, 0x0B92, 0x0B07, 0x12B2, 0x08C5, 0x0B51, 0x0381, 0x0A8D,
+ 0x01A3, 0x0896, 0x0855, 0x0BFD, 0x005D, 0x0BFE, 0x023E, 0x08AF,
+ 0x00B9, 0x0A93, 0x00B5, 0x0862, 0x0A0B, 0x0A09, 0x0A72, 0x0332,
+ 0x0AA1, 0x08C9, 0x024E, 0x1382, 0x0951, 0x00A5, 0x0A2A, 0x0059,
+ 0x0A9E, 0x0B42, 0x004E, 0x0942, 0x03ED, 0x09B2, 0x02D2, 0x0849,
+ 0x0035, 0x0216, 0x0961, 0x0BAF, 0x00AE, 0x0826, 0x0287, 0x0A1A,
+ 0x0393, 0x0221, 0x09A2, 0x086D, 0x0226, 0x0871, 0x0039, 0x082A,
+ 0x08C2, 0x08E1, 0x0845, 0x0207, 0x0B23, 0x0015, 0x00D1, 0x0B83,
+ 0x037F, 0x0252, 0x08A9, 0x0099, 0x0A13, 0x0053, 0x0807, 0x03CD,
+ 0x0BDE, 0x0016, 0x089A, 0x0232, 0x035F, 0x0A8E, 0x0AC3, 0x022F,
+ 0x0263, 0x0829, 0x004D, 0x0132, 0x0806, 0x0311, 0x01B1, 0x0941,
+ 0x0086, 0x000B, 0x1122, 0x0025, 0x0842, 0x00BD, 0x0BCF, 0x03A2,
+ 0x0043, 0x0B03, 0x0895, 0x0A8F, 0x008A, 0x09EF, 0x0253, 0x0A1B,
+ 0x0182, 0x0243, 0x0A92, 0x00CD, 0x083E, 0x030B, 0x0223, 0x081A,
+ 0x0A9F, 0x0193, 0x00BE, 0x0017, 0x0931, 0x0391, 0x037E, 0x09C1,
+ 0x0312, 0x0333, 0x03B2, 0x083D, 0x08B1, 0x00B2, 0x002E, 0x021D,
+ 0x0A9D, 0x0192, 0x02AE, 0x0102, 0x0022, 0x081B, 0x0222, 0x009E,
+ 0x021E, 0x000A, 0x089F, 0x0217, 0x0BCE, 0x0052, 0x020F, 0x0A97,
+ 0x0282, 0x008E, 0x0A3F, 0x01FD, 0x00A3, 0x0019, 0x08A2, 0x0301,
+ 0x036E, 0x01FE, 0x03BE, 0x0ABE, 0x01CE, 0x0302, 0x029B, 0x0051,
+ 0x0883, 0x008F, 0x0BAE, 0x01DF, 0x0183, 0x0912, 0x000E, 0x020D,
+ 0x01EE, 0x0B4F, 0x0033, 0x0103, 0x020E, 0x0832, 0x01AF, 0x0913,
+ 0x01DE, 0x0203, 0x001E, 0x0092, 0x0093, 0x000F, 0x015F, 0x0291,
+ 0x0281, 0x0813, 0x001F, 0x01CF, 0x033F, 0x0023, 0x01BF, 0x0202,
+ 0x016F, 0x017E, 0x03AD, 0x0201, 0x034E, 0x0BBD, 0x036D, 0x017F,
+ 0x0211, 0x038E, 0x0212, 0x032E, 0x034D, 0x035E, 0x037D, 0x039E,
+ 0x032F, 0x033E, 0x035D, 0x038F, 0x039F
+};
+
+static const uint16_t percentile_arr_10x10_1[234] {
+ 0x07CD, 0x6E21, 0x24F1, 0x8443, 0xD7AE, 0x24C2, 0x1C62, 0xCCA3,
+ 0x1C33, 0xFDEF, 0x2532, 0x55DF, 0x1472, 0x6C3E, 0x14D2, 0x34DD,
+ 0x1452, 0x745D, 0x4D51, 0x8DD1, 0x247D, 0x75FF, 0x0CB3, 0x17BE,
+ 0x6CAE, 0x17DD, 0x1571, 0x3D06, 0x4E31, 0x0DA2, 0x67BD, 0x160D,
+ 0x2C4E, 0x0D22, 0x176E, 0x3CB2, 0x142E, 0x4DFE, 0x0F4F, 0x1435,
+ 0x0F01, 0x0D42, 0x0F7D, 0x0CB5, 0x1E03, 0x149F, 0x1C96, 0x141F,
+ 0x14B9, 0x0FAF, 0x0439, 0x0E91, 0x2682, 0x1D13, 0x1FAD, 0x0407,
+ 0x3471, 0x0C86, 0x0F6D, 0x0D15, 0x0D61, 0x040B, 0x0C6D, 0x0C16,
+ 0x0C9A, 0x0D0A, 0x0593, 0x0CD1, 0x248F, 0x0C2F, 0x3C42, 0x1523,
+ 0x0445, 0x0E81, 0x0CA2, 0x1525, 0x0406, 0x1C8A, 0x0C1A, 0x04BD,
+ 0x0F5E, 0x0F3F, 0x1F4E, 0x0E1D, 0x0423, 0x0DCF, 0x044D, 0x0D92,
+ 0x0583, 0x0DB1, 0x1449, 0x15EE, 0x0F5F, 0x079F, 0x0D19, 0x0409,
+ 0x04CD, 0x05FD, 0x143D, 0x0612, 0x0D03, 0x0D82, 0x04B1, 0x0C95,
+ 0x0C2A, 0x049E, 0x05AF, 0x0D31, 0x05BE, 0x04E1, 0x0D05, 0x0516,
+ 0x0711, 0x05C1, 0x0509, 0x0D41, 0x0493, 0x048E, 0x0602, 0x05BF,
+ 0x0CA5, 0x0529, 0x0535, 0x0D12, 0x0539, 0x0451, 0x0C29, 0x071F,
+ 0x040A, 0x0F3D, 0x0432, 0x059F, 0x0425, 0x0C99, 0x05DE, 0x05CE,
+ 0x0C0F, 0x0489, 0x051A, 0x0501, 0x0415, 0x057F, 0x0431, 0x0E13,
+ 0x040D, 0x041D, 0x075D, 0x0C53, 0x0502, 0x04C1, 0x049D, 0x0426,
+ 0x040E, 0x05A1, 0x055F, 0x0781, 0x0591, 0x04A9, 0x048B, 0x0D8E,
+ 0x052E, 0x0412, 0x0521, 0x0405, 0x04AD, 0x074D, 0x0611, 0x077E,
+ 0x078F, 0x078D, 0x048D, 0x041E, 0x0487, 0x0461, 0x0C85, 0x05ED,
+ 0x0402, 0x0483, 0x0419, 0x0511, 0x0491, 0x0482, 0x059E, 0x068D,
+ 0x055D, 0x072E, 0x05DD, 0x054E, 0x0441, 0x0422, 0x052F, 0x057D,
+ 0x072D, 0x079D, 0x0CA1, 0x072F, 0x079E, 0x0581, 0x042D, 0x055E,
+ 0x0601, 0x0413, 0x0692, 0x0403, 0x051F, 0x053F, 0x054F, 0x05CD,
+ 0x070F, 0x071D, 0x05AE, 0x05BD, 0x0492, 0x056E, 0x0411, 0x0417,
+ 0x041B, 0x0421, 0x053E, 0x056F, 0x057E, 0x058F, 0x060E, 0x0622,
+ 0x0683, 0x0702, 0x070D, 0x070E, 0x071E, 0x073E, 0x076F, 0x078E,
+ 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_10x10 {
+ 10, 10,
+ { 453, 234 },
+ { 1095, 472 },
+ { 0, 70 },
+ { percentile_arr_10x10_0, percentile_arr_10x10_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (12 * 10)
+static const uint16_t percentile_arr_12x10_0[491] {
+ 0x0334, 0x9954, 0x8514, 0x7128, 0x6364, 0xC174, 0x5D34, 0x5866,
+ 0x5975, 0x5354, 0xAF14, 0x506A, 0x5108, 0x5724, 0x5308, 0x4544,
+ 0x4918, 0x4064, 0x49E2, 0x4179, 0x8163, 0x4054, 0xF81C, 0x394A,
+ 0x38F3, 0x4172, 0x38F5, 0xA06F, 0x68EA, 0x69F2, 0x3134, 0x31A4,
+ 0x305A, 0x68DA, 0x3056, 0x3146, 0x31F5, 0x3148, 0x5A61, 0x32C1,
+ 0x31D2, 0x307E, 0x29E3, 0x30E6, 0x59C3, 0x2984, 0x29B6, 0x28F9,
+ 0x5204, 0x28EE, 0x50CA, 0x2997, 0x48C6, 0x4838, 0x2953, 0x200C,
+ 0x2943, 0x2173, 0x2D08, 0x4162, 0x29B4, 0x2314, 0x21B3, 0x212B,
+ 0x210C, 0x48E3, 0x60DE, 0x205F, 0x20FE, 0x2028, 0x21A6, 0x404F,
+ 0x20D6, 0x2214, 0x2127, 0x1873, 0x40CF, 0x206E, 0x1B09, 0x21C6,
+ 0x2075, 0x19D5, 0x2305, 0x18D3, 0x2076, 0x1804, 0x230A, 0x304B,
+ 0x20BB, 0x18B6, 0x1936, 0x1B19, 0x3037, 0x187F, 0x18A7, 0x1B85,
+ 0x30BA, 0x183B, 0x1027, 0x18EF, 0x1B21, 0x1879, 0x10AB, 0x1917,
+ 0x1114, 0x18BF, 0x1074, 0x1994, 0x2847, 0x111B, 0x28F2, 0x11E5,
+ 0x19A7, 0x113A, 0x1046, 0x28B7, 0x207A, 0x182B, 0x1155, 0x104A,
+ 0x1344, 0x293B, 0x11D3, 0x2014, 0x1044, 0x1018, 0x13A1, 0x1315,
+ 0x2524, 0x20DF, 0x10E5, 0x1126, 0x12A2, 0x1824, 0x2271, 0x11F1,
+ 0x2964, 0x12D1, 0x115A, 0x092A, 0x2341, 0x1A2D, 0x12E1, 0x090A,
+ 0x13BF, 0x0A4D, 0x2119, 0x0BC1, 0x1233, 0x1A8A, 0x2008, 0x1159,
+ 0x1A89, 0x08D5, 0x1156, 0x0834, 0x13EE, 0x1169, 0x1187, 0x1AA3,
+ 0x1229, 0x1331, 0x0A85, 0x0937, 0x1704, 0x08FD, 0x2124, 0x0B13,
+ 0x1251, 0x0AAD, 0x082C, 0x091A, 0x18D9, 0x0A99, 0x1848, 0x18E9,
+ 0x0B95, 0x1144, 0x0AF1, 0x1A25, 0x131A, 0x09C5, 0x0986, 0x1BDF,
+ 0x0B24, 0x0965, 0x1262, 0x0949, 0x0872, 0x09C2, 0x12C2, 0x0916,
+ 0x085E, 0x0B06, 0x08CB, 0x08C7, 0x1242, 0x1BEF, 0x0A9A, 0x1152,
+ 0x08B3, 0x0AA9, 0x090B, 0x08D2, 0x1B22, 0x0B04, 0x0865, 0x0A15,
+ 0x1286, 0x0A83, 0x0A95, 0x09D1, 0x0A06, 0x0196, 0x1139, 0x0A3D,
+ 0x0933, 0x13B1, 0x0123, 0x0D04, 0x08E2, 0x122E, 0x08A6, 0x00CE,
+ 0x0A31, 0x1241, 0x0B51, 0x1057, 0x1171, 0x007D, 0x1145, 0x0A0A,
+ 0x0129, 0x09FF, 0x089B, 0x085B, 0x0063, 0x0AB1, 0x0A1F, 0x0A5D,
+ 0x0AA5, 0x0036, 0x0904, 0x0B86, 0x0A8B, 0x0897, 0x11E1, 0x0332,
+ 0x083F, 0x0A19, 0x02B3, 0x0859, 0x08C3, 0x0855, 0x11B5, 0x01A5,
+ 0x0AB2, 0x0392, 0x10DD, 0x09A3, 0x00ED, 0x0907, 0x1161, 0x002F,
+ 0x0887, 0x0216, 0x0ABD, 0x0B81, 0x0A93, 0x0A21, 0x003A, 0x0ACD,
+ 0x0AA1, 0x0A35, 0x0272, 0x0BDD, 0x03FE, 0x0BAF, 0x0869, 0x0213,
+ 0x088B, 0x020B, 0x00B5, 0x1035, 0x08F1, 0x0151, 0x0A4E, 0x0239,
+ 0x0BA2, 0x00AA, 0x0896, 0x0382, 0x0A08, 0x0A05, 0x0A09, 0x0142,
+ 0x086D, 0x004E, 0x0B23, 0x0106, 0x0807, 0x036F, 0x0995, 0x03FD,
+ 0x08AF, 0x08C5, 0x0062, 0x0053, 0x0B42, 0x0826, 0x021A, 0x01A2,
+ 0x09B1, 0x00C9, 0x09B2, 0x0045, 0x0207, 0x08B9, 0x00A5, 0x0AD2,
+ 0x0095, 0x003E, 0x0A32, 0x0383, 0x0849, 0x0135, 0x029E, 0x0A26,
+ 0x023E, 0x0BFF, 0x0A52, 0x0311, 0x001B, 0x0915, 0x0A8D, 0x0223,
+ 0x022A, 0x0BED, 0x0086, 0x0A96, 0x0222, 0x035F, 0x0A43, 0x085D,
+ 0x0303, 0x0393, 0x0A63, 0x082A, 0x037F, 0x0932, 0x0043, 0x0292,
+ 0x03CD, 0x0BDE, 0x009F, 0x0125, 0x08A9, 0x0253, 0x0015, 0x0192,
+ 0x0A17, 0x08C2, 0x0316, 0x00D1, 0x0282, 0x0871, 0x0312, 0x0122,
+ 0x0A9F, 0x02AE, 0x0006, 0x0A8E, 0x08E1, 0x0016, 0x0B0B, 0x00AE,
+ 0x0025, 0x0193, 0x0AC3, 0x0017, 0x0307, 0x00BD, 0x08BE, 0x0039,
+ 0x0BB2, 0x021B, 0x01FD, 0x084D, 0x03CE, 0x00A3, 0x0302, 0x0BCF,
+ 0x0033, 0x0391, 0x028F, 0x0852, 0x0287, 0x008A, 0x0333, 0x080B,
+ 0x0131, 0x01C1, 0x037E, 0x0A0F, 0x00B1, 0x002E, 0x0099, 0x0902,
+ 0x009A, 0x003D, 0x0982, 0x0301, 0x00CD, 0x0941, 0x0042, 0x0183,
+ 0x029D, 0x08A2, 0x021D, 0x001A, 0x0A97, 0x01EF, 0x01CE, 0x0051,
+ 0x0BAE, 0x022F, 0x03BE, 0x021E, 0x000A, 0x09DF, 0x0029, 0x020D,
+ 0x02BE, 0x029B, 0x09EE, 0x00B2, 0x0912, 0x036E, 0x009E, 0x0022,
+ 0x0019, 0x0892, 0x0032, 0x01FE, 0x0083, 0x023F, 0x0B96, 0x000E,
+ 0x008F, 0x0113, 0x0103, 0x001E, 0x0A0E, 0x0013, 0x008E, 0x0281,
+ 0x09AF, 0x017E, 0x0203, 0x016F, 0x0291, 0x0023, 0x0093, 0x03BD,
+ 0x001F, 0x01CF, 0x01DE, 0x0201, 0x01BF, 0x0B4F, 0x000F, 0x0202,
+ 0x037D, 0x038E, 0x0211, 0x0212, 0x034E, 0x039F, 0x03AD, 0x015F,
+ 0x017F, 0x032E, 0x033F, 0x034D, 0x035E, 0x036D, 0x032F, 0x033E,
+ 0x035D, 0x038F, 0x039E
+};
+
+static const uint16_t percentile_arr_12x10_1[240] {
+ 0x0621, 0xA443, 0xFCC2, 0x3CA3, 0x1D32, 0x14F1, 0x7462, 0x1433,
+ 0x27CD, 0x2571, 0x57AE, 0x5DD1, 0x64B3, 0x44D2, 0x2C72, 0x25A2,
+ 0x1E31, 0x55DF, 0x4C52, 0x1DEF, 0x0D51, 0x3C5D, 0x3C3E, 0x74DD,
+ 0x347D, 0x27BE, 0x5CB5, 0x17DD, 0x2C14, 0x0CAE, 0x24B2, 0x15FF,
+ 0x2701, 0x0D42, 0x1FBD, 0x0C35, 0x1603, 0x060D, 0x1D93, 0x0C96,
+ 0x1C07, 0x1522, 0x0D06, 0x0F4F, 0x0C9F, 0x1F6E, 0x0D86, 0x0C2E,
+ 0x1DFE, 0x0682, 0x1E91, 0x0F7D, 0x0C86, 0x040B, 0x1513, 0x044E,
+ 0x14D1, 0x0C39, 0x14B9, 0x1C71, 0x05B1, 0x0C1F, 0x0681, 0x1445,
+ 0x0C16, 0x0D95, 0x1583, 0x0D61, 0x0FAD, 0x1442, 0x048F, 0x0D0A,
+ 0x049A, 0x0F6D, 0x146D, 0x0C2F, 0x0D25, 0x0406, 0x0C1A, 0x0D23,
+ 0x0612, 0x0FAF, 0x0F11, 0x0592, 0x0515, 0x14E1, 0x0602, 0x048A,
+ 0x0E1D, 0x0CBD, 0x0F9F, 0x0423, 0x075E, 0x174E, 0x0426, 0x0404,
+ 0x0C22, 0x0CA2, 0x0DEE, 0x0CA5, 0x0F3F, 0x05C1, 0x0CCD, 0x0503,
+ 0x044D, 0x0D16, 0x0449, 0x0D82, 0x0613, 0x0585, 0x0519, 0x0C95,
+ 0x075F, 0x0D35, 0x04B1, 0x0509, 0x0531, 0x0DA1, 0x049E, 0x040A,
+ 0x05CF, 0x0D41, 0x0415, 0x0692, 0x05FD, 0x0C25, 0x04A1, 0x0529,
+ 0x0591, 0x0C93, 0x057F, 0x04C1, 0x0512, 0x051A, 0x078D, 0x0451,
+ 0x0C0F, 0x0487, 0x0611, 0x0432, 0x042A, 0x05AF, 0x0461, 0x072D,
+ 0x0409, 0x0405, 0x0D39, 0x05DE, 0x048E, 0x0499, 0x0483, 0x04A9,
+ 0x0491, 0x042D, 0x049D, 0x0429, 0x040E, 0x05AE, 0x0521, 0x043D,
+ 0x0581, 0x05DD, 0x0492, 0x0CAD, 0x041E, 0x058F, 0x071F, 0x072F,
+ 0x0419, 0x073D, 0x057D, 0x0511, 0x05CE, 0x041D, 0x0485, 0x056E,
+ 0x0412, 0x0431, 0x05BF, 0x0441, 0x054E, 0x0489, 0x0421, 0x0502,
+ 0x0408, 0x040D, 0x051F, 0x059F, 0x073E, 0x078F, 0x0482, 0x079D,
+ 0x0C02, 0x05BE, 0x048B, 0x0411, 0x0505, 0x057E, 0x052E, 0x074D,
+ 0x077E, 0x054F, 0x0601, 0x055F, 0x068D, 0x070D, 0x070F, 0x071E,
+ 0x072E, 0x05CD, 0x0403, 0x0501, 0x055D, 0x059E, 0x0781, 0x0413,
+ 0x0417, 0x041B, 0x0453, 0x048D, 0x052F, 0x053E, 0x053F, 0x055E,
+ 0x056F, 0x058E, 0x05BD, 0x05ED, 0x060E, 0x0622, 0x0683, 0x0702,
+ 0x070E, 0x071D, 0x075D, 0x076F, 0x078E, 0x079E, 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_12x10 =
+{
+ 12, 10,
+ { 491, 240 },
+ { 1099, 341 },
+ { 0, 23 },
+ { percentile_arr_12x10_0, percentile_arr_12x10_1 }
+};
+#endif
+
+#if ASTCENC_BLOCK_MAX_TEXELS >= (12 * 12)
+static const uint16_t percentile_arr_12x12_0[529] {
+ 0x0334, 0xF534, 0x8514, 0x8954, 0x7F14, 0xFB54, 0x7B08, 0x7128,
+ 0x7974, 0x6179, 0x6B64, 0x6908, 0x606A, 0x6724, 0xB544, 0xB066,
+ 0xA14A, 0x5118, 0x9975, 0x51F9, 0x981C, 0x49CA, 0x4854, 0x886F,
+ 0x88D4, 0x48EE, 0x41E2, 0x4163, 0x40F3, 0x4261, 0x4064, 0x407E,
+ 0x385A, 0x42C1, 0x4172, 0x38EA, 0x3946, 0x78CF, 0xA056, 0x38DE,
+ 0x3D08, 0x38F9, 0x3B14, 0x38FE, 0xA134, 0x38B8, 0x31A4, 0x71D2,
+ 0x60DA, 0x39C3, 0x99BA, 0x60CA, 0x39F2, 0x30F5, 0x304F, 0x31B6,
+ 0x31F5, 0x3204, 0x3148, 0x305F, 0x2953, 0x3194, 0x3184, 0x310C,
+ 0x889C, 0x300C, 0x2943, 0x30EF, 0x28C6, 0x2997, 0x2838, 0x58E6,
+ 0x20E4, 0x28E3, 0x2873, 0x29E3, 0x2A84, 0x28D3, 0x492B, 0x2962,
+ 0x286E, 0x20BF, 0x21AA, 0x29A6, 0x6A14, 0x2828, 0x89C6, 0x21B3,
+ 0x2305, 0x29B4, 0x2173, 0x2127, 0x20D6, 0x407F, 0x2294, 0x21D9,
+ 0x21D5, 0x2004, 0x404B, 0x18DF, 0x2079, 0x219B, 0x18A8, 0x2385,
+ 0x1936, 0x21AB, 0x188C, 0x1B09, 0x18BA, 0x203B, 0x187A, 0x1875,
+ 0x2344, 0x18BB, 0x18B6, 0x193A, 0x1837, 0x1914, 0x1846, 0x1876,
+ 0x1884, 0x1D24, 0x182B, 0x284A, 0x18A7, 0x18AB, 0x1917, 0x322D,
+ 0x1047, 0x1874, 0x1818, 0x18F2, 0x1164, 0x1B89, 0x2959, 0x1B21,
+ 0x39E5, 0x1827, 0x10F4, 0x18B7, 0x11D3, 0x1A4D, 0x1315, 0x12AD,
+ 0x1AD1, 0x3A71, 0x1319, 0x11A7, 0x2044, 0x2F04, 0x2341, 0x10E5,
+ 0x1155, 0x195A, 0x1024, 0x111B, 0x1251, 0x1233, 0x12E1, 0x13A1,
+ 0x13BF, 0x212A, 0x22A2, 0x113B, 0x23DF, 0x10D5, 0x2399, 0x0814,
+ 0x1126, 0x13EE, 0x1285, 0x10C4, 0x18FD, 0x20D9, 0x0987, 0x1242,
+ 0x29C5, 0x2313, 0x0898, 0x13C1, 0x08C8, 0x11F1, 0x1034, 0x1B24,
+ 0x0B0A, 0x11E9, 0x0808, 0x125D, 0x18E9, 0x0848, 0x1395, 0x0965,
+ 0x123D, 0x2186, 0x1295, 0x18CE, 0x098B, 0x0BEF, 0x1504, 0x082C,
+ 0x0A41, 0x1144, 0x0A89, 0x0956, 0x1331, 0x085E, 0x0B04, 0x128A,
+ 0x12A3, 0x1937, 0x19C2, 0x0952, 0x0872, 0x08B4, 0x1262, 0x1124,
+ 0x1969, 0x1063, 0x0AF1, 0x1225, 0x0894, 0x11C9, 0x18D2, 0x0ACD,
+ 0x0A29, 0x0B06, 0x09B5, 0x18C7, 0x0916, 0x1088, 0x09FF, 0x2206,
+ 0x0A15, 0x08B3, 0x0B51, 0x0A1F, 0x18CB, 0x0AC2, 0x0A2E, 0x1865,
+ 0x08AC, 0x0A31, 0x08A4, 0x138A, 0x0A99, 0x09D1, 0x0A86, 0x189B,
+ 0x0283, 0x0BDD, 0x0ABD, 0x1933, 0x083F, 0x1386, 0x0923, 0x0322,
+ 0x0869, 0x10DD, 0x13B1, 0x082F, 0x087D, 0x11B9, 0x085B, 0x08ED,
+ 0x00C3, 0x08E2, 0x084E, 0x0887, 0x0855, 0x0A0A, 0x0857, 0x0B92,
+ 0x1036, 0x12A5, 0x0293, 0x0945, 0x08A6, 0x0196, 0x19A3, 0x036F,
+ 0x0904, 0x1205, 0x09E1, 0x0381, 0x0971, 0x1219, 0x0BAF, 0x0949,
+ 0x00AF, 0x0AA9, 0x018A, 0x0907, 0x0BFD, 0x003A, 0x0BCD, 0x0AB2,
+ 0x088B, 0x0252, 0x0A4E, 0x03FF, 0x0845, 0x0897, 0x0059, 0x090B,
+ 0x0B42, 0x0807, 0x0A16, 0x0853, 0x0A8D, 0x01B2, 0x0AB1, 0x091A,
+ 0x0195, 0x0A35, 0x00B5, 0x10AA, 0x0115, 0x0A21, 0x0096, 0x0A08,
+ 0x03FE, 0x0B7F, 0x08B9, 0x12B3, 0x023E, 0x0A23, 0x029E, 0x08F1,
+ 0x01A9, 0x0BDE, 0x0843, 0x02D2, 0x0A1A, 0x08C5, 0x0151, 0x0A43,
+ 0x0332, 0x0383, 0x0826, 0x0BED, 0x10C2, 0x00AE, 0x0B82, 0x0213,
+ 0x0232, 0x085D, 0x02A1, 0x101B, 0x035F, 0x0303, 0x0A39, 0x0207,
+ 0x0A53, 0x0142, 0x01A5, 0x082A, 0x0099, 0x0A17, 0x03CF, 0x0906,
+ 0x0125, 0x0A96, 0x0A9A, 0x0209, 0x0393, 0x0961, 0x0131, 0x0A88,
+ 0x0139, 0x099A, 0x0292, 0x0272, 0x0862, 0x08BE, 0x0141, 0x02C3,
+ 0x0886, 0x0039, 0x08A9, 0x01A2, 0x01B1, 0x0851, 0x020B, 0x086D,
+ 0x0312, 0x08CD, 0x020F, 0x0311, 0x0BCE, 0x0135, 0x0006, 0x0849,
+ 0x0132, 0x0A8F, 0x022F, 0x022A, 0x0AAE, 0x0A8E, 0x0263, 0x03A2,
+ 0x083E, 0x009A, 0x021B, 0x0835, 0x0323, 0x0871, 0x0993, 0x0226,
+ 0x0302, 0x0922, 0x0119, 0x0222, 0x021D, 0x0B07, 0x08C9, 0x037E,
+ 0x08BD, 0x0042, 0x00D1, 0x0B33, 0x01C1, 0x0B9A, 0x0282, 0x088A,
+ 0x0182, 0x083D, 0x004D, 0x010A, 0x0A1E, 0x0019, 0x00B2, 0x0999,
+ 0x00A5, 0x0095, 0x0817, 0x0022, 0x031A, 0x0902, 0x00A3, 0x01BF,
+ 0x029F, 0x0816, 0x03B2, 0x0015, 0x0391, 0x0BBE, 0x01FE, 0x1129,
+ 0x002E, 0x01DF, 0x0301, 0x0033, 0x0B6E, 0x00E1, 0x0297, 0x00B1,
+ 0x009F, 0x0B16, 0x000A, 0x001A, 0x0052, 0x080B, 0x030B, 0x029D,
+ 0x0BAE, 0x01FD, 0x020E, 0x00A2, 0x0A3F, 0x0192, 0x0ABE, 0x020D,
+ 0x008F, 0x028B, 0x0083, 0x0025, 0x09EE, 0x01EF, 0x0029, 0x0291,
+ 0x0B4F, 0x0396, 0x0287, 0x008E, 0x0092, 0x0B4E, 0x017E, 0x001E,
+ 0x009E, 0x0103, 0x080F, 0x000E, 0x0113, 0x0203, 0x01CF, 0x0183,
+ 0x01CE, 0x001F, 0x0112, 0x01DE, 0x038E, 0x0832, 0x033E, 0x0212,
+ 0x029B, 0x0023, 0x016F, 0x0201, 0x09AF, 0x0202, 0x0281, 0x035E,
+ 0x034D, 0x037D, 0x03AD, 0x0013, 0x0093, 0x015F, 0x0211, 0x033F,
+ 0x036D, 0x039F, 0x03BD, 0x017F, 0x032E, 0x032F, 0x035D, 0x038F,
+ 0x039E
+};
+
+static const uint16_t percentile_arr_12x12_1[246] {
+ 0x0443, 0xFFCD, 0x2C62, 0x2E21, 0x3CF1, 0x34C2, 0x4CDD, 0x2452,
+ 0xD5DF, 0x1DD1, 0x0FAE, 0x64A3, 0x0C7D, 0x3433, 0x1CD2, 0x2DEF,
+ 0x0C3E, 0x1D71, 0xA472, 0x0D32, 0x54B3, 0x4D51, 0x445D, 0x0E31,
+ 0x1FDD, 0x0DFF, 0x0CAE, 0x45A2, 0x2FBE, 0xA4B9, 0x1C4E, 0x2C9F,
+ 0x160D, 0x0D42, 0x342E, 0x074F, 0x1414, 0x0F6E, 0x0CB2, 0x34B5,
+ 0x0DFE, 0x0D86, 0x1496, 0x1D22, 0x0691, 0x140B, 0x041F, 0x0C35,
+ 0x1D93, 0x1506, 0x1439, 0x0C9A, 0x0F01, 0x2442, 0x0C8F, 0x04D1,
+ 0x1486, 0x0C6D, 0x0513, 0x0C71, 0x0E82, 0x177D, 0x0E03, 0x07BD,
+ 0x0C2F, 0x0D83, 0x07AF, 0x0D61, 0x1407, 0x0DB1, 0x050A, 0x0C94,
+ 0x07AD, 0x0D8A, 0x0C04, 0x0416, 0x0C49, 0x0445, 0x15C1, 0x0C1A,
+ 0x0525, 0x0595, 0x0C8A, 0x075E, 0x0CBD, 0x0681, 0x0F4E, 0x075F,
+ 0x061D, 0x1541, 0x0CB1, 0x0F3F, 0x0406, 0x076D, 0x0DCF, 0x05EE,
+ 0x0D23, 0x0599, 0x0CCD, 0x0711, 0x0C23, 0x079F, 0x0D15, 0x0585,
+ 0x04A2, 0x042A, 0x0D31, 0x05BF, 0x0D92, 0x0C26, 0x043D, 0x0C93,
+ 0x0502, 0x0C15, 0x048B, 0x0D03, 0x0613, 0x0516, 0x0495, 0x0C29,
+ 0x04A5, 0x040F, 0x0425, 0x0539, 0x0D19, 0x04E1, 0x05BE, 0x0422,
+ 0x0432, 0x0C0A, 0x0431, 0x041E, 0x0492, 0x04A9, 0x0582, 0x0529,
+ 0x0487, 0x0C4D, 0x0512, 0x049E, 0x0505, 0x0451, 0x0D7F, 0x0489,
+ 0x0602, 0x05DE, 0x0591, 0x0535, 0x074D, 0x055E, 0x04C1, 0x0612,
+ 0x05DD, 0x05FD, 0x0C61, 0x0521, 0x0484, 0x05CE, 0x0581, 0x0491,
+ 0x051A, 0x04A1, 0x048E, 0x040D, 0x0499, 0x071F, 0x072E, 0x075D,
+ 0x0441, 0x0589, 0x057E, 0x0CAD, 0x0501, 0x054F, 0x0692, 0x0511,
+ 0x049D, 0x0509, 0x056E, 0x040E, 0x0409, 0x0601, 0x048D, 0x0413,
+ 0x053E, 0x0419, 0x072D, 0x0408, 0x0485, 0x042D, 0x041D, 0x05A1,
+ 0x0781, 0x0402, 0x05ED, 0x0C82, 0x0403, 0x057D, 0x05CD, 0x0611,
+ 0x0488, 0x0411, 0x054E, 0x051F, 0x053F, 0x056F, 0x059F, 0x070F,
+ 0x071D, 0x073D, 0x073E, 0x077E, 0x078F, 0x0405, 0x079D, 0x079E,
+ 0x058E, 0x0412, 0x055D, 0x05AE, 0x041B, 0x0421, 0x0453, 0x0417,
+ 0x0483, 0x052E, 0x052F, 0x055F, 0x058F, 0x059E, 0x05AF, 0x05BD,
+ 0x060E, 0x0622, 0x0683, 0x068D, 0x0702, 0x070D, 0x070E, 0x071E,
+ 0x072F, 0x076F, 0x078D, 0x078E, 0x07BF, 0x07CE
+};
+
+static const packed_percentile_table block_pcd_12x12 {
+ 12, 12,
+ { 529, 246 },
+ { 1435, 335 },
+ { 0, 22 },
+ { percentile_arr_12x12_0, percentile_arr_12x12_1 }
+};
+#endif
+
+/**
+ * @brief Fetch the packed percentile table for the given 2D block size.
+ *
+ * @param xdim The block x size.
+ * @param ydim The block y size.
+ *
+ * @return The packed table.
+ */
+static const packed_percentile_table *get_packed_table(
+ int xdim,
+ int ydim
+) {
+ int idx = (ydim << 8) | xdim;
+ switch (idx)
+ {
+#if ASTCENC_BLOCK_MAX_TEXELS >= (4 * 4)
+ case 0x0404: return &block_pcd_4x4;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (5 * 4)
+ case 0x0405: return &block_pcd_5x4;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (5 * 5)
+ case 0x0505: return &block_pcd_5x5;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (6 * 5)
+ case 0x0506: return &block_pcd_6x5;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (6 * 6)
+ case 0x0606: return &block_pcd_6x6;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (8 * 5)
+ case 0x0508: return &block_pcd_8x5;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (8 * 6)
+ case 0x0608: return &block_pcd_8x6;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (8 * 8)
+ case 0x0808: return &block_pcd_8x8;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (10 * 5)
+ case 0x050A: return &block_pcd_10x5;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (10 * 6)
+ case 0x060A: return &block_pcd_10x6;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (10 * 8)
+ case 0x080A: return &block_pcd_10x8;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (10 * 10)
+ case 0x0A0A: return &block_pcd_10x10;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (12 * 10)
+ case 0x0A0C: return &block_pcd_12x10;
+#endif
+#if ASTCENC_BLOCK_MAX_TEXELS >= (12 * 12)
+ case 0x0C0C: return &block_pcd_12x12;
+#endif
+ }
+
+ // Should never hit this with a valid 2D block size
+ return nullptr;
+}
+
+/* See header for documentation. */
+const float *get_2d_percentile_table(
+ unsigned int xdim,
+ unsigned int ydim
+) {
+ float* unpacked_table = new float[WEIGHTS_MAX_BLOCK_MODES];
+ const packed_percentile_table *apt = get_packed_table(xdim, ydim);
+
+ // Set the default percentile
+ for (unsigned int i = 0; i < WEIGHTS_MAX_BLOCK_MODES; i++)
+ {
+ unpacked_table[i] = 1.0f;
+ }
+
+ // Populate the unpacked percentile values
+ for (int i = 0; i < 2; i++)
+ {
+ unsigned int itemcount = apt->item_count[i];
+ unsigned int difscale = apt->difscales[i];
+ unsigned int accum = apt->initial_percs[i];
+ const uint16_t *item_ptr = apt->items[i];
+
+ for (unsigned int j = 0; j < itemcount; j++)
+ {
+ uint16_t item = item_ptr[j];
+ unsigned int idx = item & 0x7FF;
+ unsigned int weight = (item >> 11) & 0x1F;
+ accum += weight;
+ unpacked_table[idx] = static_cast<float>(accum) / static_cast<float>(difscale);
+ }
+ }
+
+ return unpacked_table;
+}
+#endif
+
+/* See header for documentation. */
+bool is_legal_2d_block_size(
+ unsigned int xdim,
+ unsigned int ydim
+) {
+ unsigned int idx = (xdim << 8) | ydim;
+ switch (idx)
+ {
+ case 0x0404:
+ case 0x0504:
+ case 0x0505:
+ case 0x0605:
+ case 0x0606:
+ case 0x0805:
+ case 0x0806:
+ case 0x0808:
+ case 0x0A05:
+ case 0x0A06:
+ case 0x0A08:
+ case 0x0A0A:
+ case 0x0C0A:
+ case 0x0C0C:
+ return true;
+ }
+
+ return false;
+}
+
+/* See header for documentation. */
+bool is_legal_3d_block_size(
+ unsigned int xdim,
+ unsigned int ydim,
+ unsigned int zdim
+) {
+ unsigned int idx = (xdim << 16) | (ydim << 8) | zdim;
+ switch (idx)
+ {
+ case 0x030303:
+ case 0x040303:
+ case 0x040403:
+ case 0x040404:
+ case 0x050404:
+ case 0x050504:
+ case 0x050505:
+ case 0x060505:
+ case 0x060605:
+ case 0x060606:
+ return true;
+ }
+
+ return false;
+}
diff --git a/thirdparty/astcenc/astcenc_pick_best_endpoint_format.cpp b/thirdparty/astcenc/astcenc_pick_best_endpoint_format.cpp
new file mode 100644
index 0000000000..f25140d4c7
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_pick_best_endpoint_format.cpp
@@ -0,0 +1,1350 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+
+/**
+ * @brief Functions for finding best endpoint format.
+ *
+ * We assume there are two independent sources of error in any given partition:
+ *
+ * - Encoding choice errors
+ * - Quantization errors
+ *
+ * Encoding choice errors are caused by encoder decisions. For example:
+ *
+ * - Using luminance instead of separate RGB components.
+ * - Using a constant 1.0 alpha instead of storing an alpha component.
+ * - Using RGB+scale instead of storing two full RGB endpoints.
+ *
+ * Quantization errors occur due to the limited precision we use for storage. These errors generally
+ * scale with quantization level, but are not actually independent of color encoding. In particular:
+ *
+ * - If we can use offset encoding then quantization error is halved.
+ * - If we can use blue-contraction then quantization error for RG is halved.
+ * - If we use HDR endpoints the quantization error is higher.
+ *
+ * Apart from these effects, we assume the error is proportional to the quantization step size.
+ */
+
+
+#include "astcenc_internal.h"
+#include "astcenc_vecmathlib.h"
+
+#include <assert.h>
+
+/**
+ * @brief Compute the errors of the endpoint line options for one partition.
+ *
+ * Uncorrelated data assumes storing completely independent RGBA channels for each endpoint. Same
+ * chroma data assumes storing RGBA endpoints which pass though the origin (LDR only). RGBL data
+ * assumes storing RGB + lumashift (HDR only). Luminance error assumes storing RGB channels as a
+ * single value.
+ *
+ *
+ * @param pi The partition info data.
+ * @param partition_index The partition index to compule the error for.
+ * @param blk The image block.
+ * @param uncor_pline The endpoint line assuming uncorrelated endpoints.
+ * @param[out] uncor_err The computed error for the uncorrelated endpoint line.
+ * @param samec_pline The endpoint line assuming the same chroma for both endpoints.
+ * @param[out] samec_err The computed error for the uncorrelated endpoint line.
+ * @param rgbl_pline The endpoint line assuming RGB + lumashift data.
+ * @param[out] rgbl_err The computed error for the RGB + lumashift endpoint line.
+ * @param l_pline The endpoint line assuming luminance data.
+ * @param[out] l_err The computed error for the luminance endpoint line.
+ * @param[out] a_drop_err The computed error for dropping the alpha component.
+ */
+static void compute_error_squared_rgb_single_partition(
+ const partition_info& pi,
+ int partition_index,
+ const image_block& blk,
+ const processed_line3& uncor_pline,
+ float& uncor_err,
+ const processed_line3& samec_pline,
+ float& samec_err,
+ const processed_line3& rgbl_pline,
+ float& rgbl_err,
+ const processed_line3& l_pline,
+ float& l_err,
+ float& a_drop_err
+) {
+ vfloat4 ews = blk.channel_weight;
+
+ unsigned int texel_count = pi.partition_texel_count[partition_index];
+ const uint8_t* texel_indexes = pi.texels_of_partition[partition_index];
+ promise(texel_count > 0);
+
+ vfloatacc a_drop_errv = vfloatacc::zero();
+ vfloat default_a(blk.get_default_alpha());
+
+ vfloatacc uncor_errv = vfloatacc::zero();
+ vfloat uncor_bs0(uncor_pline.bs.lane<0>());
+ vfloat uncor_bs1(uncor_pline.bs.lane<1>());
+ vfloat uncor_bs2(uncor_pline.bs.lane<2>());
+
+ vfloat uncor_amod0(uncor_pline.amod.lane<0>());
+ vfloat uncor_amod1(uncor_pline.amod.lane<1>());
+ vfloat uncor_amod2(uncor_pline.amod.lane<2>());
+
+ vfloatacc samec_errv = vfloatacc::zero();
+ vfloat samec_bs0(samec_pline.bs.lane<0>());
+ vfloat samec_bs1(samec_pline.bs.lane<1>());
+ vfloat samec_bs2(samec_pline.bs.lane<2>());
+
+ vfloatacc rgbl_errv = vfloatacc::zero();
+ vfloat rgbl_bs0(rgbl_pline.bs.lane<0>());
+ vfloat rgbl_bs1(rgbl_pline.bs.lane<1>());
+ vfloat rgbl_bs2(rgbl_pline.bs.lane<2>());
+
+ vfloat rgbl_amod0(rgbl_pline.amod.lane<0>());
+ vfloat rgbl_amod1(rgbl_pline.amod.lane<1>());
+ vfloat rgbl_amod2(rgbl_pline.amod.lane<2>());
+
+ vfloatacc l_errv = vfloatacc::zero();
+ vfloat l_bs0(l_pline.bs.lane<0>());
+ vfloat l_bs1(l_pline.bs.lane<1>());
+ vfloat l_bs2(l_pline.bs.lane<2>());
+
+ vint lane_ids = vint::lane_id();
+ for (unsigned int i = 0; i < texel_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ vint tix(texel_indexes + i);
+
+ vmask mask = lane_ids < vint(texel_count);
+ lane_ids += vint(ASTCENC_SIMD_WIDTH);
+
+ // Compute the error that arises from just ditching alpha
+ vfloat data_a = gatherf(blk.data_a, tix);
+ vfloat alpha_diff = data_a - default_a;
+ alpha_diff = alpha_diff * alpha_diff;
+
+ haccumulate(a_drop_errv, alpha_diff, mask);
+
+ vfloat data_r = gatherf(blk.data_r, tix);
+ vfloat data_g = gatherf(blk.data_g, tix);
+ vfloat data_b = gatherf(blk.data_b, tix);
+
+ // Compute uncorrelated error
+ vfloat param = data_r * uncor_bs0
+ + data_g * uncor_bs1
+ + data_b * uncor_bs2;
+
+ vfloat dist0 = (uncor_amod0 + param * uncor_bs0) - data_r;
+ vfloat dist1 = (uncor_amod1 + param * uncor_bs1) - data_g;
+ vfloat dist2 = (uncor_amod2 + param * uncor_bs2) - data_b;
+
+ vfloat error = dist0 * dist0 * ews.lane<0>()
+ + dist1 * dist1 * ews.lane<1>()
+ + dist2 * dist2 * ews.lane<2>();
+
+ haccumulate(uncor_errv, error, mask);
+
+ // Compute same chroma error - no "amod", its always zero
+ param = data_r * samec_bs0
+ + data_g * samec_bs1
+ + data_b * samec_bs2;
+
+ dist0 = (param * samec_bs0) - data_r;
+ dist1 = (param * samec_bs1) - data_g;
+ dist2 = (param * samec_bs2) - data_b;
+
+ error = dist0 * dist0 * ews.lane<0>()
+ + dist1 * dist1 * ews.lane<1>()
+ + dist2 * dist2 * ews.lane<2>();
+
+ haccumulate(samec_errv, error, mask);
+
+ // Compute rgbl error
+ param = data_r * rgbl_bs0
+ + data_g * rgbl_bs1
+ + data_b * rgbl_bs2;
+
+ dist0 = (rgbl_amod0 + param * rgbl_bs0) - data_r;
+ dist1 = (rgbl_amod1 + param * rgbl_bs1) - data_g;
+ dist2 = (rgbl_amod2 + param * rgbl_bs2) - data_b;
+
+ error = dist0 * dist0 * ews.lane<0>()
+ + dist1 * dist1 * ews.lane<1>()
+ + dist2 * dist2 * ews.lane<2>();
+
+ haccumulate(rgbl_errv, error, mask);
+
+ // Compute luma error - no "amod", its always zero
+ param = data_r * l_bs0
+ + data_g * l_bs1
+ + data_b * l_bs2;
+
+ dist0 = (param * l_bs0) - data_r;
+ dist1 = (param * l_bs1) - data_g;
+ dist2 = (param * l_bs2) - data_b;
+
+ error = dist0 * dist0 * ews.lane<0>()
+ + dist1 * dist1 * ews.lane<1>()
+ + dist2 * dist2 * ews.lane<2>();
+
+ haccumulate(l_errv, error, mask);
+ }
+
+ a_drop_err = hadd_s(a_drop_errv) * ews.lane<3>();
+ uncor_err = hadd_s(uncor_errv);
+ samec_err = hadd_s(samec_errv);
+ rgbl_err = hadd_s(rgbl_errv);
+ l_err = hadd_s(l_errv);
+}
+
+/**
+ * @brief For a given set of input colors and partitioning determine endpoint encode errors.
+ *
+ * This function determines the color error that results from RGB-scale encoding (LDR only),
+ * RGB-lumashift encoding (HDR only), luminance-encoding, and alpha drop. Also determines whether
+ * the endpoints are eligible for offset encoding or blue-contraction
+ *
+ * @param blk The image block.
+ * @param pi The partition info data.
+ * @param ep The idealized endpoints.
+ * @param[out] eci The resulting encoding choice error metrics.
+ */
+static void compute_encoding_choice_errors(
+ const image_block& blk,
+ const partition_info& pi,
+ const endpoints& ep,
+ encoding_choice_errors eci[BLOCK_MAX_PARTITIONS])
+{
+ int partition_count = pi.partition_count;
+ promise(partition_count > 0);
+
+ partition_metrics pms[BLOCK_MAX_PARTITIONS];
+
+ compute_avgs_and_dirs_3_comp_rgb(pi, blk, pms);
+
+ for (int i = 0; i < partition_count; i++)
+ {
+ partition_metrics& pm = pms[i];
+
+ line3 uncor_rgb_lines;
+ line3 samec_rgb_lines; // for LDR-RGB-scale
+ line3 rgb_luma_lines; // for HDR-RGB-scale
+
+ processed_line3 uncor_rgb_plines;
+ processed_line3 samec_rgb_plines;
+ processed_line3 rgb_luma_plines;
+ processed_line3 luminance_plines;
+
+ float uncorr_rgb_error;
+ float samechroma_rgb_error;
+ float rgb_luma_error;
+ float luminance_rgb_error;
+ float alpha_drop_error;
+
+ uncor_rgb_lines.a = pm.avg;
+ uncor_rgb_lines.b = normalize_safe(pm.dir, unit3());
+
+ samec_rgb_lines.a = vfloat4::zero();
+ samec_rgb_lines.b = normalize_safe(pm.avg, unit3());
+
+ rgb_luma_lines.a = pm.avg;
+ rgb_luma_lines.b = unit3();
+
+ uncor_rgb_plines.amod = uncor_rgb_lines.a - uncor_rgb_lines.b * dot3(uncor_rgb_lines.a, uncor_rgb_lines.b);
+ uncor_rgb_plines.bs = uncor_rgb_lines.b;
+
+ // Same chroma always goes though zero, so this is simpler than the others
+ samec_rgb_plines.amod = vfloat4::zero();
+ samec_rgb_plines.bs = samec_rgb_lines.b;
+
+ rgb_luma_plines.amod = rgb_luma_lines.a - rgb_luma_lines.b * dot3(rgb_luma_lines.a, rgb_luma_lines.b);
+ rgb_luma_plines.bs = rgb_luma_lines.b;
+
+ // Luminance always goes though zero, so this is simpler than the others
+ luminance_plines.amod = vfloat4::zero();
+ luminance_plines.bs = unit3();
+
+ compute_error_squared_rgb_single_partition(
+ pi, i, blk,
+ uncor_rgb_plines, uncorr_rgb_error,
+ samec_rgb_plines, samechroma_rgb_error,
+ rgb_luma_plines, rgb_luma_error,
+ luminance_plines, luminance_rgb_error,
+ alpha_drop_error);
+
+ // Determine if we can offset encode RGB lanes
+ vfloat4 endpt0 = ep.endpt0[i];
+ vfloat4 endpt1 = ep.endpt1[i];
+ vfloat4 endpt_diff = abs(endpt1 - endpt0);
+ vmask4 endpt_can_offset = endpt_diff < vfloat4(0.12f * 65535.0f);
+ bool can_offset_encode = (mask(endpt_can_offset) & 0x7) == 0x7;
+
+ // Store out the settings
+ eci[i].rgb_scale_error = (samechroma_rgb_error - uncorr_rgb_error) * 0.7f; // empirical
+ eci[i].rgb_luma_error = (rgb_luma_error - uncorr_rgb_error) * 1.5f; // wild guess
+ eci[i].luminance_error = (luminance_rgb_error - uncorr_rgb_error) * 3.0f; // empirical
+ eci[i].alpha_drop_error = alpha_drop_error * 3.0f;
+ eci[i].can_offset_encode = can_offset_encode;
+ eci[i].can_blue_contract = !blk.is_luminance();
+ }
+}
+
+/**
+ * @brief For a given partition compute the error for every endpoint integer count and quant level.
+ *
+ * @param encode_hdr_rgb @c true if using HDR for RGB, @c false for LDR.
+ * @param encode_hdr_alpha @c true if using HDR for alpha, @c false for LDR.
+ * @param partition_index The partition index.
+ * @param pi The partition info.
+ * @param eci The encoding choice error metrics.
+ * @param ep The idealized endpoints.
+ * @param error_weight The resulting encoding choice error metrics.
+ * @param[out] best_error The best error for each integer count and quant level.
+ * @param[out] format_of_choice The preferred endpoint format for each integer count and quant level.
+ */
+static void compute_color_error_for_every_integer_count_and_quant_level(
+ bool encode_hdr_rgb,
+ bool encode_hdr_alpha,
+ int partition_index,
+ const partition_info& pi,
+ const encoding_choice_errors& eci,
+ const endpoints& ep,
+ vfloat4 error_weight,
+ float best_error[21][4],
+ uint8_t format_of_choice[21][4]
+) {
+ int partition_size = pi.partition_texel_count[partition_index];
+
+ static const float baseline_quant_error[21 - QUANT_6] {
+ (65536.0f * 65536.0f / 18.0f) / (5 * 5),
+ (65536.0f * 65536.0f / 18.0f) / (7 * 7),
+ (65536.0f * 65536.0f / 18.0f) / (9 * 9),
+ (65536.0f * 65536.0f / 18.0f) / (11 * 11),
+ (65536.0f * 65536.0f / 18.0f) / (15 * 15),
+ (65536.0f * 65536.0f / 18.0f) / (19 * 19),
+ (65536.0f * 65536.0f / 18.0f) / (23 * 23),
+ (65536.0f * 65536.0f / 18.0f) / (31 * 31),
+ (65536.0f * 65536.0f / 18.0f) / (39 * 39),
+ (65536.0f * 65536.0f / 18.0f) / (47 * 47),
+ (65536.0f * 65536.0f / 18.0f) / (63 * 63),
+ (65536.0f * 65536.0f / 18.0f) / (79 * 79),
+ (65536.0f * 65536.0f / 18.0f) / (95 * 95),
+ (65536.0f * 65536.0f / 18.0f) / (127 * 127),
+ (65536.0f * 65536.0f / 18.0f) / (159 * 159),
+ (65536.0f * 65536.0f / 18.0f) / (191 * 191),
+ (65536.0f * 65536.0f / 18.0f) / (255 * 255)
+ };
+
+ vfloat4 ep0 = ep.endpt0[partition_index];
+ vfloat4 ep1 = ep.endpt1[partition_index];
+
+ float ep1_min = hmin_rgb_s(ep1);
+ ep1_min = astc::max(ep1_min, 0.0f);
+
+ float error_weight_rgbsum = hadd_rgb_s(error_weight);
+
+ float range_upper_limit_rgb = encode_hdr_rgb ? 61440.0f : 65535.0f;
+ float range_upper_limit_alpha = encode_hdr_alpha ? 61440.0f : 65535.0f;
+
+ // It is possible to get endpoint colors significantly outside [0,upper-limit] even if the
+ // input data are safely contained in [0,upper-limit]; we need to add an error term for this
+ vfloat4 offset(range_upper_limit_rgb, range_upper_limit_rgb, range_upper_limit_rgb, range_upper_limit_alpha);
+ vfloat4 ep0_range_error_high = max(ep0 - offset, 0.0f);
+ vfloat4 ep1_range_error_high = max(ep1 - offset, 0.0f);
+
+ vfloat4 ep0_range_error_low = min(ep0, 0.0f);
+ vfloat4 ep1_range_error_low = min(ep1, 0.0f);
+
+ vfloat4 sum_range_error =
+ (ep0_range_error_low * ep0_range_error_low) +
+ (ep1_range_error_low * ep1_range_error_low) +
+ (ep0_range_error_high * ep0_range_error_high) +
+ (ep1_range_error_high * ep1_range_error_high);
+
+ float rgb_range_error = dot3_s(sum_range_error, error_weight)
+ * 0.5f * static_cast<float>(partition_size);
+ float alpha_range_error = sum_range_error.lane<3>() * error_weight.lane<3>()
+ * 0.5f * static_cast<float>(partition_size);
+
+ if (encode_hdr_rgb)
+ {
+
+ // Collect some statistics
+ float af, cf;
+ if (ep1.lane<0>() > ep1.lane<1>() && ep1.lane<0>() > ep1.lane<2>())
+ {
+ af = ep1.lane<0>();
+ cf = ep1.lane<0>() - ep0.lane<0>();
+ }
+ else if (ep1.lane<1>() > ep1.lane<2>())
+ {
+ af = ep1.lane<1>();
+ cf = ep1.lane<1>() - ep0.lane<1>();
+ }
+ else
+ {
+ af = ep1.lane<2>();
+ cf = ep1.lane<2>() - ep0.lane<2>();
+ }
+
+ // Estimate of color-component spread in high endpoint color
+ float bf = af - ep1_min;
+ vfloat4 prd = (ep1 - vfloat4(cf)).swz<0, 1, 2>();
+ vfloat4 pdif = prd - ep0.swz<0, 1, 2>();
+ // Estimate of color-component spread in low endpoint color
+ float df = hmax_s(abs(pdif));
+
+ int b = static_cast<int>(bf);
+ int c = static_cast<int>(cf);
+ int d = static_cast<int>(df);
+
+ // Determine which one of the 6 submodes is likely to be used in case of an RGBO-mode
+ int rgbo_mode = 5; // 7 bits per component
+ // mode 4: 8 7 6
+ if (b < 32768 && c < 16384)
+ {
+ rgbo_mode = 4;
+ }
+
+ // mode 3: 9 6 7
+ if (b < 8192 && c < 16384)
+ {
+ rgbo_mode = 3;
+ }
+
+ // mode 2: 10 5 8
+ if (b < 2048 && c < 16384)
+ {
+ rgbo_mode = 2;
+ }
+
+ // mode 1: 11 6 5
+ if (b < 2048 && c < 1024)
+ {
+ rgbo_mode = 1;
+ }
+
+ // mode 0: 11 5 7
+ if (b < 1024 && c < 4096)
+ {
+ rgbo_mode = 0;
+ }
+
+ // Determine which one of the 9 submodes is likely to be used in case of an RGB-mode.
+ int rgb_mode = 8; // 8 bits per component, except 7 bits for blue
+
+ // mode 0: 9 7 6 7
+ if (b < 16384 && c < 8192 && d < 8192)
+ {
+ rgb_mode = 0;
+ }
+
+ // mode 1: 9 8 6 6
+ if (b < 32768 && c < 8192 && d < 4096)
+ {
+ rgb_mode = 1;
+ }
+
+ // mode 2: 10 6 7 7
+ if (b < 4096 && c < 8192 && d < 4096)
+ {
+ rgb_mode = 2;
+ }
+
+ // mode 3: 10 7 7 6
+ if (b < 8192 && c < 8192 && d < 2048)
+ {
+ rgb_mode = 3;
+ }
+
+ // mode 4: 11 8 6 5
+ if (b < 8192 && c < 2048 && d < 512)
+ {
+ rgb_mode = 4;
+ }
+
+ // mode 5: 11 6 8 6
+ if (b < 2048 && c < 8192 && d < 1024)
+ {
+ rgb_mode = 5;
+ }
+
+ // mode 6: 12 7 7 5
+ if (b < 2048 && c < 2048 && d < 256)
+ {
+ rgb_mode = 6;
+ }
+
+ // mode 7: 12 6 7 6
+ if (b < 1024 && c < 2048 && d < 512)
+ {
+ rgb_mode = 7;
+ }
+
+ static const float rgbo_error_scales[6] { 4.0f, 4.0f, 16.0f, 64.0f, 256.0f, 1024.0f };
+ static const float rgb_error_scales[9] { 64.0f, 64.0f, 16.0f, 16.0f, 4.0f, 4.0f, 1.0f, 1.0f, 384.0f };
+
+ float mode7mult = rgbo_error_scales[rgbo_mode] * 0.0015f; // Empirically determined ....
+ float mode11mult = rgb_error_scales[rgb_mode] * 0.010f; // Empirically determined ....
+
+
+ float lum_high = hadd_rgb_s(ep1) * (1.0f / 3.0f);
+ float lum_low = hadd_rgb_s(ep0) * (1.0f / 3.0f);
+ float lumdif = lum_high - lum_low;
+ float mode23mult = lumdif < 960 ? 4.0f : lumdif < 3968 ? 16.0f : 128.0f;
+
+ mode23mult *= 0.0005f; // Empirically determined ....
+
+ // Pick among the available HDR endpoint modes
+ for (int i = QUANT_2; i < QUANT_16; i++)
+ {
+ best_error[i][3] = ERROR_CALC_DEFAULT;
+ best_error[i][2] = ERROR_CALC_DEFAULT;
+ best_error[i][1] = ERROR_CALC_DEFAULT;
+ best_error[i][0] = ERROR_CALC_DEFAULT;
+
+ format_of_choice[i][3] = static_cast<uint8_t>(encode_hdr_alpha ? FMT_HDR_RGBA : FMT_HDR_RGB_LDR_ALPHA);
+ format_of_choice[i][2] = FMT_HDR_RGB;
+ format_of_choice[i][1] = FMT_HDR_RGB_SCALE;
+ format_of_choice[i][0] = FMT_HDR_LUMINANCE_LARGE_RANGE;
+ }
+
+ for (int i = QUANT_16; i <= QUANT_256; i++)
+ {
+ // The base_quant_error should depend on the scale-factor that would be used during
+ // actual encode of the color value
+
+ float base_quant_error = baseline_quant_error[i - QUANT_6] * static_cast<float>(partition_size);
+ float rgb_quantization_error = error_weight_rgbsum * base_quant_error * 2.0f;
+ float alpha_quantization_error = error_weight.lane<3>() * base_quant_error * 2.0f;
+ float rgba_quantization_error = rgb_quantization_error + alpha_quantization_error;
+
+ // For 8 integers, we have two encodings: one with HDR A and another one with LDR A
+
+ float full_hdr_rgba_error = rgba_quantization_error + rgb_range_error + alpha_range_error;
+ best_error[i][3] = full_hdr_rgba_error;
+ format_of_choice[i][3] = static_cast<uint8_t>(encode_hdr_alpha ? FMT_HDR_RGBA : FMT_HDR_RGB_LDR_ALPHA);
+
+ // For 6 integers, we have one HDR-RGB encoding
+ float full_hdr_rgb_error = (rgb_quantization_error * mode11mult) + rgb_range_error + eci.alpha_drop_error;
+ best_error[i][2] = full_hdr_rgb_error;
+ format_of_choice[i][2] = FMT_HDR_RGB;
+
+ // For 4 integers, we have one HDR-RGB-Scale encoding
+ float hdr_rgb_scale_error = (rgb_quantization_error * mode7mult) + rgb_range_error + eci.alpha_drop_error + eci.rgb_luma_error;
+
+ best_error[i][1] = hdr_rgb_scale_error;
+ format_of_choice[i][1] = FMT_HDR_RGB_SCALE;
+
+ // For 2 integers, we assume luminance-with-large-range
+ float hdr_luminance_error = (rgb_quantization_error * mode23mult) + rgb_range_error + eci.alpha_drop_error + eci.luminance_error;
+ best_error[i][0] = hdr_luminance_error;
+ format_of_choice[i][0] = FMT_HDR_LUMINANCE_LARGE_RANGE;
+ }
+ }
+ else
+ {
+ for (int i = QUANT_2; i < QUANT_6; i++)
+ {
+ best_error[i][3] = ERROR_CALC_DEFAULT;
+ best_error[i][2] = ERROR_CALC_DEFAULT;
+ best_error[i][1] = ERROR_CALC_DEFAULT;
+ best_error[i][0] = ERROR_CALC_DEFAULT;
+
+ format_of_choice[i][3] = FMT_RGBA;
+ format_of_choice[i][2] = FMT_RGB;
+ format_of_choice[i][1] = FMT_RGB_SCALE;
+ format_of_choice[i][0] = FMT_LUMINANCE;
+ }
+
+ float base_quant_error_rgb = error_weight_rgbsum * static_cast<float>(partition_size);
+ float base_quant_error_a = error_weight.lane<3>() * static_cast<float>(partition_size);
+ float base_quant_error_rgba = base_quant_error_rgb + base_quant_error_a;
+
+ float error_scale_bc_rgba = eci.can_blue_contract ? 0.625f : 1.0f;
+ float error_scale_oe_rgba = eci.can_offset_encode ? 0.5f : 1.0f;
+
+ float error_scale_bc_rgb = eci.can_blue_contract ? 0.5f : 1.0f;
+ float error_scale_oe_rgb = eci.can_offset_encode ? 0.25f : 1.0f;
+
+ // Pick among the available LDR endpoint modes
+ for (int i = QUANT_6; i <= QUANT_256; i++)
+ {
+ // Offset encoding not possible at higher quant levels
+ if (i >= QUANT_192)
+ {
+ error_scale_oe_rgba = 1.0f;
+ error_scale_oe_rgb = 1.0f;
+ }
+
+ float base_quant_error = baseline_quant_error[i - QUANT_6];
+ float quant_error_rgb = base_quant_error_rgb * base_quant_error;
+ float quant_error_rgba = base_quant_error_rgba * base_quant_error;
+
+ // 8 integers can encode as RGBA+RGBA
+ float full_ldr_rgba_error = quant_error_rgba
+ * error_scale_bc_rgba
+ * error_scale_oe_rgba
+ + rgb_range_error
+ + alpha_range_error;
+
+ best_error[i][3] = full_ldr_rgba_error;
+ format_of_choice[i][3] = FMT_RGBA;
+
+ // 6 integers can encode as RGB+RGB or RGBS+AA
+ float full_ldr_rgb_error = quant_error_rgb
+ * error_scale_bc_rgb
+ * error_scale_oe_rgb
+ + rgb_range_error
+ + eci.alpha_drop_error;
+
+ float rgbs_alpha_error = quant_error_rgba
+ + eci.rgb_scale_error
+ + rgb_range_error
+ + alpha_range_error;
+
+ if (rgbs_alpha_error < full_ldr_rgb_error)
+ {
+ best_error[i][2] = rgbs_alpha_error;
+ format_of_choice[i][2] = FMT_RGB_SCALE_ALPHA;
+ }
+ else
+ {
+ best_error[i][2] = full_ldr_rgb_error;
+ format_of_choice[i][2] = FMT_RGB;
+ }
+
+ // 4 integers can encode as RGBS or LA+LA
+ float ldr_rgbs_error = quant_error_rgb
+ + rgb_range_error
+ + eci.alpha_drop_error
+ + eci.rgb_scale_error;
+
+ float lum_alpha_error = quant_error_rgba
+ + rgb_range_error
+ + alpha_range_error
+ + eci.luminance_error;
+
+ if (ldr_rgbs_error < lum_alpha_error)
+ {
+ best_error[i][1] = ldr_rgbs_error;
+ format_of_choice[i][1] = FMT_RGB_SCALE;
+ }
+ else
+ {
+ best_error[i][1] = lum_alpha_error;
+ format_of_choice[i][1] = FMT_LUMINANCE_ALPHA;
+ }
+
+ // 2 integers can encode as L+L
+ float luminance_error = quant_error_rgb
+ + rgb_range_error
+ + eci.alpha_drop_error
+ + eci.luminance_error;
+
+ best_error[i][0] = luminance_error;
+ format_of_choice[i][0] = FMT_LUMINANCE;
+ }
+ }
+}
+
+/**
+ * @brief For one partition compute the best format and quantization for a given bit count.
+ *
+ * @param best_combined_error The best error for each quant level and integer count.
+ * @param best_combined_format The best format for each quant level and integer count.
+ * @param bits_available The number of bits available for encoding.
+ * @param[out] best_quant_level The output best color quant level.
+ * @param[out] best_format The output best color format.
+ *
+ * @return The output error for the best pairing.
+ */
+static float one_partition_find_best_combination_for_bitcount(
+ const float best_combined_error[21][4],
+ const uint8_t best_combined_format[21][4],
+ int bits_available,
+ uint8_t& best_quant_level,
+ uint8_t& best_format
+) {
+ int best_integer_count = 0;
+ float best_integer_count_error = ERROR_CALC_DEFAULT;
+
+ for (int integer_count = 1; integer_count <= 4; integer_count++)
+ {
+ // Compute the quantization level for a given number of integers and a given number of bits
+ int quant_level = quant_mode_table[integer_count][bits_available];
+
+ // Don't have enough bits to represent a given endpoint format at all!
+ if (quant_level < QUANT_6)
+ {
+ continue;
+ }
+
+ float integer_count_error = best_combined_error[quant_level][integer_count - 1];
+ if (integer_count_error < best_integer_count_error)
+ {
+ best_integer_count_error = integer_count_error;
+ best_integer_count = integer_count - 1;
+ }
+ }
+
+ int ql = quant_mode_table[best_integer_count + 1][bits_available];
+
+ best_quant_level = static_cast<uint8_t>(ql);
+ best_format = FMT_LUMINANCE;
+
+ if (ql >= QUANT_6)
+ {
+ best_format = best_combined_format[ql][best_integer_count];
+ }
+
+ return best_integer_count_error;
+}
+
+/**
+ * @brief For 2 partitions compute the best format combinations for every pair of quant mode and integer count.
+ *
+ * @param best_error The best error for a single endpoint quant level and integer count.
+ * @param best_format The best format for a single endpoint quant level and integer count.
+ * @param[out] best_combined_error The best combined error pairings for the 2 partitions.
+ * @param[out] best_combined_format The best combined format pairings for the 2 partitions.
+ */
+static void two_partitions_find_best_combination_for_every_quantization_and_integer_count(
+ const float best_error[2][21][4], // indexed by (partition, quant-level, integer-pair-count-minus-1)
+ const uint8_t best_format[2][21][4],
+ float best_combined_error[21][7], // indexed by (quant-level, integer-pair-count-minus-2)
+ uint8_t best_combined_format[21][7][2]
+) {
+ for (int i = QUANT_2; i <= QUANT_256; i++)
+ {
+ for (int j = 0; j < 7; j++)
+ {
+ best_combined_error[i][j] = ERROR_CALC_DEFAULT;
+ }
+ }
+
+ for (int quant = QUANT_6; quant <= QUANT_256; quant++)
+ {
+ for (int i = 0; i < 4; i++) // integer-count for first endpoint-pair
+ {
+ for (int j = 0; j < 4; j++) // integer-count for second endpoint-pair
+ {
+ int low2 = astc::min(i, j);
+ int high2 = astc::max(i, j);
+ if ((high2 - low2) > 1)
+ {
+ continue;
+ }
+
+ int intcnt = i + j;
+ float errorterm = astc::min(best_error[0][quant][i] + best_error[1][quant][j], 1e10f);
+ if (errorterm <= best_combined_error[quant][intcnt])
+ {
+ best_combined_error[quant][intcnt] = errorterm;
+ best_combined_format[quant][intcnt][0] = best_format[0][quant][i];
+ best_combined_format[quant][intcnt][1] = best_format[1][quant][j];
+ }
+ }
+ }
+ }
+}
+
+/**
+ * @brief For 2 partitions compute the best format and quantization for a given bit count.
+ *
+ * @param best_combined_error The best error for each quant level and integer count.
+ * @param best_combined_format The best format for each quant level and integer count.
+ * @param bits_available The number of bits available for encoding.
+ * @param[out] best_quant_level The output best color quant level.
+ * @param[out] best_quant_level_mod The output best color quant level assuming two more bits are available.
+ * @param[out] best_formats The output best color formats.
+ *
+ * @return The output error for the best pairing.
+ */
+static float two_partitions_find_best_combination_for_bitcount(
+ float best_combined_error[21][7],
+ uint8_t best_combined_format[21][7][2],
+ int bits_available,
+ uint8_t& best_quant_level,
+ uint8_t& best_quant_level_mod,
+ uint8_t* best_formats
+) {
+ int best_integer_count = 0;
+ float best_integer_count_error = ERROR_CALC_DEFAULT;
+
+ for (int integer_count = 2; integer_count <= 8; integer_count++)
+ {
+ // Compute the quantization level for a given number of integers and a given number of bits
+ int quant_level = quant_mode_table[integer_count][bits_available];
+
+ // Don't have enough bits to represent a given endpoint format at all!
+ if (quant_level < QUANT_6)
+ {
+ break;
+ }
+
+ float integer_count_error = best_combined_error[quant_level][integer_count - 2];
+ if (integer_count_error < best_integer_count_error)
+ {
+ best_integer_count_error = integer_count_error;
+ best_integer_count = integer_count;
+ }
+ }
+
+ int ql = quant_mode_table[best_integer_count][bits_available];
+ int ql_mod = quant_mode_table[best_integer_count][bits_available + 2];
+
+ best_quant_level = static_cast<uint8_t>(ql);
+ best_quant_level_mod = static_cast<uint8_t>(ql_mod);
+
+ if (ql >= QUANT_6)
+ {
+ for (int i = 0; i < 2; i++)
+ {
+ best_formats[i] = best_combined_format[ql][best_integer_count - 2][i];
+ }
+ }
+ else
+ {
+ for (int i = 0; i < 2; i++)
+ {
+ best_formats[i] = FMT_LUMINANCE;
+ }
+ }
+
+ return best_integer_count_error;
+}
+
+/**
+ * @brief For 3 partitions compute the best format combinations for every pair of quant mode and integer count.
+ *
+ * @param best_error The best error for a single endpoint quant level and integer count.
+ * @param best_format The best format for a single endpoint quant level and integer count.
+ * @param[out] best_combined_error The best combined error pairings for the 3 partitions.
+ * @param[out] best_combined_format The best combined format pairings for the 3 partitions.
+ */
+static void three_partitions_find_best_combination_for_every_quantization_and_integer_count(
+ const float best_error[3][21][4], // indexed by (partition, quant-level, integer-count)
+ const uint8_t best_format[3][21][4],
+ float best_combined_error[21][10],
+ uint8_t best_combined_format[21][10][3]
+) {
+ for (int i = QUANT_2; i <= QUANT_256; i++)
+ {
+ for (int j = 0; j < 10; j++)
+ {
+ best_combined_error[i][j] = ERROR_CALC_DEFAULT;
+ }
+ }
+
+ for (int quant = QUANT_6; quant <= QUANT_256; quant++)
+ {
+ for (int i = 0; i < 4; i++) // integer-count for first endpoint-pair
+ {
+ for (int j = 0; j < 4; j++) // integer-count for second endpoint-pair
+ {
+ int low2 = astc::min(i, j);
+ int high2 = astc::max(i, j);
+ if ((high2 - low2) > 1)
+ {
+ continue;
+ }
+
+ for (int k = 0; k < 4; k++) // integer-count for third endpoint-pair
+ {
+ int low3 = astc::min(k, low2);
+ int high3 = astc::max(k, high2);
+ if ((high3 - low3) > 1)
+ {
+ continue;
+ }
+
+ int intcnt = i + j + k;
+ float errorterm = astc::min(best_error[0][quant][i] + best_error[1][quant][j] + best_error[2][quant][k], 1e10f);
+ if (errorterm <= best_combined_error[quant][intcnt])
+ {
+ best_combined_error[quant][intcnt] = errorterm;
+ best_combined_format[quant][intcnt][0] = best_format[0][quant][i];
+ best_combined_format[quant][intcnt][1] = best_format[1][quant][j];
+ best_combined_format[quant][intcnt][2] = best_format[2][quant][k];
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * @brief For 3 partitions compute the best format and quantization for a given bit count.
+ *
+ * @param best_combined_error The best error for each quant level and integer count.
+ * @param best_combined_format The best format for each quant level and integer count.
+ * @param bits_available The number of bits available for encoding.
+ * @param[out] best_quant_level The output best color quant level.
+ * @param[out] best_quant_level_mod The output best color quant level assuming two more bits are available.
+ * @param[out] best_formats The output best color formats.
+ *
+ * @return The output error for the best pairing.
+ */
+static float three_partitions_find_best_combination_for_bitcount(
+ const float best_combined_error[21][10],
+ const uint8_t best_combined_format[21][10][3],
+ int bits_available,
+ uint8_t& best_quant_level,
+ uint8_t& best_quant_level_mod,
+ uint8_t* best_formats
+) {
+ int best_integer_count = 0;
+ float best_integer_count_error = ERROR_CALC_DEFAULT;
+
+ for (int integer_count = 3; integer_count <= 9; integer_count++)
+ {
+ // Compute the quantization level for a given number of integers and a given number of bits
+ int quant_level = quant_mode_table[integer_count][bits_available];
+
+ // Don't have enough bits to represent a given endpoint format at all!
+ if (quant_level < QUANT_6)
+ {
+ break;
+ }
+
+ float integer_count_error = best_combined_error[quant_level][integer_count - 3];
+ if (integer_count_error < best_integer_count_error)
+ {
+ best_integer_count_error = integer_count_error;
+ best_integer_count = integer_count;
+ }
+ }
+
+ int ql = quant_mode_table[best_integer_count][bits_available];
+ int ql_mod = quant_mode_table[best_integer_count][bits_available + 5];
+
+ best_quant_level = static_cast<uint8_t>(ql);
+ best_quant_level_mod = static_cast<uint8_t>(ql_mod);
+
+ if (ql >= QUANT_6)
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ best_formats[i] = best_combined_format[ql][best_integer_count - 3][i];
+ }
+ }
+ else
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ best_formats[i] = FMT_LUMINANCE;
+ }
+ }
+
+ return best_integer_count_error;
+}
+
+/**
+ * @brief For 4 partitions compute the best format combinations for every pair of quant mode and integer count.
+ *
+ * @param best_error The best error for a single endpoint quant level and integer count.
+ * @param best_format The best format for a single endpoint quant level and integer count.
+ * @param[out] best_combined_error The best combined error pairings for the 4 partitions.
+ * @param[out] best_combined_format The best combined format pairings for the 4 partitions.
+ */
+static void four_partitions_find_best_combination_for_every_quantization_and_integer_count(
+ const float best_error[4][21][4], // indexed by (partition, quant-level, integer-count)
+ const uint8_t best_format[4][21][4],
+ float best_combined_error[21][13],
+ uint8_t best_combined_format[21][13][4]
+) {
+ for (int i = QUANT_2; i <= QUANT_256; i++)
+ {
+ for (int j = 0; j < 13; j++)
+ {
+ best_combined_error[i][j] = ERROR_CALC_DEFAULT;
+ }
+ }
+
+ for (int quant = QUANT_6; quant <= QUANT_256; quant++)
+ {
+ for (int i = 0; i < 4; i++) // integer-count for first endpoint-pair
+ {
+ for (int j = 0; j < 4; j++) // integer-count for second endpoint-pair
+ {
+ int low2 = astc::min(i, j);
+ int high2 = astc::max(i, j);
+ if ((high2 - low2) > 1)
+ {
+ continue;
+ }
+
+ for (int k = 0; k < 4; k++) // integer-count for third endpoint-pair
+ {
+ int low3 = astc::min(k, low2);
+ int high3 = astc::max(k, high2);
+ if ((high3 - low3) > 1)
+ {
+ continue;
+ }
+
+ for (int l = 0; l < 4; l++) // integer-count for fourth endpoint-pair
+ {
+ int low4 = astc::min(l, low3);
+ int high4 = astc::max(l, high3);
+ if ((high4 - low4) > 1)
+ {
+ continue;
+ }
+
+ int intcnt = i + j + k + l;
+ float errorterm = astc::min(best_error[0][quant][i] + best_error[1][quant][j] + best_error[2][quant][k] + best_error[3][quant][l], 1e10f);
+ if (errorterm <= best_combined_error[quant][intcnt])
+ {
+ best_combined_error[quant][intcnt] = errorterm;
+ best_combined_format[quant][intcnt][0] = best_format[0][quant][i];
+ best_combined_format[quant][intcnt][1] = best_format[1][quant][j];
+ best_combined_format[quant][intcnt][2] = best_format[2][quant][k];
+ best_combined_format[quant][intcnt][3] = best_format[3][quant][l];
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * @brief For 4 partitions compute the best format and quantization for a given bit count.
+ *
+ * @param best_combined_error The best error for each quant level and integer count.
+ * @param best_combined_format The best format for each quant level and integer count.
+ * @param bits_available The number of bits available for encoding.
+ * @param[out] best_quant_level The output best color quant level.
+ * @param[out] best_quant_level_mod The output best color quant level assuming two more bits are available.
+ * @param[out] best_formats The output best color formats.
+ *
+ * @return best_error The output error for the best pairing.
+ */
+static float four_partitions_find_best_combination_for_bitcount(
+ const float best_combined_error[21][13],
+ const uint8_t best_combined_format[21][13][4],
+ int bits_available,
+ uint8_t& best_quant_level,
+ uint8_t& best_quant_level_mod,
+ uint8_t* best_formats
+) {
+ int best_integer_count = 0;
+ float best_integer_count_error = ERROR_CALC_DEFAULT;
+
+ for (int integer_count = 4; integer_count <= 9; integer_count++)
+ {
+ // Compute the quantization level for a given number of integers and a given number of bits
+ int quant_level = quant_mode_table[integer_count][bits_available];
+
+ // Don't have enough bits to represent a given endpoint format at all!
+ if (quant_level < QUANT_6)
+ {
+ break;
+ }
+
+ float integer_count_error = best_combined_error[quant_level][integer_count - 4];
+ if (integer_count_error < best_integer_count_error)
+ {
+ best_integer_count_error = integer_count_error;
+ best_integer_count = integer_count;
+ }
+ }
+
+ int ql = quant_mode_table[best_integer_count][bits_available];
+ int ql_mod = quant_mode_table[best_integer_count][bits_available + 8];
+
+ best_quant_level = static_cast<uint8_t>(ql);
+ best_quant_level_mod = static_cast<uint8_t>(ql_mod);
+
+ if (ql >= QUANT_6)
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ best_formats[i] = best_combined_format[ql][best_integer_count - 4][i];
+ }
+ }
+ else
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ best_formats[i] = FMT_LUMINANCE;
+ }
+ }
+
+ return best_integer_count_error;
+}
+
+/* See header for documentation. */
+unsigned int compute_ideal_endpoint_formats(
+ const partition_info& pi,
+ const image_block& blk,
+ const endpoints& ep,
+ // bitcounts and errors computed for the various quantization methods
+ const int8_t* qwt_bitcounts,
+ const float* qwt_errors,
+ unsigned int tune_candidate_limit,
+ unsigned int start_block_mode,
+ unsigned int end_block_mode,
+ // output data
+ uint8_t partition_format_specifiers[TUNE_MAX_TRIAL_CANDIDATES][BLOCK_MAX_PARTITIONS],
+ int block_mode[TUNE_MAX_TRIAL_CANDIDATES],
+ quant_method quant_level[TUNE_MAX_TRIAL_CANDIDATES],
+ quant_method quant_level_mod[TUNE_MAX_TRIAL_CANDIDATES],
+ compression_working_buffers& tmpbuf
+) {
+ int partition_count = pi.partition_count;
+
+ promise(partition_count > 0);
+
+ bool encode_hdr_rgb = static_cast<bool>(blk.rgb_lns[0]);
+ bool encode_hdr_alpha = static_cast<bool>(blk.alpha_lns[0]);
+
+ // Compute the errors that result from various encoding choices (such as using luminance instead
+ // of RGB, discarding Alpha, using RGB-scale in place of two separate RGB endpoints and so on)
+ encoding_choice_errors eci[BLOCK_MAX_PARTITIONS];
+ compute_encoding_choice_errors(blk, pi, ep, eci);
+
+ float best_error[BLOCK_MAX_PARTITIONS][21][4];
+ uint8_t format_of_choice[BLOCK_MAX_PARTITIONS][21][4];
+ for (int i = 0; i < partition_count; i++)
+ {
+ compute_color_error_for_every_integer_count_and_quant_level(
+ encode_hdr_rgb, encode_hdr_alpha, i,
+ pi, eci[i], ep, blk.channel_weight, best_error[i],
+ format_of_choice[i]);
+ }
+
+ float* errors_of_best_combination = tmpbuf.errors_of_best_combination;
+ uint8_t* best_quant_levels = tmpbuf.best_quant_levels;
+ uint8_t* best_quant_levels_mod = tmpbuf.best_quant_levels_mod;
+ uint8_t (&best_ep_formats)[WEIGHTS_MAX_BLOCK_MODES][BLOCK_MAX_PARTITIONS] = tmpbuf.best_ep_formats;
+
+ // Ensure that the first iteration understep contains data that will never be picked
+ vfloat clear_error(ERROR_CALC_DEFAULT);
+ vint clear_quant(0);
+
+ unsigned int packed_start_block_mode = round_down_to_simd_multiple_vla(start_block_mode);
+ storea(clear_error, errors_of_best_combination + packed_start_block_mode);
+ store_nbytes(clear_quant, best_quant_levels + packed_start_block_mode);
+ store_nbytes(clear_quant, best_quant_levels_mod + packed_start_block_mode);
+
+ // Ensure that last iteration overstep contains data that will never be picked
+ unsigned int packed_end_block_mode = round_down_to_simd_multiple_vla(end_block_mode - 1);
+ storea(clear_error, errors_of_best_combination + packed_end_block_mode);
+ store_nbytes(clear_quant, best_quant_levels + packed_end_block_mode);
+ store_nbytes(clear_quant, best_quant_levels_mod + packed_end_block_mode);
+
+ // Track a scalar best to avoid expensive search at least once ...
+ float error_of_best_combination = ERROR_CALC_DEFAULT;
+ int index_of_best_combination = -1;
+
+ // The block contains 1 partition
+ if (partition_count == 1)
+ {
+ for (unsigned int i = start_block_mode; i < end_block_mode; i++)
+ {
+ if (qwt_errors[i] >= ERROR_CALC_DEFAULT)
+ {
+ errors_of_best_combination[i] = ERROR_CALC_DEFAULT;
+ continue;
+ }
+
+ float error_of_best = one_partition_find_best_combination_for_bitcount(
+ best_error[0], format_of_choice[0], qwt_bitcounts[i],
+ best_quant_levels[i], best_ep_formats[i][0]);
+
+ float total_error = error_of_best + qwt_errors[i];
+ errors_of_best_combination[i] = total_error;
+ best_quant_levels_mod[i] = best_quant_levels[i];
+
+ if (total_error < error_of_best_combination)
+ {
+ error_of_best_combination = total_error;
+ index_of_best_combination = i;
+ }
+ }
+ }
+ // The block contains 2 partitions
+ else if (partition_count == 2)
+ {
+ float combined_best_error[21][7];
+ uint8_t formats_of_choice[21][7][2];
+
+ two_partitions_find_best_combination_for_every_quantization_and_integer_count(
+ best_error, format_of_choice, combined_best_error, formats_of_choice);
+
+ assert(start_block_mode == 0);
+ for (unsigned int i = 0; i < end_block_mode; i++)
+ {
+ if (qwt_errors[i] >= ERROR_CALC_DEFAULT)
+ {
+ errors_of_best_combination[i] = ERROR_CALC_DEFAULT;
+ continue;
+ }
+
+ float error_of_best = two_partitions_find_best_combination_for_bitcount(
+ combined_best_error, formats_of_choice, qwt_bitcounts[i],
+ best_quant_levels[i], best_quant_levels_mod[i],
+ best_ep_formats[i]);
+
+ float total_error = error_of_best + qwt_errors[i];
+ errors_of_best_combination[i] = total_error;
+
+ if (total_error < error_of_best_combination)
+ {
+ error_of_best_combination = total_error;
+ index_of_best_combination = i;
+ }
+ }
+ }
+ // The block contains 3 partitions
+ else if (partition_count == 3)
+ {
+ float combined_best_error[21][10];
+ uint8_t formats_of_choice[21][10][3];
+
+ three_partitions_find_best_combination_for_every_quantization_and_integer_count(
+ best_error, format_of_choice, combined_best_error, formats_of_choice);
+
+ assert(start_block_mode == 0);
+ for (unsigned int i = 0; i < end_block_mode; i++)
+ {
+ if (qwt_errors[i] >= ERROR_CALC_DEFAULT)
+ {
+ errors_of_best_combination[i] = ERROR_CALC_DEFAULT;
+ continue;
+ }
+
+ float error_of_best = three_partitions_find_best_combination_for_bitcount(
+ combined_best_error, formats_of_choice, qwt_bitcounts[i],
+ best_quant_levels[i], best_quant_levels_mod[i],
+ best_ep_formats[i]);
+
+ float total_error = error_of_best + qwt_errors[i];
+ errors_of_best_combination[i] = total_error;
+
+ if (total_error < error_of_best_combination)
+ {
+ error_of_best_combination = total_error;
+ index_of_best_combination = i;
+ }
+ }
+ }
+ // The block contains 4 partitions
+ else // if (partition_count == 4)
+ {
+ assert(partition_count == 4);
+ float combined_best_error[21][13];
+ uint8_t formats_of_choice[21][13][4];
+
+ four_partitions_find_best_combination_for_every_quantization_and_integer_count(
+ best_error, format_of_choice, combined_best_error, formats_of_choice);
+
+ assert(start_block_mode == 0);
+ for (unsigned int i = 0; i < end_block_mode; i++)
+ {
+ if (qwt_errors[i] >= ERROR_CALC_DEFAULT)
+ {
+ errors_of_best_combination[i] = ERROR_CALC_DEFAULT;
+ continue;
+ }
+
+ float error_of_best = four_partitions_find_best_combination_for_bitcount(
+ combined_best_error, formats_of_choice, qwt_bitcounts[i],
+ best_quant_levels[i], best_quant_levels_mod[i],
+ best_ep_formats[i]);
+
+ float total_error = error_of_best + qwt_errors[i];
+ errors_of_best_combination[i] = total_error;
+
+ if (total_error < error_of_best_combination)
+ {
+ error_of_best_combination = total_error;
+ index_of_best_combination = i;
+ }
+ }
+ }
+
+ int best_error_weights[TUNE_MAX_TRIAL_CANDIDATES];
+
+ // Fast path the first result and avoid the list search for trial 0
+ best_error_weights[0] = index_of_best_combination;
+ if (index_of_best_combination >= 0)
+ {
+ errors_of_best_combination[index_of_best_combination] = ERROR_CALC_DEFAULT;
+ }
+
+ // Search the remaining results and pick the best candidate modes for trial 1+
+ for (unsigned int i = 1; i < tune_candidate_limit; i++)
+ {
+ vint vbest_error_index(-1);
+ vfloat vbest_ep_error(ERROR_CALC_DEFAULT);
+
+ start_block_mode = round_down_to_simd_multiple_vla(start_block_mode);
+ vint lane_ids = vint::lane_id() + vint(start_block_mode);
+ for (unsigned int j = start_block_mode; j < end_block_mode; j += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat err = vfloat(errors_of_best_combination + j);
+ vmask mask = err < vbest_ep_error;
+ vbest_ep_error = select(vbest_ep_error, err, mask);
+ vbest_error_index = select(vbest_error_index, lane_ids, mask);
+ lane_ids += vint(ASTCENC_SIMD_WIDTH);
+ }
+
+ // Pick best mode from the SIMD result, using lowest matching index to ensure invariance
+ vmask lanes_min_error = vbest_ep_error == hmin(vbest_ep_error);
+ vbest_error_index = select(vint(0x7FFFFFFF), vbest_error_index, lanes_min_error);
+ vbest_error_index = hmin(vbest_error_index);
+ int best_error_index = vbest_error_index.lane<0>();
+
+ best_error_weights[i] = best_error_index;
+
+ // Max the error for this candidate so we don't pick it again
+ if (best_error_index >= 0)
+ {
+ errors_of_best_combination[best_error_index] = ERROR_CALC_DEFAULT;
+ }
+ // Early-out if no more candidates are valid
+ else
+ {
+ break;
+ }
+ }
+
+ for (unsigned int i = 0; i < tune_candidate_limit; i++)
+ {
+ if (best_error_weights[i] < 0)
+ {
+ return i;
+ }
+
+ block_mode[i] = best_error_weights[i];
+
+ quant_level[i] = static_cast<quant_method>(best_quant_levels[best_error_weights[i]]);
+ quant_level_mod[i] = static_cast<quant_method>(best_quant_levels_mod[best_error_weights[i]]);
+
+ assert(quant_level[i] >= QUANT_6 && quant_level[i] <= QUANT_256);
+ assert(quant_level_mod[i] >= QUANT_6 && quant_level_mod[i] <= QUANT_256);
+
+ for (int j = 0; j < partition_count; j++)
+ {
+ partition_format_specifiers[i][j] = best_ep_formats[best_error_weights[i]][j];
+ }
+ }
+
+ return tune_candidate_limit;
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_platform_isa_detection.cpp b/thirdparty/astcenc/astcenc_platform_isa_detection.cpp
new file mode 100644
index 0000000000..8ed98437ea
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_platform_isa_detection.cpp
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2020-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Platform-specific function implementations.
+ *
+ * This module contains functions for querying the host extended ISA support.
+ */
+
+// Include before the defines below to pick up any auto-setup based on compiler
+// built-in config, if not being set explicitly by the build system
+#include "astcenc_internal.h"
+
+#if (ASTCENC_SSE > 0) || (ASTCENC_AVX > 0) || \
+ (ASTCENC_POPCNT > 0) || (ASTCENC_F16C > 0)
+
+static bool g_init { false };
+
+/** Does this CPU support SSE 4.1? Set to -1 if not yet initialized. */
+static bool g_cpu_has_sse41 { false };
+
+/** Does this CPU support AVX2? Set to -1 if not yet initialized. */
+static bool g_cpu_has_avx2 { false };
+
+/** Does this CPU support POPCNT? Set to -1 if not yet initialized. */
+static bool g_cpu_has_popcnt { false };
+
+/** Does this CPU support F16C? Set to -1 if not yet initialized. */
+static bool g_cpu_has_f16c { false };
+
+/* ============================================================================
+ Platform code for Visual Studio
+============================================================================ */
+#if !defined(__clang__) && defined(_MSC_VER)
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <intrin.h>
+
+/**
+ * @brief Detect platform CPU ISA support and update global trackers.
+ */
+static void detect_cpu_isa()
+{
+ int data[4];
+
+ __cpuid(data, 0);
+ int num_id = data[0];
+
+ if (num_id >= 1)
+ {
+ __cpuidex(data, 1, 0);
+ // SSE41 = Bank 1, ECX, bit 19
+ g_cpu_has_sse41 = data[2] & (1 << 19) ? true : false;
+ // POPCNT = Bank 1, ECX, bit 23
+ g_cpu_has_popcnt = data[2] & (1 << 23) ? true : false;
+ // F16C = Bank 1, ECX, bit 29
+ g_cpu_has_f16c = data[2] & (1 << 29) ? true : false;
+ }
+
+ if (num_id >= 7)
+ {
+ __cpuidex(data, 7, 0);
+ // AVX2 = Bank 7, EBX, bit 5
+ g_cpu_has_avx2 = data[1] & (1 << 5) ? true : false;
+ }
+
+ // Ensure state bits are updated before init flag is updated
+ MemoryBarrier();
+ g_init = true;
+}
+
+/* ============================================================================
+ Platform code for GCC and Clang
+============================================================================ */
+#else
+#include <cpuid.h>
+
+/**
+ * @brief Detect platform CPU ISA support and update global trackers.
+ */
+static void detect_cpu_isa()
+{
+ unsigned int data[4];
+
+ if (__get_cpuid_count(1, 0, &data[0], &data[1], &data[2], &data[3]))
+ {
+ // SSE41 = Bank 1, ECX, bit 19
+ g_cpu_has_sse41 = data[2] & (1 << 19) ? true : false;
+ // POPCNT = Bank 1, ECX, bit 23
+ g_cpu_has_popcnt = data[2] & (1 << 23) ? true : false;
+ // F16C = Bank 1, ECX, bit 29
+ g_cpu_has_f16c = data[2] & (1 << 29) ? true : false;
+ }
+
+ g_cpu_has_avx2 = 0;
+ if (__get_cpuid_count(7, 0, &data[0], &data[1], &data[2], &data[3]))
+ {
+ // AVX2 = Bank 7, EBX, bit 5
+ g_cpu_has_avx2 = data[1] & (1 << 5) ? true : false;
+ }
+
+ // Ensure state bits are updated before init flag is updated
+ __sync_synchronize();
+ g_init = true;
+}
+#endif
+
+/* See header for documentation. */
+bool cpu_supports_popcnt()
+{
+ if (!g_init)
+ {
+ detect_cpu_isa();
+ }
+
+ return g_cpu_has_popcnt;
+}
+
+/* See header for documentation. */
+bool cpu_supports_f16c()
+{
+ if (!g_init)
+ {
+ detect_cpu_isa();
+ }
+
+ return g_cpu_has_f16c;
+}
+
+/* See header for documentation. */
+bool cpu_supports_sse41()
+{
+ if (!g_init)
+ {
+ detect_cpu_isa();
+ }
+
+ return g_cpu_has_sse41;
+}
+
+/* See header for documentation. */
+bool cpu_supports_avx2()
+{
+ if (!g_init)
+ {
+ detect_cpu_isa();
+ }
+
+ return g_cpu_has_avx2;
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_quantization.cpp b/thirdparty/astcenc/astcenc_quantization.cpp
new file mode 100644
index 0000000000..478a21ead7
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_quantization.cpp
@@ -0,0 +1,904 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2021 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Functions and data tables for numeric quantization..
+ */
+
+#include "astcenc_internal.h"
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+
+// Starts from QUANT_6
+// Not scrambled
+const uint8_t color_unquant_to_uquant_tables[17][256] {
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 51, 51, 51, 51, 51,
+ 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
+ 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51,
+ 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 102, 102, 102,
+ 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
+ 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
+ 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102,
+ 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
+ 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
+ 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
+ 153, 153, 153, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204,
+ 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204,
+ 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204,
+ 204, 204, 204, 204, 204, 204, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36,
+ 36, 36, 36, 36, 36, 36, 36, 73, 73, 73, 73, 73, 73, 73, 73, 73,
+ 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73,
+ 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 73, 109, 109, 109, 109,
+ 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109,
+ 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109,
+ 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146,
+ 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146, 146,
+ 146, 146, 146, 146, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182,
+ 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182, 182,
+ 182, 182, 182, 182, 182, 182, 182, 182, 182, 219, 219, 219, 219, 219, 219, 219,
+ 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219,
+ 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 219, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 56, 56, 56, 56, 56,
+ 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
+ 56, 56, 56, 56, 56, 56, 56, 84, 84, 84, 84, 84, 84, 84, 84, 84,
+ 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84,
+ 84, 84, 84, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
+ 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113, 113,
+ 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142,
+ 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 171, 171, 171,
+ 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171, 171,
+ 171, 171, 171, 171, 171, 171, 171, 171, 171, 199, 199, 199, 199, 199, 199, 199,
+ 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199, 199,
+ 199, 199, 199, 199, 199, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227,
+ 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227, 227,
+ 227, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 23, 23,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 23, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
+ 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 69, 69, 69, 69, 69, 69,
+ 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69,
+ 69, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92, 92,
+ 92, 92, 92, 92, 92, 92, 92, 92, 92, 116, 116, 116, 116, 116, 116, 116,
+ 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116, 116,
+ 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139, 139,
+ 139, 139, 139, 139, 139, 139, 139, 163, 163, 163, 163, 163, 163, 163, 163, 163,
+ 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 163, 186,
+ 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186, 186,
+ 186, 186, 186, 186, 186, 186, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209,
+ 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 209, 232, 232, 232,
+ 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232, 232,
+ 232, 232, 232, 232, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17,
+ 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 34, 34, 34, 34, 34, 34,
+ 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 51, 51, 51, 51, 51,
+ 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 68, 68, 68, 68,
+ 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 68, 85, 85, 85,
+ 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, 102, 102,
+ 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 102, 119,
+ 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119, 119,
+ 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 136,
+ 136, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153, 153,
+ 153, 153, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170,
+ 170, 170, 170, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187,
+ 187, 187, 187, 187, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204,
+ 204, 204, 204, 204, 204, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221, 221,
+ 221, 221, 221, 221, 221, 221, 238, 238, 238, 238, 238, 238, 238, 238, 238, 238,
+ 238, 238, 238, 238, 238, 238, 238, 255, 255, 255, 255, 255, 255, 255, 255, 255
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+ 27, 27, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
+ 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 54, 67, 67, 67,
+ 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 80, 80, 80, 94, 94, 94, 94, 94, 94, 94, 94,
+ 94, 94, 94, 94, 94, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107, 107,
+ 107, 107, 107, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121,
+ 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 148, 148, 148,
+ 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 161, 161, 161, 161, 161,
+ 161, 161, 161, 161, 161, 161, 161, 161, 175, 175, 175, 175, 175, 175, 175, 175,
+ 175, 175, 175, 175, 175, 175, 188, 188, 188, 188, 188, 188, 188, 188, 188, 188,
+ 188, 188, 188, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201, 201,
+ 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 215, 228, 228,
+ 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 228, 242, 242, 242, 242, 242,
+ 242, 242, 242, 242, 242, 242, 242, 242, 242, 255, 255, 255, 255, 255, 255, 255
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+ 11, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 33, 33, 33, 33,
+ 33, 33, 33, 33, 33, 33, 33, 44, 44, 44, 44, 44, 44, 44, 44, 44,
+ 44, 44, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 66, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 66, 77, 77, 77, 77, 77, 77, 77, 77,
+ 77, 77, 77, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 88, 99, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99, 99, 110, 110, 110, 110, 110, 110, 110,
+ 110, 110, 110, 110, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121, 121,
+ 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 134, 145, 145, 145, 145,
+ 145, 145, 145, 145, 145, 145, 145, 156, 156, 156, 156, 156, 156, 156, 156, 156,
+ 156, 156, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 167, 178, 178, 178,
+ 178, 178, 178, 178, 178, 178, 178, 178, 189, 189, 189, 189, 189, 189, 189, 189,
+ 189, 189, 189, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 211, 211,
+ 211, 211, 211, 211, 211, 211, 211, 211, 211, 222, 222, 222, 222, 222, 222, 222,
+ 222, 222, 222, 222, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 244,
+ 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 255, 255, 255, 255, 255, 255
+ },
+ {
+ 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 16, 16, 16,
+ 16, 16, 16, 16, 16, 24, 24, 24, 24, 24, 24, 24, 24, 33, 33, 33,
+ 33, 33, 33, 33, 33, 33, 41, 41, 41, 41, 41, 41, 41, 41, 49, 49,
+ 49, 49, 49, 49, 49, 49, 57, 57, 57, 57, 57, 57, 57, 57, 66, 66,
+ 66, 66, 66, 66, 66, 66, 66, 74, 74, 74, 74, 74, 74, 74, 74, 82,
+ 82, 82, 82, 82, 82, 82, 82, 90, 90, 90, 90, 90, 90, 90, 90, 99,
+ 99, 99, 99, 99, 99, 99, 99, 99, 107, 107, 107, 107, 107, 107, 107, 107,
+ 115, 115, 115, 115, 115, 115, 115, 115, 123, 123, 123, 123, 123, 123, 123, 123,
+ 132, 132, 132, 132, 132, 132, 132, 132, 140, 140, 140, 140, 140, 140, 140, 140,
+ 148, 148, 148, 148, 148, 148, 148, 148, 156, 156, 156, 156, 156, 156, 156, 156,
+ 156, 165, 165, 165, 165, 165, 165, 165, 165, 173, 173, 173, 173, 173, 173, 173,
+ 173, 181, 181, 181, 181, 181, 181, 181, 181, 189, 189, 189, 189, 189, 189, 189,
+ 189, 189, 198, 198, 198, 198, 198, 198, 198, 198, 206, 206, 206, 206, 206, 206,
+ 206, 206, 214, 214, 214, 214, 214, 214, 214, 214, 222, 222, 222, 222, 222, 222,
+ 222, 222, 222, 231, 231, 231, 231, 231, 231, 231, 231, 239, 239, 239, 239, 239,
+ 239, 239, 239, 247, 247, 247, 247, 247, 247, 247, 247, 255, 255, 255, 255, 255
+ },
+ {
+ 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 13, 13, 13, 13, 13, 13,
+ 13, 19, 19, 19, 19, 19, 19, 26, 26, 26, 26, 26, 26, 26, 32, 32,
+ 32, 32, 32, 32, 39, 39, 39, 39, 39, 39, 39, 45, 45, 45, 45, 45,
+ 45, 52, 52, 52, 52, 52, 52, 52, 58, 58, 58, 58, 58, 58, 65, 65,
+ 65, 65, 65, 65, 65, 71, 71, 71, 71, 71, 71, 78, 78, 78, 78, 78,
+ 78, 78, 84, 84, 84, 84, 84, 84, 91, 91, 91, 91, 91, 91, 91, 97,
+ 97, 97, 97, 97, 97, 104, 104, 104, 104, 104, 104, 104, 110, 110, 110, 110,
+ 110, 110, 117, 117, 117, 117, 117, 117, 117, 123, 123, 123, 123, 123, 123, 123,
+ 132, 132, 132, 132, 132, 132, 132, 138, 138, 138, 138, 138, 138, 138, 145, 145,
+ 145, 145, 145, 145, 151, 151, 151, 151, 151, 151, 151, 158, 158, 158, 158, 158,
+ 158, 164, 164, 164, 164, 164, 164, 164, 171, 171, 171, 171, 171, 171, 177, 177,
+ 177, 177, 177, 177, 177, 184, 184, 184, 184, 184, 184, 190, 190, 190, 190, 190,
+ 190, 190, 197, 197, 197, 197, 197, 197, 203, 203, 203, 203, 203, 203, 203, 210,
+ 210, 210, 210, 210, 210, 216, 216, 216, 216, 216, 216, 216, 223, 223, 223, 223,
+ 223, 223, 229, 229, 229, 229, 229, 229, 229, 236, 236, 236, 236, 236, 236, 242,
+ 242, 242, 242, 242, 242, 242, 249, 249, 249, 249, 249, 249, 255, 255, 255, 255
+ },
+ {
+ 0, 0, 0, 5, 5, 5, 5, 5, 5, 11, 11, 11, 11, 11, 16, 16,
+ 16, 16, 16, 21, 21, 21, 21, 21, 21, 27, 27, 27, 27, 27, 32, 32,
+ 32, 32, 32, 32, 38, 38, 38, 38, 38, 43, 43, 43, 43, 43, 48, 48,
+ 48, 48, 48, 48, 54, 54, 54, 54, 54, 59, 59, 59, 59, 59, 59, 65,
+ 65, 65, 65, 65, 70, 70, 70, 70, 70, 70, 76, 76, 76, 76, 76, 81,
+ 81, 81, 81, 81, 86, 86, 86, 86, 86, 86, 92, 92, 92, 92, 92, 97,
+ 97, 97, 97, 97, 97, 103, 103, 103, 103, 103, 108, 108, 108, 108, 108, 113,
+ 113, 113, 113, 113, 113, 119, 119, 119, 119, 119, 124, 124, 124, 124, 124, 124,
+ 131, 131, 131, 131, 131, 131, 136, 136, 136, 136, 136, 142, 142, 142, 142, 142,
+ 142, 147, 147, 147, 147, 147, 152, 152, 152, 152, 152, 158, 158, 158, 158, 158,
+ 158, 163, 163, 163, 163, 163, 169, 169, 169, 169, 169, 169, 174, 174, 174, 174,
+ 174, 179, 179, 179, 179, 179, 185, 185, 185, 185, 185, 185, 190, 190, 190, 190,
+ 190, 196, 196, 196, 196, 196, 196, 201, 201, 201, 201, 201, 207, 207, 207, 207,
+ 207, 207, 212, 212, 212, 212, 212, 217, 217, 217, 217, 217, 223, 223, 223, 223,
+ 223, 223, 228, 228, 228, 228, 228, 234, 234, 234, 234, 234, 234, 239, 239, 239,
+ 239, 239, 244, 244, 244, 244, 244, 250, 250, 250, 250, 250, 250, 255, 255, 255
+ },
+ {
+ 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12, 16,
+ 16, 16, 16, 20, 20, 20, 20, 24, 24, 24, 24, 28, 28, 28, 28, 32,
+ 32, 32, 32, 36, 36, 36, 36, 40, 40, 40, 40, 44, 44, 44, 44, 48,
+ 48, 48, 48, 52, 52, 52, 52, 56, 56, 56, 56, 60, 60, 60, 60, 65,
+ 65, 65, 65, 65, 69, 69, 69, 69, 73, 73, 73, 73, 77, 77, 77, 77,
+ 81, 81, 81, 81, 85, 85, 85, 85, 89, 89, 89, 89, 93, 93, 93, 93,
+ 97, 97, 97, 97, 101, 101, 101, 101, 105, 105, 105, 105, 109, 109, 109, 109,
+ 113, 113, 113, 113, 117, 117, 117, 117, 121, 121, 121, 121, 125, 125, 125, 125,
+ 130, 130, 130, 130, 134, 134, 134, 134, 138, 138, 138, 138, 142, 142, 142, 142,
+ 146, 146, 146, 146, 150, 150, 150, 150, 154, 154, 154, 154, 158, 158, 158, 158,
+ 162, 162, 162, 162, 166, 166, 166, 166, 170, 170, 170, 170, 174, 174, 174, 174,
+ 178, 178, 178, 178, 182, 182, 182, 182, 186, 186, 186, 186, 190, 190, 190, 190,
+ 190, 195, 195, 195, 195, 199, 199, 199, 199, 203, 203, 203, 203, 207, 207, 207,
+ 207, 211, 211, 211, 211, 215, 215, 215, 215, 219, 219, 219, 219, 223, 223, 223,
+ 223, 227, 227, 227, 227, 231, 231, 231, 231, 235, 235, 235, 235, 239, 239, 239,
+ 239, 243, 243, 243, 243, 247, 247, 247, 247, 251, 251, 251, 251, 255, 255, 255
+ },
+ {
+ 0, 0, 3, 3, 3, 6, 6, 6, 9, 9, 9, 9, 13, 13, 13, 16,
+ 16, 16, 19, 19, 19, 22, 22, 22, 25, 25, 25, 25, 29, 29, 29, 32,
+ 32, 32, 35, 35, 35, 38, 38, 38, 38, 42, 42, 42, 45, 45, 45, 48,
+ 48, 48, 51, 51, 51, 54, 54, 54, 54, 58, 58, 58, 61, 61, 61, 64,
+ 64, 64, 67, 67, 67, 67, 71, 71, 71, 74, 74, 74, 77, 77, 77, 80,
+ 80, 80, 83, 83, 83, 83, 87, 87, 87, 90, 90, 90, 93, 93, 93, 96,
+ 96, 96, 96, 100, 100, 100, 103, 103, 103, 106, 106, 106, 109, 109, 109, 112,
+ 112, 112, 112, 116, 116, 116, 119, 119, 119, 122, 122, 122, 125, 125, 125, 125,
+ 130, 130, 130, 130, 133, 133, 133, 136, 136, 136, 139, 139, 139, 143, 143, 143,
+ 143, 146, 146, 146, 149, 149, 149, 152, 152, 152, 155, 155, 155, 159, 159, 159,
+ 159, 162, 162, 162, 165, 165, 165, 168, 168, 168, 172, 172, 172, 172, 175, 175,
+ 175, 178, 178, 178, 181, 181, 181, 184, 184, 184, 188, 188, 188, 188, 191, 191,
+ 191, 194, 194, 194, 197, 197, 197, 201, 201, 201, 201, 204, 204, 204, 207, 207,
+ 207, 210, 210, 210, 213, 213, 213, 217, 217, 217, 217, 220, 220, 220, 223, 223,
+ 223, 226, 226, 226, 230, 230, 230, 230, 233, 233, 233, 236, 236, 236, 239, 239,
+ 239, 242, 242, 242, 246, 246, 246, 246, 249, 249, 249, 252, 252, 252, 255, 255
+ },
+ {
+ 0, 0, 2, 2, 5, 5, 5, 8, 8, 8, 10, 10, 13, 13, 13, 16,
+ 16, 16, 18, 18, 21, 21, 21, 24, 24, 24, 26, 26, 29, 29, 29, 32,
+ 32, 32, 35, 35, 35, 37, 37, 40, 40, 40, 43, 43, 43, 45, 45, 48,
+ 48, 48, 51, 51, 51, 53, 53, 56, 56, 56, 59, 59, 59, 61, 61, 64,
+ 64, 64, 67, 67, 67, 70, 70, 70, 72, 72, 75, 75, 75, 78, 78, 78,
+ 80, 80, 83, 83, 83, 86, 86, 86, 88, 88, 91, 91, 91, 94, 94, 94,
+ 96, 96, 99, 99, 99, 102, 102, 102, 104, 104, 107, 107, 107, 110, 110, 110,
+ 112, 112, 115, 115, 115, 118, 118, 118, 120, 120, 123, 123, 123, 126, 126, 126,
+ 129, 129, 129, 132, 132, 132, 135, 135, 137, 137, 137, 140, 140, 140, 143, 143,
+ 145, 145, 145, 148, 148, 148, 151, 151, 153, 153, 153, 156, 156, 156, 159, 159,
+ 161, 161, 161, 164, 164, 164, 167, 167, 169, 169, 169, 172, 172, 172, 175, 175,
+ 177, 177, 177, 180, 180, 180, 183, 183, 185, 185, 185, 188, 188, 188, 191, 191,
+ 191, 194, 194, 196, 196, 196, 199, 199, 199, 202, 202, 204, 204, 204, 207, 207,
+ 207, 210, 210, 212, 212, 212, 215, 215, 215, 218, 218, 220, 220, 220, 223, 223,
+ 223, 226, 226, 226, 229, 229, 231, 231, 231, 234, 234, 234, 237, 237, 239, 239,
+ 239, 242, 242, 242, 245, 245, 247, 247, 247, 250, 250, 250, 253, 253, 255, 255
+ },
+ {
+ 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14,
+ 16, 16, 18, 18, 20, 20, 22, 22, 24, 24, 26, 26, 28, 28, 30, 30,
+ 32, 32, 34, 34, 36, 36, 38, 38, 40, 40, 42, 42, 44, 44, 46, 46,
+ 48, 48, 50, 50, 52, 52, 54, 54, 56, 56, 58, 58, 60, 60, 62, 62,
+ 64, 64, 66, 66, 68, 68, 70, 70, 72, 72, 74, 74, 76, 76, 78, 78,
+ 80, 80, 82, 82, 84, 84, 86, 86, 88, 88, 90, 90, 92, 92, 94, 94,
+ 96, 96, 98, 98, 100, 100, 102, 102, 104, 104, 106, 106, 108, 108, 110, 110,
+ 112, 112, 114, 114, 116, 116, 118, 118, 120, 120, 122, 122, 124, 124, 126, 126,
+ 129, 129, 131, 131, 133, 133, 135, 135, 137, 137, 139, 139, 141, 141, 143, 143,
+ 145, 145, 147, 147, 149, 149, 151, 151, 153, 153, 155, 155, 157, 157, 159, 159,
+ 161, 161, 163, 163, 165, 165, 167, 167, 169, 169, 171, 171, 173, 173, 175, 175,
+ 177, 177, 179, 179, 181, 181, 183, 183, 185, 185, 187, 187, 189, 189, 191, 191,
+ 193, 193, 195, 195, 197, 197, 199, 199, 201, 201, 203, 203, 205, 205, 207, 207,
+ 209, 209, 211, 211, 213, 213, 215, 215, 217, 217, 219, 219, 221, 221, 223, 223,
+ 225, 225, 227, 227, 229, 229, 231, 231, 233, 233, 235, 235, 237, 237, 239, 239,
+ 241, 241, 243, 243, 245, 245, 247, 247, 249, 249, 251, 251, 253, 253, 255, 255
+ },
+ {
+ 0, 1, 1, 3, 4, 4, 6, 6, 8, 9, 9, 11, 12, 12, 14, 14,
+ 16, 17, 17, 19, 20, 20, 22, 22, 24, 25, 25, 27, 28, 28, 30, 30,
+ 32, 33, 33, 35, 36, 36, 38, 38, 40, 41, 41, 43, 44, 44, 46, 46,
+ 48, 49, 49, 51, 52, 52, 54, 54, 56, 57, 57, 59, 60, 60, 62, 62,
+ 64, 65, 65, 67, 68, 68, 70, 70, 72, 73, 73, 75, 76, 76, 78, 78,
+ 80, 81, 81, 83, 84, 84, 86, 86, 88, 89, 89, 91, 92, 92, 94, 94,
+ 96, 97, 97, 99, 100, 100, 102, 102, 104, 105, 105, 107, 108, 108, 110, 110,
+ 112, 113, 113, 115, 116, 116, 118, 118, 120, 121, 121, 123, 124, 124, 126, 126,
+ 129, 129, 131, 131, 132, 134, 134, 135, 137, 137, 139, 139, 140, 142, 142, 143,
+ 145, 145, 147, 147, 148, 150, 150, 151, 153, 153, 155, 155, 156, 158, 158, 159,
+ 161, 161, 163, 163, 164, 166, 166, 167, 169, 169, 171, 171, 172, 174, 174, 175,
+ 177, 177, 179, 179, 180, 182, 182, 183, 185, 185, 187, 187, 188, 190, 190, 191,
+ 193, 193, 195, 195, 196, 198, 198, 199, 201, 201, 203, 203, 204, 206, 206, 207,
+ 209, 209, 211, 211, 212, 214, 214, 215, 217, 217, 219, 219, 220, 222, 222, 223,
+ 225, 225, 227, 227, 228, 230, 230, 231, 233, 233, 235, 235, 236, 238, 238, 239,
+ 241, 241, 243, 243, 244, 246, 246, 247, 249, 249, 251, 251, 252, 254, 254, 255
+ },
+ {
+ 0, 1, 2, 2, 4, 5, 6, 6, 8, 9, 10, 10, 12, 13, 14, 14,
+ 16, 17, 18, 18, 20, 21, 22, 22, 24, 25, 26, 26, 28, 29, 30, 30,
+ 32, 33, 34, 34, 36, 37, 38, 38, 40, 41, 42, 42, 44, 45, 46, 46,
+ 48, 49, 50, 50, 52, 53, 54, 54, 56, 57, 58, 58, 60, 61, 62, 62,
+ 64, 65, 66, 66, 68, 69, 70, 70, 72, 73, 74, 74, 76, 77, 78, 78,
+ 80, 81, 82, 82, 84, 85, 86, 86, 88, 89, 90, 90, 92, 93, 94, 94,
+ 96, 97, 98, 98, 100, 101, 102, 102, 104, 105, 106, 106, 108, 109, 110, 110,
+ 112, 113, 114, 114, 116, 117, 118, 118, 120, 121, 122, 122, 124, 125, 126, 126,
+ 129, 129, 130, 131, 133, 133, 134, 135, 137, 137, 138, 139, 141, 141, 142, 143,
+ 145, 145, 146, 147, 149, 149, 150, 151, 153, 153, 154, 155, 157, 157, 158, 159,
+ 161, 161, 162, 163, 165, 165, 166, 167, 169, 169, 170, 171, 173, 173, 174, 175,
+ 177, 177, 178, 179, 181, 181, 182, 183, 185, 185, 186, 187, 189, 189, 190, 191,
+ 193, 193, 194, 195, 197, 197, 198, 199, 201, 201, 202, 203, 205, 205, 206, 207,
+ 209, 209, 210, 211, 213, 213, 214, 215, 217, 217, 218, 219, 221, 221, 222, 223,
+ 225, 225, 226, 227, 229, 229, 230, 231, 233, 233, 234, 235, 237, 237, 238, 239,
+ 241, 241, 242, 243, 245, 245, 246, 247, 249, 249, 250, 251, 253, 253, 254, 255
+ },
+ {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
+ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
+ 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
+ 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+ 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
+ 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
+ 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
+ 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
+ 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
+ }
+};
+
+// Starts from QUANT_6
+// Scrambled
+const uint8_t color_uquant_to_scrambled_pquant_tables[17][256] {
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 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, 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, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 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,
+ 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, 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
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 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, 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, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 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, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 11, 11, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 3,
+ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 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, 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, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
+ 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 11, 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, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4,
+ 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 8, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 6, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 10, 10, 10, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18,
+ 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 11, 11, 11, 11, 11,
+ 11, 11, 11, 11, 11, 11, 11, 11, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
+ 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 9, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1
+ },
+ {
+ 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 8, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 10, 10, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 4, 4, 12, 12, 12, 12, 12, 12, 12, 12,
+ 12, 12, 12, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 6, 14, 14, 14, 14, 14, 14, 14,
+ 14, 14, 14, 14, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
+ 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 15, 15, 15, 15,
+ 15, 15, 15, 15, 15, 15, 15, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 13, 13, 13,
+ 13, 13, 13, 13, 13, 13, 13, 13, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 11, 11,
+ 11, 11, 11, 11, 11, 11, 11, 11, 11, 3, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 9,
+ 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1, 1, 1
+ },
+ {
+ 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
+ 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4,
+ 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6,
+ 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8,
+ 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 10,
+ 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 12,
+ 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13,
+ 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15,
+ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17,
+ 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 20, 20, 20, 20, 20, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21,
+ 21, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23,
+ 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25,
+ 25, 25, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27,
+ 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29,
+ 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31
+ },
+ {
+ 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 16, 16, 16, 16, 16, 16,
+ 16, 24, 24, 24, 24, 24, 24, 32, 32, 32, 32, 32, 32, 32, 2, 2,
+ 2, 2, 2, 2, 10, 10, 10, 10, 10, 10, 10, 18, 18, 18, 18, 18,
+ 18, 26, 26, 26, 26, 26, 26, 26, 34, 34, 34, 34, 34, 34, 4, 4,
+ 4, 4, 4, 4, 4, 12, 12, 12, 12, 12, 12, 20, 20, 20, 20, 20,
+ 20, 20, 28, 28, 28, 28, 28, 28, 36, 36, 36, 36, 36, 36, 36, 6,
+ 6, 6, 6, 6, 6, 14, 14, 14, 14, 14, 14, 14, 22, 22, 22, 22,
+ 22, 22, 30, 30, 30, 30, 30, 30, 30, 38, 38, 38, 38, 38, 38, 38,
+ 39, 39, 39, 39, 39, 39, 39, 31, 31, 31, 31, 31, 31, 31, 23, 23,
+ 23, 23, 23, 23, 15, 15, 15, 15, 15, 15, 15, 7, 7, 7, 7, 7,
+ 7, 37, 37, 37, 37, 37, 37, 37, 29, 29, 29, 29, 29, 29, 21, 21,
+ 21, 21, 21, 21, 21, 13, 13, 13, 13, 13, 13, 5, 5, 5, 5, 5,
+ 5, 5, 35, 35, 35, 35, 35, 35, 27, 27, 27, 27, 27, 27, 27, 19,
+ 19, 19, 19, 19, 19, 11, 11, 11, 11, 11, 11, 11, 3, 3, 3, 3,
+ 3, 3, 33, 33, 33, 33, 33, 33, 33, 25, 25, 25, 25, 25, 25, 17,
+ 17, 17, 17, 17, 17, 17, 9, 9, 9, 9, 9, 9, 1, 1, 1, 1
+ },
+ {
+ 0, 0, 0, 16, 16, 16, 16, 16, 16, 32, 32, 32, 32, 32, 2, 2,
+ 2, 2, 2, 18, 18, 18, 18, 18, 18, 34, 34, 34, 34, 34, 4, 4,
+ 4, 4, 4, 4, 20, 20, 20, 20, 20, 36, 36, 36, 36, 36, 6, 6,
+ 6, 6, 6, 6, 22, 22, 22, 22, 22, 38, 38, 38, 38, 38, 38, 8,
+ 8, 8, 8, 8, 24, 24, 24, 24, 24, 24, 40, 40, 40, 40, 40, 10,
+ 10, 10, 10, 10, 26, 26, 26, 26, 26, 26, 42, 42, 42, 42, 42, 12,
+ 12, 12, 12, 12, 12, 28, 28, 28, 28, 28, 44, 44, 44, 44, 44, 14,
+ 14, 14, 14, 14, 14, 30, 30, 30, 30, 30, 46, 46, 46, 46, 46, 46,
+ 47, 47, 47, 47, 47, 47, 31, 31, 31, 31, 31, 15, 15, 15, 15, 15,
+ 15, 45, 45, 45, 45, 45, 29, 29, 29, 29, 29, 13, 13, 13, 13, 13,
+ 13, 43, 43, 43, 43, 43, 27, 27, 27, 27, 27, 27, 11, 11, 11, 11,
+ 11, 41, 41, 41, 41, 41, 25, 25, 25, 25, 25, 25, 9, 9, 9, 9,
+ 9, 39, 39, 39, 39, 39, 39, 23, 23, 23, 23, 23, 7, 7, 7, 7,
+ 7, 7, 37, 37, 37, 37, 37, 21, 21, 21, 21, 21, 5, 5, 5, 5,
+ 5, 5, 35, 35, 35, 35, 35, 19, 19, 19, 19, 19, 19, 3, 3, 3,
+ 3, 3, 33, 33, 33, 33, 33, 17, 17, 17, 17, 17, 17, 1, 1, 1
+ },
+ {
+ 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4,
+ 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8,
+ 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12,
+ 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16,
+ 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
+ 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
+ 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
+ 28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
+ 32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
+ 36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
+ 40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
+ 44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
+ 47, 48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51,
+ 51, 52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55,
+ 55, 56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59,
+ 59, 60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63
+ },
+ {
+ 0, 0, 16, 16, 16, 32, 32, 32, 48, 48, 48, 48, 64, 64, 64, 2,
+ 2, 2, 18, 18, 18, 34, 34, 34, 50, 50, 50, 50, 66, 66, 66, 4,
+ 4, 4, 20, 20, 20, 36, 36, 36, 36, 52, 52, 52, 68, 68, 68, 6,
+ 6, 6, 22, 22, 22, 38, 38, 38, 38, 54, 54, 54, 70, 70, 70, 8,
+ 8, 8, 24, 24, 24, 24, 40, 40, 40, 56, 56, 56, 72, 72, 72, 10,
+ 10, 10, 26, 26, 26, 26, 42, 42, 42, 58, 58, 58, 74, 74, 74, 12,
+ 12, 12, 12, 28, 28, 28, 44, 44, 44, 60, 60, 60, 76, 76, 76, 14,
+ 14, 14, 14, 30, 30, 30, 46, 46, 46, 62, 62, 62, 78, 78, 78, 78,
+ 79, 79, 79, 79, 63, 63, 63, 47, 47, 47, 31, 31, 31, 15, 15, 15,
+ 15, 77, 77, 77, 61, 61, 61, 45, 45, 45, 29, 29, 29, 13, 13, 13,
+ 13, 75, 75, 75, 59, 59, 59, 43, 43, 43, 27, 27, 27, 27, 11, 11,
+ 11, 73, 73, 73, 57, 57, 57, 41, 41, 41, 25, 25, 25, 25, 9, 9,
+ 9, 71, 71, 71, 55, 55, 55, 39, 39, 39, 39, 23, 23, 23, 7, 7,
+ 7, 69, 69, 69, 53, 53, 53, 37, 37, 37, 37, 21, 21, 21, 5, 5,
+ 5, 67, 67, 67, 51, 51, 51, 51, 35, 35, 35, 19, 19, 19, 3, 3,
+ 3, 65, 65, 65, 49, 49, 49, 49, 33, 33, 33, 17, 17, 17, 1, 1
+ },
+ {
+ 0, 0, 32, 32, 64, 64, 64, 2, 2, 2, 34, 34, 66, 66, 66, 4,
+ 4, 4, 36, 36, 68, 68, 68, 6, 6, 6, 38, 38, 70, 70, 70, 8,
+ 8, 8, 40, 40, 40, 72, 72, 10, 10, 10, 42, 42, 42, 74, 74, 12,
+ 12, 12, 44, 44, 44, 76, 76, 14, 14, 14, 46, 46, 46, 78, 78, 16,
+ 16, 16, 48, 48, 48, 80, 80, 80, 18, 18, 50, 50, 50, 82, 82, 82,
+ 20, 20, 52, 52, 52, 84, 84, 84, 22, 22, 54, 54, 54, 86, 86, 86,
+ 24, 24, 56, 56, 56, 88, 88, 88, 26, 26, 58, 58, 58, 90, 90, 90,
+ 28, 28, 60, 60, 60, 92, 92, 92, 30, 30, 62, 62, 62, 94, 94, 94,
+ 95, 95, 95, 63, 63, 63, 31, 31, 93, 93, 93, 61, 61, 61, 29, 29,
+ 91, 91, 91, 59, 59, 59, 27, 27, 89, 89, 89, 57, 57, 57, 25, 25,
+ 87, 87, 87, 55, 55, 55, 23, 23, 85, 85, 85, 53, 53, 53, 21, 21,
+ 83, 83, 83, 51, 51, 51, 19, 19, 81, 81, 81, 49, 49, 49, 17, 17,
+ 17, 79, 79, 47, 47, 47, 15, 15, 15, 77, 77, 45, 45, 45, 13, 13,
+ 13, 75, 75, 43, 43, 43, 11, 11, 11, 73, 73, 41, 41, 41, 9, 9,
+ 9, 71, 71, 71, 39, 39, 7, 7, 7, 69, 69, 69, 37, 37, 5, 5,
+ 5, 67, 67, 67, 35, 35, 3, 3, 3, 65, 65, 65, 33, 33, 1, 1
+ },
+ {
+ 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7,
+ 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15,
+ 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23,
+ 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 31, 31,
+ 32, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37, 38, 38, 39, 39,
+ 40, 40, 41, 41, 42, 42, 43, 43, 44, 44, 45, 45, 46, 46, 47, 47,
+ 48, 48, 49, 49, 50, 50, 51, 51, 52, 52, 53, 53, 54, 54, 55, 55,
+ 56, 56, 57, 57, 58, 58, 59, 59, 60, 60, 61, 61, 62, 62, 63, 63,
+ 64, 64, 65, 65, 66, 66, 67, 67, 68, 68, 69, 69, 70, 70, 71, 71,
+ 72, 72, 73, 73, 74, 74, 75, 75, 76, 76, 77, 77, 78, 78, 79, 79,
+ 80, 80, 81, 81, 82, 82, 83, 83, 84, 84, 85, 85, 86, 86, 87, 87,
+ 88, 88, 89, 89, 90, 90, 91, 91, 92, 92, 93, 93, 94, 94, 95, 95,
+ 96, 96, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 102, 102, 103, 103,
+ 104, 104, 105, 105, 106, 106, 107, 107, 108, 108, 109, 109, 110, 110, 111, 111,
+ 112, 112, 113, 113, 114, 114, 115, 115, 116, 116, 117, 117, 118, 118, 119, 119,
+ 120, 120, 121, 121, 122, 122, 123, 123, 124, 124, 125, 125, 126, 126, 127, 127
+ },
+ {
+ 0, 32, 32, 64, 96, 96, 128, 128, 2, 34, 34, 66, 98, 98, 130, 130,
+ 4, 36, 36, 68, 100, 100, 132, 132, 6, 38, 38, 70, 102, 102, 134, 134,
+ 8, 40, 40, 72, 104, 104, 136, 136, 10, 42, 42, 74, 106, 106, 138, 138,
+ 12, 44, 44, 76, 108, 108, 140, 140, 14, 46, 46, 78, 110, 110, 142, 142,
+ 16, 48, 48, 80, 112, 112, 144, 144, 18, 50, 50, 82, 114, 114, 146, 146,
+ 20, 52, 52, 84, 116, 116, 148, 148, 22, 54, 54, 86, 118, 118, 150, 150,
+ 24, 56, 56, 88, 120, 120, 152, 152, 26, 58, 58, 90, 122, 122, 154, 154,
+ 28, 60, 60, 92, 124, 124, 156, 156, 30, 62, 62, 94, 126, 126, 158, 158,
+ 159, 159, 127, 127, 95, 63, 63, 31, 157, 157, 125, 125, 93, 61, 61, 29,
+ 155, 155, 123, 123, 91, 59, 59, 27, 153, 153, 121, 121, 89, 57, 57, 25,
+ 151, 151, 119, 119, 87, 55, 55, 23, 149, 149, 117, 117, 85, 53, 53, 21,
+ 147, 147, 115, 115, 83, 51, 51, 19, 145, 145, 113, 113, 81, 49, 49, 17,
+ 143, 143, 111, 111, 79, 47, 47, 15, 141, 141, 109, 109, 77, 45, 45, 13,
+ 139, 139, 107, 107, 75, 43, 43, 11, 137, 137, 105, 105, 73, 41, 41, 9,
+ 135, 135, 103, 103, 71, 39, 39, 7, 133, 133, 101, 101, 69, 37, 37, 5,
+ 131, 131, 99, 99, 67, 35, 35, 3, 129, 129, 97, 97, 65, 33, 33, 1
+ },
+ {
+ 0, 64, 128, 128, 2, 66, 130, 130, 4, 68, 132, 132, 6, 70, 134, 134,
+ 8, 72, 136, 136, 10, 74, 138, 138, 12, 76, 140, 140, 14, 78, 142, 142,
+ 16, 80, 144, 144, 18, 82, 146, 146, 20, 84, 148, 148, 22, 86, 150, 150,
+ 24, 88, 152, 152, 26, 90, 154, 154, 28, 92, 156, 156, 30, 94, 158, 158,
+ 32, 96, 160, 160, 34, 98, 162, 162, 36, 100, 164, 164, 38, 102, 166, 166,
+ 40, 104, 168, 168, 42, 106, 170, 170, 44, 108, 172, 172, 46, 110, 174, 174,
+ 48, 112, 176, 176, 50, 114, 178, 178, 52, 116, 180, 180, 54, 118, 182, 182,
+ 56, 120, 184, 184, 58, 122, 186, 186, 60, 124, 188, 188, 62, 126, 190, 190,
+ 191, 191, 127, 63, 189, 189, 125, 61, 187, 187, 123, 59, 185, 185, 121, 57,
+ 183, 183, 119, 55, 181, 181, 117, 53, 179, 179, 115, 51, 177, 177, 113, 49,
+ 175, 175, 111, 47, 173, 173, 109, 45, 171, 171, 107, 43, 169, 169, 105, 41,
+ 167, 167, 103, 39, 165, 165, 101, 37, 163, 163, 99, 35, 161, 161, 97, 33,
+ 159, 159, 95, 31, 157, 157, 93, 29, 155, 155, 91, 27, 153, 153, 89, 25,
+ 151, 151, 87, 23, 149, 149, 85, 21, 147, 147, 83, 19, 145, 145, 81, 17,
+ 143, 143, 79, 15, 141, 141, 77, 13, 139, 139, 75, 11, 137, 137, 73, 9,
+ 135, 135, 71, 7, 133, 133, 69, 5, 131, 131, 67, 3, 129, 129, 65, 1
+ },
+ {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
+ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
+ 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
+ 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+ 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
+ 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
+ 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
+ 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
+ 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
+ }
+};
+
+#endif
+
+// Starts from QUANT_6
+// Scrambled
+static const uint8_t color_scrambled_pquant_to_uquant_q6[6] {
+ 0, 255, 51, 204, 102, 153
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q8[8] {
+ 0, 36, 73, 109, 146, 182, 219, 255
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q10[10] {
+ 0, 255, 28, 227, 56, 199, 84, 171, 113, 142
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q12[12] {
+ 0, 255, 69, 186, 23, 232, 92, 163, 46, 209, 116, 139
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q16[16] {
+ 0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q20[20] {
+ 0, 255, 67, 188, 13, 242, 80, 175, 27, 228, 94, 161, 40, 215, 107, 148,
+ 54, 201, 121, 134
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q24[24] {
+ 0, 255, 33, 222, 66, 189, 99, 156, 11, 244, 44, 211, 77, 178, 110, 145,
+ 22, 233, 55, 200, 88, 167, 121, 134
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q32[32] {
+ 0, 8, 16, 24, 33, 41, 49, 57, 66, 74, 82, 90, 99, 107, 115, 123,
+ 132, 140, 148, 156, 165, 173, 181, 189, 198, 206, 214, 222, 231, 239, 247, 255
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q40[40] {
+ 0, 255, 32, 223, 65, 190, 97, 158, 6, 249, 39, 216, 71, 184, 104, 151,
+ 13, 242, 45, 210, 78, 177, 110, 145, 19, 236, 52, 203, 84, 171, 117, 138,
+ 26, 229, 58, 197, 91, 164, 123, 132
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q48[48] {
+ 0, 255, 16, 239, 32, 223, 48, 207, 65, 190, 81, 174, 97, 158, 113, 142,
+ 5, 250, 21, 234, 38, 217, 54, 201, 70, 185, 86, 169, 103, 152, 119, 136,
+ 11, 244, 27, 228, 43, 212, 59, 196, 76, 179, 92, 163, 108, 147, 124, 131
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q64[64] {
+ 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60,
+ 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125,
+ 130, 134, 138, 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190,
+ 195, 199, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255,
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q80[80] {
+ 0, 255, 16, 239, 32, 223, 48, 207, 64, 191, 80, 175, 96, 159, 112, 143,
+ 3, 252, 19, 236, 35, 220, 51, 204, 67, 188, 83, 172, 100, 155, 116, 139,
+ 6, 249, 22, 233, 38, 217, 54, 201, 71, 184, 87, 168, 103, 152, 119, 136,
+ 9, 246, 25, 230, 42, 213, 58, 197, 74, 181, 90, 165, 106, 149, 122, 133,
+ 13, 242, 29, 226, 45, 210, 61, 194, 77, 178, 93, 162, 109, 146, 125, 130
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q96[96] {
+ 0, 255, 8, 247, 16, 239, 24, 231, 32, 223, 40, 215, 48, 207, 56, 199,
+ 64, 191, 72, 183, 80, 175, 88, 167, 96, 159, 104, 151, 112, 143, 120, 135,
+ 2, 253, 10, 245, 18, 237, 26, 229, 35, 220, 43, 212, 51, 204, 59, 196,
+ 67, 188, 75, 180, 83, 172, 91, 164, 99, 156, 107, 148, 115, 140, 123, 132,
+ 5, 250, 13, 242, 21, 234, 29, 226, 37, 218, 45, 210, 53, 202, 61, 194,
+ 70, 185, 78, 177, 86, 169, 94, 161, 102, 153, 110, 145, 118, 137, 126, 129
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q128[128] {
+ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
+ 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62,
+ 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94,
+ 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126,
+ 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159,
+ 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191,
+ 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223,
+ 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q160[160] {
+ 0, 255, 8, 247, 16, 239, 24, 231, 32, 223, 40, 215, 48, 207, 56, 199,
+ 64, 191, 72, 183, 80, 175, 88, 167, 96, 159, 104, 151, 112, 143, 120, 135,
+ 1, 254, 9, 246, 17, 238, 25, 230, 33, 222, 41, 214, 49, 206, 57, 198,
+ 65, 190, 73, 182, 81, 174, 89, 166, 97, 158, 105, 150, 113, 142, 121, 134,
+ 3, 252, 11, 244, 19, 236, 27, 228, 35, 220, 43, 212, 51, 204, 59, 196,
+ 67, 188, 75, 180, 83, 172, 91, 164, 99, 156, 107, 148, 115, 140, 123, 132,
+ 4, 251, 12, 243, 20, 235, 28, 227, 36, 219, 44, 211, 52, 203, 60, 195,
+ 68, 187, 76, 179, 84, 171, 92, 163, 100, 155, 108, 147, 116, 139, 124, 131,
+ 6, 249, 14, 241, 22, 233, 30, 225, 38, 217, 46, 209, 54, 201, 62, 193,
+ 70, 185, 78, 177, 86, 169, 94, 161, 102, 153, 110, 145, 118, 137, 126, 129
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q192[192] {
+ 0, 255, 4, 251, 8, 247, 12, 243, 16, 239, 20, 235, 24, 231, 28, 227,
+ 32, 223, 36, 219, 40, 215, 44, 211, 48, 207, 52, 203, 56, 199, 60, 195,
+ 64, 191, 68, 187, 72, 183, 76, 179, 80, 175, 84, 171, 88, 167, 92, 163,
+ 96, 159, 100, 155, 104, 151, 108, 147, 112, 143, 116, 139, 120, 135, 124, 131,
+ 1, 254, 5, 250, 9, 246, 13, 242, 17, 238, 21, 234, 25, 230, 29, 226,
+ 33, 222, 37, 218, 41, 214, 45, 210, 49, 206, 53, 202, 57, 198, 61, 194,
+ 65, 190, 69, 186, 73, 182, 77, 178, 81, 174, 85, 170, 89, 166, 93, 162,
+ 97, 158, 101, 154, 105, 150, 109, 146, 113, 142, 117, 138, 121, 134, 125, 130,
+ 2, 253, 6, 249, 10, 245, 14, 241, 18, 237, 22, 233, 26, 229, 30, 225,
+ 34, 221, 38, 217, 42, 213, 46, 209, 50, 205, 54, 201, 58, 197, 62, 193,
+ 66, 189, 70, 185, 74, 181, 78, 177, 82, 173, 86, 169, 90, 165, 94, 161,
+ 98, 157, 102, 153, 106, 149, 110, 145, 114, 141, 118, 137, 122, 133, 126, 129
+};
+
+static const uint8_t color_scrambled_pquant_to_uquant_q256[256] {
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
+ 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79,
+ 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
+ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127,
+ 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
+ 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+ 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175,
+ 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191,
+ 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207,
+ 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223,
+ 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255
+};
+
+const uint8_t* color_scrambled_pquant_to_uquant_tables[17] {
+ color_scrambled_pquant_to_uquant_q6,
+ color_scrambled_pquant_to_uquant_q8,
+ color_scrambled_pquant_to_uquant_q10,
+ color_scrambled_pquant_to_uquant_q12,
+ color_scrambled_pquant_to_uquant_q16,
+ color_scrambled_pquant_to_uquant_q20,
+ color_scrambled_pquant_to_uquant_q24,
+ color_scrambled_pquant_to_uquant_q32,
+ color_scrambled_pquant_to_uquant_q40,
+ color_scrambled_pquant_to_uquant_q48,
+ color_scrambled_pquant_to_uquant_q64,
+ color_scrambled_pquant_to_uquant_q80,
+ color_scrambled_pquant_to_uquant_q96,
+ color_scrambled_pquant_to_uquant_q128,
+ color_scrambled_pquant_to_uquant_q160,
+ color_scrambled_pquant_to_uquant_q192,
+ color_scrambled_pquant_to_uquant_q256
+};
+
+// The quant_mode_table[integer_count/2][bits] gives us the quantization level for a given integer
+// count and number of bits that the integer may fit into.
+const int8_t quant_mode_table[10][128] {
+ {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+ },
+ {
+ -1, -1, 0, 0, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20
+ },
+ {
+ -1, -1, -1, -1, 0, 0, 0, 1, 2, 2, 3, 4, 5, 5, 6, 7,
+ 8, 8, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 17, 18, 19,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20
+ },
+ {
+ -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3,
+ 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11,
+ 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20
+ },
+ {
+ -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 1, 1, 1,
+ 2, 2, 2, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7,
+ 8, 8, 8, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 13, 13, 13,
+ 14, 14, 14, 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 19, 19, 19,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20
+ },
+ {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 4, 4, 5, 5,
+ 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8, 9, 9, 10, 10,
+ 10, 10, 11, 11, 11, 11, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14,
+ 15, 15, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 19, 19, 19, 19,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20
+ },
+ {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+ 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
+ 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
+ 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
+ 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20
+ },
+ {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2,
+ 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6,
+ 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 9, 9, 9,
+ 9, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13,
+ 13, 13, 13, 13, 14, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16,
+ 16, 16, 17, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 19,
+ 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20
+ },
+ {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
+ 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4,
+ 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7,
+ 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10,
+ 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13,
+ 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16,
+ 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 19, 19
+ },
+ {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1,
+ 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4,
+ 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6,
+ 6, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9,
+ 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11,
+ 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14,
+ 14, 14, 15, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 17, 17
+ }
+};
diff --git a/thirdparty/astcenc/astcenc_symbolic_physical.cpp b/thirdparty/astcenc/astcenc_symbolic_physical.cpp
new file mode 100644
index 0000000000..80221a6013
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_symbolic_physical.cpp
@@ -0,0 +1,534 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2021 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Functions for converting between symbolic and physical encodings.
+ */
+
+#include "astcenc_internal.h"
+
+#include <cassert>
+
+/**
+ * @brief Write up to 8 bits at an arbitrary bit offset.
+ *
+ * The stored value is at most 8 bits, but can be stored at an offset of between 0 and 7 bits so
+ * may span two separate bytes in memory.
+ *
+ * @param value The value to write.
+ * @param bitcount The number of bits to write, starting from LSB.
+ * @param bitoffset The bit offset to store at, between 0 and 7.
+ * @param[in,out] ptr The data pointer to write to.
+ */
+static inline void write_bits(
+ int value,
+ int bitcount,
+ int bitoffset,
+ uint8_t* ptr
+) {
+ int mask = (1 << bitcount) - 1;
+ value &= mask;
+ ptr += bitoffset >> 3;
+ bitoffset &= 7;
+ value <<= bitoffset;
+ mask <<= bitoffset;
+ mask = ~mask;
+
+ ptr[0] &= mask;
+ ptr[0] |= value;
+ ptr[1] &= mask >> 8;
+ ptr[1] |= value >> 8;
+}
+
+/**
+ * @brief Read up to 8 bits at an arbitrary bit offset.
+ *
+ * The stored value is at most 8 bits, but can be stored at an offset of between 0 and 7 bits so may
+ * span two separate bytes in memory.
+ *
+ * @param bitcount The number of bits to read.
+ * @param bitoffset The bit offset to read from, between 0 and 7.
+ * @param[in,out] ptr The data pointer to read from.
+ *
+ * @return The read value.
+ */
+static inline int read_bits(
+ int bitcount,
+ int bitoffset,
+ const uint8_t* ptr
+) {
+ int mask = (1 << bitcount) - 1;
+ ptr += bitoffset >> 3;
+ bitoffset &= 7;
+ int value = ptr[0] | (ptr[1] << 8);
+ value >>= bitoffset;
+ value &= mask;
+ return value;
+}
+
+/**
+ * @brief Reverse bits in a byte.
+ *
+ * @param p The value to reverse.
+ *
+ * @return The reversed result.
+ */
+static inline int bitrev8(int p)
+{
+ p = ((p & 0x0F) << 4) | ((p >> 4) & 0x0F);
+ p = ((p & 0x33) << 2) | ((p >> 2) & 0x33);
+ p = ((p & 0x55) << 1) | ((p >> 1) & 0x55);
+ return p;
+}
+
+/* See header for documentation. */
+void symbolic_to_physical(
+ const block_size_descriptor& bsd,
+ const symbolic_compressed_block& scb,
+ physical_compressed_block& pcb
+) {
+ assert(scb.block_type != SYM_BTYPE_ERROR);
+
+ // Constant color block using UNORM16 colors
+ if (scb.block_type == SYM_BTYPE_CONST_U16)
+ {
+ // There is currently no attempt to coalesce larger void-extents
+ static const uint8_t cbytes[8] { 0xFC, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+ for (unsigned int i = 0; i < 8; i++)
+ {
+ pcb.data[i] = cbytes[i];
+ }
+
+ for (unsigned int i = 0; i < BLOCK_MAX_COMPONENTS; i++)
+ {
+ pcb.data[2 * i + 8] = scb.constant_color[i] & 0xFF;
+ pcb.data[2 * i + 9] = (scb.constant_color[i] >> 8) & 0xFF;
+ }
+
+ return;
+ }
+
+ // Constant color block using FP16 colors
+ if (scb.block_type == SYM_BTYPE_CONST_F16)
+ {
+ // There is currently no attempt to coalesce larger void-extents
+ static const uint8_t cbytes[8] { 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
+ for (unsigned int i = 0; i < 8; i++)
+ {
+ pcb.data[i] = cbytes[i];
+ }
+
+ for (unsigned int i = 0; i < BLOCK_MAX_COMPONENTS; i++)
+ {
+ pcb.data[2 * i + 8] = scb.constant_color[i] & 0xFF;
+ pcb.data[2 * i + 9] = (scb.constant_color[i] >> 8) & 0xFF;
+ }
+
+ return;
+ }
+
+ unsigned int partition_count = scb.partition_count;
+
+ // Compress the weights.
+ // They are encoded as an ordinary integer-sequence, then bit-reversed
+ uint8_t weightbuf[16] { 0 };
+
+ const auto& bm = bsd.get_block_mode(scb.block_mode);
+ const auto& di = bsd.get_decimation_info(bm.decimation_mode);
+ int weight_count = di.weight_count;
+ quant_method weight_quant_method = bm.get_weight_quant_mode();
+ float weight_quant_levels = static_cast<float>(get_quant_level(weight_quant_method));
+ int is_dual_plane = bm.is_dual_plane;
+
+ const auto& qat = quant_and_xfer_tables[weight_quant_method];
+
+ int real_weight_count = is_dual_plane ? 2 * weight_count : weight_count;
+
+ int bits_for_weights = get_ise_sequence_bitcount(real_weight_count, weight_quant_method);
+
+ uint8_t weights[64];
+ if (is_dual_plane)
+ {
+ for (int i = 0; i < weight_count; i++)
+ {
+ float uqw = static_cast<float>(scb.weights[i]);
+ float qw = (uqw / 64.0f) * (weight_quant_levels - 1.0f);
+ int qwi = static_cast<int>(qw + 0.5f);
+ weights[2 * i] = qat.scramble_map[qwi];
+
+ uqw = static_cast<float>(scb.weights[i + WEIGHTS_PLANE2_OFFSET]);
+ qw = (uqw / 64.0f) * (weight_quant_levels - 1.0f);
+ qwi = static_cast<int>(qw + 0.5f);
+ weights[2 * i + 1] = qat.scramble_map[qwi];
+ }
+ }
+ else
+ {
+ for (int i = 0; i < weight_count; i++)
+ {
+ float uqw = static_cast<float>(scb.weights[i]);
+ float qw = (uqw / 64.0f) * (weight_quant_levels - 1.0f);
+ int qwi = static_cast<int>(qw + 0.5f);
+ weights[i] = qat.scramble_map[qwi];
+ }
+ }
+
+ encode_ise(weight_quant_method, real_weight_count, weights, weightbuf, 0);
+
+ for (int i = 0; i < 16; i++)
+ {
+ pcb.data[i] = static_cast<uint8_t>(bitrev8(weightbuf[15 - i]));
+ }
+
+ write_bits(scb.block_mode, 11, 0, pcb.data);
+ write_bits(partition_count - 1, 2, 11, pcb.data);
+
+ int below_weights_pos = 128 - bits_for_weights;
+
+ // Encode partition index and color endpoint types for blocks with 2+ partitions
+ if (partition_count > 1)
+ {
+ write_bits(scb.partition_index, 6, 13, pcb.data);
+ write_bits(scb.partition_index >> 6, PARTITION_INDEX_BITS - 6, 19, pcb.data);
+
+ if (scb.color_formats_matched)
+ {
+ write_bits(scb.color_formats[0] << 2, 6, 13 + PARTITION_INDEX_BITS, pcb.data);
+ }
+ else
+ {
+ // Check endpoint types for each partition to determine the lowest class present
+ int low_class = 4;
+
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ int class_of_format = scb.color_formats[i] >> 2;
+ low_class = astc::min(class_of_format, low_class);
+ }
+
+ if (low_class == 3)
+ {
+ low_class = 2;
+ }
+
+ int encoded_type = low_class + 1;
+ int bitpos = 2;
+
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ int classbit_of_format = (scb.color_formats[i] >> 2) - low_class;
+ encoded_type |= classbit_of_format << bitpos;
+ bitpos++;
+ }
+
+ for (unsigned int i = 0; i < partition_count; i++)
+ {
+ int lowbits_of_format = scb.color_formats[i] & 3;
+ encoded_type |= lowbits_of_format << bitpos;
+ bitpos += 2;
+ }
+
+ int encoded_type_lowpart = encoded_type & 0x3F;
+ int encoded_type_highpart = encoded_type >> 6;
+ int encoded_type_highpart_size = (3 * partition_count) - 4;
+ int encoded_type_highpart_pos = 128 - bits_for_weights - encoded_type_highpart_size;
+ write_bits(encoded_type_lowpart, 6, 13 + PARTITION_INDEX_BITS, pcb.data);
+ write_bits(encoded_type_highpart, encoded_type_highpart_size, encoded_type_highpart_pos, pcb.data);
+ below_weights_pos -= encoded_type_highpart_size;
+ }
+ }
+ else
+ {
+ write_bits(scb.color_formats[0], 4, 13, pcb.data);
+ }
+
+ // In dual-plane mode, encode the color component of the second plane of weights
+ if (is_dual_plane)
+ {
+ write_bits(scb.plane2_component, 2, below_weights_pos - 2, pcb.data);
+ }
+
+ // Encode the color components
+ uint8_t values_to_encode[32];
+ int valuecount_to_encode = 0;
+
+ const uint8_t* pack_table = color_uquant_to_scrambled_pquant_tables[scb.quant_mode - QUANT_6];
+ for (unsigned int i = 0; i < scb.partition_count; i++)
+ {
+ int vals = 2 * (scb.color_formats[i] >> 2) + 2;
+ assert(vals <= 8);
+ for (int j = 0; j < vals; j++)
+ {
+ values_to_encode[j + valuecount_to_encode] = pack_table[scb.color_values[i][j]];
+ }
+ valuecount_to_encode += vals;
+ }
+
+ encode_ise(scb.get_color_quant_mode(), valuecount_to_encode, values_to_encode, pcb.data,
+ scb.partition_count == 1 ? 17 : 19 + PARTITION_INDEX_BITS);
+}
+
+/* See header for documentation. */
+void physical_to_symbolic(
+ const block_size_descriptor& bsd,
+ const physical_compressed_block& pcb,
+ symbolic_compressed_block& scb
+) {
+ uint8_t bswapped[16];
+
+ scb.block_type = SYM_BTYPE_NONCONST;
+
+ // Extract header fields
+ int block_mode = read_bits(11, 0, pcb.data);
+ if ((block_mode & 0x1FF) == 0x1FC)
+ {
+ // Constant color block
+
+ // Check what format the data has
+ if (block_mode & 0x200)
+ {
+ scb.block_type = SYM_BTYPE_CONST_F16;
+ }
+ else
+ {
+ scb.block_type = SYM_BTYPE_CONST_U16;
+ }
+
+ scb.partition_count = 0;
+ for (int i = 0; i < 4; i++)
+ {
+ scb.constant_color[i] = pcb.data[2 * i + 8] | (pcb.data[2 * i + 9] << 8);
+ }
+
+ // Additionally, check that the void-extent
+ if (bsd.zdim == 1)
+ {
+ // 2D void-extent
+ int rsvbits = read_bits(2, 10, pcb.data);
+ if (rsvbits != 3)
+ {
+ scb.block_type = SYM_BTYPE_ERROR;
+ return;
+ }
+
+ int vx_low_s = read_bits(8, 12, pcb.data) | (read_bits(5, 12 + 8, pcb.data) << 8);
+ int vx_high_s = read_bits(8, 25, pcb.data) | (read_bits(5, 25 + 8, pcb.data) << 8);
+ int vx_low_t = read_bits(8, 38, pcb.data) | (read_bits(5, 38 + 8, pcb.data) << 8);
+ int vx_high_t = read_bits(8, 51, pcb.data) | (read_bits(5, 51 + 8, pcb.data) << 8);
+
+ int all_ones = vx_low_s == 0x1FFF && vx_high_s == 0x1FFF && vx_low_t == 0x1FFF && vx_high_t == 0x1FFF;
+
+ if ((vx_low_s >= vx_high_s || vx_low_t >= vx_high_t) && !all_ones)
+ {
+ scb.block_type = SYM_BTYPE_ERROR;
+ return;
+ }
+ }
+ else
+ {
+ // 3D void-extent
+ int vx_low_s = read_bits(9, 10, pcb.data);
+ int vx_high_s = read_bits(9, 19, pcb.data);
+ int vx_low_t = read_bits(9, 28, pcb.data);
+ int vx_high_t = read_bits(9, 37, pcb.data);
+ int vx_low_p = read_bits(9, 46, pcb.data);
+ int vx_high_p = read_bits(9, 55, pcb.data);
+
+ int all_ones = vx_low_s == 0x1FF && vx_high_s == 0x1FF && vx_low_t == 0x1FF && vx_high_t == 0x1FF && vx_low_p == 0x1FF && vx_high_p == 0x1FF;
+
+ if ((vx_low_s >= vx_high_s || vx_low_t >= vx_high_t || vx_low_p >= vx_high_p) && !all_ones)
+ {
+ scb.block_type = SYM_BTYPE_ERROR;
+ return;
+ }
+ }
+
+ return;
+ }
+
+ unsigned int packed_index = bsd.block_mode_packed_index[block_mode];
+ if (packed_index == BLOCK_BAD_BLOCK_MODE)
+ {
+ scb.block_type = SYM_BTYPE_ERROR;
+ return;
+ }
+
+ const auto& bm = bsd.get_block_mode(block_mode);
+ const auto& di = bsd.get_decimation_info(bm.decimation_mode);
+
+ int weight_count = di.weight_count;
+ promise(weight_count > 0);
+
+ quant_method weight_quant_method = static_cast<quant_method>(bm.quant_mode);
+ int is_dual_plane = bm.is_dual_plane;
+
+ int real_weight_count = is_dual_plane ? 2 * weight_count : weight_count;
+
+ int partition_count = read_bits(2, 11, pcb.data) + 1;
+ promise(partition_count > 0);
+
+ scb.block_mode = static_cast<uint16_t>(block_mode);
+ scb.partition_count = static_cast<uint8_t>(partition_count);
+
+ for (int i = 0; i < 16; i++)
+ {
+ bswapped[i] = static_cast<uint8_t>(bitrev8(pcb.data[15 - i]));
+ }
+
+ int bits_for_weights = get_ise_sequence_bitcount(real_weight_count, weight_quant_method);
+
+ int below_weights_pos = 128 - bits_for_weights;
+
+ uint8_t indices[64];
+ const auto& qat = quant_and_xfer_tables[weight_quant_method];
+
+ decode_ise(weight_quant_method, real_weight_count, bswapped, indices, 0);
+
+ if (is_dual_plane)
+ {
+ for (int i = 0; i < weight_count; i++)
+ {
+ scb.weights[i] = qat.unscramble_and_unquant_map[indices[2 * i]];
+ scb.weights[i + WEIGHTS_PLANE2_OFFSET] = qat.unscramble_and_unquant_map[indices[2 * i + 1]];
+ }
+ }
+ else
+ {
+ for (int i = 0; i < weight_count; i++)
+ {
+ scb.weights[i] = qat.unscramble_and_unquant_map[indices[i]];
+ }
+ }
+
+ if (is_dual_plane && partition_count == 4)
+ {
+ scb.block_type = SYM_BTYPE_ERROR;
+ return;
+ }
+
+ scb.color_formats_matched = 0;
+
+ // Determine the format of each endpoint pair
+ int color_formats[BLOCK_MAX_PARTITIONS];
+ int encoded_type_highpart_size = 0;
+ if (partition_count == 1)
+ {
+ color_formats[0] = read_bits(4, 13, pcb.data);
+ scb.partition_index = 0;
+ }
+ else
+ {
+ encoded_type_highpart_size = (3 * partition_count) - 4;
+ below_weights_pos -= encoded_type_highpart_size;
+ int encoded_type = read_bits(6, 13 + PARTITION_INDEX_BITS, pcb.data) | (read_bits(encoded_type_highpart_size, below_weights_pos, pcb.data) << 6);
+ int baseclass = encoded_type & 0x3;
+ if (baseclass == 0)
+ {
+ for (int i = 0; i < partition_count; i++)
+ {
+ color_formats[i] = (encoded_type >> 2) & 0xF;
+ }
+
+ below_weights_pos += encoded_type_highpart_size;
+ scb.color_formats_matched = 1;
+ encoded_type_highpart_size = 0;
+ }
+ else
+ {
+ int bitpos = 2;
+ baseclass--;
+
+ for (int i = 0; i < partition_count; i++)
+ {
+ color_formats[i] = (((encoded_type >> bitpos) & 1) + baseclass) << 2;
+ bitpos++;
+ }
+
+ for (int i = 0; i < partition_count; i++)
+ {
+ color_formats[i] |= (encoded_type >> bitpos) & 3;
+ bitpos += 2;
+ }
+ }
+ scb.partition_index = static_cast<uint16_t>(read_bits(6, 13, pcb.data) | (read_bits(PARTITION_INDEX_BITS - 6, 19, pcb.data) << 6));
+ }
+
+ for (int i = 0; i < partition_count; i++)
+ {
+ scb.color_formats[i] = static_cast<uint8_t>(color_formats[i]);
+ }
+
+ // Determine number of color endpoint integers
+ int color_integer_count = 0;
+ for (int i = 0; i < partition_count; i++)
+ {
+ int endpoint_class = color_formats[i] >> 2;
+ color_integer_count += (endpoint_class + 1) * 2;
+ }
+
+ if (color_integer_count > 18)
+ {
+ scb.block_type = SYM_BTYPE_ERROR;
+ return;
+ }
+
+ // Determine the color endpoint format to use
+ static const int color_bits_arr[5] { -1, 115 - 4, 113 - 4 - PARTITION_INDEX_BITS, 113 - 4 - PARTITION_INDEX_BITS, 113 - 4 - PARTITION_INDEX_BITS };
+ int color_bits = color_bits_arr[partition_count] - bits_for_weights - encoded_type_highpart_size;
+ if (is_dual_plane)
+ {
+ color_bits -= 2;
+ }
+
+ if (color_bits < 0)
+ {
+ color_bits = 0;
+ }
+
+ int color_quant_level = quant_mode_table[color_integer_count >> 1][color_bits];
+ if (color_quant_level < QUANT_6)
+ {
+ scb.block_type = SYM_BTYPE_ERROR;
+ return;
+ }
+
+ // Unpack the integer color values and assign to endpoints
+ scb.quant_mode = static_cast<quant_method>(color_quant_level);
+
+ uint8_t values_to_decode[32];
+ decode_ise(static_cast<quant_method>(color_quant_level), color_integer_count, pcb.data,
+ values_to_decode, (partition_count == 1 ? 17 : 19 + PARTITION_INDEX_BITS));
+
+ int valuecount_to_decode = 0;
+ const uint8_t* unpack_table = color_scrambled_pquant_to_uquant_tables[scb.quant_mode - QUANT_6];
+ for (int i = 0; i < partition_count; i++)
+ {
+ int vals = 2 * (color_formats[i] >> 2) + 2;
+ for (int j = 0; j < vals; j++)
+ {
+ scb.color_values[i][j] = unpack_table[values_to_decode[j + valuecount_to_decode]];
+ }
+ valuecount_to_decode += vals;
+ }
+
+ // Fetch component for second-plane in the case of dual plane of weights.
+ scb.plane2_component = -1;
+ if (is_dual_plane)
+ {
+ scb.plane2_component = static_cast<int8_t>(read_bits(2, below_weights_pos - 2, pcb.data));
+ }
+}
diff --git a/thirdparty/astcenc/astcenc_vecmathlib.h b/thirdparty/astcenc/astcenc_vecmathlib.h
new file mode 100644
index 0000000000..d48f1d73ea
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_vecmathlib.h
@@ -0,0 +1,570 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2019-2022 Arm Limited
+// Copyright 2008 Jose Fonseca
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/*
+ * This module implements vector support for floats, ints, and vector lane
+ * control masks. It provides access to both explicit vector width types, and
+ * flexible N-wide types where N can be determined at compile time.
+ *
+ * The design of this module encourages use of vector length agnostic code, via
+ * the vint, vfloat, and vmask types. These will take on the widest SIMD vector
+ * with that is available at compile time. The current vector width is
+ * accessible for e.g. loop strides via the ASTCENC_SIMD_WIDTH constant.
+ *
+ * Explicit scalar types are accessible via the vint1, vfloat1, vmask1 types.
+ * These are provided primarily for prototyping and algorithm debug of VLA
+ * implementations.
+ *
+ * Explicit 4-wide types are accessible via the vint4, vfloat4, and vmask4
+ * types. These are provided for use by VLA code, but are also expected to be
+ * used as a fixed-width type and will supported a reference C++ fallback for
+ * use on platforms without SIMD intrinsics.
+ *
+ * Explicit 8-wide types are accessible via the vint8, vfloat8, and vmask8
+ * types. These are provide for use by VLA code, and are not expected to be
+ * used as a fixed-width type in normal code. No reference C implementation is
+ * provided on platforms without underlying SIMD intrinsics.
+ *
+ * With the current implementation ISA support is provided for:
+ *
+ * * 1-wide for scalar reference.
+ * * 4-wide for Armv8-A NEON.
+ * * 4-wide for x86-64 SSE2.
+ * * 4-wide for x86-64 SSE4.1.
+ * * 8-wide for x86-64 AVX2.
+ */
+
+#ifndef ASTC_VECMATHLIB_H_INCLUDED
+#define ASTC_VECMATHLIB_H_INCLUDED
+
+#if ASTCENC_SSE != 0 || ASTCENC_AVX != 0
+ #include <immintrin.h>
+#elif ASTCENC_NEON != 0
+ #include <arm_neon.h>
+#endif
+
+#if !defined(__clang__) && defined(_MSC_VER)
+ #define ASTCENC_SIMD_INLINE __forceinline
+ #define ASTCENC_NO_INLINE
+#elif defined(__GNUC__) && !defined(__clang__)
+ #define ASTCENC_SIMD_INLINE __attribute__((always_inline)) inline
+ #define ASTCENC_NO_INLINE __attribute__ ((noinline))
+#else
+ #define ASTCENC_SIMD_INLINE __attribute__((always_inline, nodebug)) inline
+ #define ASTCENC_NO_INLINE __attribute__ ((noinline))
+#endif
+
+#if ASTCENC_AVX >= 2
+ /* If we have AVX2 expose 8-wide VLA. */
+ #include "astcenc_vecmathlib_sse_4.h"
+ #include "astcenc_vecmathlib_common_4.h"
+ #include "astcenc_vecmathlib_avx2_8.h"
+
+ #define ASTCENC_SIMD_WIDTH 8
+
+ using vfloat = vfloat8;
+
+ #if defined(ASTCENC_NO_INVARIANCE)
+ using vfloatacc = vfloat8;
+ #else
+ using vfloatacc = vfloat4;
+ #endif
+
+ using vint = vint8;
+ using vmask = vmask8;
+
+ constexpr auto loada = vfloat8::loada;
+ constexpr auto load1 = vfloat8::load1;
+
+#elif ASTCENC_SSE >= 20
+ /* If we have SSE expose 4-wide VLA, and 4-wide fixed width. */
+ #include "astcenc_vecmathlib_sse_4.h"
+ #include "astcenc_vecmathlib_common_4.h"
+
+ #define ASTCENC_SIMD_WIDTH 4
+
+ using vfloat = vfloat4;
+ using vfloatacc = vfloat4;
+ using vint = vint4;
+ using vmask = vmask4;
+
+ constexpr auto loada = vfloat4::loada;
+ constexpr auto load1 = vfloat4::load1;
+
+#elif ASTCENC_NEON > 0
+ /* If we have NEON expose 4-wide VLA. */
+ #include "astcenc_vecmathlib_neon_4.h"
+ #include "astcenc_vecmathlib_common_4.h"
+
+ #define ASTCENC_SIMD_WIDTH 4
+
+ using vfloat = vfloat4;
+ using vfloatacc = vfloat4;
+ using vint = vint4;
+ using vmask = vmask4;
+
+ constexpr auto loada = vfloat4::loada;
+ constexpr auto load1 = vfloat4::load1;
+
+#else
+ // If we have nothing expose 4-wide VLA, and 4-wide fixed width.
+
+ // Note: We no longer expose the 1-wide scalar fallback because it is not
+ // invariant with the 4-wide path due to algorithms that use horizontal
+ // operations that accumulate a local vector sum before accumulating into
+ // a running sum.
+ //
+ // For 4 items adding into an accumulator using 1-wide vectors the sum is:
+ //
+ // result = ((((sum + l0) + l1) + l2) + l3)
+ //
+ // ... whereas the accumulator for a 4-wide vector sum is:
+ //
+ // result = sum + ((l0 + l2) + (l1 + l3))
+ //
+ // In "normal maths" this is the same, but the floating point reassociation
+ // differences mean that these will not produce the same result.
+
+ #include "astcenc_vecmathlib_none_4.h"
+ #include "astcenc_vecmathlib_common_4.h"
+
+ #define ASTCENC_SIMD_WIDTH 4
+
+ using vfloat = vfloat4;
+ using vfloatacc = vfloat4;
+ using vint = vint4;
+ using vmask = vmask4;
+
+ constexpr auto loada = vfloat4::loada;
+ constexpr auto load1 = vfloat4::load1;
+#endif
+
+/**
+ * @brief Round a count down to the largest multiple of 8.
+ *
+ * @param count The unrounded value.
+ *
+ * @return The rounded value.
+ */
+ASTCENC_SIMD_INLINE unsigned int round_down_to_simd_multiple_8(unsigned int count)
+{
+ return count & static_cast<unsigned int>(~(8 - 1));
+}
+
+/**
+ * @brief Round a count down to the largest multiple of 4.
+ *
+ * @param count The unrounded value.
+ *
+ * @return The rounded value.
+ */
+ASTCENC_SIMD_INLINE unsigned int round_down_to_simd_multiple_4(unsigned int count)
+{
+ return count & static_cast<unsigned int>(~(4 - 1));
+}
+
+/**
+ * @brief Round a count down to the largest multiple of the SIMD width.
+ *
+ * Assumption that the vector width is a power of two ...
+ *
+ * @param count The unrounded value.
+ *
+ * @return The rounded value.
+ */
+ASTCENC_SIMD_INLINE unsigned int round_down_to_simd_multiple_vla(unsigned int count)
+{
+ return count & static_cast<unsigned int>(~(ASTCENC_SIMD_WIDTH - 1));
+}
+
+/**
+ * @brief Round a count up to the largest multiple of the SIMD width.
+ *
+ * Assumption that the vector width is a power of two ...
+ *
+ * @param count The unrounded value.
+ *
+ * @return The rounded value.
+ */
+ASTCENC_SIMD_INLINE unsigned int round_up_to_simd_multiple_vla(unsigned int count)
+{
+ unsigned int multiples = (count + ASTCENC_SIMD_WIDTH - 1) / ASTCENC_SIMD_WIDTH;
+ return multiples * ASTCENC_SIMD_WIDTH;
+}
+
+/**
+ * @brief Return @c a with lanes negated if the @c b lane is negative.
+ */
+ASTCENC_SIMD_INLINE vfloat change_sign(vfloat a, vfloat b)
+{
+ vint ia = float_as_int(a);
+ vint ib = float_as_int(b);
+ vint sign_mask(static_cast<int>(0x80000000));
+ vint r = ia ^ (ib & sign_mask);
+ return int_as_float(r);
+}
+
+/**
+ * @brief Return fast, but approximate, vector atan(x).
+ *
+ * Max error of this implementation is 0.004883.
+ */
+ASTCENC_SIMD_INLINE vfloat atan(vfloat x)
+{
+ vmask c = abs(x) > vfloat(1.0f);
+ vfloat z = change_sign(vfloat(astc::PI_OVER_TWO), x);
+ vfloat y = select(x, vfloat(1.0f) / x, c);
+ y = y / (y * y * vfloat(0.28f) + vfloat(1.0f));
+ return select(y, z - y, c);
+}
+
+/**
+ * @brief Return fast, but approximate, vector atan2(x, y).
+ */
+ASTCENC_SIMD_INLINE vfloat atan2(vfloat y, vfloat x)
+{
+ vfloat z = atan(abs(y / x));
+ vmask xmask = vmask(float_as_int(x).m);
+ return change_sign(select_msb(z, vfloat(astc::PI) - z, xmask), y);
+}
+
+/*
+ * @brief Factory that returns a unit length 4 component vfloat4.
+ */
+static ASTCENC_SIMD_INLINE vfloat4 unit4()
+{
+ return vfloat4(0.5f);
+}
+
+/**
+ * @brief Factory that returns a unit length 3 component vfloat4.
+ */
+static ASTCENC_SIMD_INLINE vfloat4 unit3()
+{
+ float val = 0.577350258827209473f;
+ return vfloat4(val, val, val, 0.0f);
+}
+
+/**
+ * @brief Factory that returns a unit length 2 component vfloat4.
+ */
+static ASTCENC_SIMD_INLINE vfloat4 unit2()
+{
+ float val = 0.707106769084930420f;
+ return vfloat4(val, val, 0.0f, 0.0f);
+}
+
+/**
+ * @brief Factory that returns a 3 component vfloat4.
+ */
+static ASTCENC_SIMD_INLINE vfloat4 vfloat3(float a, float b, float c)
+{
+ return vfloat4(a, b, c, 0.0f);
+}
+
+/**
+ * @brief Factory that returns a 2 component vfloat4.
+ */
+static ASTCENC_SIMD_INLINE vfloat4 vfloat2(float a, float b)
+{
+ return vfloat4(a, b, 0.0f, 0.0f);
+}
+
+/**
+ * @brief Normalize a non-zero length vector to unit length.
+ */
+static ASTCENC_SIMD_INLINE vfloat4 normalize(vfloat4 a)
+{
+ vfloat4 length = dot(a, a);
+ return a / sqrt(length);
+}
+
+/**
+ * @brief Normalize a vector, returning @c safe if len is zero.
+ */
+static ASTCENC_SIMD_INLINE vfloat4 normalize_safe(vfloat4 a, vfloat4 safe)
+{
+ vfloat4 length = dot(a, a);
+ if (length.lane<0>() != 0.0f)
+ {
+ return a / sqrt(length);
+ }
+
+ return safe;
+}
+
+
+
+#define POLY0(x, c0) ( c0)
+#define POLY1(x, c0, c1) ((POLY0(x, c1) * x) + c0)
+#define POLY2(x, c0, c1, c2) ((POLY1(x, c1, c2) * x) + c0)
+#define POLY3(x, c0, c1, c2, c3) ((POLY2(x, c1, c2, c3) * x) + c0)
+#define POLY4(x, c0, c1, c2, c3, c4) ((POLY3(x, c1, c2, c3, c4) * x) + c0)
+#define POLY5(x, c0, c1, c2, c3, c4, c5) ((POLY4(x, c1, c2, c3, c4, c5) * x) + c0)
+
+/**
+ * @brief Compute an approximate exp2(x) for each lane in the vector.
+ *
+ * Based on 5th degree minimax polynomials, ported from this blog
+ * https://jrfonseca.blogspot.com/2008/09/fast-sse2-pow-tables-or-polynomials.html
+ */
+static ASTCENC_SIMD_INLINE vfloat4 exp2(vfloat4 x)
+{
+ x = clamp(-126.99999f, 129.0f, x);
+
+ vint4 ipart = float_to_int(x - 0.5f);
+ vfloat4 fpart = x - int_to_float(ipart);
+
+ // Integer contrib, using 1 << ipart
+ vfloat4 iexp = int_as_float(lsl<23>(ipart + 127));
+
+ // Fractional contrib, using polynomial fit of 2^x in range [-0.5, 0.5)
+ vfloat4 fexp = POLY5(fpart,
+ 9.9999994e-1f,
+ 6.9315308e-1f,
+ 2.4015361e-1f,
+ 5.5826318e-2f,
+ 8.9893397e-3f,
+ 1.8775767e-3f);
+
+ return iexp * fexp;
+}
+
+/**
+ * @brief Compute an approximate log2(x) for each lane in the vector.
+ *
+ * Based on 5th degree minimax polynomials, ported from this blog
+ * https://jrfonseca.blogspot.com/2008/09/fast-sse2-pow-tables-or-polynomials.html
+ */
+static ASTCENC_SIMD_INLINE vfloat4 log2(vfloat4 x)
+{
+ vint4 exp(0x7F800000);
+ vint4 mant(0x007FFFFF);
+ vint4 one(0x3F800000);
+
+ vint4 i = float_as_int(x);
+
+ vfloat4 e = int_to_float(lsr<23>(i & exp) - 127);
+
+ vfloat4 m = int_as_float((i & mant) | one);
+
+ // Polynomial fit of log2(x)/(x - 1), for x in range [1, 2)
+ vfloat4 p = POLY4(m,
+ 2.8882704548164776201f,
+ -2.52074962577807006663f,
+ 1.48116647521213171641f,
+ -0.465725644288844778798f,
+ 0.0596515482674574969533f);
+
+ // Increases the polynomial degree, but ensures that log2(1) == 0
+ p = p * (m - 1.0f);
+
+ return p + e;
+}
+
+/**
+ * @brief Compute an approximate pow(x, y) for each lane in the vector.
+ *
+ * Power function based on the exp2(log2(x) * y) transform.
+ */
+static ASTCENC_SIMD_INLINE vfloat4 pow(vfloat4 x, vfloat4 y)
+{
+ vmask4 zero_mask = y == vfloat4(0.0f);
+ vfloat4 estimate = exp2(log2(x) * y);
+
+ // Guarantee that y == 0 returns exactly 1.0f
+ return select(estimate, vfloat4(1.0f), zero_mask);
+}
+
+/**
+ * @brief Count the leading zeros for each lane in @c a.
+ *
+ * Valid for all data values of @c a; will return a per-lane value [0, 32].
+ */
+static ASTCENC_SIMD_INLINE vint4 clz(vint4 a)
+{
+ // This function is a horrible abuse of floating point exponents to convert
+ // the original integer value into a 2^N encoding we can recover easily.
+
+ // Convert to float without risk of rounding up by keeping only top 8 bits.
+ // This trick is is guaranteed to keep top 8 bits and clear the 9th.
+ a = (~lsr<8>(a)) & a;
+ a = float_as_int(int_to_float(a));
+
+ // Extract and unbias exponent
+ a = vint4(127 + 31) - lsr<23>(a);
+
+ // Clamp result to a valid 32-bit range
+ return clamp(0, 32, a);
+}
+
+/**
+ * @brief Return lanewise 2^a for each lane in @c a.
+ *
+ * Use of signed int means that this is only valid for values in range [0, 31].
+ */
+static ASTCENC_SIMD_INLINE vint4 two_to_the_n(vint4 a)
+{
+ // 2^30 is the largest signed number than can be represented
+ assert(all(a < vint4(31)));
+
+ // This function is a horrible abuse of floating point to use the exponent
+ // and float conversion to generate a 2^N multiple.
+
+ // Bias the exponent
+ vint4 exp = a + 127;
+ exp = lsl<23>(exp);
+
+ // Reinterpret the bits as a float, and then convert to an int
+ vfloat4 f = int_as_float(exp);
+ return float_to_int(f);
+}
+
+/**
+ * @brief Convert unorm16 [0, 65535] to float16 in range [0, 1].
+ */
+static ASTCENC_SIMD_INLINE vint4 unorm16_to_sf16(vint4 p)
+{
+ vint4 fp16_one = vint4(0x3C00);
+ vint4 fp16_small = lsl<8>(p);
+
+ vmask4 is_one = p == vint4(0xFFFF);
+ vmask4 is_small = p < vint4(4);
+
+ // Manually inline clz() on Visual Studio to avoid release build codegen bug
+ // see https://github.com/ARM-software/astc-encoder/issues/259
+#if !defined(__clang__) && defined(_MSC_VER)
+ vint4 a = (~lsr<8>(p)) & p;
+ a = float_as_int(int_to_float(a));
+ a = vint4(127 + 31) - lsr<23>(a);
+ vint4 lz = clamp(0, 32, a) - 16;
+#else
+ vint4 lz = clz(p) - 16;
+#endif
+
+ p = p * two_to_the_n(lz + 1);
+ p = p & vint4(0xFFFF);
+
+ p = lsr<6>(p);
+
+ p = p | lsl<10>(vint4(14) - lz);
+
+ vint4 r = select(p, fp16_one, is_one);
+ r = select(r, fp16_small, is_small);
+ return r;
+}
+
+/**
+ * @brief Convert 16-bit LNS to float16.
+ */
+static ASTCENC_SIMD_INLINE vint4 lns_to_sf16(vint4 p)
+{
+ vint4 mc = p & 0x7FF;
+ vint4 ec = lsr<11>(p);
+
+ vint4 mc_512 = mc * 3;
+ vmask4 mask_512 = mc < vint4(512);
+
+ vint4 mc_1536 = mc * 4 - 512;
+ vmask4 mask_1536 = mc < vint4(1536);
+
+ vint4 mc_else = mc * 5 - 2048;
+
+ vint4 mt = mc_else;
+ mt = select(mt, mc_1536, mask_1536);
+ mt = select(mt, mc_512, mask_512);
+
+ vint4 res = lsl<10>(ec) | lsr<3>(mt);
+ return min(res, vint4(0x7BFF));
+}
+
+/**
+ * @brief Extract mantissa and exponent of a float value.
+ *
+ * @param a The input value.
+ * @param[out] exp The output exponent.
+ *
+ * @return The mantissa.
+ */
+static ASTCENC_SIMD_INLINE vfloat4 frexp(vfloat4 a, vint4& exp)
+{
+ // Interpret the bits as an integer
+ vint4 ai = float_as_int(a);
+
+ // Extract and unbias the exponent
+ exp = (lsr<23>(ai) & 0xFF) - 126;
+
+ // Extract and unbias the mantissa
+ vint4 manti = (ai & static_cast<int>(0x807FFFFF)) | 0x3F000000;
+ return int_as_float(manti);
+}
+
+/**
+ * @brief Convert float to 16-bit LNS.
+ */
+static ASTCENC_SIMD_INLINE vfloat4 float_to_lns(vfloat4 a)
+{
+ vint4 exp;
+ vfloat4 mant = frexp(a, exp);
+
+ // Do these early before we start messing about ...
+ vmask4 mask_underflow_nan = ~(a > vfloat4(1.0f / 67108864.0f));
+ vmask4 mask_infinity = a >= vfloat4(65536.0f);
+
+ // If input is smaller than 2^-14, multiply by 2^25 and don't bias.
+ vmask4 exp_lt_m13 = exp < vint4(-13);
+
+ vfloat4 a1a = a * 33554432.0f;
+ vint4 expa = vint4::zero();
+
+ vfloat4 a1b = (mant - 0.5f) * 4096;
+ vint4 expb = exp + 14;
+
+ a = select(a1b, a1a, exp_lt_m13);
+ exp = select(expb, expa, exp_lt_m13);
+
+ vmask4 a_lt_384 = a < vfloat4(384.0f);
+ vmask4 a_lt_1408 = a <= vfloat4(1408.0f);
+
+ vfloat4 a2a = a * (4.0f / 3.0f);
+ vfloat4 a2b = a + 128.0f;
+ vfloat4 a2c = (a + 512.0f) * (4.0f / 5.0f);
+
+ a = a2c;
+ a = select(a, a2b, a_lt_1408);
+ a = select(a, a2a, a_lt_384);
+
+ a = a + (int_to_float(exp) * 2048.0f) + 1.0f;
+
+ a = select(a, vfloat4(65535.0f), mask_infinity);
+ a = select(a, vfloat4::zero(), mask_underflow_nan);
+
+ return a;
+}
+
+namespace astc
+{
+
+static ASTCENC_SIMD_INLINE float pow(float x, float y)
+{
+ return pow(vfloat4(x), vfloat4(y)).lane<0>();
+}
+
+}
+
+#endif // #ifndef ASTC_VECMATHLIB_H_INCLUDED
diff --git a/thirdparty/astcenc/astcenc_vecmathlib_avx2_8.h b/thirdparty/astcenc/astcenc_vecmathlib_avx2_8.h
new file mode 100644
index 0000000000..a785aca75b
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_vecmathlib_avx2_8.h
@@ -0,0 +1,1204 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2019-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief 8x32-bit vectors, implemented using AVX2.
+ *
+ * This module implements 8-wide 32-bit float, int, and mask vectors for x86
+ * AVX2.
+ *
+ * There is a baseline level of functionality provided by all vector widths and
+ * implementations. This is implemented using identical function signatures,
+ * modulo data type, so we can use them as substitutable implementations in VLA
+ * code.
+ */
+
+#ifndef ASTC_VECMATHLIB_AVX2_8_H_INCLUDED
+#define ASTC_VECMATHLIB_AVX2_8_H_INCLUDED
+
+#ifndef ASTCENC_SIMD_INLINE
+ #error "Include astcenc_vecmathlib.h, do not include directly"
+#endif
+
+#include <cstdio>
+
+// Define convenience intrinsics that are missing on older compilers
+#define astcenc_mm256_set_m128i(m, n) _mm256_insertf128_si256(_mm256_castsi128_si256((n)), (m), 1)
+
+// ============================================================================
+// vfloat8 data type
+// ============================================================================
+
+/**
+ * @brief Data type for 8-wide floats.
+ */
+struct vfloat8
+{
+ /**
+ * @brief Construct from zero-initialized value.
+ */
+ ASTCENC_SIMD_INLINE vfloat8() = default;
+
+ /**
+ * @brief Construct from 4 values loaded from an unaligned address.
+ *
+ * Consider using loada() which is better with vectors if data is aligned
+ * to vector length.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat8(const float *p)
+ {
+ m = _mm256_loadu_ps(p);
+ }
+
+ /**
+ * @brief Construct from 1 scalar value replicated across all lanes.
+ *
+ * Consider using zero() for constexpr zeros.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat8(float a)
+ {
+ m = _mm256_set1_ps(a);
+ }
+
+ /**
+ * @brief Construct from 8 scalar values.
+ *
+ * The value of @c a is stored to lane 0 (LSB) in the SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat8(
+ float a, float b, float c, float d,
+ float e, float f, float g, float h)
+ {
+ m = _mm256_set_ps(h, g, f, e, d, c, b, a);
+ }
+
+ /**
+ * @brief Construct from an existing SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat8(__m256 a)
+ {
+ m = a;
+ }
+
+ /**
+ * @brief Get the scalar value of a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE float lane() const
+ {
+ #if !defined(__clang__) && defined(_MSC_VER)
+ return m.m256_f32[l];
+ #else
+ union { __m256 m; float f[8]; } cvt;
+ cvt.m = m;
+ return cvt.f[l];
+ #endif
+ }
+
+ /**
+ * @brief Factory that returns a vector of zeros.
+ */
+ static ASTCENC_SIMD_INLINE vfloat8 zero()
+ {
+ return vfloat8(_mm256_setzero_ps());
+ }
+
+ /**
+ * @brief Factory that returns a replicated scalar loaded from memory.
+ */
+ static ASTCENC_SIMD_INLINE vfloat8 load1(const float* p)
+ {
+ return vfloat8(_mm256_broadcast_ss(p));
+ }
+
+ /**
+ * @brief Factory that returns a vector loaded from 32B aligned memory.
+ */
+ static ASTCENC_SIMD_INLINE vfloat8 loada(const float* p)
+ {
+ return vfloat8(_mm256_load_ps(p));
+ }
+
+ /**
+ * @brief Factory that returns a vector containing the lane IDs.
+ */
+ static ASTCENC_SIMD_INLINE vfloat8 lane_id()
+ {
+ return vfloat8(_mm256_set_ps(7, 6, 5, 4, 3, 2, 1, 0));
+ }
+
+ /**
+ * @brief The vector ...
+ */
+ __m256 m;
+};
+
+// ============================================================================
+// vint8 data type
+// ============================================================================
+
+/**
+ * @brief Data type for 8-wide ints.
+ */
+struct vint8
+{
+ /**
+ * @brief Construct from zero-initialized value.
+ */
+ ASTCENC_SIMD_INLINE vint8() = default;
+
+ /**
+ * @brief Construct from 8 values loaded from an unaligned address.
+ *
+ * Consider using loada() which is better with vectors if data is aligned
+ * to vector length.
+ */
+ ASTCENC_SIMD_INLINE explicit vint8(const int *p)
+ {
+ m = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(p));
+ }
+
+ /**
+ * @brief Construct from 8 uint8_t loaded from an unaligned address.
+ */
+ ASTCENC_SIMD_INLINE explicit vint8(const uint8_t *p)
+ {
+ // _mm_loadu_si64 would be nicer syntax, but missing on older GCC
+ m = _mm256_cvtepu8_epi32(_mm_cvtsi64_si128(*reinterpret_cast<const long long*>(p)));
+ }
+
+ /**
+ * @brief Construct from 1 scalar value replicated across all lanes.
+ *
+ * Consider using vfloat4::zero() for constexpr zeros.
+ */
+ ASTCENC_SIMD_INLINE explicit vint8(int a)
+ {
+ m = _mm256_set1_epi32(a);
+ }
+
+ /**
+ * @brief Construct from 8 scalar values.
+ *
+ * The value of @c a is stored to lane 0 (LSB) in the SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vint8(
+ int a, int b, int c, int d,
+ int e, int f, int g, int h)
+ {
+ m = _mm256_set_epi32(h, g, f, e, d, c, b, a);
+ }
+
+ /**
+ * @brief Construct from an existing SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vint8(__m256i a)
+ {
+ m = a;
+ }
+
+ /**
+ * @brief Get the scalar from a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE int lane() const
+ {
+ #if !defined(__clang__) && defined(_MSC_VER)
+ return m.m256i_i32[l];
+ #else
+ union { __m256i m; int f[8]; } cvt;
+ cvt.m = m;
+ return cvt.f[l];
+ #endif
+ }
+
+ /**
+ * @brief Factory that returns a vector of zeros.
+ */
+ static ASTCENC_SIMD_INLINE vint8 zero()
+ {
+ return vint8(_mm256_setzero_si256());
+ }
+
+ /**
+ * @brief Factory that returns a replicated scalar loaded from memory.
+ */
+ static ASTCENC_SIMD_INLINE vint8 load1(const int* p)
+ {
+ __m128i a = _mm_set1_epi32(*p);
+ return vint8(_mm256_broadcastd_epi32(a));
+ }
+
+ /**
+ * @brief Factory that returns a vector loaded from 32B aligned memory.
+ */
+ static ASTCENC_SIMD_INLINE vint8 loada(const int* p)
+ {
+ return vint8(_mm256_load_si256(reinterpret_cast<const __m256i*>(p)));
+ }
+
+ /**
+ * @brief Factory that returns a vector containing the lane IDs.
+ */
+ static ASTCENC_SIMD_INLINE vint8 lane_id()
+ {
+ return vint8(_mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0));
+ }
+
+ /**
+ * @brief The vector ...
+ */
+ __m256i m;
+};
+
+// ============================================================================
+// vmask8 data type
+// ============================================================================
+
+/**
+ * @brief Data type for 8-wide control plane masks.
+ */
+struct vmask8
+{
+ /**
+ * @brief Construct from an existing SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask8(__m256 a)
+ {
+ m = a;
+ }
+
+ /**
+ * @brief Construct from an existing SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask8(__m256i a)
+ {
+ m = _mm256_castsi256_ps(a);
+ }
+
+ /**
+ * @brief Construct from 1 scalar value.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask8(bool a)
+ {
+ vint8 mask(a == false ? 0 : -1);
+ m = _mm256_castsi256_ps(mask.m);
+ }
+
+ /**
+ * @brief The vector ...
+ */
+ __m256 m;
+};
+
+// ============================================================================
+// vmask8 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: mask union (or).
+ */
+ASTCENC_SIMD_INLINE vmask8 operator|(vmask8 a, vmask8 b)
+{
+ return vmask8(_mm256_or_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: mask intersect (and).
+ */
+ASTCENC_SIMD_INLINE vmask8 operator&(vmask8 a, vmask8 b)
+{
+ return vmask8(_mm256_and_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: mask difference (xor).
+ */
+ASTCENC_SIMD_INLINE vmask8 operator^(vmask8 a, vmask8 b)
+{
+ return vmask8(_mm256_xor_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: mask invert (not).
+ */
+ASTCENC_SIMD_INLINE vmask8 operator~(vmask8 a)
+{
+ return vmask8(_mm256_xor_si256(_mm256_castps_si256(a.m), _mm256_set1_epi32(-1)));
+}
+
+/**
+ * @brief Return a 8-bit mask code indicating mask status.
+ *
+ * bit0 = lane 0
+ */
+ASTCENC_SIMD_INLINE unsigned int mask(vmask8 a)
+{
+ return static_cast<unsigned int>(_mm256_movemask_ps(a.m));
+}
+
+/**
+ * @brief True if any lanes are enabled, false otherwise.
+ */
+ASTCENC_SIMD_INLINE bool any(vmask8 a)
+{
+ return mask(a) != 0;
+}
+
+/**
+ * @brief True if all lanes are enabled, false otherwise.
+ */
+ASTCENC_SIMD_INLINE bool all(vmask8 a)
+{
+ return mask(a) == 0xFF;
+}
+
+// ============================================================================
+// vint8 operators and functions
+// ============================================================================
+/**
+ * @brief Overload: vector by vector addition.
+ */
+ASTCENC_SIMD_INLINE vint8 operator+(vint8 a, vint8 b)
+{
+ return vint8(_mm256_add_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector incremental addition.
+ */
+ASTCENC_SIMD_INLINE vint8& operator+=(vint8& a, const vint8& b)
+{
+ a = a + b;
+ return a;
+}
+
+/**
+ * @brief Overload: vector by vector subtraction.
+ */
+ASTCENC_SIMD_INLINE vint8 operator-(vint8 a, vint8 b)
+{
+ return vint8(_mm256_sub_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector multiplication.
+ */
+ASTCENC_SIMD_INLINE vint8 operator*(vint8 a, vint8 b)
+{
+ return vint8(_mm256_mullo_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector bit invert.
+ */
+ASTCENC_SIMD_INLINE vint8 operator~(vint8 a)
+{
+ return vint8(_mm256_xor_si256(a.m, _mm256_set1_epi32(-1)));
+}
+
+/**
+ * @brief Overload: vector by vector bitwise or.
+ */
+ASTCENC_SIMD_INLINE vint8 operator|(vint8 a, vint8 b)
+{
+ return vint8(_mm256_or_si256(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector bitwise and.
+ */
+ASTCENC_SIMD_INLINE vint8 operator&(vint8 a, vint8 b)
+{
+ return vint8(_mm256_and_si256(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector bitwise xor.
+ */
+ASTCENC_SIMD_INLINE vint8 operator^(vint8 a, vint8 b)
+{
+ return vint8(_mm256_xor_si256(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector equality.
+ */
+ASTCENC_SIMD_INLINE vmask8 operator==(vint8 a, vint8 b)
+{
+ return vmask8(_mm256_cmpeq_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector inequality.
+ */
+ASTCENC_SIMD_INLINE vmask8 operator!=(vint8 a, vint8 b)
+{
+ return ~vmask8(_mm256_cmpeq_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector less than.
+ */
+ASTCENC_SIMD_INLINE vmask8 operator<(vint8 a, vint8 b)
+{
+ return vmask8(_mm256_cmpgt_epi32(b.m, a.m));
+}
+
+/**
+ * @brief Overload: vector by vector greater than.
+ */
+ASTCENC_SIMD_INLINE vmask8 operator>(vint8 a, vint8 b)
+{
+ return vmask8(_mm256_cmpgt_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Logical shift left.
+ */
+template <int s> ASTCENC_SIMD_INLINE vint8 lsl(vint8 a)
+{
+ return vint8(_mm256_slli_epi32(a.m, s));
+}
+
+/**
+ * @brief Arithmetic shift right.
+ */
+template <int s> ASTCENC_SIMD_INLINE vint8 asr(vint8 a)
+{
+ return vint8(_mm256_srai_epi32(a.m, s));
+}
+
+/**
+ * @brief Logical shift right.
+ */
+template <int s> ASTCENC_SIMD_INLINE vint8 lsr(vint8 a)
+{
+ return vint8(_mm256_srli_epi32(a.m, s));
+}
+
+/**
+ * @brief Return the min vector of two vectors.
+ */
+ASTCENC_SIMD_INLINE vint8 min(vint8 a, vint8 b)
+{
+ return vint8(_mm256_min_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Return the max vector of two vectors.
+ */
+ASTCENC_SIMD_INLINE vint8 max(vint8 a, vint8 b)
+{
+ return vint8(_mm256_max_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Return the horizontal minimum of a vector.
+ */
+ASTCENC_SIMD_INLINE vint8 hmin(vint8 a)
+{
+ __m128i m = _mm_min_epi32(_mm256_extracti128_si256(a.m, 0), _mm256_extracti128_si256(a.m, 1));
+ m = _mm_min_epi32(m, _mm_shuffle_epi32(m, _MM_SHUFFLE(0,0,3,2)));
+ m = _mm_min_epi32(m, _mm_shuffle_epi32(m, _MM_SHUFFLE(0,0,0,1)));
+ m = _mm_shuffle_epi32(m, _MM_SHUFFLE(0,0,0,0));
+
+ __m256i r = astcenc_mm256_set_m128i(m, m);
+ vint8 vmin(r);
+ return vmin;
+}
+
+/**
+ * @brief Return the horizontal maximum of a vector.
+ */
+ASTCENC_SIMD_INLINE vint8 hmax(vint8 a)
+{
+ __m128i m = _mm_max_epi32(_mm256_extracti128_si256(a.m, 0), _mm256_extracti128_si256(a.m, 1));
+ m = _mm_max_epi32(m, _mm_shuffle_epi32(m, _MM_SHUFFLE(0,0,3,2)));
+ m = _mm_max_epi32(m, _mm_shuffle_epi32(m, _MM_SHUFFLE(0,0,0,1)));
+ m = _mm_shuffle_epi32(m, _MM_SHUFFLE(0,0,0,0));
+
+ __m256i r = astcenc_mm256_set_m128i(m, m);
+ vint8 vmax(r);
+ return vmax;
+}
+
+/**
+ * @brief Store a vector to a 16B aligned memory address.
+ */
+ASTCENC_SIMD_INLINE void storea(vint8 a, int* p)
+{
+ _mm256_store_si256(reinterpret_cast<__m256i*>(p), a.m);
+}
+
+/**
+ * @brief Store a vector to an unaligned memory address.
+ */
+ASTCENC_SIMD_INLINE void store(vint8 a, int* p)
+{
+ _mm256_storeu_si256(reinterpret_cast<__m256i*>(p), a.m);
+}
+
+/**
+ * @brief Store lowest N (vector width) bytes into an unaligned address.
+ */
+ASTCENC_SIMD_INLINE void store_nbytes(vint8 a, uint8_t* p)
+{
+ // This is the most logical implementation, but the convenience intrinsic
+ // is missing on older compilers (supported in g++ 9 and clang++ 9).
+ // _mm_storeu_si64(ptr, _mm256_extracti128_si256(v.m, 0))
+ _mm_storel_epi64(reinterpret_cast<__m128i*>(p), _mm256_extracti128_si256(a.m, 0));
+}
+
+/**
+ * @brief Gather N (vector width) indices from the array.
+ */
+ASTCENC_SIMD_INLINE vint8 gatheri(const int* base, vint8 indices)
+{
+ return vint8(_mm256_i32gather_epi32(base, indices.m, 4));
+}
+
+/**
+ * @brief Pack low 8 bits of N (vector width) lanes into bottom of vector.
+ */
+ASTCENC_SIMD_INLINE vint8 pack_low_bytes(vint8 v)
+{
+ __m256i shuf = _mm256_set_epi8(0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 28, 24, 20, 16,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 12, 8, 4, 0);
+ __m256i a = _mm256_shuffle_epi8(v.m, shuf);
+ __m128i a0 = _mm256_extracti128_si256(a, 0);
+ __m128i a1 = _mm256_extracti128_si256(a, 1);
+ __m128i b = _mm_unpacklo_epi32(a0, a1);
+
+ __m256i r = astcenc_mm256_set_m128i(b, b);
+ return vint8(r);
+}
+
+/**
+ * @brief Return lanes from @c b if @c cond is set, else @c a.
+ */
+ASTCENC_SIMD_INLINE vint8 select(vint8 a, vint8 b, vmask8 cond)
+{
+ __m256i condi = _mm256_castps_si256(cond.m);
+ return vint8(_mm256_blendv_epi8(a.m, b.m, condi));
+}
+
+// ============================================================================
+// vfloat4 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: vector by vector addition.
+ */
+ASTCENC_SIMD_INLINE vfloat8 operator+(vfloat8 a, vfloat8 b)
+{
+ return vfloat8(_mm256_add_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector incremental addition.
+ */
+ASTCENC_SIMD_INLINE vfloat8& operator+=(vfloat8& a, const vfloat8& b)
+{
+ a = a + b;
+ return a;
+}
+
+/**
+ * @brief Overload: vector by vector subtraction.
+ */
+ASTCENC_SIMD_INLINE vfloat8 operator-(vfloat8 a, vfloat8 b)
+{
+ return vfloat8(_mm256_sub_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector multiplication.
+ */
+ASTCENC_SIMD_INLINE vfloat8 operator*(vfloat8 a, vfloat8 b)
+{
+ return vfloat8(_mm256_mul_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by scalar multiplication.
+ */
+ASTCENC_SIMD_INLINE vfloat8 operator*(vfloat8 a, float b)
+{
+ return vfloat8(_mm256_mul_ps(a.m, _mm256_set1_ps(b)));
+}
+
+/**
+ * @brief Overload: scalar by vector multiplication.
+ */
+ASTCENC_SIMD_INLINE vfloat8 operator*(float a, vfloat8 b)
+{
+ return vfloat8(_mm256_mul_ps(_mm256_set1_ps(a), b.m));
+}
+
+/**
+ * @brief Overload: vector by vector division.
+ */
+ASTCENC_SIMD_INLINE vfloat8 operator/(vfloat8 a, vfloat8 b)
+{
+ return vfloat8(_mm256_div_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by scalar division.
+ */
+ASTCENC_SIMD_INLINE vfloat8 operator/(vfloat8 a, float b)
+{
+ return vfloat8(_mm256_div_ps(a.m, _mm256_set1_ps(b)));
+}
+
+
+/**
+ * @brief Overload: scalar by vector division.
+ */
+ASTCENC_SIMD_INLINE vfloat8 operator/(float a, vfloat8 b)
+{
+ return vfloat8(_mm256_div_ps(_mm256_set1_ps(a), b.m));
+}
+
+
+/**
+ * @brief Overload: vector by vector equality.
+ */
+ASTCENC_SIMD_INLINE vmask8 operator==(vfloat8 a, vfloat8 b)
+{
+ return vmask8(_mm256_cmp_ps(a.m, b.m, _CMP_EQ_OQ));
+}
+
+/**
+ * @brief Overload: vector by vector inequality.
+ */
+ASTCENC_SIMD_INLINE vmask8 operator!=(vfloat8 a, vfloat8 b)
+{
+ return vmask8(_mm256_cmp_ps(a.m, b.m, _CMP_NEQ_OQ));
+}
+
+/**
+ * @brief Overload: vector by vector less than.
+ */
+ASTCENC_SIMD_INLINE vmask8 operator<(vfloat8 a, vfloat8 b)
+{
+ return vmask8(_mm256_cmp_ps(a.m, b.m, _CMP_LT_OQ));
+}
+
+/**
+ * @brief Overload: vector by vector greater than.
+ */
+ASTCENC_SIMD_INLINE vmask8 operator>(vfloat8 a, vfloat8 b)
+{
+ return vmask8(_mm256_cmp_ps(a.m, b.m, _CMP_GT_OQ));
+}
+
+/**
+ * @brief Overload: vector by vector less than or equal.
+ */
+ASTCENC_SIMD_INLINE vmask8 operator<=(vfloat8 a, vfloat8 b)
+{
+ return vmask8(_mm256_cmp_ps(a.m, b.m, _CMP_LE_OQ));
+}
+
+/**
+ * @brief Overload: vector by vector greater than or equal.
+ */
+ASTCENC_SIMD_INLINE vmask8 operator>=(vfloat8 a, vfloat8 b)
+{
+ return vmask8(_mm256_cmp_ps(a.m, b.m, _CMP_GE_OQ));
+}
+
+/**
+ * @brief Return the min vector of two vectors.
+ *
+ * If either lane value is NaN, @c b will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat8 min(vfloat8 a, vfloat8 b)
+{
+ return vfloat8(_mm256_min_ps(a.m, b.m));
+}
+
+/**
+ * @brief Return the min vector of a vector and a scalar.
+ *
+ * If either lane value is NaN, @c b will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat8 min(vfloat8 a, float b)
+{
+ return min(a, vfloat8(b));
+}
+
+/**
+ * @brief Return the max vector of two vectors.
+ *
+ * If either lane value is NaN, @c b will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat8 max(vfloat8 a, vfloat8 b)
+{
+ return vfloat8(_mm256_max_ps(a.m, b.m));
+}
+
+/**
+ * @brief Return the max vector of a vector and a scalar.
+ *
+ * If either lane value is NaN, @c b will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat8 max(vfloat8 a, float b)
+{
+ return max(a, vfloat8(b));
+}
+
+/**
+ * @brief Return the clamped value between min and max.
+ *
+ * It is assumed that neither @c min nor @c max are NaN values. If @c a is NaN
+ * then @c min will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat8 clamp(float min, float max, vfloat8 a)
+{
+ // Do not reorder - second operand will return if either is NaN
+ a.m = _mm256_max_ps(a.m, _mm256_set1_ps(min));
+ a.m = _mm256_min_ps(a.m, _mm256_set1_ps(max));
+ return a;
+}
+
+/**
+ * @brief Return a clamped value between 0.0f and max.
+ *
+ * It is assumed that @c max is not a NaN value. If @c a is NaN then zero will
+ * be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat8 clampz(float max, vfloat8 a)
+{
+ a.m = _mm256_max_ps(a.m, _mm256_setzero_ps());
+ a.m = _mm256_min_ps(a.m, _mm256_set1_ps(max));
+ return a;
+}
+
+/**
+ * @brief Return a clamped value between 0.0f and 1.0f.
+ *
+ * If @c a is NaN then zero will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat8 clampzo(vfloat8 a)
+{
+ a.m = _mm256_max_ps(a.m, _mm256_setzero_ps());
+ a.m = _mm256_min_ps(a.m, _mm256_set1_ps(1.0f));
+ return a;
+}
+
+/**
+ * @brief Return the absolute value of the float vector.
+ */
+ASTCENC_SIMD_INLINE vfloat8 abs(vfloat8 a)
+{
+ __m256 msk = _mm256_castsi256_ps(_mm256_set1_epi32(0x7fffffff));
+ return vfloat8(_mm256_and_ps(a.m, msk));
+}
+
+/**
+ * @brief Return a float rounded to the nearest integer value.
+ */
+ASTCENC_SIMD_INLINE vfloat8 round(vfloat8 a)
+{
+ constexpr int flags = _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC;
+ return vfloat8(_mm256_round_ps(a.m, flags));
+}
+
+/**
+ * @brief Return the horizontal minimum of a vector.
+ */
+ASTCENC_SIMD_INLINE vfloat8 hmin(vfloat8 a)
+{
+ __m128 vlow = _mm256_castps256_ps128(a.m);
+ __m128 vhigh = _mm256_extractf128_ps(a.m, 1);
+ vlow = _mm_min_ps(vlow, vhigh);
+
+ // First do an horizontal reduction.
+ __m128 shuf = _mm_shuffle_ps(vlow, vlow, _MM_SHUFFLE(2, 3, 0, 1));
+ __m128 mins = _mm_min_ps(vlow, shuf);
+ shuf = _mm_movehl_ps(shuf, mins);
+ mins = _mm_min_ss(mins, shuf);
+
+ // This is the most logical implementation, but the convenience intrinsic
+ // is missing on older compilers (supported in g++ 9 and clang++ 9).
+ //__m256i r = _mm256_set_m128(m, m)
+ __m256 r = _mm256_insertf128_ps(_mm256_castps128_ps256(mins), mins, 1);
+
+ return vfloat8(_mm256_permute_ps(r, 0));
+}
+
+/**
+ * @brief Return the horizontal minimum of a vector.
+ */
+ASTCENC_SIMD_INLINE float hmin_s(vfloat8 a)
+{
+ return hmin(a).lane<0>();
+}
+
+/**
+ * @brief Return the horizontal maximum of a vector.
+ */
+ASTCENC_SIMD_INLINE vfloat8 hmax(vfloat8 a)
+{
+ __m128 vlow = _mm256_castps256_ps128(a.m);
+ __m128 vhigh = _mm256_extractf128_ps(a.m, 1);
+ vhigh = _mm_max_ps(vlow, vhigh);
+
+ // First do an horizontal reduction.
+ __m128 shuf = _mm_shuffle_ps(vhigh, vhigh, _MM_SHUFFLE(2, 3, 0, 1));
+ __m128 maxs = _mm_max_ps(vhigh, shuf);
+ shuf = _mm_movehl_ps(shuf,maxs);
+ maxs = _mm_max_ss(maxs, shuf);
+
+ // This is the most logical implementation, but the convenience intrinsic
+ // is missing on older compilers (supported in g++ 9 and clang++ 9).
+ //__m256i r = _mm256_set_m128(m, m)
+ __m256 r = _mm256_insertf128_ps(_mm256_castps128_ps256(maxs), maxs, 1);
+ return vfloat8(_mm256_permute_ps(r, 0));
+}
+
+/**
+ * @brief Return the horizontal maximum of a vector.
+ */
+ASTCENC_SIMD_INLINE float hmax_s(vfloat8 a)
+{
+ return hmax(a).lane<0>();
+}
+
+/**
+ * @brief Return the horizontal sum of a vector.
+ */
+ASTCENC_SIMD_INLINE float hadd_s(vfloat8 a)
+{
+ // Two sequential 4-wide adds gives invariance with 4-wide code
+ vfloat4 lo(_mm256_extractf128_ps(a.m, 0));
+ vfloat4 hi(_mm256_extractf128_ps(a.m, 1));
+ return hadd_s(lo) + hadd_s(hi);
+}
+
+/**
+ * @brief Return lanes from @c b if @c cond is set, else @c a.
+ */
+ASTCENC_SIMD_INLINE vfloat8 select(vfloat8 a, vfloat8 b, vmask8 cond)
+{
+ return vfloat8(_mm256_blendv_ps(a.m, b.m, cond.m));
+}
+
+/**
+ * @brief Return lanes from @c b if MSB of @c cond is set, else @c a.
+ */
+ASTCENC_SIMD_INLINE vfloat8 select_msb(vfloat8 a, vfloat8 b, vmask8 cond)
+{
+ return vfloat8(_mm256_blendv_ps(a.m, b.m, cond.m));
+}
+
+/**
+ * @brief Accumulate lane-wise sums for a vector, folded 4-wide.
+ *
+ * This is invariant with 4-wide implementations.
+ */
+ASTCENC_SIMD_INLINE void haccumulate(vfloat4& accum, vfloat8 a)
+{
+ vfloat4 lo(_mm256_extractf128_ps(a.m, 0));
+ haccumulate(accum, lo);
+
+ vfloat4 hi(_mm256_extractf128_ps(a.m, 1));
+ haccumulate(accum, hi);
+}
+
+/**
+ * @brief Accumulate lane-wise sums for a vector.
+ *
+ * This is NOT invariant with 4-wide implementations.
+ */
+ASTCENC_SIMD_INLINE void haccumulate(vfloat8& accum, vfloat8 a)
+{
+ accum += a;
+}
+
+/**
+ * @brief Accumulate masked lane-wise sums for a vector, folded 4-wide.
+ *
+ * This is invariant with 4-wide implementations.
+ */
+ASTCENC_SIMD_INLINE void haccumulate(vfloat4& accum, vfloat8 a, vmask8 m)
+{
+ a = select(vfloat8::zero(), a, m);
+ haccumulate(accum, a);
+}
+
+/**
+ * @brief Accumulate masked lane-wise sums for a vector.
+ *
+ * This is NOT invariant with 4-wide implementations.
+ */
+ASTCENC_SIMD_INLINE void haccumulate(vfloat8& accum, vfloat8 a, vmask8 m)
+{
+ a = select(vfloat8::zero(), a, m);
+ haccumulate(accum, a);
+}
+
+/**
+ * @brief Return the sqrt of the lanes in the vector.
+ */
+ASTCENC_SIMD_INLINE vfloat8 sqrt(vfloat8 a)
+{
+ return vfloat8(_mm256_sqrt_ps(a.m));
+}
+
+/**
+ * @brief Load a vector of gathered results from an array;
+ */
+ASTCENC_SIMD_INLINE vfloat8 gatherf(const float* base, vint8 indices)
+{
+ return vfloat8(_mm256_i32gather_ps(base, indices.m, 4));
+}
+
+/**
+ * @brief Store a vector to an unaligned memory address.
+ */
+ASTCENC_SIMD_INLINE void store(vfloat8 a, float* p)
+{
+ _mm256_storeu_ps(p, a.m);
+}
+
+/**
+ * @brief Store a vector to a 32B aligned memory address.
+ */
+ASTCENC_SIMD_INLINE void storea(vfloat8 a, float* p)
+{
+ _mm256_store_ps(p, a.m);
+}
+
+/**
+ * @brief Return a integer value for a float vector, using truncation.
+ */
+ASTCENC_SIMD_INLINE vint8 float_to_int(vfloat8 a)
+{
+ return vint8(_mm256_cvttps_epi32(a.m));
+}
+
+/**
+ * @brief Return a integer value for a float vector, using round-to-nearest.
+ */
+ASTCENC_SIMD_INLINE vint8 float_to_int_rtn(vfloat8 a)
+{
+ a = round(a);
+ return vint8(_mm256_cvttps_epi32(a.m));
+}
+
+
+/**
+ * @brief Return a float value for an integer vector.
+ */
+ASTCENC_SIMD_INLINE vfloat8 int_to_float(vint8 a)
+{
+ return vfloat8(_mm256_cvtepi32_ps(a.m));
+}
+
+/**
+ * @brief Return a float value as an integer bit pattern (i.e. no conversion).
+ *
+ * It is a common trick to convert floats into integer bit patterns, perform
+ * some bit hackery based on knowledge they are IEEE 754 layout, and then
+ * convert them back again. This is the first half of that flip.
+ */
+ASTCENC_SIMD_INLINE vint8 float_as_int(vfloat8 a)
+{
+ return vint8(_mm256_castps_si256(a.m));
+}
+
+/**
+ * @brief Return a integer value as a float bit pattern (i.e. no conversion).
+ *
+ * It is a common trick to convert floats into integer bit patterns, perform
+ * some bit hackery based on knowledge they are IEEE 754 layout, and then
+ * convert them back again. This is the second half of that flip.
+ */
+ASTCENC_SIMD_INLINE vfloat8 int_as_float(vint8 a)
+{
+ return vfloat8(_mm256_castsi256_ps(a.m));
+}
+
+/**
+ * @brief Prepare a vtable lookup table for use with the native SIMD size.
+ */
+ASTCENC_SIMD_INLINE void vtable_prepare(vint4 t0, vint8& t0p)
+{
+ // AVX2 duplicates the table within each 128-bit lane
+ __m128i t0n = t0.m;
+ t0p = vint8(astcenc_mm256_set_m128i(t0n, t0n));
+}
+
+/**
+ * @brief Prepare a vtable lookup table for use with the native SIMD size.
+ */
+ASTCENC_SIMD_INLINE void vtable_prepare(vint4 t0, vint4 t1, vint8& t0p, vint8& t1p)
+{
+ // AVX2 duplicates the table within each 128-bit lane
+ __m128i t0n = t0.m;
+ t0p = vint8(astcenc_mm256_set_m128i(t0n, t0n));
+
+ __m128i t1n = _mm_xor_si128(t0.m, t1.m);
+ t1p = vint8(astcenc_mm256_set_m128i(t1n, t1n));
+}
+
+/**
+ * @brief Prepare a vtable lookup table for use with the native SIMD size.
+ */
+ASTCENC_SIMD_INLINE void vtable_prepare(
+ vint4 t0, vint4 t1, vint4 t2, vint4 t3,
+ vint8& t0p, vint8& t1p, vint8& t2p, vint8& t3p)
+{
+ // AVX2 duplicates the table within each 128-bit lane
+ __m128i t0n = t0.m;
+ t0p = vint8(astcenc_mm256_set_m128i(t0n, t0n));
+
+ __m128i t1n = _mm_xor_si128(t0.m, t1.m);
+ t1p = vint8(astcenc_mm256_set_m128i(t1n, t1n));
+
+ __m128i t2n = _mm_xor_si128(t1.m, t2.m);
+ t2p = vint8(astcenc_mm256_set_m128i(t2n, t2n));
+
+ __m128i t3n = _mm_xor_si128(t2.m, t3.m);
+ t3p = vint8(astcenc_mm256_set_m128i(t3n, t3n));
+}
+
+/**
+ * @brief Perform an 8-bit 16-entry table lookup, with 32-bit indexes.
+ */
+ASTCENC_SIMD_INLINE vint8 vtable_8bt_32bi(vint8 t0, vint8 idx)
+{
+ // Set index byte MSB to 1 for unused bytes so shuffle returns zero
+ __m256i idxx = _mm256_or_si256(idx.m, _mm256_set1_epi32(static_cast<int>(0xFFFFFF00)));
+
+ __m256i result = _mm256_shuffle_epi8(t0.m, idxx);
+ return vint8(result);
+}
+
+/**
+ * @brief Perform an 8-bit 32-entry table lookup, with 32-bit indexes.
+ */
+ASTCENC_SIMD_INLINE vint8 vtable_8bt_32bi(vint8 t0, vint8 t1, vint8 idx)
+{
+ // Set index byte MSB to 1 for unused bytes so shuffle returns zero
+ __m256i idxx = _mm256_or_si256(idx.m, _mm256_set1_epi32(static_cast<int>(0xFFFFFF00)));
+
+ __m256i result = _mm256_shuffle_epi8(t0.m, idxx);
+ idxx = _mm256_sub_epi8(idxx, _mm256_set1_epi8(16));
+
+ __m256i result2 = _mm256_shuffle_epi8(t1.m, idxx);
+ result = _mm256_xor_si256(result, result2);
+ return vint8(result);
+}
+
+/**
+ * @brief Perform an 8-bit 64-entry table lookup, with 32-bit indexes.
+ */
+ASTCENC_SIMD_INLINE vint8 vtable_8bt_32bi(vint8 t0, vint8 t1, vint8 t2, vint8 t3, vint8 idx)
+{
+ // Set index byte MSB to 1 for unused bytes so shuffle returns zero
+ __m256i idxx = _mm256_or_si256(idx.m, _mm256_set1_epi32(static_cast<int>(0xFFFFFF00)));
+
+ __m256i result = _mm256_shuffle_epi8(t0.m, idxx);
+ idxx = _mm256_sub_epi8(idxx, _mm256_set1_epi8(16));
+
+ __m256i result2 = _mm256_shuffle_epi8(t1.m, idxx);
+ result = _mm256_xor_si256(result, result2);
+ idxx = _mm256_sub_epi8(idxx, _mm256_set1_epi8(16));
+
+ result2 = _mm256_shuffle_epi8(t2.m, idxx);
+ result = _mm256_xor_si256(result, result2);
+ idxx = _mm256_sub_epi8(idxx, _mm256_set1_epi8(16));
+
+ result2 = _mm256_shuffle_epi8(t3.m, idxx);
+ result = _mm256_xor_si256(result, result2);
+
+ return vint8(result);
+}
+
+/**
+ * @brief Return a vector of interleaved RGBA data.
+ *
+ * Input vectors have the value stored in the bottom 8 bits of each lane,
+ * with high bits set to zero.
+ *
+ * Output vector stores a single RGBA texel packed in each lane.
+ */
+ASTCENC_SIMD_INLINE vint8 interleave_rgba8(vint8 r, vint8 g, vint8 b, vint8 a)
+{
+ return r + lsl<8>(g) + lsl<16>(b) + lsl<24>(a);
+}
+
+/**
+ * @brief Store a vector, skipping masked lanes.
+ *
+ * All masked lanes must be at the end of vector, after all non-masked lanes.
+ */
+ASTCENC_SIMD_INLINE void store_lanes_masked(int* base, vint8 data, vmask8 mask)
+{
+ _mm256_maskstore_epi32(base, _mm256_castps_si256(mask.m), data.m);
+}
+
+/**
+ * @brief Debug function to print a vector of ints.
+ */
+ASTCENC_SIMD_INLINE void print(vint8 a)
+{
+ alignas(ASTCENC_VECALIGN) int v[8];
+ storea(a, v);
+ printf("v8_i32:\n %8d %8d %8d %8d %8d %8d %8d %8d\n",
+ v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
+}
+
+/**
+ * @brief Debug function to print a vector of ints.
+ */
+ASTCENC_SIMD_INLINE void printx(vint8 a)
+{
+ alignas(ASTCENC_VECALIGN) int v[8];
+ storea(a, v);
+ printf("v8_i32:\n %08x %08x %08x %08x %08x %08x %08x %08x\n",
+ v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7]);
+}
+
+/**
+ * @brief Debug function to print a vector of floats.
+ */
+ASTCENC_SIMD_INLINE void print(vfloat8 a)
+{
+ alignas(ASTCENC_VECALIGN) float v[8];
+ storea(a, v);
+ printf("v8_f32:\n %0.4f %0.4f %0.4f %0.4f %0.4f %0.4f %0.4f %0.4f\n",
+ static_cast<double>(v[0]), static_cast<double>(v[1]),
+ static_cast<double>(v[2]), static_cast<double>(v[3]),
+ static_cast<double>(v[4]), static_cast<double>(v[5]),
+ static_cast<double>(v[6]), static_cast<double>(v[7]));
+}
+
+/**
+ * @brief Debug function to print a vector of masks.
+ */
+ASTCENC_SIMD_INLINE void print(vmask8 a)
+{
+ print(select(vint8(0), vint8(1), a));
+}
+
+#endif // #ifndef ASTC_VECMATHLIB_AVX2_8_H_INCLUDED
diff --git a/thirdparty/astcenc/astcenc_vecmathlib_common_4.h b/thirdparty/astcenc/astcenc_vecmathlib_common_4.h
new file mode 100644
index 0000000000..86ee4fd3e1
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_vecmathlib_common_4.h
@@ -0,0 +1,423 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2020-2021 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Generic 4x32-bit vector functions.
+ *
+ * This module implements generic 4-wide vector functions that are valid for
+ * all instruction sets, typically implemented using lower level 4-wide
+ * operations that are ISA-specific.
+ */
+
+#ifndef ASTC_VECMATHLIB_COMMON_4_H_INCLUDED
+#define ASTC_VECMATHLIB_COMMON_4_H_INCLUDED
+
+#ifndef ASTCENC_SIMD_INLINE
+ #error "Include astcenc_vecmathlib.h, do not include directly"
+#endif
+
+#include <cstdio>
+
+// ============================================================================
+// vmask4 operators and functions
+// ============================================================================
+
+/**
+ * @brief True if any lanes are enabled, false otherwise.
+ */
+ASTCENC_SIMD_INLINE bool any(vmask4 a)
+{
+ return mask(a) != 0;
+}
+
+/**
+ * @brief True if all lanes are enabled, false otherwise.
+ */
+ASTCENC_SIMD_INLINE bool all(vmask4 a)
+{
+ return mask(a) == 0xF;
+}
+
+// ============================================================================
+// vint4 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: vector by scalar addition.
+ */
+ASTCENC_SIMD_INLINE vint4 operator+(vint4 a, int b)
+{
+ return a + vint4(b);
+}
+
+/**
+ * @brief Overload: vector by vector incremental addition.
+ */
+ASTCENC_SIMD_INLINE vint4& operator+=(vint4& a, const vint4& b)
+{
+ a = a + b;
+ return a;
+}
+
+/**
+ * @brief Overload: vector by scalar subtraction.
+ */
+ASTCENC_SIMD_INLINE vint4 operator-(vint4 a, int b)
+{
+ return a - vint4(b);
+}
+
+/**
+ * @brief Overload: vector by scalar multiplication.
+ */
+ASTCENC_SIMD_INLINE vint4 operator*(vint4 a, int b)
+{
+ return a * vint4(b);
+}
+
+/**
+ * @brief Overload: vector by scalar bitwise or.
+ */
+ASTCENC_SIMD_INLINE vint4 operator|(vint4 a, int b)
+{
+ return a | vint4(b);
+}
+
+/**
+ * @brief Overload: vector by scalar bitwise and.
+ */
+ASTCENC_SIMD_INLINE vint4 operator&(vint4 a, int b)
+{
+ return a & vint4(b);
+}
+
+/**
+ * @brief Overload: vector by scalar bitwise xor.
+ */
+ASTCENC_SIMD_INLINE vint4 operator^(vint4 a, int b)
+{
+ return a ^ vint4(b);
+}
+
+/**
+ * @brief Return the clamped value between min and max.
+ */
+ASTCENC_SIMD_INLINE vint4 clamp(int minv, int maxv, vint4 a)
+{
+ return min(max(a, vint4(minv)), vint4(maxv));
+}
+
+/**
+ * @brief Return the horizontal sum of RGB vector lanes as a scalar.
+ */
+ASTCENC_SIMD_INLINE int hadd_rgb_s(vint4 a)
+{
+ return a.lane<0>() + a.lane<1>() + a.lane<2>();
+}
+
+// ============================================================================
+// vfloat4 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: vector by vector incremental addition.
+ */
+ASTCENC_SIMD_INLINE vfloat4& operator+=(vfloat4& a, const vfloat4& b)
+{
+ a = a + b;
+ return a;
+}
+
+/**
+ * @brief Overload: vector by scalar addition.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator+(vfloat4 a, float b)
+{
+ return a + vfloat4(b);
+}
+
+/**
+ * @brief Overload: vector by scalar subtraction.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator-(vfloat4 a, float b)
+{
+ return a - vfloat4(b);
+}
+
+/**
+ * @brief Overload: vector by scalar multiplication.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator*(vfloat4 a, float b)
+{
+ return a * vfloat4(b);
+}
+
+/**
+ * @brief Overload: scalar by vector multiplication.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator*(float a, vfloat4 b)
+{
+ return vfloat4(a) * b;
+}
+
+/**
+ * @brief Overload: vector by scalar division.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator/(vfloat4 a, float b)
+{
+ return a / vfloat4(b);
+}
+
+/**
+ * @brief Overload: scalar by vector division.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator/(float a, vfloat4 b)
+{
+ return vfloat4(a) / b;
+}
+
+/**
+ * @brief Return the min vector of a vector and a scalar.
+ *
+ * If either lane value is NaN, @c b will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat4 min(vfloat4 a, float b)
+{
+ return min(a, vfloat4(b));
+}
+
+/**
+ * @brief Return the max vector of a vector and a scalar.
+ *
+ * If either lane value is NaN, @c b will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat4 max(vfloat4 a, float b)
+{
+ return max(a, vfloat4(b));
+}
+
+/**
+ * @brief Return the clamped value between min and max.
+ *
+ * It is assumed that neither @c min nor @c max are NaN values. If @c a is NaN
+ * then @c min will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat4 clamp(float minv, float maxv, vfloat4 a)
+{
+ // Do not reorder - second operand will return if either is NaN
+ return min(max(a, minv), maxv);
+}
+
+/**
+ * @brief Return the clamped value between 0.0f and max.
+ *
+ * It is assumed that @c max is not a NaN value. If @c a is NaN then zero will
+ * be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat4 clampz(float maxv, vfloat4 a)
+{
+ // Do not reorder - second operand will return if either is NaN
+ return min(max(a, vfloat4::zero()), maxv);
+}
+
+/**
+ * @brief Return the clamped value between 0.0f and 1.0f.
+ *
+ * If @c a is NaN then zero will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat4 clampzo(vfloat4 a)
+{
+ // Do not reorder - second operand will return if either is NaN
+ return min(max(a, vfloat4::zero()), 1.0f);
+}
+
+/**
+ * @brief Return the horizontal minimum of a vector.
+ */
+ASTCENC_SIMD_INLINE float hmin_s(vfloat4 a)
+{
+ return hmin(a).lane<0>();
+}
+
+/**
+ * @brief Return the horizontal min of RGB vector lanes as a scalar.
+ */
+ASTCENC_SIMD_INLINE float hmin_rgb_s(vfloat4 a)
+{
+ a.set_lane<3>(a.lane<0>());
+ return hmin_s(a);
+}
+
+/**
+ * @brief Return the horizontal maximum of a vector.
+ */
+ASTCENC_SIMD_INLINE float hmax_s(vfloat4 a)
+{
+ return hmax(a).lane<0>();
+}
+
+/**
+ * @brief Accumulate lane-wise sums for a vector.
+ */
+ASTCENC_SIMD_INLINE void haccumulate(vfloat4& accum, vfloat4 a)
+{
+ accum = accum + a;
+}
+
+/**
+ * @brief Accumulate lane-wise sums for a masked vector.
+ */
+ASTCENC_SIMD_INLINE void haccumulate(vfloat4& accum, vfloat4 a, vmask4 m)
+{
+ a = select(vfloat4::zero(), a, m);
+ haccumulate(accum, a);
+}
+
+/**
+ * @brief Return the horizontal sum of RGB vector lanes as a scalar.
+ */
+ASTCENC_SIMD_INLINE float hadd_rgb_s(vfloat4 a)
+{
+ return a.lane<0>() + a.lane<1>() + a.lane<2>();
+}
+
+#if !defined(ASTCENC_USE_NATIVE_DOT_PRODUCT)
+
+/**
+ * @brief Return the dot product for the full 4 lanes, returning scalar.
+ */
+ASTCENC_SIMD_INLINE float dot_s(vfloat4 a, vfloat4 b)
+{
+ vfloat4 m = a * b;
+ return hadd_s(m);
+}
+
+/**
+ * @brief Return the dot product for the full 4 lanes, returning vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 dot(vfloat4 a, vfloat4 b)
+{
+ vfloat4 m = a * b;
+ return vfloat4(hadd_s(m));
+}
+
+/**
+ * @brief Return the dot product for the bottom 3 lanes, returning scalar.
+ */
+ASTCENC_SIMD_INLINE float dot3_s(vfloat4 a, vfloat4 b)
+{
+ vfloat4 m = a * b;
+ return hadd_rgb_s(m);
+}
+
+/**
+ * @brief Return the dot product for the bottom 3 lanes, returning vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 dot3(vfloat4 a, vfloat4 b)
+{
+ vfloat4 m = a * b;
+ float d3 = hadd_rgb_s(m);
+ return vfloat4(d3, d3, d3, 0.0f);
+}
+
+#endif
+
+#if !defined(ASTCENC_USE_NATIVE_POPCOUNT)
+
+/**
+ * @brief Population bit count.
+ *
+ * @param v The value to population count.
+ *
+ * @return The number of 1 bits.
+ */
+static inline int popcount(uint64_t v)
+{
+ uint64_t mask1 = 0x5555555555555555ULL;
+ uint64_t mask2 = 0x3333333333333333ULL;
+ uint64_t mask3 = 0x0F0F0F0F0F0F0F0FULL;
+ v -= (v >> 1) & mask1;
+ v = (v & mask2) + ((v >> 2) & mask2);
+ v += v >> 4;
+ v &= mask3;
+ v *= 0x0101010101010101ULL;
+ v >>= 56;
+ return static_cast<int>(v);
+}
+
+#endif
+
+/**
+ * @brief Apply signed bit transfer.
+ *
+ * @param input0 The first encoded endpoint.
+ * @param input1 The second encoded endpoint.
+ */
+static ASTCENC_SIMD_INLINE void bit_transfer_signed(
+ vint4& input0,
+ vint4& input1
+) {
+ input1 = lsr<1>(input1) | (input0 & 0x80);
+ input0 = lsr<1>(input0) & 0x3F;
+
+ vmask4 mask = (input0 & 0x20) != vint4::zero();
+ input0 = select(input0, input0 - 0x40, mask);
+}
+
+/**
+ * @brief Debug function to print a vector of ints.
+ */
+ASTCENC_SIMD_INLINE void print(vint4 a)
+{
+ alignas(16) int v[4];
+ storea(a, v);
+ printf("v4_i32:\n %8d %8d %8d %8d\n",
+ v[0], v[1], v[2], v[3]);
+}
+
+/**
+ * @brief Debug function to print a vector of ints.
+ */
+ASTCENC_SIMD_INLINE void printx(vint4 a)
+{
+ alignas(16) int v[4];
+ storea(a, v);
+ printf("v4_i32:\n %08x %08x %08x %08x\n",
+ v[0], v[1], v[2], v[3]);
+}
+
+/**
+ * @brief Debug function to print a vector of floats.
+ */
+ASTCENC_SIMD_INLINE void print(vfloat4 a)
+{
+ alignas(16) float v[4];
+ storea(a, v);
+ printf("v4_f32:\n %0.4f %0.4f %0.4f %0.4f\n",
+ static_cast<double>(v[0]), static_cast<double>(v[1]),
+ static_cast<double>(v[2]), static_cast<double>(v[3]));
+}
+
+/**
+ * @brief Debug function to print a vector of masks.
+ */
+ASTCENC_SIMD_INLINE void print(vmask4 a)
+{
+ print(select(vint4(0), vint4(1), a));
+}
+
+#endif // #ifndef ASTC_VECMATHLIB_COMMON_4_H_INCLUDED
diff --git a/thirdparty/astcenc/astcenc_vecmathlib_neon_4.h b/thirdparty/astcenc/astcenc_vecmathlib_neon_4.h
new file mode 100644
index 0000000000..e742eae6cb
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_vecmathlib_neon_4.h
@@ -0,0 +1,1072 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2019-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief 4x32-bit vectors, implemented using Armv8-A NEON.
+ *
+ * This module implements 4-wide 32-bit float, int, and mask vectors for
+ * Armv8-A NEON.
+ *
+ * There is a baseline level of functionality provided by all vector widths and
+ * implementations. This is implemented using identical function signatures,
+ * modulo data type, so we can use them as substitutable implementations in VLA
+ * code.
+ *
+ * The 4-wide vectors are also used as a fixed-width type, and significantly
+ * extend the functionality above that available to VLA code.
+ */
+
+#ifndef ASTC_VECMATHLIB_NEON_4_H_INCLUDED
+#define ASTC_VECMATHLIB_NEON_4_H_INCLUDED
+
+#ifndef ASTCENC_SIMD_INLINE
+ #error "Include astcenc_vecmathlib.h, do not include directly"
+#endif
+
+#include <cstdio>
+
+// ============================================================================
+// vfloat4 data type
+// ============================================================================
+
+/**
+ * @brief Data type for 4-wide floats.
+ */
+struct vfloat4
+{
+ /**
+ * @brief Construct from zero-initialized value.
+ */
+ ASTCENC_SIMD_INLINE vfloat4() = default;
+
+ /**
+ * @brief Construct from 4 values loaded from an unaligned address.
+ *
+ * Consider using loada() which is better with vectors if data is aligned
+ * to vector length.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat4(const float *p)
+ {
+ m = vld1q_f32(p);
+ }
+
+ /**
+ * @brief Construct from 1 scalar value replicated across all lanes.
+ *
+ * Consider using zero() for constexpr zeros.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat4(float a)
+ {
+ m = vdupq_n_f32(a);
+ }
+
+ /**
+ * @brief Construct from 4 scalar values.
+ *
+ * The value of @c a is stored to lane 0 (LSB) in the SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat4(float a, float b, float c, float d)
+ {
+ float v[4] { a, b, c, d };
+ m = vld1q_f32(v);
+ }
+
+ /**
+ * @brief Construct from an existing SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat4(float32x4_t a)
+ {
+ m = a;
+ }
+
+ /**
+ * @brief Get the scalar value of a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE float lane() const
+ {
+ return vgetq_lane_f32(m, l);
+ }
+
+ /**
+ * @brief Set the scalar value of a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE void set_lane(float a)
+ {
+ m = vsetq_lane_f32(a, m, l);
+ }
+
+ /**
+ * @brief Factory that returns a vector of zeros.
+ */
+ static ASTCENC_SIMD_INLINE vfloat4 zero()
+ {
+ return vfloat4(vdupq_n_f32(0.0f));
+ }
+
+ /**
+ * @brief Factory that returns a replicated scalar loaded from memory.
+ */
+ static ASTCENC_SIMD_INLINE vfloat4 load1(const float* p)
+ {
+ return vfloat4(vld1q_dup_f32(p));
+ }
+
+ /**
+ * @brief Factory that returns a vector loaded from 16B aligned memory.
+ */
+ static ASTCENC_SIMD_INLINE vfloat4 loada(const float* p)
+ {
+ return vfloat4(vld1q_f32(p));
+ }
+
+ /**
+ * @brief Factory that returns a vector containing the lane IDs.
+ */
+ static ASTCENC_SIMD_INLINE vfloat4 lane_id()
+ {
+ alignas(16) float data[4] { 0.0f, 1.0f, 2.0f, 3.0f };
+ return vfloat4(vld1q_f32(data));
+ }
+
+ /**
+ * @brief Return a swizzled float 2.
+ */
+ template <int l0, int l1> ASTCENC_SIMD_INLINE vfloat4 swz() const
+ {
+ return vfloat4(lane<l0>(), lane<l1>(), 0.0f, 0.0f);
+ }
+
+ /**
+ * @brief Return a swizzled float 3.
+ */
+ template <int l0, int l1, int l2> ASTCENC_SIMD_INLINE vfloat4 swz() const
+ {
+ return vfloat4(lane<l0>(), lane<l1>(), lane<l2>(), 0.0f);
+ }
+
+ /**
+ * @brief Return a swizzled float 4.
+ */
+ template <int l0, int l1, int l2, int l3> ASTCENC_SIMD_INLINE vfloat4 swz() const
+ {
+ return vfloat4(lane<l0>(), lane<l1>(), lane<l2>(), lane<l3>());
+ }
+
+ /**
+ * @brief The vector ...
+ */
+ float32x4_t m;
+};
+
+// ============================================================================
+// vint4 data type
+// ============================================================================
+
+/**
+ * @brief Data type for 4-wide ints.
+ */
+struct vint4
+{
+ /**
+ * @brief Construct from zero-initialized value.
+ */
+ ASTCENC_SIMD_INLINE vint4() = default;
+
+ /**
+ * @brief Construct from 4 values loaded from an unaligned address.
+ *
+ * Consider using loada() which is better with vectors if data is aligned
+ * to vector length.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(const int *p)
+ {
+ m = vld1q_s32(p);
+ }
+
+ /**
+ * @brief Construct from 4 uint8_t loaded from an unaligned address.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(const uint8_t *p)
+ {
+ // Cast is safe - NEON loads are allowed to be unaligned
+ uint32x2_t t8 = vld1_dup_u32(reinterpret_cast<const uint32_t*>(p));
+ uint16x4_t t16 = vget_low_u16(vmovl_u8(vreinterpret_u8_u32(t8)));
+ m = vreinterpretq_s32_u32(vmovl_u16(t16));
+ }
+
+ /**
+ * @brief Construct from 1 scalar value replicated across all lanes.
+ *
+ * Consider using vfloat4::zero() for constexpr zeros.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(int a)
+ {
+ m = vdupq_n_s32(a);
+ }
+
+ /**
+ * @brief Construct from 4 scalar values.
+ *
+ * The value of @c a is stored to lane 0 (LSB) in the SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(int a, int b, int c, int d)
+ {
+ int v[4] { a, b, c, d };
+ m = vld1q_s32(v);
+ }
+
+ /**
+ * @brief Construct from an existing SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(int32x4_t a)
+ {
+ m = a;
+ }
+
+ /**
+ * @brief Get the scalar from a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE int lane() const
+ {
+ return vgetq_lane_s32(m, l);
+ }
+
+ /**
+ * @brief Set the scalar value of a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE void set_lane(int a)
+ {
+ m = vsetq_lane_s32(a, m, l);
+ }
+
+ /**
+ * @brief Factory that returns a vector of zeros.
+ */
+ static ASTCENC_SIMD_INLINE vint4 zero()
+ {
+ return vint4(0);
+ }
+
+ /**
+ * @brief Factory that returns a replicated scalar loaded from memory.
+ */
+ static ASTCENC_SIMD_INLINE vint4 load1(const int* p)
+ {
+ return vint4(*p);
+ }
+
+ /**
+ * @brief Factory that returns a vector loaded from 16B aligned memory.
+ */
+ static ASTCENC_SIMD_INLINE vint4 loada(const int* p)
+ {
+ return vint4(p);
+ }
+
+ /**
+ * @brief Factory that returns a vector containing the lane IDs.
+ */
+ static ASTCENC_SIMD_INLINE vint4 lane_id()
+ {
+ alignas(16) static const int data[4] { 0, 1, 2, 3 };
+ return vint4(vld1q_s32(data));
+ }
+
+ /**
+ * @brief The vector ...
+ */
+ int32x4_t m;
+};
+
+// ============================================================================
+// vmask4 data type
+// ============================================================================
+
+/**
+ * @brief Data type for 4-wide control plane masks.
+ */
+struct vmask4
+{
+ /**
+ * @brief Construct from an existing SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask4(uint32x4_t a)
+ {
+ m = a;
+ }
+
+#if !defined(_MSC_VER)
+ /**
+ * @brief Construct from an existing SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask4(int32x4_t a)
+ {
+ m = vreinterpretq_u32_s32(a);
+ }
+#endif
+
+ /**
+ * @brief Construct from 1 scalar value.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask4(bool a)
+ {
+ m = vreinterpretq_u32_s32(vdupq_n_s32(a == true ? -1 : 0));
+ }
+
+ /**
+ * @brief Construct from 4 scalar values.
+ *
+ * The value of @c a is stored to lane 0 (LSB) in the SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask4(bool a, bool b, bool c, bool d)
+ {
+ int v[4] {
+ a == true ? -1 : 0,
+ b == true ? -1 : 0,
+ c == true ? -1 : 0,
+ d == true ? -1 : 0
+ };
+
+ int32x4_t ms = vld1q_s32(v);
+ m = vreinterpretq_u32_s32(ms);
+ }
+
+ /**
+ * @brief Get the scalar from a single lane.
+ */
+ template <int32_t l> ASTCENC_SIMD_INLINE uint32_t lane() const
+ {
+ return vgetq_lane_u32(m, l);
+ }
+
+ /**
+ * @brief The vector ...
+ */
+ uint32x4_t m;
+};
+
+// ============================================================================
+// vmask4 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: mask union (or).
+ */
+ASTCENC_SIMD_INLINE vmask4 operator|(vmask4 a, vmask4 b)
+{
+ return vmask4(vorrq_u32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: mask intersect (and).
+ */
+ASTCENC_SIMD_INLINE vmask4 operator&(vmask4 a, vmask4 b)
+{
+ return vmask4(vandq_u32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: mask difference (xor).
+ */
+ASTCENC_SIMD_INLINE vmask4 operator^(vmask4 a, vmask4 b)
+{
+ return vmask4(veorq_u32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: mask invert (not).
+ */
+ASTCENC_SIMD_INLINE vmask4 operator~(vmask4 a)
+{
+ return vmask4(vmvnq_u32(a.m));
+}
+
+/**
+ * @brief Return a 4-bit mask code indicating mask status.
+ *
+ * bit0 = lane 0
+ */
+ASTCENC_SIMD_INLINE unsigned int mask(vmask4 a)
+{
+ static const int shifta[4] { 0, 1, 2, 3 };
+ static const int32x4_t shift = vld1q_s32(shifta);
+
+ uint32x4_t tmp = vshrq_n_u32(a.m, 31);
+ return vaddvq_u32(vshlq_u32(tmp, shift));
+}
+
+// ============================================================================
+// vint4 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: vector by vector addition.
+ */
+ASTCENC_SIMD_INLINE vint4 operator+(vint4 a, vint4 b)
+{
+ return vint4(vaddq_s32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector subtraction.
+ */
+ASTCENC_SIMD_INLINE vint4 operator-(vint4 a, vint4 b)
+{
+ return vint4(vsubq_s32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector multiplication.
+ */
+ASTCENC_SIMD_INLINE vint4 operator*(vint4 a, vint4 b)
+{
+ return vint4(vmulq_s32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector bit invert.
+ */
+ASTCENC_SIMD_INLINE vint4 operator~(vint4 a)
+{
+ return vint4(vmvnq_s32(a.m));
+}
+
+/**
+ * @brief Overload: vector by vector bitwise or.
+ */
+ASTCENC_SIMD_INLINE vint4 operator|(vint4 a, vint4 b)
+{
+ return vint4(vorrq_s32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector bitwise and.
+ */
+ASTCENC_SIMD_INLINE vint4 operator&(vint4 a, vint4 b)
+{
+ return vint4(vandq_s32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector bitwise xor.
+ */
+ASTCENC_SIMD_INLINE vint4 operator^(vint4 a, vint4 b)
+{
+ return vint4(veorq_s32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector equality.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator==(vint4 a, vint4 b)
+{
+ return vmask4(vceqq_s32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector inequality.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator!=(vint4 a, vint4 b)
+{
+ return ~vmask4(vceqq_s32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector less than.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator<(vint4 a, vint4 b)
+{
+ return vmask4(vcltq_s32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector greater than.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator>(vint4 a, vint4 b)
+{
+ return vmask4(vcgtq_s32(a.m, b.m));
+}
+
+/**
+ * @brief Logical shift left.
+ */
+template <int s> ASTCENC_SIMD_INLINE vint4 lsl(vint4 a)
+{
+ return vint4(vshlq_s32(a.m, vdupq_n_s32(s)));
+}
+
+/**
+ * @brief Logical shift right.
+ */
+template <int s> ASTCENC_SIMD_INLINE vint4 lsr(vint4 a)
+{
+ uint32x4_t ua = vreinterpretq_u32_s32(a.m);
+ ua = vshlq_u32(ua, vdupq_n_s32(-s));
+ return vint4(vreinterpretq_s32_u32(ua));
+}
+
+/**
+ * @brief Arithmetic shift right.
+ */
+template <int s> ASTCENC_SIMD_INLINE vint4 asr(vint4 a)
+{
+ return vint4(vshlq_s32(a.m, vdupq_n_s32(-s)));
+}
+
+/**
+ * @brief Return the min vector of two vectors.
+ */
+ASTCENC_SIMD_INLINE vint4 min(vint4 a, vint4 b)
+{
+ return vint4(vminq_s32(a.m, b.m));
+}
+
+/**
+ * @brief Return the max vector of two vectors.
+ */
+ASTCENC_SIMD_INLINE vint4 max(vint4 a, vint4 b)
+{
+ return vint4(vmaxq_s32(a.m, b.m));
+}
+
+/**
+ * @brief Return the horizontal minimum of a vector.
+ */
+ASTCENC_SIMD_INLINE vint4 hmin(vint4 a)
+{
+ return vint4(vminvq_s32(a.m));
+}
+
+/**
+ * @brief Return the horizontal maximum of a vector.
+ */
+ASTCENC_SIMD_INLINE vint4 hmax(vint4 a)
+{
+ return vint4(vmaxvq_s32(a.m));
+}
+
+/**
+ * @brief Return the horizontal sum of a vector.
+ */
+ASTCENC_SIMD_INLINE int hadd_s(vint4 a)
+{
+ int32x2_t t = vadd_s32(vget_high_s32(a.m), vget_low_s32(a.m));
+ return vget_lane_s32(vpadd_s32(t, t), 0);
+}
+
+/**
+ * @brief Store a vector to a 16B aligned memory address.
+ */
+ASTCENC_SIMD_INLINE void storea(vint4 a, int* p)
+{
+ vst1q_s32(p, a.m);
+}
+
+/**
+ * @brief Store a vector to an unaligned memory address.
+ */
+ASTCENC_SIMD_INLINE void store(vint4 a, int* p)
+{
+ vst1q_s32(p, a.m);
+}
+
+/**
+ * @brief Store lowest N (vector width) bytes into an unaligned address.
+ */
+ASTCENC_SIMD_INLINE void store_nbytes(vint4 a, uint8_t* p)
+{
+ vst1q_lane_s32(reinterpret_cast<int32_t*>(p), a.m, 0);
+}
+
+/**
+ * @brief Gather N (vector width) indices from the array.
+ */
+ASTCENC_SIMD_INLINE vint4 gatheri(const int* base, vint4 indices)
+{
+ alignas(16) int idx[4];
+ storea(indices, idx);
+ alignas(16) int vals[4];
+ vals[0] = base[idx[0]];
+ vals[1] = base[idx[1]];
+ vals[2] = base[idx[2]];
+ vals[3] = base[idx[3]];
+ return vint4(vals);
+}
+
+/**
+ * @brief Pack low 8 bits of N (vector width) lanes into bottom of vector.
+ */
+ASTCENC_SIMD_INLINE vint4 pack_low_bytes(vint4 a)
+{
+ alignas(16) uint8_t shuf[16] {
+ 0, 4, 8, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+ uint8x16_t idx = vld1q_u8(shuf);
+ int8x16_t av = vreinterpretq_s8_s32(a.m);
+ return vint4(vreinterpretq_s32_s8(vqtbl1q_s8(av, idx)));
+}
+
+/**
+ * @brief Return lanes from @c b if @c cond is set, else @c a.
+ */
+ASTCENC_SIMD_INLINE vint4 select(vint4 a, vint4 b, vmask4 cond)
+{
+ return vint4(vbslq_s32(cond.m, b.m, a.m));
+}
+
+// ============================================================================
+// vfloat4 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: vector by vector addition.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator+(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(vaddq_f32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector subtraction.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator-(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(vsubq_f32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector multiplication.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator*(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(vmulq_f32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector division.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator/(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(vdivq_f32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector equality.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator==(vfloat4 a, vfloat4 b)
+{
+ return vmask4(vceqq_f32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector inequality.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator!=(vfloat4 a, vfloat4 b)
+{
+ return vmask4(vmvnq_u32(vceqq_f32(a.m, b.m)));
+}
+
+/**
+ * @brief Overload: vector by vector less than.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator<(vfloat4 a, vfloat4 b)
+{
+ return vmask4(vcltq_f32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector greater than.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator>(vfloat4 a, vfloat4 b)
+{
+ return vmask4(vcgtq_f32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector less than or equal.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator<=(vfloat4 a, vfloat4 b)
+{
+ return vmask4(vcleq_f32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector greater than or equal.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator>=(vfloat4 a, vfloat4 b)
+{
+ return vmask4(vcgeq_f32(a.m, b.m));
+}
+
+/**
+ * @brief Return the min vector of two vectors.
+ *
+ * If either lane value is NaN, @c b will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat4 min(vfloat4 a, vfloat4 b)
+{
+ // Do not reorder - second operand will return if either is NaN
+ return vfloat4(vminnmq_f32(a.m, b.m));
+}
+
+/**
+ * @brief Return the max vector of two vectors.
+ *
+ * If either lane value is NaN, @c b will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat4 max(vfloat4 a, vfloat4 b)
+{
+ // Do not reorder - second operand will return if either is NaN
+ return vfloat4(vmaxnmq_f32(a.m, b.m));
+}
+
+/**
+ * @brief Return the absolute value of the float vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 abs(vfloat4 a)
+{
+ float32x4_t zero = vdupq_n_f32(0.0f);
+ float32x4_t inv = vsubq_f32(zero, a.m);
+ return vfloat4(vmaxq_f32(a.m, inv));
+}
+
+/**
+ * @brief Return a float rounded to the nearest integer value.
+ */
+ASTCENC_SIMD_INLINE vfloat4 round(vfloat4 a)
+{
+ return vfloat4(vrndnq_f32(a.m));
+}
+
+/**
+ * @brief Return the horizontal minimum of a vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 hmin(vfloat4 a)
+{
+ return vfloat4(vminvq_f32(a.m));
+}
+
+/**
+ * @brief Return the horizontal maximum of a vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 hmax(vfloat4 a)
+{
+ return vfloat4(vmaxvq_f32(a.m));
+}
+
+/**
+ * @brief Return the horizontal sum of a vector.
+ */
+ASTCENC_SIMD_INLINE float hadd_s(vfloat4 a)
+{
+ // Perform halving add to ensure invariance; we cannot use vaddqv as this
+ // does (0 + 1 + 2 + 3) which is not invariant with x86 (0 + 2) + (1 + 3).
+ float32x2_t t = vadd_f32(vget_high_f32(a.m), vget_low_f32(a.m));
+ return vget_lane_f32(vpadd_f32(t, t), 0);
+}
+
+/**
+ * @brief Return the sqrt of the lanes in the vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 sqrt(vfloat4 a)
+{
+ return vfloat4(vsqrtq_f32(a.m));
+}
+
+/**
+ * @brief Return lanes from @c b if @c cond is set, else @c a.
+ */
+ASTCENC_SIMD_INLINE vfloat4 select(vfloat4 a, vfloat4 b, vmask4 cond)
+{
+ return vfloat4(vbslq_f32(cond.m, b.m, a.m));
+}
+
+/**
+ * @brief Return lanes from @c b if MSB of @c cond is set, else @c a.
+ */
+ASTCENC_SIMD_INLINE vfloat4 select_msb(vfloat4 a, vfloat4 b, vmask4 cond)
+{
+ static const uint32x4_t msb = vdupq_n_u32(0x80000000u);
+ uint32x4_t mask = vcgeq_u32(cond.m, msb);
+ return vfloat4(vbslq_f32(mask, b.m, a.m));
+}
+
+/**
+ * @brief Load a vector of gathered results from an array;
+ */
+ASTCENC_SIMD_INLINE vfloat4 gatherf(const float* base, vint4 indices)
+{
+ alignas(16) int idx[4];
+ storea(indices, idx);
+ alignas(16) float vals[4];
+ vals[0] = base[idx[0]];
+ vals[1] = base[idx[1]];
+ vals[2] = base[idx[2]];
+ vals[3] = base[idx[3]];
+ return vfloat4(vals);
+}
+
+/**
+ * @brief Store a vector to an unaligned memory address.
+ */
+ASTCENC_SIMD_INLINE void store(vfloat4 a, float* p)
+{
+ vst1q_f32(p, a.m);
+}
+
+/**
+ * @brief Store a vector to a 16B aligned memory address.
+ */
+ASTCENC_SIMD_INLINE void storea(vfloat4 a, float* p)
+{
+ vst1q_f32(p, a.m);
+}
+
+/**
+ * @brief Return a integer value for a float vector, using truncation.
+ */
+ASTCENC_SIMD_INLINE vint4 float_to_int(vfloat4 a)
+{
+ return vint4(vcvtq_s32_f32(a.m));
+}
+
+/**
+ * @brief Return a integer value for a float vector, using round-to-nearest.
+ */
+ASTCENC_SIMD_INLINE vint4 float_to_int_rtn(vfloat4 a)
+{
+ a = round(a);
+ return vint4(vcvtq_s32_f32(a.m));
+}
+
+/**
+ * @brief Return a float value for an integer vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 int_to_float(vint4 a)
+{
+ return vfloat4(vcvtq_f32_s32(a.m));
+}
+
+/**
+ * @brief Return a float16 value for a float vector, using round-to-nearest.
+ */
+ASTCENC_SIMD_INLINE vint4 float_to_float16(vfloat4 a)
+{
+ // Generate float16 value
+ float16x4_t f16 = vcvt_f16_f32(a.m);
+
+ // Convert each 16-bit float pattern to a 32-bit pattern
+ uint16x4_t u16 = vreinterpret_u16_f16(f16);
+ uint32x4_t u32 = vmovl_u16(u16);
+ return vint4(vreinterpretq_s32_u32(u32));
+}
+
+/**
+ * @brief Return a float16 value for a float scalar, using round-to-nearest.
+ */
+static inline uint16_t float_to_float16(float a)
+{
+ vfloat4 av(a);
+ return static_cast<uint16_t>(float_to_float16(av).lane<0>());
+}
+
+/**
+ * @brief Return a float value for a float16 vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 float16_to_float(vint4 a)
+{
+ // Convert each 32-bit float pattern to a 16-bit pattern
+ uint32x4_t u32 = vreinterpretq_u32_s32(a.m);
+ uint16x4_t u16 = vmovn_u32(u32);
+ float16x4_t f16 = vreinterpret_f16_u16(u16);
+
+ // Generate float16 value
+ return vfloat4(vcvt_f32_f16(f16));
+}
+
+/**
+ * @brief Return a float value for a float16 scalar.
+ */
+ASTCENC_SIMD_INLINE float float16_to_float(uint16_t a)
+{
+ vint4 av(a);
+ return float16_to_float(av).lane<0>();
+}
+
+/**
+ * @brief Return a float value as an integer bit pattern (i.e. no conversion).
+ *
+ * It is a common trick to convert floats into integer bit patterns, perform
+ * some bit hackery based on knowledge they are IEEE 754 layout, and then
+ * convert them back again. This is the first half of that flip.
+ */
+ASTCENC_SIMD_INLINE vint4 float_as_int(vfloat4 a)
+{
+ return vint4(vreinterpretq_s32_f32(a.m));
+}
+
+/**
+ * @brief Return a integer value as a float bit pattern (i.e. no conversion).
+ *
+ * It is a common trick to convert floats into integer bit patterns, perform
+ * some bit hackery based on knowledge they are IEEE 754 layout, and then
+ * convert them back again. This is the second half of that flip.
+ */
+ASTCENC_SIMD_INLINE vfloat4 int_as_float(vint4 v)
+{
+ return vfloat4(vreinterpretq_f32_s32(v.m));
+}
+
+/**
+ * @brief Prepare a vtable lookup table for use with the native SIMD size.
+ */
+ASTCENC_SIMD_INLINE void vtable_prepare(vint4 t0, vint4& t0p)
+{
+ t0p = t0;
+}
+
+
+/**
+ * @brief Prepare a vtable lookup table for use with the native SIMD size.
+ */
+ASTCENC_SIMD_INLINE void vtable_prepare(vint4 t0, vint4 t1, vint4& t0p, vint4& t1p)
+{
+ t0p = t0;
+ t1p = t1;
+}
+
+/**
+ * @brief Prepare a vtable lookup table for use with the native SIMD size.
+ */
+ASTCENC_SIMD_INLINE void vtable_prepare(
+ vint4 t0, vint4 t1, vint4 t2, vint4 t3,
+ vint4& t0p, vint4& t1p, vint4& t2p, vint4& t3p)
+{
+ t0p = t0;
+ t1p = t1;
+ t2p = t2;
+ t3p = t3;
+}
+
+/**
+ * @brief Perform an 8-bit 16-entry table lookup, with 32-bit indexes.
+ */
+ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 idx)
+{
+ int8x16_t table {
+ vreinterpretq_s8_s32(t0.m)
+ };
+
+ // Set index byte above max index for unused bytes so table lookup returns zero
+ int32x4_t idx_masked = vorrq_s32(idx.m, vdupq_n_s32(0xFFFFFF00));
+ uint8x16_t idx_bytes = vreinterpretq_u8_s32(idx_masked);
+
+ return vint4(vreinterpretq_s32_s8(vqtbl1q_s8(table, idx_bytes)));
+}
+
+/**
+ * @brief Perform an 8-bit 32-entry table lookup, with 32-bit indexes.
+ */
+ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 t1, vint4 idx)
+{
+ int8x16x2_t table {
+ vreinterpretq_s8_s32(t0.m),
+ vreinterpretq_s8_s32(t1.m)
+ };
+
+ // Set index byte above max index for unused bytes so table lookup returns zero
+ int32x4_t idx_masked = vorrq_s32(idx.m, vdupq_n_s32(0xFFFFFF00));
+ uint8x16_t idx_bytes = vreinterpretq_u8_s32(idx_masked);
+
+ return vint4(vreinterpretq_s32_s8(vqtbl2q_s8(table, idx_bytes)));
+}
+
+/**
+ * @brief Perform an 8-bit 64-entry table lookup, with 32-bit indexes.
+ */
+ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 t1, vint4 t2, vint4 t3, vint4 idx)
+{
+ int8x16x4_t table {
+ vreinterpretq_s8_s32(t0.m),
+ vreinterpretq_s8_s32(t1.m),
+ vreinterpretq_s8_s32(t2.m),
+ vreinterpretq_s8_s32(t3.m)
+ };
+
+ // Set index byte above max index for unused bytes so table lookup returns zero
+ int32x4_t idx_masked = vorrq_s32(idx.m, vdupq_n_s32(0xFFFFFF00));
+ uint8x16_t idx_bytes = vreinterpretq_u8_s32(idx_masked);
+
+ return vint4(vreinterpretq_s32_s8(vqtbl4q_s8(table, idx_bytes)));
+}
+
+/**
+ * @brief Return a vector of interleaved RGBA data.
+ *
+ * Input vectors have the value stored in the bottom 8 bits of each lane,
+ * with high bits set to zero.
+ *
+ * Output vector stores a single RGBA texel packed in each lane.
+ */
+ASTCENC_SIMD_INLINE vint4 interleave_rgba8(vint4 r, vint4 g, vint4 b, vint4 a)
+{
+ return r + lsl<8>(g) + lsl<16>(b) + lsl<24>(a);
+}
+
+/**
+ * @brief Store a vector, skipping masked lanes.
+ *
+ * All masked lanes must be at the end of vector, after all non-masked lanes.
+ */
+ASTCENC_SIMD_INLINE void store_lanes_masked(int* base, vint4 data, vmask4 mask)
+{
+ if (mask.lane<3>())
+ {
+ store(data, base);
+ }
+ else if (mask.lane<2>())
+ {
+ base[0] = data.lane<0>();
+ base[1] = data.lane<1>();
+ base[2] = data.lane<2>();
+ }
+ else if (mask.lane<1>())
+ {
+ base[0] = data.lane<0>();
+ base[1] = data.lane<1>();
+ }
+ else if (mask.lane<0>())
+ {
+ base[0] = data.lane<0>();
+ }
+}
+
+#define ASTCENC_USE_NATIVE_POPCOUNT 1
+
+/**
+ * @brief Population bit count.
+ *
+ * @param v The value to population count.
+ *
+ * @return The number of 1 bits.
+ */
+ASTCENC_SIMD_INLINE int popcount(uint64_t v)
+{
+ return static_cast<int>(vaddlv_u8(vcnt_u8(vcreate_u8(v))));
+}
+
+#endif // #ifndef ASTC_VECMATHLIB_NEON_4_H_INCLUDED
diff --git a/thirdparty/astcenc/astcenc_vecmathlib_none_4.h b/thirdparty/astcenc/astcenc_vecmathlib_none_4.h
new file mode 100644
index 0000000000..d9b52be3e4
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_vecmathlib_none_4.h
@@ -0,0 +1,1169 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2019-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief 4x32-bit vectors, implemented using plain C++.
+ *
+ * This module implements 4-wide 32-bit float, int, and mask vectors. This
+ * module provides a scalar fallback for VLA code, primarily useful for
+ * debugging VLA algorithms without the complexity of handling SIMD. Only the
+ * baseline level of functionality needed to support VLA is provided.
+ *
+ * Note that the vector conditional operators implemented by this module are
+ * designed to behave like SIMD conditional operators that generate lane masks.
+ * Rather than returning 0/1 booleans like normal C++ code they will return
+ * 0/-1 to give a full lane-width bitmask.
+ *
+ * Note that the documentation for this module still talks about "vectors" to
+ * help developers think about the implied VLA behavior when writing optimized
+ * paths.
+ */
+
+#ifndef ASTC_VECMATHLIB_NONE_4_H_INCLUDED
+#define ASTC_VECMATHLIB_NONE_4_H_INCLUDED
+
+#ifndef ASTCENC_SIMD_INLINE
+ #error "Include astcenc_vecmathlib.h, do not include directly"
+#endif
+
+#include <algorithm>
+#include <cstdio>
+#include <cstring>
+#include <cfenv>
+
+// ============================================================================
+// vfloat4 data type
+// ============================================================================
+
+/**
+ * @brief Data type for 4-wide floats.
+ */
+struct vfloat4
+{
+ /**
+ * @brief Construct from zero-initialized value.
+ */
+ ASTCENC_SIMD_INLINE vfloat4() = default;
+
+ /**
+ * @brief Construct from 4 values loaded from an unaligned address.
+ *
+ * Consider using loada() which is better with wider VLA vectors if data is
+ * aligned to vector length.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat4(const float* p)
+ {
+ m[0] = p[0];
+ m[1] = p[1];
+ m[2] = p[2];
+ m[3] = p[3];
+ }
+
+ /**
+ * @brief Construct from 4 scalar values replicated across all lanes.
+ *
+ * Consider using zero() for constexpr zeros.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat4(float a)
+ {
+ m[0] = a;
+ m[1] = a;
+ m[2] = a;
+ m[3] = a;
+ }
+
+ /**
+ * @brief Construct from 4 scalar values.
+ *
+ * The value of @c a is stored to lane 0 (LSB) in the SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat4(float a, float b, float c, float d)
+ {
+ m[0] = a;
+ m[1] = b;
+ m[2] = c;
+ m[3] = d;
+ }
+
+ /**
+ * @brief Get the scalar value of a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE float lane() const
+ {
+ return m[l];
+ }
+
+ /**
+ * @brief Set the scalar value of a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE void set_lane(float a)
+ {
+ m[l] = a;
+ }
+
+ /**
+ * @brief Factory that returns a vector of zeros.
+ */
+ static ASTCENC_SIMD_INLINE vfloat4 zero()
+ {
+ return vfloat4(0.0f);
+ }
+
+ /**
+ * @brief Factory that returns a replicated scalar loaded from memory.
+ */
+ static ASTCENC_SIMD_INLINE vfloat4 load1(const float* p)
+ {
+ return vfloat4(*p);
+ }
+
+ /**
+ * @brief Factory that returns a vector loaded from aligned memory.
+ */
+ static ASTCENC_SIMD_INLINE vfloat4 loada(const float* p)
+ {
+ return vfloat4(p);
+ }
+
+ /**
+ * @brief Factory that returns a vector containing the lane IDs.
+ */
+ static ASTCENC_SIMD_INLINE vfloat4 lane_id()
+ {
+ return vfloat4(0.0f, 1.0f, 2.0f, 3.0f);
+ }
+
+ /**
+ * @brief Return a swizzled float 2.
+ */
+ template <int l0, int l1> ASTCENC_SIMD_INLINE vfloat4 swz() const
+ {
+ return vfloat4(lane<l0>(), lane<l1>(), 0.0f, 0.0f);
+ }
+
+ /**
+ * @brief Return a swizzled float 3.
+ */
+ template <int l0, int l1, int l2> ASTCENC_SIMD_INLINE vfloat4 swz() const
+ {
+ return vfloat4(lane<l0>(), lane<l1>(), lane<l2>(), 0.0f);
+ }
+
+ /**
+ * @brief Return a swizzled float 4.
+ */
+ template <int l0, int l1, int l2, int l3> ASTCENC_SIMD_INLINE vfloat4 swz() const
+ {
+ return vfloat4(lane<l0>(), lane<l1>(), lane<l2>(), lane<l3>());
+ }
+
+ /**
+ * @brief The vector ...
+ */
+ float m[4];
+};
+
+// ============================================================================
+// vint4 data type
+// ============================================================================
+
+/**
+ * @brief Data type for 4-wide ints.
+ */
+struct vint4
+{
+ /**
+ * @brief Construct from zero-initialized value.
+ */
+ ASTCENC_SIMD_INLINE vint4() = default;
+
+ /**
+ * @brief Construct from 4 values loaded from an unaligned address.
+ *
+ * Consider using vint4::loada() which is better with wider VLA vectors
+ * if data is aligned.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(const int* p)
+ {
+ m[0] = p[0];
+ m[1] = p[1];
+ m[2] = p[2];
+ m[3] = p[3];
+ }
+
+ /**
+ * @brief Construct from 4 uint8_t loaded from an unaligned address.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(const uint8_t *p)
+ {
+ m[0] = p[0];
+ m[1] = p[1];
+ m[2] = p[2];
+ m[3] = p[3];
+ }
+
+ /**
+ * @brief Construct from 4 scalar values.
+ *
+ * The value of @c a is stored to lane 0 (LSB) in the SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(int a, int b, int c, int d)
+ {
+ m[0] = a;
+ m[1] = b;
+ m[2] = c;
+ m[3] = d;
+ }
+
+
+ /**
+ * @brief Construct from 4 scalar values replicated across all lanes.
+ *
+ * Consider using vint4::zero() for constexpr zeros.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(int a)
+ {
+ m[0] = a;
+ m[1] = a;
+ m[2] = a;
+ m[3] = a;
+ }
+
+ /**
+ * @brief Get the scalar value of a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE int lane() const
+ {
+ return m[l];
+ }
+
+ /**
+ * @brief Set the scalar value of a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE void set_lane(int a)
+ {
+ m[l] = a;
+ }
+
+ /**
+ * @brief Factory that returns a vector of zeros.
+ */
+ static ASTCENC_SIMD_INLINE vint4 zero()
+ {
+ return vint4(0);
+ }
+
+ /**
+ * @brief Factory that returns a replicated scalar loaded from memory.
+ */
+ static ASTCENC_SIMD_INLINE vint4 load1(const int* p)
+ {
+ return vint4(*p);
+ }
+
+ /**
+ * @brief Factory that returns a vector loaded from 16B aligned memory.
+ */
+ static ASTCENC_SIMD_INLINE vint4 loada(const int* p)
+ {
+ return vint4(p);
+ }
+
+ /**
+ * @brief Factory that returns a vector containing the lane IDs.
+ */
+ static ASTCENC_SIMD_INLINE vint4 lane_id()
+ {
+ return vint4(0, 1, 2, 3);
+ }
+
+ /**
+ * @brief The vector ...
+ */
+ int m[4];
+};
+
+// ============================================================================
+// vmask4 data type
+// ============================================================================
+
+/**
+ * @brief Data type for 4-wide control plane masks.
+ */
+struct vmask4
+{
+ /**
+ * @brief Construct from an existing mask value.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask4(int* p)
+ {
+ m[0] = p[0];
+ m[1] = p[1];
+ m[2] = p[2];
+ m[3] = p[3];
+ }
+
+ /**
+ * @brief Construct from 1 scalar value.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask4(bool a)
+ {
+ m[0] = a == false ? 0 : -1;
+ m[1] = a == false ? 0 : -1;
+ m[2] = a == false ? 0 : -1;
+ m[3] = a == false ? 0 : -1;
+ }
+
+ /**
+ * @brief Construct from 4 scalar values.
+ *
+ * The value of @c a is stored to lane 0 (LSB) in the SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask4(bool a, bool b, bool c, bool d)
+ {
+ m[0] = a == false ? 0 : -1;
+ m[1] = b == false ? 0 : -1;
+ m[2] = c == false ? 0 : -1;
+ m[3] = d == false ? 0 : -1;
+ }
+
+
+ /**
+ * @brief The vector ...
+ */
+ int m[4];
+};
+
+// ============================================================================
+// vmask4 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: mask union (or).
+ */
+ASTCENC_SIMD_INLINE vmask4 operator|(vmask4 a, vmask4 b)
+{
+ return vmask4(a.m[0] | b.m[0],
+ a.m[1] | b.m[1],
+ a.m[2] | b.m[2],
+ a.m[3] | b.m[3]);
+}
+
+/**
+ * @brief Overload: mask intersect (and).
+ */
+ASTCENC_SIMD_INLINE vmask4 operator&(vmask4 a, vmask4 b)
+{
+ return vmask4(a.m[0] & b.m[0],
+ a.m[1] & b.m[1],
+ a.m[2] & b.m[2],
+ a.m[3] & b.m[3]);
+}
+
+/**
+ * @brief Overload: mask difference (xor).
+ */
+ASTCENC_SIMD_INLINE vmask4 operator^(vmask4 a, vmask4 b)
+{
+ return vmask4(a.m[0] ^ b.m[0],
+ a.m[1] ^ b.m[1],
+ a.m[2] ^ b.m[2],
+ a.m[3] ^ b.m[3]);
+}
+
+/**
+ * @brief Overload: mask invert (not).
+ */
+ASTCENC_SIMD_INLINE vmask4 operator~(vmask4 a)
+{
+ return vmask4(~a.m[0],
+ ~a.m[1],
+ ~a.m[2],
+ ~a.m[3]);
+}
+
+/**
+ * @brief Return a 1-bit mask code indicating mask status.
+ *
+ * bit0 = lane 0
+ */
+ASTCENC_SIMD_INLINE unsigned int mask(vmask4 a)
+{
+ return ((a.m[0] >> 31) & 0x1) |
+ ((a.m[1] >> 30) & 0x2) |
+ ((a.m[2] >> 29) & 0x4) |
+ ((a.m[3] >> 28) & 0x8);
+}
+
+// ============================================================================
+// vint4 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: vector by vector addition.
+ */
+ASTCENC_SIMD_INLINE vint4 operator+(vint4 a, vint4 b)
+{
+ return vint4(a.m[0] + b.m[0],
+ a.m[1] + b.m[1],
+ a.m[2] + b.m[2],
+ a.m[3] + b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector subtraction.
+ */
+ASTCENC_SIMD_INLINE vint4 operator-(vint4 a, vint4 b)
+{
+ return vint4(a.m[0] - b.m[0],
+ a.m[1] - b.m[1],
+ a.m[2] - b.m[2],
+ a.m[3] - b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector multiplication.
+ */
+ASTCENC_SIMD_INLINE vint4 operator*(vint4 a, vint4 b)
+{
+ return vint4(a.m[0] * b.m[0],
+ a.m[1] * b.m[1],
+ a.m[2] * b.m[2],
+ a.m[3] * b.m[3]);
+}
+
+/**
+ * @brief Overload: vector bit invert.
+ */
+ASTCENC_SIMD_INLINE vint4 operator~(vint4 a)
+{
+ return vint4(~a.m[0],
+ ~a.m[1],
+ ~a.m[2],
+ ~a.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector bitwise or.
+ */
+ASTCENC_SIMD_INLINE vint4 operator|(vint4 a, vint4 b)
+{
+ return vint4(a.m[0] | b.m[0],
+ a.m[1] | b.m[1],
+ a.m[2] | b.m[2],
+ a.m[3] | b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector bitwise and.
+ */
+ASTCENC_SIMD_INLINE vint4 operator&(vint4 a, vint4 b)
+{
+ return vint4(a.m[0] & b.m[0],
+ a.m[1] & b.m[1],
+ a.m[2] & b.m[2],
+ a.m[3] & b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector bitwise xor.
+ */
+ASTCENC_SIMD_INLINE vint4 operator^(vint4 a, vint4 b)
+{
+ return vint4(a.m[0] ^ b.m[0],
+ a.m[1] ^ b.m[1],
+ a.m[2] ^ b.m[2],
+ a.m[3] ^ b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector equality.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator==(vint4 a, vint4 b)
+{
+ return vmask4(a.m[0] == b.m[0],
+ a.m[1] == b.m[1],
+ a.m[2] == b.m[2],
+ a.m[3] == b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector inequality.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator!=(vint4 a, vint4 b)
+{
+ return vmask4(a.m[0] != b.m[0],
+ a.m[1] != b.m[1],
+ a.m[2] != b.m[2],
+ a.m[3] != b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector less than.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator<(vint4 a, vint4 b)
+{
+ return vmask4(a.m[0] < b.m[0],
+ a.m[1] < b.m[1],
+ a.m[2] < b.m[2],
+ a.m[3] < b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector greater than.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator>(vint4 a, vint4 b)
+{
+ return vmask4(a.m[0] > b.m[0],
+ a.m[1] > b.m[1],
+ a.m[2] > b.m[2],
+ a.m[3] > b.m[3]);
+}
+
+/**
+ * @brief Logical shift left.
+ */
+template <int s> ASTCENC_SIMD_INLINE vint4 lsl(vint4 a)
+{
+ return vint4(a.m[0] << s,
+ a.m[1] << s,
+ a.m[2] << s,
+ a.m[3] << s);
+}
+
+/**
+ * @brief Logical shift right.
+ */
+template <int s> ASTCENC_SIMD_INLINE vint4 lsr(vint4 a)
+{
+ unsigned int as0 = static_cast<unsigned int>(a.m[0]) >> s;
+ unsigned int as1 = static_cast<unsigned int>(a.m[1]) >> s;
+ unsigned int as2 = static_cast<unsigned int>(a.m[2]) >> s;
+ unsigned int as3 = static_cast<unsigned int>(a.m[3]) >> s;
+
+ return vint4(static_cast<int>(as0),
+ static_cast<int>(as1),
+ static_cast<int>(as2),
+ static_cast<int>(as3));
+}
+
+/**
+ * @brief Arithmetic shift right.
+ */
+template <int s> ASTCENC_SIMD_INLINE vint4 asr(vint4 a)
+{
+ return vint4(a.m[0] >> s,
+ a.m[1] >> s,
+ a.m[2] >> s,
+ a.m[3] >> s);
+}
+
+/**
+ * @brief Return the min vector of two vectors.
+ */
+ASTCENC_SIMD_INLINE vint4 min(vint4 a, vint4 b)
+{
+ return vint4(a.m[0] < b.m[0] ? a.m[0] : b.m[0],
+ a.m[1] < b.m[1] ? a.m[1] : b.m[1],
+ a.m[2] < b.m[2] ? a.m[2] : b.m[2],
+ a.m[3] < b.m[3] ? a.m[3] : b.m[3]);
+}
+
+/**
+ * @brief Return the min vector of two vectors.
+ */
+ASTCENC_SIMD_INLINE vint4 max(vint4 a, vint4 b)
+{
+ return vint4(a.m[0] > b.m[0] ? a.m[0] : b.m[0],
+ a.m[1] > b.m[1] ? a.m[1] : b.m[1],
+ a.m[2] > b.m[2] ? a.m[2] : b.m[2],
+ a.m[3] > b.m[3] ? a.m[3] : b.m[3]);
+}
+
+/**
+ * @brief Return the horizontal minimum of a single vector.
+ */
+ASTCENC_SIMD_INLINE vint4 hmin(vint4 a)
+{
+ int b = std::min(a.m[0], a.m[1]);
+ int c = std::min(a.m[2], a.m[3]);
+ return vint4(std::min(b, c));
+}
+
+/**
+ * @brief Return the horizontal maximum of a single vector.
+ */
+ASTCENC_SIMD_INLINE vint4 hmax(vint4 a)
+{
+ int b = std::max(a.m[0], a.m[1]);
+ int c = std::max(a.m[2], a.m[3]);
+ return vint4(std::max(b, c));
+}
+
+/**
+ * @brief Return the horizontal sum of vector lanes as a scalar.
+ */
+ASTCENC_SIMD_INLINE int hadd_s(vint4 a)
+{
+ return a.m[0] + a.m[1] + a.m[2] + a.m[3];
+}
+
+/**
+ * @brief Store a vector to an aligned memory address.
+ */
+ASTCENC_SIMD_INLINE void storea(vint4 a, int* p)
+{
+ p[0] = a.m[0];
+ p[1] = a.m[1];
+ p[2] = a.m[2];
+ p[3] = a.m[3];
+}
+
+/**
+ * @brief Store a vector to an unaligned memory address.
+ */
+ASTCENC_SIMD_INLINE void store(vint4 a, int* p)
+{
+ p[0] = a.m[0];
+ p[1] = a.m[1];
+ p[2] = a.m[2];
+ p[3] = a.m[3];
+}
+
+/**
+ * @brief Store lowest N (vector width) bytes into an unaligned address.
+ */
+ASTCENC_SIMD_INLINE void store_nbytes(vint4 a, uint8_t* p)
+{
+ int* pi = reinterpret_cast<int*>(p);
+ *pi = a.m[0];
+}
+
+/**
+ * @brief Gather N (vector width) indices from the array.
+ */
+ASTCENC_SIMD_INLINE vint4 gatheri(const int* base, vint4 indices)
+{
+ return vint4(base[indices.m[0]],
+ base[indices.m[1]],
+ base[indices.m[2]],
+ base[indices.m[3]]);
+}
+
+/**
+ * @brief Pack low 8 bits of N (vector width) lanes into bottom of vector.
+ */
+ASTCENC_SIMD_INLINE vint4 pack_low_bytes(vint4 a)
+{
+ int b0 = a.m[0] & 0xFF;
+ int b1 = a.m[1] & 0xFF;
+ int b2 = a.m[2] & 0xFF;
+ int b3 = a.m[3] & 0xFF;
+
+ int b = b0 | (b1 << 8) | (b2 << 16) | (b3 << 24);
+ return vint4(b, 0, 0, 0);
+}
+
+/**
+ * @brief Return lanes from @c b if MSB of @c cond is set, else @c a.
+ */
+ASTCENC_SIMD_INLINE vint4 select(vint4 a, vint4 b, vmask4 cond)
+{
+ return vint4((cond.m[0] & static_cast<int>(0x80000000)) ? b.m[0] : a.m[0],
+ (cond.m[1] & static_cast<int>(0x80000000)) ? b.m[1] : a.m[1],
+ (cond.m[2] & static_cast<int>(0x80000000)) ? b.m[2] : a.m[2],
+ (cond.m[3] & static_cast<int>(0x80000000)) ? b.m[3] : a.m[3]);
+}
+
+// ============================================================================
+// vfloat4 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: vector by vector addition.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator+(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(a.m[0] + b.m[0],
+ a.m[1] + b.m[1],
+ a.m[2] + b.m[2],
+ a.m[3] + b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector subtraction.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator-(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(a.m[0] - b.m[0],
+ a.m[1] - b.m[1],
+ a.m[2] - b.m[2],
+ a.m[3] - b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector multiplication.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator*(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(a.m[0] * b.m[0],
+ a.m[1] * b.m[1],
+ a.m[2] * b.m[2],
+ a.m[3] * b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector division.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator/(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(a.m[0] / b.m[0],
+ a.m[1] / b.m[1],
+ a.m[2] / b.m[2],
+ a.m[3] / b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector equality.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator==(vfloat4 a, vfloat4 b)
+{
+ return vmask4(a.m[0] == b.m[0],
+ a.m[1] == b.m[1],
+ a.m[2] == b.m[2],
+ a.m[3] == b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector inequality.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator!=(vfloat4 a, vfloat4 b)
+{
+ return vmask4(a.m[0] != b.m[0],
+ a.m[1] != b.m[1],
+ a.m[2] != b.m[2],
+ a.m[3] != b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector less than.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator<(vfloat4 a, vfloat4 b)
+{
+ return vmask4(a.m[0] < b.m[0],
+ a.m[1] < b.m[1],
+ a.m[2] < b.m[2],
+ a.m[3] < b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector greater than.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator>(vfloat4 a, vfloat4 b)
+{
+ return vmask4(a.m[0] > b.m[0],
+ a.m[1] > b.m[1],
+ a.m[2] > b.m[2],
+ a.m[3] > b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector less than or equal.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator<=(vfloat4 a, vfloat4 b)
+{
+ return vmask4(a.m[0] <= b.m[0],
+ a.m[1] <= b.m[1],
+ a.m[2] <= b.m[2],
+ a.m[3] <= b.m[3]);
+}
+
+/**
+ * @brief Overload: vector by vector greater than or equal.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator>=(vfloat4 a, vfloat4 b)
+{
+ return vmask4(a.m[0] >= b.m[0],
+ a.m[1] >= b.m[1],
+ a.m[2] >= b.m[2],
+ a.m[3] >= b.m[3]);
+}
+
+/**
+ * @brief Return the min vector of two vectors.
+ *
+ * If either lane value is NaN, @c b will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat4 min(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(a.m[0] < b.m[0] ? a.m[0] : b.m[0],
+ a.m[1] < b.m[1] ? a.m[1] : b.m[1],
+ a.m[2] < b.m[2] ? a.m[2] : b.m[2],
+ a.m[3] < b.m[3] ? a.m[3] : b.m[3]);
+}
+
+/**
+ * @brief Return the max vector of two vectors.
+ *
+ * If either lane value is NaN, @c b will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat4 max(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(a.m[0] > b.m[0] ? a.m[0] : b.m[0],
+ a.m[1] > b.m[1] ? a.m[1] : b.m[1],
+ a.m[2] > b.m[2] ? a.m[2] : b.m[2],
+ a.m[3] > b.m[3] ? a.m[3] : b.m[3]);
+}
+
+/**
+ * @brief Return the absolute value of the float vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 abs(vfloat4 a)
+{
+ return vfloat4(std::abs(a.m[0]),
+ std::abs(a.m[1]),
+ std::abs(a.m[2]),
+ std::abs(a.m[3]));
+}
+
+/**
+ * @brief Return a float rounded to the nearest integer value.
+ */
+ASTCENC_SIMD_INLINE vfloat4 round(vfloat4 a)
+{
+ assert(std::fegetround() == FE_TONEAREST);
+ return vfloat4(std::nearbyint(a.m[0]),
+ std::nearbyint(a.m[1]),
+ std::nearbyint(a.m[2]),
+ std::nearbyint(a.m[3]));
+}
+
+/**
+ * @brief Return the horizontal minimum of a vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 hmin(vfloat4 a)
+{
+ float tmp1 = std::min(a.m[0], a.m[1]);
+ float tmp2 = std::min(a.m[2], a.m[3]);
+ return vfloat4(std::min(tmp1, tmp2));
+}
+
+/**
+ * @brief Return the horizontal maximum of a vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 hmax(vfloat4 a)
+{
+ float tmp1 = std::max(a.m[0], a.m[1]);
+ float tmp2 = std::max(a.m[2], a.m[3]);
+ return vfloat4(std::max(tmp1, tmp2));
+}
+
+/**
+ * @brief Return the horizontal sum of a vector.
+ */
+ASTCENC_SIMD_INLINE float hadd_s(vfloat4 a)
+{
+ // Use halving add, gives invariance with SIMD versions
+ return (a.m[0] + a.m[2]) + (a.m[1] + a.m[3]);
+}
+
+/**
+ * @brief Return the sqrt of the lanes in the vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 sqrt(vfloat4 a)
+{
+ return vfloat4(std::sqrt(a.m[0]),
+ std::sqrt(a.m[1]),
+ std::sqrt(a.m[2]),
+ std::sqrt(a.m[3]));
+}
+
+/**
+ * @brief Return lanes from @c b if @c cond is set, else @c a.
+ */
+ASTCENC_SIMD_INLINE vfloat4 select(vfloat4 a, vfloat4 b, vmask4 cond)
+{
+ return vfloat4((cond.m[0] & static_cast<int>(0x80000000)) ? b.m[0] : a.m[0],
+ (cond.m[1] & static_cast<int>(0x80000000)) ? b.m[1] : a.m[1],
+ (cond.m[2] & static_cast<int>(0x80000000)) ? b.m[2] : a.m[2],
+ (cond.m[3] & static_cast<int>(0x80000000)) ? b.m[3] : a.m[3]);
+}
+
+/**
+ * @brief Return lanes from @c b if MSB of @c cond is set, else @c a.
+ */
+ASTCENC_SIMD_INLINE vfloat4 select_msb(vfloat4 a, vfloat4 b, vmask4 cond)
+{
+ return vfloat4((cond.m[0] & static_cast<int>(0x80000000)) ? b.m[0] : a.m[0],
+ (cond.m[1] & static_cast<int>(0x80000000)) ? b.m[1] : a.m[1],
+ (cond.m[2] & static_cast<int>(0x80000000)) ? b.m[2] : a.m[2],
+ (cond.m[3] & static_cast<int>(0x80000000)) ? b.m[3] : a.m[3]);
+}
+
+/**
+ * @brief Load a vector of gathered results from an array;
+ */
+ASTCENC_SIMD_INLINE vfloat4 gatherf(const float* base, vint4 indices)
+{
+ return vfloat4(base[indices.m[0]],
+ base[indices.m[1]],
+ base[indices.m[2]],
+ base[indices.m[3]]);
+}
+
+/**
+ * @brief Store a vector to an unaligned memory address.
+ */
+ASTCENC_SIMD_INLINE void store(vfloat4 a, float* ptr)
+{
+ ptr[0] = a.m[0];
+ ptr[1] = a.m[1];
+ ptr[2] = a.m[2];
+ ptr[3] = a.m[3];
+}
+
+/**
+ * @brief Store a vector to an aligned memory address.
+ */
+ASTCENC_SIMD_INLINE void storea(vfloat4 a, float* ptr)
+{
+ ptr[0] = a.m[0];
+ ptr[1] = a.m[1];
+ ptr[2] = a.m[2];
+ ptr[3] = a.m[3];
+}
+
+/**
+ * @brief Return a integer value for a float vector, using truncation.
+ */
+ASTCENC_SIMD_INLINE vint4 float_to_int(vfloat4 a)
+{
+ return vint4(static_cast<int>(a.m[0]),
+ static_cast<int>(a.m[1]),
+ static_cast<int>(a.m[2]),
+ static_cast<int>(a.m[3]));
+}
+
+/**f
+ * @brief Return a integer value for a float vector, using round-to-nearest.
+ */
+ASTCENC_SIMD_INLINE vint4 float_to_int_rtn(vfloat4 a)
+{
+ return vint4(static_cast<int>(a.m[0] + 0.5f),
+ static_cast<int>(a.m[1] + 0.5f),
+ static_cast<int>(a.m[2] + 0.5f),
+ static_cast<int>(a.m[3] + 0.5f));
+}
+
+/**
+ * @brief Return a float value for a integer vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 int_to_float(vint4 a)
+{
+ return vfloat4(static_cast<float>(a.m[0]),
+ static_cast<float>(a.m[1]),
+ static_cast<float>(a.m[2]),
+ static_cast<float>(a.m[3]));
+}
+
+/**
+ * @brief Return a float16 value for a float vector, using round-to-nearest.
+ */
+ASTCENC_SIMD_INLINE vint4 float_to_float16(vfloat4 a)
+{
+ return vint4(
+ float_to_sf16(a.lane<0>()),
+ float_to_sf16(a.lane<1>()),
+ float_to_sf16(a.lane<2>()),
+ float_to_sf16(a.lane<3>()));
+}
+
+/**
+ * @brief Return a float16 value for a float scalar, using round-to-nearest.
+ */
+static inline uint16_t float_to_float16(float a)
+{
+ return float_to_sf16(a);
+}
+
+/**
+ * @brief Return a float value for a float16 vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 float16_to_float(vint4 a)
+{
+ return vfloat4(
+ sf16_to_float(static_cast<uint16_t>(a.lane<0>())),
+ sf16_to_float(static_cast<uint16_t>(a.lane<1>())),
+ sf16_to_float(static_cast<uint16_t>(a.lane<2>())),
+ sf16_to_float(static_cast<uint16_t>(a.lane<3>())));
+}
+
+/**
+ * @brief Return a float value for a float16 scalar.
+ */
+ASTCENC_SIMD_INLINE float float16_to_float(uint16_t a)
+{
+ return sf16_to_float(a);
+}
+
+/**
+ * @brief Return a float value as an integer bit pattern (i.e. no conversion).
+ *
+ * It is a common trick to convert floats into integer bit patterns, perform
+ * some bit hackery based on knowledge they are IEEE 754 layout, and then
+ * convert them back again. This is the first half of that flip.
+ */
+ASTCENC_SIMD_INLINE vint4 float_as_int(vfloat4 a)
+{
+ vint4 r;
+ memcpy(r.m, a.m, 4 * 4);
+ return r;
+}
+
+/**
+ * @brief Return a integer value as a float bit pattern (i.e. no conversion).
+ *
+ * It is a common trick to convert floats into integer bit patterns, perform
+ * some bit hackery based on knowledge they are IEEE 754 layout, and then
+ * convert them back again. This is the second half of that flip.
+ */
+ASTCENC_SIMD_INLINE vfloat4 int_as_float(vint4 a)
+{
+ vfloat4 r;
+ memcpy(r.m, a.m, 4 * 4);
+ return r;
+}
+
+/**
+ * @brief Prepare a vtable lookup table for use with the native SIMD size.
+ */
+ASTCENC_SIMD_INLINE void vtable_prepare(vint4 t0, vint4& t0p)
+{
+ t0p = t0;
+}
+
+/**
+ * @brief Prepare a vtable lookup table for use with the native SIMD size.
+ */
+ASTCENC_SIMD_INLINE void vtable_prepare(vint4 t0, vint4 t1, vint4& t0p, vint4& t1p)
+{
+ t0p = t0;
+ t1p = t1;
+}
+
+/**
+ * @brief Prepare a vtable lookup table for use with the native SIMD size.
+ */
+ASTCENC_SIMD_INLINE void vtable_prepare(
+ vint4 t0, vint4 t1, vint4 t2, vint4 t3,
+ vint4& t0p, vint4& t1p, vint4& t2p, vint4& t3p)
+{
+ t0p = t0;
+ t1p = t1;
+ t2p = t2;
+ t3p = t3;
+}
+
+/**
+ * @brief Perform an 8-bit 32-entry table lookup, with 32-bit indexes.
+ */
+ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 idx)
+{
+ uint8_t table[16];
+ storea(t0, reinterpret_cast<int*>(table + 0));
+
+ return vint4(table[idx.lane<0>()],
+ table[idx.lane<1>()],
+ table[idx.lane<2>()],
+ table[idx.lane<3>()]);
+}
+
+
+/**
+ * @brief Perform an 8-bit 32-entry table lookup, with 32-bit indexes.
+ */
+ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 t1, vint4 idx)
+{
+ uint8_t table[32];
+ storea(t0, reinterpret_cast<int*>(table + 0));
+ storea(t1, reinterpret_cast<int*>(table + 16));
+
+ return vint4(table[idx.lane<0>()],
+ table[idx.lane<1>()],
+ table[idx.lane<2>()],
+ table[idx.lane<3>()]);
+}
+
+/**
+ * @brief Perform an 8-bit 64-entry table lookup, with 32-bit indexes.
+ */
+ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 t1, vint4 t2, vint4 t3, vint4 idx)
+{
+ uint8_t table[64];
+ storea(t0, reinterpret_cast<int*>(table + 0));
+ storea(t1, reinterpret_cast<int*>(table + 16));
+ storea(t2, reinterpret_cast<int*>(table + 32));
+ storea(t3, reinterpret_cast<int*>(table + 48));
+
+ return vint4(table[idx.lane<0>()],
+ table[idx.lane<1>()],
+ table[idx.lane<2>()],
+ table[idx.lane<3>()]);
+}
+
+/**
+ * @brief Return a vector of interleaved RGBA data.
+ *
+ * Input vectors have the value stored in the bottom 8 bits of each lane,
+ * with high bits set to zero.
+ *
+ * Output vector stores a single RGBA texel packed in each lane.
+ */
+ASTCENC_SIMD_INLINE vint4 interleave_rgba8(vint4 r, vint4 g, vint4 b, vint4 a)
+{
+ return r + lsl<8>(g) + lsl<16>(b) + lsl<24>(a);
+}
+
+/**
+ * @brief Store a vector, skipping masked lanes.
+ *
+ * All masked lanes must be at the end of vector, after all non-masked lanes.
+ */
+ASTCENC_SIMD_INLINE void store_lanes_masked(int* base, vint4 data, vmask4 mask)
+{
+ if (mask.m[3])
+ {
+ store(data, base);
+ }
+ else if (mask.m[2])
+ {
+ base[0] = data.lane<0>();
+ base[1] = data.lane<1>();
+ base[2] = data.lane<2>();
+ }
+ else if (mask.m[1])
+ {
+ base[0] = data.lane<0>();
+ base[1] = data.lane<1>();
+ }
+ else if (mask.m[0])
+ {
+ base[0] = data.lane<0>();
+ }
+}
+
+#endif // #ifndef ASTC_VECMATHLIB_NONE_4_H_INCLUDED
diff --git a/thirdparty/astcenc/astcenc_vecmathlib_sse_4.h b/thirdparty/astcenc/astcenc_vecmathlib_sse_4.h
new file mode 100644
index 0000000000..76fe577a89
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_vecmathlib_sse_4.h
@@ -0,0 +1,1283 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2019-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief 4x32-bit vectors, implemented using SSE.
+ *
+ * This module implements 4-wide 32-bit float, int, and mask vectors for x86
+ * SSE. The implementation requires at least SSE2, but higher levels of SSE can
+ * be selected at compile time to improve performance.
+ *
+ * There is a baseline level of functionality provided by all vector widths and
+ * implementations. This is implemented using identical function signatures,
+ * modulo data type, so we can use them as substitutable implementations in VLA
+ * code.
+ *
+ * The 4-wide vectors are also used as a fixed-width type, and significantly
+ * extend the functionality above that available to VLA code.
+ */
+
+#ifndef ASTC_VECMATHLIB_SSE_4_H_INCLUDED
+#define ASTC_VECMATHLIB_SSE_4_H_INCLUDED
+
+#ifndef ASTCENC_SIMD_INLINE
+ #error "Include astcenc_vecmathlib.h, do not include directly"
+#endif
+
+#include <cstdio>
+
+// ============================================================================
+// vfloat4 data type
+// ============================================================================
+
+/**
+ * @brief Data type for 4-wide floats.
+ */
+struct vfloat4
+{
+ /**
+ * @brief Construct from zero-initialized value.
+ */
+ ASTCENC_SIMD_INLINE vfloat4() = default;
+
+ /**
+ * @brief Construct from 4 values loaded from an unaligned address.
+ *
+ * Consider using loada() which is better with vectors if data is aligned
+ * to vector length.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat4(const float *p)
+ {
+ m = _mm_loadu_ps(p);
+ }
+
+ /**
+ * @brief Construct from 1 scalar value replicated across all lanes.
+ *
+ * Consider using zero() for constexpr zeros.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat4(float a)
+ {
+ m = _mm_set1_ps(a);
+ }
+
+ /**
+ * @brief Construct from 4 scalar values.
+ *
+ * The value of @c a is stored to lane 0 (LSB) in the SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat4(float a, float b, float c, float d)
+ {
+ m = _mm_set_ps(d, c, b, a);
+ }
+
+ /**
+ * @brief Construct from an existing SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vfloat4(__m128 a)
+ {
+ m = a;
+ }
+
+ /**
+ * @brief Get the scalar value of a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE float lane() const
+ {
+ return _mm_cvtss_f32(_mm_shuffle_ps(m, m, l));
+ }
+
+ /**
+ * @brief Set the scalar value of a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE void set_lane(float a)
+ {
+#if ASTCENC_SSE >= 41
+ __m128 v = _mm_set1_ps(a);
+ m = _mm_insert_ps(m, v, l << 6 | l << 4);
+#else
+ alignas(16) float idx[4];
+ _mm_store_ps(idx, m);
+ idx[l] = a;
+ m = _mm_load_ps(idx);
+#endif
+ }
+
+ /**
+ * @brief Factory that returns a vector of zeros.
+ */
+ static ASTCENC_SIMD_INLINE vfloat4 zero()
+ {
+ return vfloat4(_mm_setzero_ps());
+ }
+
+ /**
+ * @brief Factory that returns a replicated scalar loaded from memory.
+ */
+ static ASTCENC_SIMD_INLINE vfloat4 load1(const float* p)
+ {
+ return vfloat4(_mm_load_ps1(p));
+ }
+
+ /**
+ * @brief Factory that returns a vector loaded from 16B aligned memory.
+ */
+ static ASTCENC_SIMD_INLINE vfloat4 loada(const float* p)
+ {
+ return vfloat4(_mm_load_ps(p));
+ }
+
+ /**
+ * @brief Factory that returns a vector containing the lane IDs.
+ */
+ static ASTCENC_SIMD_INLINE vfloat4 lane_id()
+ {
+ return vfloat4(_mm_set_ps(3, 2, 1, 0));
+ }
+
+ /**
+ * @brief Return a swizzled float 2.
+ */
+ template <int l0, int l1> ASTCENC_SIMD_INLINE vfloat4 swz() const
+ {
+ vfloat4 result(_mm_shuffle_ps(m, m, l0 | l1 << 2));
+ result.set_lane<2>(0.0f);
+ result.set_lane<3>(0.0f);
+ return result;
+ }
+
+ /**
+ * @brief Return a swizzled float 3.
+ */
+ template <int l0, int l1, int l2> ASTCENC_SIMD_INLINE vfloat4 swz() const
+ {
+ vfloat4 result(_mm_shuffle_ps(m, m, l0 | l1 << 2 | l2 << 4));
+ result.set_lane<3>(0.0f);
+ return result;
+ }
+
+ /**
+ * @brief Return a swizzled float 4.
+ */
+ template <int l0, int l1, int l2, int l3> ASTCENC_SIMD_INLINE vfloat4 swz() const
+ {
+ return vfloat4(_mm_shuffle_ps(m, m, l0 | l1 << 2 | l2 << 4 | l3 << 6));
+ }
+
+ /**
+ * @brief The vector ...
+ */
+ __m128 m;
+};
+
+// ============================================================================
+// vint4 data type
+// ============================================================================
+
+/**
+ * @brief Data type for 4-wide ints.
+ */
+struct vint4
+{
+ /**
+ * @brief Construct from zero-initialized value.
+ */
+ ASTCENC_SIMD_INLINE vint4() = default;
+
+ /**
+ * @brief Construct from 4 values loaded from an unaligned address.
+ *
+ * Consider using loada() which is better with vectors if data is aligned
+ * to vector length.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(const int *p)
+ {
+ m = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p));
+ }
+
+ /**
+ * @brief Construct from 4 uint8_t loaded from an unaligned address.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(const uint8_t *p)
+ {
+ // _mm_loadu_si32 would be nicer syntax, but missing on older GCC
+ __m128i t = _mm_cvtsi32_si128(*reinterpret_cast<const int*>(p));
+
+#if ASTCENC_SSE >= 41
+ m = _mm_cvtepu8_epi32(t);
+#else
+ t = _mm_unpacklo_epi8(t, _mm_setzero_si128());
+ m = _mm_unpacklo_epi16(t, _mm_setzero_si128());
+#endif
+ }
+
+ /**
+ * @brief Construct from 1 scalar value replicated across all lanes.
+ *
+ * Consider using vfloat4::zero() for constexpr zeros.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(int a)
+ {
+ m = _mm_set1_epi32(a);
+ }
+
+ /**
+ * @brief Construct from 4 scalar values.
+ *
+ * The value of @c a is stored to lane 0 (LSB) in the SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(int a, int b, int c, int d)
+ {
+ m = _mm_set_epi32(d, c, b, a);
+ }
+
+ /**
+ * @brief Construct from an existing SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vint4(__m128i a)
+ {
+ m = a;
+ }
+
+ /**
+ * @brief Get the scalar from a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE int lane() const
+ {
+ return _mm_cvtsi128_si32(_mm_shuffle_epi32(m, l));
+ }
+
+ /**
+ * @brief Set the scalar value of a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE void set_lane(int a)
+ {
+#if ASTCENC_SSE >= 41
+ m = _mm_insert_epi32(m, a, l);
+#else
+ alignas(16) int idx[4];
+ _mm_store_si128(reinterpret_cast<__m128i*>(idx), m);
+ idx[l] = a;
+ m = _mm_load_si128(reinterpret_cast<const __m128i*>(idx));
+#endif
+ }
+
+ /**
+ * @brief Factory that returns a vector of zeros.
+ */
+ static ASTCENC_SIMD_INLINE vint4 zero()
+ {
+ return vint4(_mm_setzero_si128());
+ }
+
+ /**
+ * @brief Factory that returns a replicated scalar loaded from memory.
+ */
+ static ASTCENC_SIMD_INLINE vint4 load1(const int* p)
+ {
+ return vint4(*p);
+ }
+
+ /**
+ * @brief Factory that returns a vector loaded from 16B aligned memory.
+ */
+ static ASTCENC_SIMD_INLINE vint4 loada(const int* p)
+ {
+ return vint4(_mm_load_si128(reinterpret_cast<const __m128i*>(p)));
+ }
+
+ /**
+ * @brief Factory that returns a vector containing the lane IDs.
+ */
+ static ASTCENC_SIMD_INLINE vint4 lane_id()
+ {
+ return vint4(_mm_set_epi32(3, 2, 1, 0));
+ }
+
+ /**
+ * @brief The vector ...
+ */
+ __m128i m;
+};
+
+// ============================================================================
+// vmask4 data type
+// ============================================================================
+
+/**
+ * @brief Data type for 4-wide control plane masks.
+ */
+struct vmask4
+{
+ /**
+ * @brief Construct from an existing SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask4(__m128 a)
+ {
+ m = a;
+ }
+
+ /**
+ * @brief Construct from an existing SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask4(__m128i a)
+ {
+ m = _mm_castsi128_ps(a);
+ }
+
+ /**
+ * @brief Construct from 1 scalar value.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask4(bool a)
+ {
+ vint4 mask(a == false ? 0 : -1);
+ m = _mm_castsi128_ps(mask.m);
+ }
+
+ /**
+ * @brief Construct from 4 scalar values.
+ *
+ * The value of @c a is stored to lane 0 (LSB) in the SIMD register.
+ */
+ ASTCENC_SIMD_INLINE explicit vmask4(bool a, bool b, bool c, bool d)
+ {
+ vint4 mask(a == false ? 0 : -1,
+ b == false ? 0 : -1,
+ c == false ? 0 : -1,
+ d == false ? 0 : -1);
+
+ m = _mm_castsi128_ps(mask.m);
+ }
+
+ /**
+ * @brief Get the scalar value of a single lane.
+ */
+ template <int l> ASTCENC_SIMD_INLINE float lane() const
+ {
+ return _mm_cvtss_f32(_mm_shuffle_ps(m, m, l));
+ }
+
+ /**
+ * @brief The vector ...
+ */
+ __m128 m;
+};
+
+// ============================================================================
+// vmask4 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: mask union (or).
+ */
+ASTCENC_SIMD_INLINE vmask4 operator|(vmask4 a, vmask4 b)
+{
+ return vmask4(_mm_or_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: mask intersect (and).
+ */
+ASTCENC_SIMD_INLINE vmask4 operator&(vmask4 a, vmask4 b)
+{
+ return vmask4(_mm_and_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: mask difference (xor).
+ */
+ASTCENC_SIMD_INLINE vmask4 operator^(vmask4 a, vmask4 b)
+{
+ return vmask4(_mm_xor_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: mask invert (not).
+ */
+ASTCENC_SIMD_INLINE vmask4 operator~(vmask4 a)
+{
+ return vmask4(_mm_xor_si128(_mm_castps_si128(a.m), _mm_set1_epi32(-1)));
+}
+
+/**
+ * @brief Return a 4-bit mask code indicating mask status.
+ *
+ * bit0 = lane 0
+ */
+ASTCENC_SIMD_INLINE unsigned int mask(vmask4 a)
+{
+ return static_cast<unsigned int>(_mm_movemask_ps(a.m));
+}
+
+// ============================================================================
+// vint4 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: vector by vector addition.
+ */
+ASTCENC_SIMD_INLINE vint4 operator+(vint4 a, vint4 b)
+{
+ return vint4(_mm_add_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector subtraction.
+ */
+ASTCENC_SIMD_INLINE vint4 operator-(vint4 a, vint4 b)
+{
+ return vint4(_mm_sub_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector multiplication.
+ */
+ASTCENC_SIMD_INLINE vint4 operator*(vint4 a, vint4 b)
+{
+#if ASTCENC_SSE >= 41
+ return vint4(_mm_mullo_epi32 (a.m, b.m));
+#else
+ __m128i t1 = _mm_mul_epu32(a.m, b.m);
+ __m128i t2 = _mm_mul_epu32(
+ _mm_srli_si128(a.m, 4),
+ _mm_srli_si128(b.m, 4));
+ __m128i r = _mm_unpacklo_epi32(
+ _mm_shuffle_epi32(t1, _MM_SHUFFLE (0, 0, 2, 0)),
+ _mm_shuffle_epi32(t2, _MM_SHUFFLE (0, 0, 2, 0)));
+ return vint4(r);
+#endif
+}
+
+/**
+ * @brief Overload: vector bit invert.
+ */
+ASTCENC_SIMD_INLINE vint4 operator~(vint4 a)
+{
+ return vint4(_mm_xor_si128(a.m, _mm_set1_epi32(-1)));
+}
+
+/**
+ * @brief Overload: vector by vector bitwise or.
+ */
+ASTCENC_SIMD_INLINE vint4 operator|(vint4 a, vint4 b)
+{
+ return vint4(_mm_or_si128(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector bitwise and.
+ */
+ASTCENC_SIMD_INLINE vint4 operator&(vint4 a, vint4 b)
+{
+ return vint4(_mm_and_si128(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector bitwise xor.
+ */
+ASTCENC_SIMD_INLINE vint4 operator^(vint4 a, vint4 b)
+{
+ return vint4(_mm_xor_si128(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector equality.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator==(vint4 a, vint4 b)
+{
+ return vmask4(_mm_cmpeq_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector inequality.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator!=(vint4 a, vint4 b)
+{
+ return ~vmask4(_mm_cmpeq_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector less than.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator<(vint4 a, vint4 b)
+{
+ return vmask4(_mm_cmplt_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector greater than.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator>(vint4 a, vint4 b)
+{
+ return vmask4(_mm_cmpgt_epi32(a.m, b.m));
+}
+
+/**
+ * @brief Logical shift left.
+ */
+template <int s> ASTCENC_SIMD_INLINE vint4 lsl(vint4 a)
+{
+ return vint4(_mm_slli_epi32(a.m, s));
+}
+
+/**
+ * @brief Logical shift right.
+ */
+template <int s> ASTCENC_SIMD_INLINE vint4 lsr(vint4 a)
+{
+ return vint4(_mm_srli_epi32(a.m, s));
+}
+
+/**
+ * @brief Arithmetic shift right.
+ */
+template <int s> ASTCENC_SIMD_INLINE vint4 asr(vint4 a)
+{
+ return vint4(_mm_srai_epi32(a.m, s));
+}
+
+/**
+ * @brief Return the min vector of two vectors.
+ */
+ASTCENC_SIMD_INLINE vint4 min(vint4 a, vint4 b)
+{
+#if ASTCENC_SSE >= 41
+ return vint4(_mm_min_epi32(a.m, b.m));
+#else
+ vmask4 d = a < b;
+ __m128i ap = _mm_and_si128(_mm_castps_si128(d.m), a.m);
+ __m128i bp = _mm_andnot_si128(_mm_castps_si128(d.m), b.m);
+ return vint4(_mm_or_si128(ap,bp));
+#endif
+}
+
+/**
+ * @brief Return the max vector of two vectors.
+ */
+ASTCENC_SIMD_INLINE vint4 max(vint4 a, vint4 b)
+{
+#if ASTCENC_SSE >= 41
+ return vint4(_mm_max_epi32(a.m, b.m));
+#else
+ vmask4 d = a > b;
+ __m128i ap = _mm_and_si128(_mm_castps_si128(d.m), a.m);
+ __m128i bp = _mm_andnot_si128(_mm_castps_si128(d.m), b.m);
+ return vint4(_mm_or_si128(ap,bp));
+#endif
+}
+
+/**
+ * @brief Return the horizontal minimum of a vector.
+ */
+ASTCENC_SIMD_INLINE vint4 hmin(vint4 a)
+{
+ a = min(a, vint4(_mm_shuffle_epi32(a.m, _MM_SHUFFLE(0, 0, 3, 2))));
+ a = min(a, vint4(_mm_shuffle_epi32(a.m, _MM_SHUFFLE(0, 0, 0, 1))));
+ return vint4(_mm_shuffle_epi32(a.m, _MM_SHUFFLE(0, 0, 0, 0)));
+}
+
+/*
+ * @brief Return the horizontal maximum of a vector.
+ */
+ASTCENC_SIMD_INLINE vint4 hmax(vint4 a)
+{
+ a = max(a, vint4(_mm_shuffle_epi32(a.m, _MM_SHUFFLE(0, 0, 3, 2))));
+ a = max(a, vint4(_mm_shuffle_epi32(a.m, _MM_SHUFFLE(0, 0, 0, 1))));
+ return vint4(_mm_shuffle_epi32(a.m, _MM_SHUFFLE(0, 0, 0, 0)));
+}
+
+/**
+ * @brief Return the horizontal sum of a vector as a scalar.
+ */
+ASTCENC_SIMD_INLINE int hadd_s(vint4 a)
+{
+ // Add top and bottom halves, lane 1/0
+ __m128i fold = _mm_castps_si128(_mm_movehl_ps(_mm_castsi128_ps(a.m),
+ _mm_castsi128_ps(a.m)));
+ __m128i t = _mm_add_epi32(a.m, fold);
+
+ // Add top and bottom halves, lane 0 (_mm_hadd_ps exists but slow)
+ t = _mm_add_epi32(t, _mm_shuffle_epi32(t, 0x55));
+
+ return _mm_cvtsi128_si32(t);
+}
+
+/**
+ * @brief Store a vector to a 16B aligned memory address.
+ */
+ASTCENC_SIMD_INLINE void storea(vint4 a, int* p)
+{
+ _mm_store_si128(reinterpret_cast<__m128i*>(p), a.m);
+}
+
+/**
+ * @brief Store a vector to an unaligned memory address.
+ */
+ASTCENC_SIMD_INLINE void store(vint4 a, int* p)
+{
+ // Cast due to missing intrinsics
+ _mm_storeu_ps(reinterpret_cast<float*>(p), _mm_castsi128_ps(a.m));
+}
+
+/**
+ * @brief Store lowest N (vector width) bytes into an unaligned address.
+ */
+ASTCENC_SIMD_INLINE void store_nbytes(vint4 a, uint8_t* p)
+{
+ // Cast due to missing intrinsics
+ _mm_store_ss(reinterpret_cast<float*>(p), _mm_castsi128_ps(a.m));
+}
+
+/**
+ * @brief Gather N (vector width) indices from the array.
+ */
+ASTCENC_SIMD_INLINE vint4 gatheri(const int* base, vint4 indices)
+{
+#if ASTCENC_AVX >= 2
+ return vint4(_mm_i32gather_epi32(base, indices.m, 4));
+#else
+ alignas(16) int idx[4];
+ storea(indices, idx);
+ return vint4(base[idx[0]], base[idx[1]], base[idx[2]], base[idx[3]]);
+#endif
+}
+
+/**
+ * @brief Pack low 8 bits of N (vector width) lanes into bottom of vector.
+ */
+ASTCENC_SIMD_INLINE vint4 pack_low_bytes(vint4 a)
+{
+#if ASTCENC_SSE >= 41
+ __m128i shuf = _mm_set_epi8(0,0,0,0, 0,0,0,0, 0,0,0,0, 12,8,4,0);
+ return vint4(_mm_shuffle_epi8(a.m, shuf));
+#else
+ __m128i va = _mm_unpacklo_epi8(a.m, _mm_shuffle_epi32(a.m, _MM_SHUFFLE(1,1,1,1)));
+ __m128i vb = _mm_unpackhi_epi8(a.m, _mm_shuffle_epi32(a.m, _MM_SHUFFLE(3,3,3,3)));
+ return vint4(_mm_unpacklo_epi16(va, vb));
+#endif
+}
+
+/**
+ * @brief Return lanes from @c b if @c cond is set, else @c a.
+ */
+ASTCENC_SIMD_INLINE vint4 select(vint4 a, vint4 b, vmask4 cond)
+{
+ __m128i condi = _mm_castps_si128(cond.m);
+
+#if ASTCENC_SSE >= 41
+ return vint4(_mm_blendv_epi8(a.m, b.m, condi));
+#else
+ return vint4(_mm_or_si128(_mm_and_si128(condi, b.m), _mm_andnot_si128(condi, a.m)));
+#endif
+}
+
+// ============================================================================
+// vfloat4 operators and functions
+// ============================================================================
+
+/**
+ * @brief Overload: vector by vector addition.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator+(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(_mm_add_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector subtraction.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator-(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(_mm_sub_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector multiplication.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator*(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(_mm_mul_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector division.
+ */
+ASTCENC_SIMD_INLINE vfloat4 operator/(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(_mm_div_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector equality.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator==(vfloat4 a, vfloat4 b)
+{
+ return vmask4(_mm_cmpeq_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector inequality.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator!=(vfloat4 a, vfloat4 b)
+{
+ return vmask4(_mm_cmpneq_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector less than.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator<(vfloat4 a, vfloat4 b)
+{
+ return vmask4(_mm_cmplt_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector greater than.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator>(vfloat4 a, vfloat4 b)
+{
+ return vmask4(_mm_cmpgt_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector less than or equal.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator<=(vfloat4 a, vfloat4 b)
+{
+ return vmask4(_mm_cmple_ps(a.m, b.m));
+}
+
+/**
+ * @brief Overload: vector by vector greater than or equal.
+ */
+ASTCENC_SIMD_INLINE vmask4 operator>=(vfloat4 a, vfloat4 b)
+{
+ return vmask4(_mm_cmpge_ps(a.m, b.m));
+}
+
+/**
+ * @brief Return the min vector of two vectors.
+ *
+ * If either lane value is NaN, @c b will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat4 min(vfloat4 a, vfloat4 b)
+{
+ // Do not reorder - second operand will return if either is NaN
+ return vfloat4(_mm_min_ps(a.m, b.m));
+}
+
+/**
+ * @brief Return the max vector of two vectors.
+ *
+ * If either lane value is NaN, @c b will be returned for that lane.
+ */
+ASTCENC_SIMD_INLINE vfloat4 max(vfloat4 a, vfloat4 b)
+{
+ // Do not reorder - second operand will return if either is NaN
+ return vfloat4(_mm_max_ps(a.m, b.m));
+}
+
+/**
+ * @brief Return the absolute value of the float vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 abs(vfloat4 a)
+{
+ return vfloat4(_mm_max_ps(_mm_sub_ps(_mm_setzero_ps(), a.m), a.m));
+}
+
+/**
+ * @brief Return a float rounded to the nearest integer value.
+ */
+ASTCENC_SIMD_INLINE vfloat4 round(vfloat4 a)
+{
+#if ASTCENC_SSE >= 41
+ constexpr int flags = _MM_FROUND_TO_NEAREST_INT | _MM_FROUND_NO_EXC;
+ return vfloat4(_mm_round_ps(a.m, flags));
+#else
+ __m128 v = a.m;
+ __m128 neg_zero = _mm_castsi128_ps(_mm_set1_epi32(static_cast<int>(0x80000000)));
+ __m128 no_fraction = _mm_set1_ps(8388608.0f);
+ __m128 abs_mask = _mm_castsi128_ps(_mm_set1_epi32(0x7FFFFFFF));
+ __m128 sign = _mm_and_ps(v, neg_zero);
+ __m128 s_magic = _mm_or_ps(no_fraction, sign);
+ __m128 r1 = _mm_add_ps(v, s_magic);
+ r1 = _mm_sub_ps(r1, s_magic);
+ __m128 r2 = _mm_and_ps(v, abs_mask);
+ __m128 mask = _mm_cmple_ps(r2, no_fraction);
+ r2 = _mm_andnot_ps(mask, v);
+ r1 = _mm_and_ps(r1, mask);
+ return vfloat4(_mm_xor_ps(r1, r2));
+#endif
+}
+
+/**
+ * @brief Return the horizontal minimum of a vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 hmin(vfloat4 a)
+{
+ a = min(a, vfloat4(_mm_shuffle_ps(a.m, a.m, _MM_SHUFFLE(0, 0, 3, 2))));
+ a = min(a, vfloat4(_mm_shuffle_ps(a.m, a.m, _MM_SHUFFLE(0, 0, 0, 1))));
+ return vfloat4(_mm_shuffle_ps(a.m, a.m, _MM_SHUFFLE(0, 0, 0, 0)));
+}
+
+/**
+ * @brief Return the horizontal maximum of a vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 hmax(vfloat4 a)
+{
+ a = max(a, vfloat4(_mm_shuffle_ps(a.m, a.m, _MM_SHUFFLE(0, 0, 3, 2))));
+ a = max(a, vfloat4(_mm_shuffle_ps(a.m, a.m, _MM_SHUFFLE(0, 0, 0, 1))));
+ return vfloat4(_mm_shuffle_ps(a.m, a.m, _MM_SHUFFLE(0, 0, 0, 0)));
+}
+
+/**
+ * @brief Return the horizontal sum of a vector as a scalar.
+ */
+ASTCENC_SIMD_INLINE float hadd_s(vfloat4 a)
+{
+ // Add top and bottom halves, lane 1/0
+ __m128 t = _mm_add_ps(a.m, _mm_movehl_ps(a.m, a.m));
+
+ // Add top and bottom halves, lane 0 (_mm_hadd_ps exists but slow)
+ t = _mm_add_ss(t, _mm_shuffle_ps(t, t, 0x55));
+
+ return _mm_cvtss_f32(t);
+}
+
+/**
+ * @brief Return the sqrt of the lanes in the vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 sqrt(vfloat4 a)
+{
+ return vfloat4(_mm_sqrt_ps(a.m));
+}
+
+/**
+ * @brief Return lanes from @c b if @c cond is set, else @c a.
+ */
+ASTCENC_SIMD_INLINE vfloat4 select(vfloat4 a, vfloat4 b, vmask4 cond)
+{
+#if ASTCENC_SSE >= 41
+ return vfloat4(_mm_blendv_ps(a.m, b.m, cond.m));
+#else
+ return vfloat4(_mm_or_ps(_mm_and_ps(cond.m, b.m), _mm_andnot_ps(cond.m, a.m)));
+#endif
+}
+
+/**
+ * @brief Return lanes from @c b if MSB of @c cond is set, else @c a.
+ */
+ASTCENC_SIMD_INLINE vfloat4 select_msb(vfloat4 a, vfloat4 b, vmask4 cond)
+{
+#if ASTCENC_SSE >= 41
+ return vfloat4(_mm_blendv_ps(a.m, b.m, cond.m));
+#else
+ __m128 d = _mm_castsi128_ps(_mm_srai_epi32(_mm_castps_si128(cond.m), 31));
+ return vfloat4(_mm_or_ps(_mm_and_ps(d, b.m), _mm_andnot_ps(d, a.m)));
+#endif
+}
+
+/**
+ * @brief Load a vector of gathered results from an array;
+ */
+ASTCENC_SIMD_INLINE vfloat4 gatherf(const float* base, vint4 indices)
+{
+#if ASTCENC_AVX >= 2
+ return vfloat4(_mm_i32gather_ps(base, indices.m, 4));
+#else
+ alignas(16) int idx[4];
+ storea(indices, idx);
+ return vfloat4(base[idx[0]], base[idx[1]], base[idx[2]], base[idx[3]]);
+#endif
+}
+
+/**
+ * @brief Store a vector to an unaligned memory address.
+ */
+ASTCENC_SIMD_INLINE void store(vfloat4 a, float* p)
+{
+ _mm_storeu_ps(p, a.m);
+}
+
+/**
+ * @brief Store a vector to a 16B aligned memory address.
+ */
+ASTCENC_SIMD_INLINE void storea(vfloat4 a, float* p)
+{
+ _mm_store_ps(p, a.m);
+}
+
+/**
+ * @brief Return a integer value for a float vector, using truncation.
+ */
+ASTCENC_SIMD_INLINE vint4 float_to_int(vfloat4 a)
+{
+ return vint4(_mm_cvttps_epi32(a.m));
+}
+
+/**
+ * @brief Return a integer value for a float vector, using round-to-nearest.
+ */
+ASTCENC_SIMD_INLINE vint4 float_to_int_rtn(vfloat4 a)
+{
+ a = round(a);
+ return vint4(_mm_cvttps_epi32(a.m));
+}
+
+/**
+ * @brief Return a float value for an integer vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 int_to_float(vint4 a)
+{
+ return vfloat4(_mm_cvtepi32_ps(a.m));
+}
+
+/**
+ * @brief Return a float16 value for a float vector, using round-to-nearest.
+ */
+ASTCENC_SIMD_INLINE vint4 float_to_float16(vfloat4 a)
+{
+#if ASTCENC_F16C >= 1
+ __m128i packedf16 = _mm_cvtps_ph(a.m, 0);
+ __m128i f16 = _mm_cvtepu16_epi32(packedf16);
+ return vint4(f16);
+#else
+ return vint4(
+ float_to_sf16(a.lane<0>()),
+ float_to_sf16(a.lane<1>()),
+ float_to_sf16(a.lane<2>()),
+ float_to_sf16(a.lane<3>()));
+#endif
+}
+
+/**
+ * @brief Return a float16 value for a float scalar, using round-to-nearest.
+ */
+static inline uint16_t float_to_float16(float a)
+{
+#if ASTCENC_F16C >= 1
+ __m128i f16 = _mm_cvtps_ph(_mm_set1_ps(a), 0);
+ return static_cast<uint16_t>(_mm_cvtsi128_si32(f16));
+#else
+ return float_to_sf16(a);
+#endif
+}
+
+/**
+ * @brief Return a float value for a float16 vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 float16_to_float(vint4 a)
+{
+#if ASTCENC_F16C >= 1
+ __m128i packed = _mm_packs_epi32(a.m, a.m);
+ __m128 f32 = _mm_cvtph_ps(packed);
+ return vfloat4(f32);
+#else
+ return vfloat4(
+ sf16_to_float(static_cast<uint16_t>(a.lane<0>())),
+ sf16_to_float(static_cast<uint16_t>(a.lane<1>())),
+ sf16_to_float(static_cast<uint16_t>(a.lane<2>())),
+ sf16_to_float(static_cast<uint16_t>(a.lane<3>())));
+#endif
+}
+
+/**
+ * @brief Return a float value for a float16 scalar.
+ */
+ASTCENC_SIMD_INLINE float float16_to_float(uint16_t a)
+{
+#if ASTCENC_F16C >= 1
+ __m128i packed = _mm_set1_epi16(static_cast<short>(a));
+ __m128 f32 = _mm_cvtph_ps(packed);
+ return _mm_cvtss_f32(f32);
+#else
+ return sf16_to_float(a);
+#endif
+}
+
+/**
+ * @brief Return a float value as an integer bit pattern (i.e. no conversion).
+ *
+ * It is a common trick to convert floats into integer bit patterns, perform
+ * some bit hackery based on knowledge they are IEEE 754 layout, and then
+ * convert them back again. This is the first half of that flip.
+ */
+ASTCENC_SIMD_INLINE vint4 float_as_int(vfloat4 a)
+{
+ return vint4(_mm_castps_si128(a.m));
+}
+
+/**
+ * @brief Return a integer value as a float bit pattern (i.e. no conversion).
+ *
+ * It is a common trick to convert floats into integer bit patterns, perform
+ * some bit hackery based on knowledge they are IEEE 754 layout, and then
+ * convert them back again. This is the second half of that flip.
+ */
+ASTCENC_SIMD_INLINE vfloat4 int_as_float(vint4 v)
+{
+ return vfloat4(_mm_castsi128_ps(v.m));
+}
+
+/**
+ * @brief Prepare a vtable lookup table for use with the native SIMD size.
+ */
+ASTCENC_SIMD_INLINE void vtable_prepare(vint4 t0, vint4& t0p)
+{
+ t0p = t0;
+}
+
+/**
+ * @brief Prepare a vtable lookup table for use with the native SIMD size.
+ */
+ASTCENC_SIMD_INLINE void vtable_prepare(vint4 t0, vint4 t1, vint4& t0p, vint4& t1p)
+{
+#if ASTCENC_SSE >= 30
+ t0p = t0;
+ t1p = t0 ^ t1;
+#else
+ t0p = t0;
+ t1p = t1;
+#endif
+}
+
+/**
+ * @brief Prepare a vtable lookup table for use with the native SIMD size.
+ */
+ASTCENC_SIMD_INLINE void vtable_prepare(
+ vint4 t0, vint4 t1, vint4 t2, vint4 t3,
+ vint4& t0p, vint4& t1p, vint4& t2p, vint4& t3p)
+{
+#if ASTCENC_SSE >= 30
+ t0p = t0;
+ t1p = t0 ^ t1;
+ t2p = t1 ^ t2;
+ t3p = t2 ^ t3;
+#else
+ t0p = t0;
+ t1p = t1;
+ t2p = t2;
+ t3p = t3;
+#endif
+}
+
+/**
+ * @brief Perform an 8-bit 16-entry table lookup, with 32-bit indexes.
+ */
+ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 idx)
+{
+#if ASTCENC_SSE >= 30
+ // Set index byte MSB to 1 for unused bytes so shuffle returns zero
+ __m128i idxx = _mm_or_si128(idx.m, _mm_set1_epi32(static_cast<int>(0xFFFFFF00)));
+
+ __m128i result = _mm_shuffle_epi8(t0.m, idxx);
+ return vint4(result);
+#else
+ alignas(ASTCENC_VECALIGN) uint8_t table[16];
+ storea(t0, reinterpret_cast<int*>(table + 0));
+
+ return vint4(table[idx.lane<0>()],
+ table[idx.lane<1>()],
+ table[idx.lane<2>()],
+ table[idx.lane<3>()]);
+#endif
+}
+
+/**
+ * @brief Perform an 8-bit 32-entry table lookup, with 32-bit indexes.
+ */
+ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 t1, vint4 idx)
+{
+#if ASTCENC_SSE >= 30
+ // Set index byte MSB to 1 for unused bytes so shuffle returns zero
+ __m128i idxx = _mm_or_si128(idx.m, _mm_set1_epi32(static_cast<int>(0xFFFFFF00)));
+
+ __m128i result = _mm_shuffle_epi8(t0.m, idxx);
+ idxx = _mm_sub_epi8(idxx, _mm_set1_epi8(16));
+
+ __m128i result2 = _mm_shuffle_epi8(t1.m, idxx);
+ result = _mm_xor_si128(result, result2);
+
+ return vint4(result);
+#else
+ alignas(ASTCENC_VECALIGN) uint8_t table[32];
+ storea(t0, reinterpret_cast<int*>(table + 0));
+ storea(t1, reinterpret_cast<int*>(table + 16));
+
+ return vint4(table[idx.lane<0>()],
+ table[idx.lane<1>()],
+ table[idx.lane<2>()],
+ table[idx.lane<3>()]);
+#endif
+}
+
+/**
+ * @brief Perform an 8-bit 64-entry table lookup, with 32-bit indexes.
+ */
+ASTCENC_SIMD_INLINE vint4 vtable_8bt_32bi(vint4 t0, vint4 t1, vint4 t2, vint4 t3, vint4 idx)
+{
+#if ASTCENC_SSE >= 30
+ // Set index byte MSB to 1 for unused bytes so shuffle returns zero
+ __m128i idxx = _mm_or_si128(idx.m, _mm_set1_epi32(static_cast<int>(0xFFFFFF00)));
+
+ __m128i result = _mm_shuffle_epi8(t0.m, idxx);
+ idxx = _mm_sub_epi8(idxx, _mm_set1_epi8(16));
+
+ __m128i result2 = _mm_shuffle_epi8(t1.m, idxx);
+ result = _mm_xor_si128(result, result2);
+ idxx = _mm_sub_epi8(idxx, _mm_set1_epi8(16));
+
+ result2 = _mm_shuffle_epi8(t2.m, idxx);
+ result = _mm_xor_si128(result, result2);
+ idxx = _mm_sub_epi8(idxx, _mm_set1_epi8(16));
+
+ result2 = _mm_shuffle_epi8(t3.m, idxx);
+ result = _mm_xor_si128(result, result2);
+
+ return vint4(result);
+#else
+ alignas(ASTCENC_VECALIGN) uint8_t table[64];
+ storea(t0, reinterpret_cast<int*>(table + 0));
+ storea(t1, reinterpret_cast<int*>(table + 16));
+ storea(t2, reinterpret_cast<int*>(table + 32));
+ storea(t3, reinterpret_cast<int*>(table + 48));
+
+ return vint4(table[idx.lane<0>()],
+ table[idx.lane<1>()],
+ table[idx.lane<2>()],
+ table[idx.lane<3>()]);
+#endif
+}
+
+/**
+ * @brief Return a vector of interleaved RGBA data.
+ *
+ * Input vectors have the value stored in the bottom 8 bits of each lane,
+ * with high bits set to zero.
+ *
+ * Output vector stores a single RGBA texel packed in each lane.
+ */
+ASTCENC_SIMD_INLINE vint4 interleave_rgba8(vint4 r, vint4 g, vint4 b, vint4 a)
+{
+// Workaround an XCode compiler internal fault; note is slower than slli_epi32
+// so we should revert this when we get the opportunity
+#if defined(__APPLE__)
+ __m128i value = r.m;
+ value = _mm_add_epi32(value, _mm_bslli_si128(g.m, 1));
+ value = _mm_add_epi32(value, _mm_bslli_si128(b.m, 2));
+ value = _mm_add_epi32(value, _mm_bslli_si128(a.m, 3));
+ return vint4(value);
+#else
+ __m128i value = r.m;
+ value = _mm_add_epi32(value, _mm_slli_epi32(g.m, 8));
+ value = _mm_add_epi32(value, _mm_slli_epi32(b.m, 16));
+ value = _mm_add_epi32(value, _mm_slli_epi32(a.m, 24));
+ return vint4(value);
+#endif
+}
+
+/**
+ * @brief Store a vector, skipping masked lanes.
+ *
+ * All masked lanes must be at the end of vector, after all non-masked lanes.
+ */
+ASTCENC_SIMD_INLINE void store_lanes_masked(int* base, vint4 data, vmask4 mask)
+{
+#if ASTCENC_AVX >= 2
+ _mm_maskstore_epi32(base, _mm_castps_si128(mask.m), data.m);
+#else
+ // Note - we cannot use _mm_maskmoveu_si128 as the underlying hardware doesn't guarantee
+ // fault suppression on masked lanes so we can get page faults at the end of an image.
+ if (mask.lane<3>() != 0.0f)
+ {
+ store(data, base);
+ }
+ else if (mask.lane<2>() != 0.0f)
+ {
+ base[0] = data.lane<0>();
+ base[1] = data.lane<1>();
+ base[2] = data.lane<2>();
+ }
+ else if (mask.lane<1>() != 0.0f)
+ {
+ base[0] = data.lane<0>();
+ base[1] = data.lane<1>();
+ }
+ else if (mask.lane<0>() != 0.0f)
+ {
+ base[0] = data.lane<0>();
+ }
+#endif
+}
+
+#if defined(ASTCENC_NO_INVARIANCE) && (ASTCENC_SSE >= 41)
+
+#define ASTCENC_USE_NATIVE_DOT_PRODUCT 1
+
+/**
+ * @brief Return the dot product for the full 4 lanes, returning scalar.
+ */
+ASTCENC_SIMD_INLINE float dot_s(vfloat4 a, vfloat4 b)
+{
+ return _mm_cvtss_f32(_mm_dp_ps(a.m, b.m, 0xFF));
+}
+
+/**
+ * @brief Return the dot product for the full 4 lanes, returning vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 dot(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(_mm_dp_ps(a.m, b.m, 0xFF));
+}
+
+/**
+ * @brief Return the dot product for the bottom 3 lanes, returning scalar.
+ */
+ASTCENC_SIMD_INLINE float dot3_s(vfloat4 a, vfloat4 b)
+{
+ return _mm_cvtss_f32(_mm_dp_ps(a.m, b.m, 0x77));
+}
+
+/**
+ * @brief Return the dot product for the bottom 3 lanes, returning vector.
+ */
+ASTCENC_SIMD_INLINE vfloat4 dot3(vfloat4 a, vfloat4 b)
+{
+ return vfloat4(_mm_dp_ps(a.m, b.m, 0x77));
+}
+
+#endif // #if defined(ASTCENC_NO_INVARIANCE) && (ASTCENC_SSE >= 41)
+
+#if ASTCENC_POPCNT >= 1
+
+#define ASTCENC_USE_NATIVE_POPCOUNT 1
+
+/**
+ * @brief Population bit count.
+ *
+ * @param v The value to population count.
+ *
+ * @return The number of 1 bits.
+ */
+ASTCENC_SIMD_INLINE int popcount(uint64_t v)
+{
+ return static_cast<int>(_mm_popcnt_u64(v));
+}
+
+#endif // ASTCENC_POPCNT >= 1
+
+#endif // #ifndef ASTC_VECMATHLIB_SSE_4_H_INCLUDED
diff --git a/thirdparty/astcenc/astcenc_weight_align.cpp b/thirdparty/astcenc/astcenc_weight_align.cpp
new file mode 100644
index 0000000000..e40a318cf5
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_weight_align.cpp
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2022 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+#if !defined(ASTCENC_DECOMPRESS_ONLY)
+
+/**
+ * @brief Functions for angular-sum algorithm for weight alignment.
+ *
+ * This algorithm works as follows:
+ * - we compute a complex number P as (cos s*i, sin s*i) for each weight,
+ * where i is the input value and s is a scaling factor based on the spacing between the weights.
+ * - we then add together complex numbers for all the weights.
+ * - we then compute the length and angle of the resulting sum.
+ *
+ * This should produce the following results:
+ * - perfect alignment results in a vector whose length is equal to the sum of lengths of all inputs
+ * - even distribution results in a vector of length 0.
+ * - all samples identical results in perfect alignment for every scaling.
+ *
+ * For each scaling factor within a given set, we compute an alignment factor from 0 to 1. This
+ * should then result in some scalings standing out as having particularly good alignment factors;
+ * we can use this to produce a set of candidate scale/shift values for various quantization levels;
+ * we should then actually try them and see what happens.
+ */
+
+#include "astcenc_internal.h"
+#include "astcenc_vecmathlib.h"
+
+#include <stdio.h>
+#include <cassert>
+#include <cstring>
+
+static constexpr unsigned int ANGULAR_STEPS { 32 };
+
+static_assert((ANGULAR_STEPS % ASTCENC_SIMD_WIDTH) == 0,
+ "ANGULAR_STEPS must be multiple of ASTCENC_SIMD_WIDTH");
+
+static_assert(ANGULAR_STEPS >= 32,
+ "ANGULAR_STEPS must be at least max(steps_for_quant_level)");
+
+// Store a reduced sin/cos table for 64 possible weight values; this causes
+// slight quality loss compared to using sin() and cos() directly. Must be 2^N.
+static constexpr unsigned int SINCOS_STEPS { 64 };
+
+static const uint8_t steps_for_quant_level[12] {
+ 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32
+};
+
+alignas(ASTCENC_VECALIGN) static float sin_table[SINCOS_STEPS][ANGULAR_STEPS];
+alignas(ASTCENC_VECALIGN) static float cos_table[SINCOS_STEPS][ANGULAR_STEPS];
+
+#if defined(ASTCENC_DIAGNOSTICS)
+ static bool print_once { true };
+#endif
+
+/* See header for documentation. */
+void prepare_angular_tables()
+{
+ for (unsigned int i = 0; i < ANGULAR_STEPS; i++)
+ {
+ float angle_step = static_cast<float>(i + 1);
+
+ for (unsigned int j = 0; j < SINCOS_STEPS; j++)
+ {
+ sin_table[j][i] = static_cast<float>(sinf((2.0f * astc::PI / (SINCOS_STEPS - 1.0f)) * angle_step * static_cast<float>(j)));
+ cos_table[j][i] = static_cast<float>(cosf((2.0f * astc::PI / (SINCOS_STEPS - 1.0f)) * angle_step * static_cast<float>(j)));
+ }
+ }
+}
+
+/**
+ * @brief Compute the angular alignment factors and offsets.
+ *
+ * @param weight_count The number of (decimated) weights.
+ * @param dec_weight_ideal_value The ideal decimated unquantized weight values.
+ * @param max_angular_steps The maximum number of steps to be tested.
+ * @param[out] offsets The output angular offsets array.
+ */
+static void compute_angular_offsets(
+ unsigned int weight_count,
+ const float* dec_weight_ideal_value,
+ unsigned int max_angular_steps,
+ float* offsets
+) {
+ promise(weight_count > 0);
+ promise(max_angular_steps > 0);
+
+ alignas(ASTCENC_VECALIGN) int isamplev[BLOCK_MAX_WEIGHTS];
+
+ // Precompute isample; arrays are always allocated 64 elements long
+ for (unsigned int i = 0; i < weight_count; i += ASTCENC_SIMD_WIDTH)
+ {
+ // Add 2^23 and interpreting bits extracts round-to-nearest int
+ vfloat sample = loada(dec_weight_ideal_value + i) * (SINCOS_STEPS - 1.0f) + vfloat(12582912.0f);
+ vint isample = float_as_int(sample) & vint((SINCOS_STEPS - 1));
+ storea(isample, isamplev + i);
+ }
+
+ // Arrays are multiple of SIMD width (ANGULAR_STEPS), safe to overshoot max
+ vfloat mult = vfloat(1.0f / (2.0f * astc::PI));
+
+ for (unsigned int i = 0; i < max_angular_steps; i += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat anglesum_x = vfloat::zero();
+ vfloat anglesum_y = vfloat::zero();
+
+ for (unsigned int j = 0; j < weight_count; j++)
+ {
+ int isample = isamplev[j];
+ anglesum_x += loada(cos_table[isample] + i);
+ anglesum_y += loada(sin_table[isample] + i);
+ }
+
+ vfloat angle = atan2(anglesum_y, anglesum_x);
+ vfloat ofs = angle * mult;
+ storea(ofs, offsets + i);
+ }
+}
+
+/**
+ * @brief For a given step size compute the lowest and highest weight.
+ *
+ * Compute the lowest and highest weight that results from quantizing using the given stepsize and
+ * offset, and then compute the resulting error. The cut errors indicate the error that results from
+ * forcing samples that should have had one weight value one step up or down.
+ *
+ * @param weight_count The number of (decimated) weights.
+ * @param dec_weight_ideal_value The ideal decimated unquantized weight values.
+ * @param max_angular_steps The maximum number of steps to be tested.
+ * @param max_quant_steps The maximum quantization level to be tested.
+ * @param offsets The angular offsets array.
+ * @param[out] lowest_weight Per angular step, the lowest weight.
+ * @param[out] weight_span Per angular step, the span between lowest and highest weight.
+ * @param[out] error Per angular step, the error.
+ * @param[out] cut_low_weight_error Per angular step, the low weight cut error.
+ * @param[out] cut_high_weight_error Per angular step, the high weight cut error.
+ */
+static void compute_lowest_and_highest_weight(
+ unsigned int weight_count,
+ const float* dec_weight_ideal_value,
+ unsigned int max_angular_steps,
+ unsigned int max_quant_steps,
+ const float* offsets,
+ float* lowest_weight,
+ int* weight_span,
+ float* error,
+ float* cut_low_weight_error,
+ float* cut_high_weight_error
+) {
+ promise(weight_count > 0);
+ promise(max_angular_steps > 0);
+
+ vfloat rcp_stepsize = vfloat::lane_id() + vfloat(1.0f);
+
+ // Arrays are ANGULAR_STEPS long, so always safe to run full vectors
+ for (unsigned int sp = 0; sp < max_angular_steps; sp += ASTCENC_SIMD_WIDTH)
+ {
+ vfloat minidx(128.0f);
+ vfloat maxidx(-128.0f);
+ vfloat errval = vfloat::zero();
+ vfloat cut_low_weight_err = vfloat::zero();
+ vfloat cut_high_weight_err = vfloat::zero();
+ vfloat offset = loada(offsets + sp);
+
+ for (unsigned int j = 0; j < weight_count; j++)
+ {
+ vfloat sval = load1(dec_weight_ideal_value + j) * rcp_stepsize - offset;
+ vfloat svalrte = round(sval);
+ vfloat diff = sval - svalrte;
+ errval += diff * diff;
+
+ // Reset tracker on min hit
+ vmask mask = svalrte < minidx;
+ minidx = select(minidx, svalrte, mask);
+ cut_low_weight_err = select(cut_low_weight_err, vfloat::zero(), mask);
+
+ // Accumulate on min hit
+ mask = svalrte == minidx;
+ vfloat accum = cut_low_weight_err + vfloat(1.0f) - vfloat(2.0f) * diff;
+ cut_low_weight_err = select(cut_low_weight_err, accum, mask);
+
+ // Reset tracker on max hit
+ mask = svalrte > maxidx;
+ maxidx = select(maxidx, svalrte, mask);
+ cut_high_weight_err = select(cut_high_weight_err, vfloat::zero(), mask);
+
+ // Accumulate on max hit
+ mask = svalrte == maxidx;
+ accum = cut_high_weight_err + vfloat(1.0f) + vfloat(2.0f) * diff;
+ cut_high_weight_err = select(cut_high_weight_err, accum, mask);
+ }
+
+ // Write out min weight and weight span; clamp span to a usable range
+ vint span = float_to_int(maxidx - minidx + vfloat(1));
+ span = min(span, vint(max_quant_steps + 3));
+ span = max(span, vint(2));
+ storea(minidx, lowest_weight + sp);
+ storea(span, weight_span + sp);
+
+ // The cut_(lowest/highest)_weight_error indicate the error that results from forcing
+ // samples that should have had the weight value one step (up/down).
+ vfloat ssize = 1.0f / rcp_stepsize;
+ vfloat errscale = ssize * ssize;
+ storea(errval * errscale, error + sp);
+ storea(cut_low_weight_err * errscale, cut_low_weight_error + sp);
+ storea(cut_high_weight_err * errscale, cut_high_weight_error + sp);
+
+ rcp_stepsize = rcp_stepsize + vfloat(ASTCENC_SIMD_WIDTH);
+ }
+}
+
+/**
+ * @brief The main function for the angular algorithm.
+ *
+ * @param weight_count The number of (decimated) weights.
+ * @param dec_weight_ideal_value The ideal decimated unquantized weight values.
+ * @param max_quant_level The maximum quantization level to be tested.
+ * @param[out] low_value Per angular step, the lowest weight value.
+ * @param[out] high_value Per angular step, the highest weight value.
+ */
+static void compute_angular_endpoints_for_quant_levels(
+ unsigned int weight_count,
+ const float* dec_weight_ideal_value,
+ unsigned int max_quant_level,
+ float low_value[TUNE_MAX_ANGULAR_QUANT + 1],
+ float high_value[TUNE_MAX_ANGULAR_QUANT + 1]
+) {
+ unsigned int max_quant_steps = steps_for_quant_level[max_quant_level];
+ unsigned int max_angular_steps = steps_for_quant_level[max_quant_level];
+
+ alignas(ASTCENC_VECALIGN) float angular_offsets[ANGULAR_STEPS];
+
+ compute_angular_offsets(weight_count, dec_weight_ideal_value,
+ max_angular_steps, angular_offsets);
+
+ alignas(ASTCENC_VECALIGN) float lowest_weight[ANGULAR_STEPS];
+ alignas(ASTCENC_VECALIGN) int32_t weight_span[ANGULAR_STEPS];
+ alignas(ASTCENC_VECALIGN) float error[ANGULAR_STEPS];
+ alignas(ASTCENC_VECALIGN) float cut_low_weight_error[ANGULAR_STEPS];
+ alignas(ASTCENC_VECALIGN) float cut_high_weight_error[ANGULAR_STEPS];
+
+ compute_lowest_and_highest_weight(weight_count, dec_weight_ideal_value,
+ max_angular_steps, max_quant_steps,
+ angular_offsets, lowest_weight, weight_span, error,
+ cut_low_weight_error, cut_high_weight_error);
+
+ // For each quantization level, find the best error terms. Use packed vectors so data-dependent
+ // branches can become selects. This involves some integer to float casts, but the values are
+ // small enough so they never round the wrong way.
+ vfloat4 best_results[36];
+
+ // Initialize the array to some safe defaults
+ promise(max_quant_steps > 0);
+ for (unsigned int i = 0; i < (max_quant_steps + 4); i++)
+ {
+ // Lane<0> = Best error
+ // Lane<1> = Best scale; -1 indicates no solution found
+ // Lane<2> = Cut low weight
+ best_results[i] = vfloat4(ERROR_CALC_DEFAULT, -1.0f, 0.0f, 0.0f);
+ }
+
+ promise(max_angular_steps > 0);
+ for (unsigned int i = 0; i < max_angular_steps; i++)
+ {
+ float i_flt = static_cast<float>(i);
+
+ int idx_span = weight_span[i];
+
+ float error_cut_low = error[i] + cut_low_weight_error[i];
+ float error_cut_high = error[i] + cut_high_weight_error[i];
+ float error_cut_low_high = error[i] + cut_low_weight_error[i] + cut_high_weight_error[i];
+
+ // Check best error against record N
+ vfloat4 best_result = best_results[idx_span];
+ vfloat4 new_result = vfloat4(error[i], i_flt, 0.0f, 0.0f);
+ vmask4 mask = vfloat4(best_result.lane<0>()) > vfloat4(error[i]);
+ best_results[idx_span] = select(best_result, new_result, mask);
+
+ // Check best error against record N-1 with either cut low or cut high
+ best_result = best_results[idx_span - 1];
+
+ new_result = vfloat4(error_cut_low, i_flt, 1.0f, 0.0f);
+ mask = vfloat4(best_result.lane<0>()) > vfloat4(error_cut_low);
+ best_result = select(best_result, new_result, mask);
+
+ new_result = vfloat4(error_cut_high, i_flt, 0.0f, 0.0f);
+ mask = vfloat4(best_result.lane<0>()) > vfloat4(error_cut_high);
+ best_results[idx_span - 1] = select(best_result, new_result, mask);
+
+ // Check best error against record N-2 with both cut low and high
+ best_result = best_results[idx_span - 2];
+ new_result = vfloat4(error_cut_low_high, i_flt, 1.0f, 0.0f);
+ mask = vfloat4(best_result.lane<0>()) > vfloat4(error_cut_low_high);
+ best_results[idx_span - 2] = select(best_result, new_result, mask);
+ }
+
+ for (unsigned int i = 0; i <= max_quant_level; i++)
+ {
+ unsigned int q = steps_for_quant_level[i];
+ int bsi = static_cast<int>(best_results[q].lane<1>());
+
+ // Did we find anything?
+#if defined(ASTCENC_DIAGNOSTICS)
+ if ((bsi < 0) && print_once)
+ {
+ print_once = false;
+ printf("INFO: Unable to find full encoding within search error limit.\n\n");
+ }
+#endif
+
+ bsi = astc::max(0, bsi);
+
+ float lwi = lowest_weight[bsi] + best_results[q].lane<2>();
+ float hwi = lwi + static_cast<float>(q) - 1.0f;
+
+ float stepsize = 1.0f / (1.0f + static_cast<float>(bsi));
+ low_value[i] = (angular_offsets[bsi] + lwi) * stepsize;
+ high_value[i] = (angular_offsets[bsi] + hwi) * stepsize;
+ }
+}
+
+/* See header for documentation. */
+void compute_angular_endpoints_1plane(
+ bool only_always,
+ const block_size_descriptor& bsd,
+ const float* dec_weight_ideal_value,
+ unsigned int max_weight_quant,
+ compression_working_buffers& tmpbuf
+) {
+ float (&low_value)[WEIGHTS_MAX_BLOCK_MODES] = tmpbuf.weight_low_value1;
+ float (&high_value)[WEIGHTS_MAX_BLOCK_MODES] = tmpbuf.weight_high_value1;
+
+ float (&low_values)[WEIGHTS_MAX_DECIMATION_MODES][TUNE_MAX_ANGULAR_QUANT + 1] = tmpbuf.weight_low_values1;
+ float (&high_values)[WEIGHTS_MAX_DECIMATION_MODES][TUNE_MAX_ANGULAR_QUANT + 1] = tmpbuf.weight_high_values1;
+
+ unsigned int max_decimation_modes = only_always ? bsd.decimation_mode_count_always
+ : bsd.decimation_mode_count_selected;
+ promise(max_decimation_modes > 0);
+ for (unsigned int i = 0; i < max_decimation_modes; i++)
+ {
+ const decimation_mode& dm = bsd.decimation_modes[i];
+ if (!dm.is_ref_1_plane(static_cast<quant_method>(max_weight_quant)))
+ {
+ continue;
+ }
+
+ unsigned int weight_count = bsd.get_decimation_info(i).weight_count;
+
+ unsigned int max_precision = dm.maxprec_1plane;
+ if (max_precision > TUNE_MAX_ANGULAR_QUANT)
+ {
+ max_precision = TUNE_MAX_ANGULAR_QUANT;
+ }
+
+ if (max_precision > max_weight_quant)
+ {
+ max_precision = max_weight_quant;
+ }
+
+ compute_angular_endpoints_for_quant_levels(
+ weight_count,
+ dec_weight_ideal_value + i * BLOCK_MAX_WEIGHTS,
+ max_precision, low_values[i], high_values[i]);
+ }
+
+ unsigned int max_block_modes = only_always ? bsd.block_mode_count_1plane_always
+ : bsd.block_mode_count_1plane_selected;
+ promise(max_block_modes > 0);
+ for (unsigned int i = 0; i < max_block_modes; i++)
+ {
+ const block_mode& bm = bsd.block_modes[i];
+ assert(!bm.is_dual_plane);
+
+ unsigned int quant_mode = bm.quant_mode;
+ unsigned int decim_mode = bm.decimation_mode;
+
+ if (quant_mode <= TUNE_MAX_ANGULAR_QUANT)
+ {
+ low_value[i] = low_values[decim_mode][quant_mode];
+ high_value[i] = high_values[decim_mode][quant_mode];
+ }
+ else
+ {
+ low_value[i] = 0.0f;
+ high_value[i] = 1.0f;
+ }
+ }
+}
+
+/* See header for documentation. */
+void compute_angular_endpoints_2planes(
+ const block_size_descriptor& bsd,
+ const float* dec_weight_ideal_value,
+ unsigned int max_weight_quant,
+ compression_working_buffers& tmpbuf
+) {
+ float (&low_value1)[WEIGHTS_MAX_BLOCK_MODES] = tmpbuf.weight_low_value1;
+ float (&high_value1)[WEIGHTS_MAX_BLOCK_MODES] = tmpbuf.weight_high_value1;
+ float (&low_value2)[WEIGHTS_MAX_BLOCK_MODES] = tmpbuf.weight_low_value2;
+ float (&high_value2)[WEIGHTS_MAX_BLOCK_MODES] = tmpbuf.weight_high_value2;
+
+ float (&low_values1)[WEIGHTS_MAX_DECIMATION_MODES][TUNE_MAX_ANGULAR_QUANT + 1] = tmpbuf.weight_low_values1;
+ float (&high_values1)[WEIGHTS_MAX_DECIMATION_MODES][TUNE_MAX_ANGULAR_QUANT + 1] = tmpbuf.weight_high_values1;
+ float (&low_values2)[WEIGHTS_MAX_DECIMATION_MODES][TUNE_MAX_ANGULAR_QUANT + 1] = tmpbuf.weight_low_values2;
+ float (&high_values2)[WEIGHTS_MAX_DECIMATION_MODES][TUNE_MAX_ANGULAR_QUANT + 1] = tmpbuf.weight_high_values2;
+
+ promise(bsd.decimation_mode_count_selected > 0);
+ for (unsigned int i = 0; i < bsd.decimation_mode_count_selected; i++)
+ {
+ const decimation_mode& dm = bsd.decimation_modes[i];
+ if (!dm.is_ref_2_plane(static_cast<quant_method>(max_weight_quant)))
+ {
+ continue;
+ }
+
+ unsigned int weight_count = bsd.get_decimation_info(i).weight_count;
+
+ unsigned int max_precision = dm.maxprec_2planes;
+ if (max_precision > TUNE_MAX_ANGULAR_QUANT)
+ {
+ max_precision = TUNE_MAX_ANGULAR_QUANT;
+ }
+
+ if (max_precision > max_weight_quant)
+ {
+ max_precision = max_weight_quant;
+ }
+
+ compute_angular_endpoints_for_quant_levels(
+ weight_count,
+ dec_weight_ideal_value + i * BLOCK_MAX_WEIGHTS,
+ max_precision, low_values1[i], high_values1[i]);
+
+ compute_angular_endpoints_for_quant_levels(
+ weight_count,
+ dec_weight_ideal_value + i * BLOCK_MAX_WEIGHTS + WEIGHTS_PLANE2_OFFSET,
+ max_precision, low_values2[i], high_values2[i]);
+ }
+
+ unsigned int start = bsd.block_mode_count_1plane_selected;
+ unsigned int end = bsd.block_mode_count_1plane_2plane_selected;
+ for (unsigned int i = start; i < end; i++)
+ {
+ const block_mode& bm = bsd.block_modes[i];
+ unsigned int quant_mode = bm.quant_mode;
+ unsigned int decim_mode = bm.decimation_mode;
+
+ if (quant_mode <= TUNE_MAX_ANGULAR_QUANT)
+ {
+ low_value1[i] = low_values1[decim_mode][quant_mode];
+ high_value1[i] = high_values1[decim_mode][quant_mode];
+ low_value2[i] = low_values2[decim_mode][quant_mode];
+ high_value2[i] = high_values2[decim_mode][quant_mode];
+ }
+ else
+ {
+ low_value1[i] = 0.0f;
+ high_value1[i] = 1.0f;
+ low_value2[i] = 0.0f;
+ high_value2[i] = 1.0f;
+ }
+ }
+}
+
+#endif
diff --git a/thirdparty/astcenc/astcenc_weight_quant_xfer_tables.cpp b/thirdparty/astcenc/astcenc_weight_quant_xfer_tables.cpp
new file mode 100644
index 0000000000..8fdf73adc2
--- /dev/null
+++ b/thirdparty/astcenc/astcenc_weight_quant_xfer_tables.cpp
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: Apache-2.0
+// ----------------------------------------------------------------------------
+// Copyright 2011-2021 Arm Limited
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy
+// of the License at:
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+// ----------------------------------------------------------------------------
+
+/**
+ * @brief Data tables for quantization transfer.
+ */
+
+#include "astcenc_internal.h"
+
+#define _ 0 // Using _ to indicate an entry that will not be used.
+
+const quant_and_transfer_table quant_and_xfer_tables[12] {
+ // QUANT2, range 0..1
+ {
+ {0, 64},
+ {0, 1},
+ {0, 64},
+ {0x4000,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,
+ _,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,
+ 0x4000}
+ },
+ // QUANT_3, range 0..2
+ {
+ {0, 32, 64},
+ {0, 1, 2},
+ {0, 32, 64},
+ {0x2000,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,
+ _,_,0x4000,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,
+ _,_,_,_,0x4020}
+ },
+ // QUANT_4, range 0..3
+ {
+ {0, 21, 43, 64},
+ {0, 1, 2, 3},
+ {0, 21, 43, 64},
+ {0x1500,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,0x2b00,_,_,_,_,
+ _,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,0x4015,_,_,_,_,_,_,_,_,_,_,_,_,
+ _,_,_,_,_,_,_,_,0x402b}
+ },
+ //QUANT_5, range 0..4
+ {
+ {0, 16, 32, 48, 64},
+ {0, 1, 2, 3, 4},
+ {0, 16, 32, 48, 64},
+ {0x1000,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,0x2000,_,_,_,_,_,_,_,_,_,
+ _,_,_,_,_,_,0x3010,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,0x4020,_,_,_,
+ _,_,_,_,_,_,_,_,_,_,_,_,0x4030}
+ },
+ // QUANT_6, range 0..5
+ {
+ {0, 12, 25, 39, 52, 64},
+ {0, 2, 4, 5, 3, 1},
+ {0, 64, 12, 52, 25, 39},
+ {0x0c00,_,_,_,_,_,_,_,_,_,_,_,0x1900,_,_,_,_,_,_,_,_,_,_,_,_,
+ 0x270c,_,_,_,_,_,_,_,_,_,_,_,_,_,0x3419,_,_,_,_,_,_,_,_,_,_,
+ _,_,0x4027,_,_,_,_,_,_,_,_,_,_,_,0x4034}
+ },
+ // QUANT_8, range 0..7
+ {
+ {0, 9, 18, 27, 37, 46, 55, 64},
+ {0, 1, 2, 3, 4, 5, 6, 7},
+ {0, 9, 18, 27, 37, 46, 55, 64},
+ {0x0900,_,_,_,_,_,_,_,_,0x1200,_,_,_,_,_,_,_,_,0x1b09,_,_,
+ _,_,_,_,_,_,0x2512,_,_,_,_,_,_,_,_,_,0x2e1b,_,_,_,_,_,_,_,_,
+ 0x3725,_,_,_,_,_,_,_,_,0x402e,_,_,_,_,_,_,_,_,0x4037}
+ },
+ // QUANT_10, range 0..9
+ {
+ {0, 7, 14, 21, 28, 36, 43, 50, 57, 64},
+ {0, 2, 4, 6, 8, 9, 7, 5, 3, 1},
+ {0, 64, 7, 57, 14, 50, 21, 43, 28, 36},
+ {0x0700,_,_,_,_,_,_,0x0e00,_,_,_,_,_,_,0x1507,_,_,_,_,_,_,
+ 0x1c0e,_,_,_,_,_,_,0x2415,_,_,_,_,_,_,_,0x2b1c,_,_,_,_,_,
+ _,0x3224,_,_,_,_,_,_,0x392b,_,_,_,_,_,_,0x4032,_,_,_,_,_,
+ _,0x4039}
+ },
+ // QUANT_12, range 0..11
+ {
+ {0, 5, 11, 17, 23, 28, 36, 41, 47, 53, 59, 64},
+ {0, 4, 8, 2, 6, 10, 11, 7, 3, 9, 5, 1},
+ {0, 64, 17, 47, 5, 59, 23, 41, 11, 53, 28, 36},
+ {0x0500,_,_,_,_,0x0b00,_,_,_,_,_,0x1105,_,_,_,_,_,
+ 0x170b,_,_,_,_,_,0x1c11,_,_,_,_,0x2417,_,_,_,_,_,_,_,
+ 0x291c,_,_,_,_,0x2f24,_,_,_,_,_,0x3529,_,_,_,_,_,
+ 0x3b2f,_,_,_,_,_,0x4035,_,_,_,_,0x403b}
+ },
+ // QUANT_16, range 0..15
+ {
+ {0, 4, 8, 12, 17, 21, 25, 29, 35, 39, 43, 47, 52, 56, 60, 64},
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
+ {0, 4, 8, 12, 17, 21, 25, 29, 35, 39, 43, 47, 52, 56, 60, 64},
+ {0x0400,_,_,_,0x0800,_,_,_,0x0c04,_,_,_,0x1108,_,_,_,_,
+ 0x150c,_,_,_,0x1911,_,_,_,0x1d15,_,_,_,0x2319,_,_,_,_,
+ _,0x271d,_,_,_,0x2b23,_,_,_,0x2f27,_,_,_,0x342b,_,_,_,
+ _,0x382f,_,_,_,0x3c34,_,_,_,0x4038,_,_,_,0x403c}
+ },
+ // QUANT_20, range 0..19
+ {
+ {0, 3, 6, 9, 13, 16, 19, 23, 26, 29, 35, 38, 41, 45, 48, 51, 55, 58, 61, 64},
+ {0, 4, 8, 12, 16, 2, 6, 10, 14, 18, 19, 15, 11, 7, 3, 17, 13, 9, 5, 1},
+ {0, 64, 16, 48, 3, 61, 19, 45, 6, 58, 23, 41, 9, 55, 26, 38, 13, 51, 29, 35},
+ {0x0300,_,_,0x0600,_,_,0x0903,_,_,0x0d06,_,_,_,
+ 0x1009,_,_,0x130d,_,_,0x1710,_,_,_,0x1a13,_,_,
+ 0x1d17,_,_,0x231a,_,_,_,_,_,0x261d,_,_,0x2923,_,_,
+ 0x2d26,_,_,_,0x3029,_,_,0x332d,_,_,0x3730,_,_,_,
+ 0x3a33,_,_,0x3d37,_,_,0x403a,_,_,0x403d}
+ },
+ // QUANT_24, range 0..23
+ {
+ {0, 2, 5, 8, 11, 13, 16, 19, 22, 24, 27, 30, 34, 37, 40, 42, 45, 48, 51, 53, 56, 59, 62, 64},
+ {0, 8, 16, 2, 10, 18, 4, 12, 20, 6, 14, 22, 23, 15, 7, 21, 13, 5, 19, 11, 3, 17, 9, 1},
+ {0, 64, 8, 56, 16, 48, 24, 40, 2, 62, 11, 53, 19, 45, 27, 37, 5, 59, 13, 51, 22, 42, 30, 34},
+ {0x0200,_,0x0500,_,_,0x0802,_,_,0x0b05,_,_,0x0d08,
+ _,0x100b,_,_,0x130d,_,_,0x1610,_,_,0x1813,_,
+ 0x1b16,_,_,0x1e18,_,_,0x221b,_,_,_,0x251e,_,_,
+ 0x2822,_,_,0x2a25,_,0x2d28,_,_,0x302a,_,_,0x332d,
+ _,_,0x3530,_,0x3833,_,_,0x3b35,_,_,0x3e38,_,_,
+ 0x403b,_,0x403e}
+ },
+ // QUANT_32, range 0..31
+ {
+ {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64},
+ {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31},
+ {0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64},
+ {0x0200,_,0x0400,_,0x0602,_,0x0804,_,0x0a06,_,
+ 0x0c08,_,0x0e0a,_,0x100c,_,0x120e,_,0x1410,_,
+ 0x1612,_,0x1814,_,0x1a16,_,0x1c18,_,0x1e1a,_,
+ 0x221c,_,_,_,0x241e,_,0x2622,_,0x2824,_,0x2a26,_,
+ 0x2c28,_,0x2e2a,_,0x302c,_,0x322e,_,0x3430,_,
+ 0x3632,_,0x3834,_,0x3a36,_,0x3c38,_,0x3e3a,_,
+ 0x403c,_,0x403e}
+ }
+};